diff --git a/src/graphvisualizer/containers/ContentResizerPane.java b/src/graphvisualizer/containers/ContentResizerPane.java index aac1502..4c85b0d 100644 --- a/src/graphvisualizer/containers/ContentResizerPane.java +++ b/src/graphvisualizer/containers/ContentResizerPane.java @@ -32,57 +32,53 @@ import javafx.scene.transform.Scale; /** - * * @author brunomnsilva */ public class ContentResizerPane extends Pane { - private final Node content; - private final DoubleProperty resizeFActor = new SimpleDoubleProperty(1); + private final Node content; + private final DoubleProperty resizeFActor = new SimpleDoubleProperty(1); - public ContentResizerPane(Node content) { - this.content = content; + public ContentResizerPane(Node content) { + this.content = content; - getChildren().add(content); + getChildren().add(content); - Scale scale = new Scale(1.0, 1.0); - content.getTransforms().add(scale); + Scale scale = new Scale(1.0, 1.0); + content.getTransforms().add(scale); - resizeFActor.addListener((ObservableValue observable, Number oldValue, Number newValue) -> { - scale.setX(newValue.doubleValue()); - scale.setY(newValue.doubleValue()); - requestLayout(); + resizeFActor.addListener( + (ObservableValue observable, Number oldValue, Number newValue) -> { + scale.setX(newValue.doubleValue()); + scale.setY(newValue.doubleValue()); + requestLayout(); }); - } - + } - @Override - protected void layoutChildren() { - Pos pos = Pos.TOP_LEFT; - double width = getWidth(); - double height = getHeight(); - double top = getInsets().getTop(); - double right = getInsets().getRight(); - double left = getInsets().getLeft(); - double bottom = getInsets().getBottom(); - double contentWidth = (width - left - right) / resizeFActor.get(); - double contentHeight = (height - top - bottom) / resizeFActor.get(); - layoutInArea(content, left, top, - contentWidth, contentHeight, - 0, null, - pos.getHpos(), - pos.getVpos()); - } + @Override + protected void layoutChildren() { + Pos pos = Pos.TOP_LEFT; + double width = getWidth(); + double height = getHeight(); + double top = getInsets().getTop(); + double right = getInsets().getRight(); + double left = getInsets().getLeft(); + double bottom = getInsets().getBottom(); + double contentWidth = (width - left - right) / resizeFActor.get(); + double contentHeight = (height - top - bottom) / resizeFActor.get(); + layoutInArea( + content, left, top, contentWidth, contentHeight, 0, null, pos.getHpos(), pos.getVpos()); + } - public final Double getResizeFactor() { - return resizeFActor.get(); - } + public final Double getResizeFactor() { + return resizeFActor.get(); + } - public final void setResizeFactor(Double resizeFactor) { - this.resizeFActor.set(resizeFactor); - } + public final void setResizeFactor(Double resizeFactor) { + this.resizeFActor.set(resizeFactor); + } - public final DoubleProperty resizeFactorProperty() { - return resizeFActor; - } + public final DoubleProperty resizeFactorProperty() { + return resizeFActor; + } } diff --git a/src/graphvisualizer/containers/ContentZoomPane.java b/src/graphvisualizer/containers/ContentZoomPane.java index 16842a4..80c6bb8 100644 --- a/src/graphvisualizer/containers/ContentZoomPane.java +++ b/src/graphvisualizer/containers/ContentZoomPane.java @@ -36,169 +36,164 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; import javafx.scene.text.Text; /** * This class provides zooming and panning for a JavaFX node. * - * It shows the zoom level with a slider control and reacts to mouse scrolls and - * mouse dragging. + *

It shows the zoom level with a slider control and reacts to mouse scrolls and mouse dragging. * - * The content node is out forward in the z-index so it can react to mouse - * events first. The node should consume any event not meant to propagate to - * this pane. + *

The content node is out forward in the z-index so it can react to mouse events first. The node + * should consume any event not meant to propagate to this pane. * * @author brunomnsilva */ public class ContentZoomPane extends BorderPane { - /* - PAN AND ZOOM - */ - private final DoubleProperty scaleFactorProperty = new ReadOnlyDoubleWrapper(1); - private final Node content; + /* + PAN AND ZOOM + */ + private final DoubleProperty scaleFactorProperty = new ReadOnlyDoubleWrapper(1); + private final Node content; - private static final double MIN_SCALE = 1; - private static final double MAX_SCALE = 5; - private static final double SCROLL_DELTA = 0.25; + private static final double MIN_SCALE = 1; + private static final double MAX_SCALE = 5; + private static final double SCROLL_DELTA = 0.25; - public ContentZoomPane(Node content) { - if (content == null) { - throw new IllegalArgumentException("Content cannot be null."); - } - - this.content = content; + public ContentZoomPane(Node content) { + if (content == null) { + throw new IllegalArgumentException("Content cannot be null."); + } - Node center = content; - content.toFront(); + this.content = content; - setCenter(center); - setTop(createSlider()); + Node center = content; + content.toFront(); - enablePanAndZoom(); - } + setCenter(center); + setTop(createSlider()); - private Node createSlider() { + enablePanAndZoom(); + } - Slider slider = new Slider(MIN_SCALE, MAX_SCALE, MIN_SCALE); - slider.setOrientation(Orientation.HORIZONTAL); - slider.setShowTickMarks(true); - slider.setShowTickLabels(true); - slider.setMajorTickUnit(SCROLL_DELTA); - slider.setMinorTickCount(1); - slider.setBlockIncrement(0.125f); - slider.setSnapToTicks(true); + private Node createSlider() { - Text label = new Text("Zoom"); + Slider slider = new Slider(MIN_SCALE, MAX_SCALE, MIN_SCALE); + slider.setOrientation(Orientation.HORIZONTAL); + slider.setShowTickMarks(true); + slider.setShowTickLabels(true); + slider.setMajorTickUnit(SCROLL_DELTA); + slider.setMinorTickCount(1); + slider.setBlockIncrement(0.125f); + slider.setSnapToTicks(true); - HBox paneSlider = new HBox(label, slider); + Text label = new Text("Zoom"); - paneSlider.setPadding(new Insets(10, 10, 10, 10)); - paneSlider.setSpacing(20); - HBox.setHgrow(slider, Priority.ALWAYS); + HBox paneSlider = new HBox(label, slider); - slider.valueProperty().bind(this.scaleFactorProperty()); - - return paneSlider; - } + paneSlider.setPadding(new Insets(10, 10, 10, 10)); + paneSlider.setSpacing(20); + HBox.setHgrow(slider, Priority.ALWAYS); - public void setContentPivot(double x, double y) { - content.setTranslateX(content.getTranslateX() - x); - content.setTranslateY(content.getTranslateY() - y); - } + slider.valueProperty().bind(this.scaleFactorProperty()); - public static double boundValue(double value, double min, double max) { + return paneSlider; + } - if (Double.compare(value, min) < 0) { - return min; - } + public void setContentPivot(double x, double y) { + content.setTranslateX(content.getTranslateX() - x); + content.setTranslateY(content.getTranslateY() - y); + } - if (Double.compare(value, max) > 0) { - return max; - } + public static double boundValue(double value, double min, double max) { - return value; + if (Double.compare(value, min) < 0) { + return min; } - private void enablePanAndZoom() { + if (Double.compare(value, max) > 0) { + return max; + } - setOnScroll((ScrollEvent event) -> { + return value; + } - double direction = event.getDeltaY() >= 0 ? 1 : -1; + private void enablePanAndZoom() { - double currentScale = scaleFactorProperty.getValue(); - double computedScale = currentScale + direction * SCROLL_DELTA; + setOnScroll( + (ScrollEvent event) -> { + double direction = event.getDeltaY() >= 0 ? 1 : -1; - computedScale = boundValue(computedScale, MIN_SCALE, MAX_SCALE); + double currentScale = scaleFactorProperty.getValue(); + double computedScale = currentScale + direction * SCROLL_DELTA; - if (currentScale != computedScale) { + computedScale = boundValue(computedScale, MIN_SCALE, MAX_SCALE); - content.setScaleX(computedScale); - content.setScaleY(computedScale); + if (currentScale != computedScale) { - if (computedScale == 1) { - content.setTranslateX(-getTranslateX()); - content.setTranslateY(-getTranslateY()); - } else { - scaleFactorProperty.setValue(computedScale); + content.setScaleX(computedScale); + content.setScaleY(computedScale); - Bounds bounds = content.localToScene(content.getBoundsInLocal()); - double f = (computedScale / currentScale) - 1; - double dx = (event.getX() - (bounds.getWidth() / 2 + bounds.getMinX())); - double dy = (event.getY() - (bounds.getHeight() / 2 + bounds.getMinY())); + if (computedScale == 1) { + content.setTranslateX(-getTranslateX()); + content.setTranslateY(-getTranslateY()); + } else { + scaleFactorProperty.setValue(computedScale); - setContentPivot(f * dx, f * dy); - } + Bounds bounds = content.localToScene(content.getBoundsInLocal()); + double f = (computedScale / currentScale) - 1; + double dx = (event.getX() - (bounds.getWidth() / 2 + bounds.getMinX())); + double dy = (event.getY() - (bounds.getHeight() / 2 + bounds.getMinY())); + setContentPivot(f * dx, f * dy); } - //do not propagate - event.consume(); - + } + // do not propagate + event.consume(); }); - final DragContext sceneDragContext = new DragContext(); + final DragContext sceneDragContext = new DragContext(); - setOnMousePressed((MouseEvent event) -> { + setOnMousePressed( + (MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + getScene().setCursor(Cursor.MOVE); - if (event.isSecondaryButtonDown()) { - getScene().setCursor(Cursor.MOVE); - - sceneDragContext.mouseAnchorX = event.getX(); - sceneDragContext.mouseAnchorY = event.getY(); - - sceneDragContext.translateAnchorX = content.getTranslateX(); - sceneDragContext.translateAnchorY = content.getTranslateY(); - } + sceneDragContext.mouseAnchorX = event.getX(); + sceneDragContext.mouseAnchorY = event.getY(); + sceneDragContext.translateAnchorX = content.getTranslateX(); + sceneDragContext.translateAnchorY = content.getTranslateY(); + } }); - setOnMouseReleased((MouseEvent event) -> { - getScene().setCursor(Cursor.DEFAULT); + setOnMouseReleased( + (MouseEvent event) -> { + getScene().setCursor(Cursor.DEFAULT); }); - setOnMouseDragged((MouseEvent event) -> { - if (event.isSecondaryButtonDown()) { - - content.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX); - content.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY); - } - }); - - } + setOnMouseDragged( + (MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { - public DoubleProperty scaleFactorProperty() { - return scaleFactorProperty; - } - - class DragContext { + content.setTranslateX( + sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX); + content.setTranslateY( + sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY); + } + }); + } - double mouseAnchorX; - double mouseAnchorY; + public DoubleProperty scaleFactorProperty() { + return scaleFactorProperty; + } - double translateAnchorX; - double translateAnchorY; + class DragContext { - } + double mouseAnchorX; + double mouseAnchorY; + double translateAnchorX; + double translateAnchorY; + } } diff --git a/src/graphvisualizer/containers/MenuPane.java b/src/graphvisualizer/containers/MenuPane.java index 8d7f43b..e8cf5cd 100644 --- a/src/graphvisualizer/containers/MenuPane.java +++ b/src/graphvisualizer/containers/MenuPane.java @@ -1,6 +1,10 @@ package graphvisualizer.containers; import graphvisualizer.graphview.SmartGraphPanel; +import java.io.File; +import java.net.MalformedURLException; +import java.util.logging.Level; +import java.util.logging.Logger; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; @@ -8,89 +12,85 @@ import javafx.scene.control.TextArea; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import java.io.File; -import java.net.MalformedURLException; -import java.util.logging.Level; -import java.util.logging.Logger; public class MenuPane extends VBox { - private Button strongConnectivityButton; - private Button cycleDetectionButton; - private Button shortestPathButton; - private Button resetButton; - private Button addVertexButton; - private TextArea statusBox; - - public MenuPane() { - setSpacing(40); - setAlignment(Pos.TOP_CENTER); - createButton(); - createStatusBox(); - loadStylesheet(); - } - - private void loadStylesheet() { - try { - getStylesheets().add(new File("menu.css").toURI().toURL().toExternalForm()); - this.getStyleClass().add("menu"); - } catch (MalformedURLException ex) { - Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - - private void createButton() { - strongConnectivityButton = new Button("STRONG CONNECTIVITY"); - strongConnectivityButton.getStyleClass().add("function-button"); - getChildren().add(strongConnectivityButton); - - cycleDetectionButton = new Button("CYCLE DETECTION"); - cycleDetectionButton.getStyleClass().add("function-button"); - getChildren().add(cycleDetectionButton); - - shortestPathButton = new Button("SHORTEST PATH"); - shortestPathButton.getStyleClass().add("function-button"); - getChildren().add(shortestPathButton); - - addVertexButton = new Button("ADD VERTEX"); - addVertexButton.getStyleClass().add("add-vertex-button"); - getChildren().add(addVertexButton); - - resetButton = new Button("RESET"); - resetButton.getStyleClass().add("reset-button"); - getChildren().add(resetButton); - } - - private void createStatusBox() { - statusBox = new TextArea(); - statusBox.setWrapText(true); - statusBox.setEditable(false); - statusBox.setFocusTraversable(false); - statusBox.getStyleClass().add("status-box"); - getChildren().add(statusBox); - setVgrow(statusBox, Priority.ALWAYS); - } - - public TextArea getStatusBox() { - return statusBox; - } - - public void setStrongConnectivityButtonAction(EventHandler actionEvent) { - strongConnectivityButton.setOnAction(actionEvent); - } - - public void setCycleDetectionButtonAction(EventHandler actionEvent) { - cycleDetectionButton.setOnAction(actionEvent); - } - - public void setShortestPathButtonAction(EventHandler actionEvent) { - shortestPathButton.setOnAction(actionEvent); - } - - public void setResetButtonAction(EventHandler actionEvent) { - resetButton.setOnAction(actionEvent); - } - - public void setAddVertexButtonAction(EventHandler actionEvent) { - addVertexButton.setOnAction(actionEvent); + private Button strongConnectivityButton; + private Button cycleDetectionButton; + private Button shortestPathButton; + private Button resetButton; + private Button addVertexButton; + private TextArea statusBox; + + public MenuPane() { + setSpacing(40); + setAlignment(Pos.TOP_CENTER); + createButton(); + createStatusBox(); + loadStylesheet(); + } + + private void loadStylesheet() { + try { + getStylesheets().add(new File("menu.css").toURI().toURL().toExternalForm()); + this.getStyleClass().add("menu"); + } catch (MalformedURLException ex) { + Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); } + } + + private void createButton() { + strongConnectivityButton = new Button("STRONG CONNECTIVITY"); + strongConnectivityButton.getStyleClass().add("function-button"); + getChildren().add(strongConnectivityButton); + + cycleDetectionButton = new Button("CYCLE DETECTION"); + cycleDetectionButton.getStyleClass().add("function-button"); + getChildren().add(cycleDetectionButton); + + shortestPathButton = new Button("SHORTEST PATH"); + shortestPathButton.getStyleClass().add("function-button"); + getChildren().add(shortestPathButton); + + addVertexButton = new Button("ADD VERTEX"); + addVertexButton.getStyleClass().add("add-vertex-button"); + getChildren().add(addVertexButton); + + resetButton = new Button("RESET"); + resetButton.getStyleClass().add("reset-button"); + getChildren().add(resetButton); + } + + private void createStatusBox() { + statusBox = new TextArea(); + statusBox.setWrapText(true); + statusBox.setEditable(false); + statusBox.setFocusTraversable(false); + statusBox.getStyleClass().add("status-box"); + getChildren().add(statusBox); + setVgrow(statusBox, Priority.ALWAYS); + } + + public TextArea getStatusBox() { + return statusBox; + } + + public void setStrongConnectivityButtonAction(EventHandler actionEvent) { + strongConnectivityButton.setOnAction(actionEvent); + } + + public void setCycleDetectionButtonAction(EventHandler actionEvent) { + cycleDetectionButton.setOnAction(actionEvent); + } + + public void setShortestPathButtonAction(EventHandler actionEvent) { + shortestPathButton.setOnAction(actionEvent); + } + + public void setResetButtonAction(EventHandler actionEvent) { + resetButton.setOnAction(actionEvent); + } + + public void setAddVertexButtonAction(EventHandler actionEvent) { + addVertexButton.setOnAction(actionEvent); + } } diff --git a/src/graphvisualizer/containers/SmartGraphDemoContainer.java b/src/graphvisualizer/containers/SmartGraphDemoContainer.java index 008eec1..c308ab4 100644 --- a/src/graphvisualizer/containers/SmartGraphDemoContainer.java +++ b/src/graphvisualizer/containers/SmartGraphDemoContainer.java @@ -23,26 +23,22 @@ */ package graphvisualizer.containers; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Menu; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; import graphvisualizer.graphview.SmartGraphPanel; +import javafx.scene.layout.BorderPane; /** - * * @author Bruno Silva */ public class SmartGraphDemoContainer extends BorderPane { - private MenuPane menu; + private MenuPane menu; - public SmartGraphDemoContainer(SmartGraphPanel graphView) { - setCenter(new ContentResizerPane(graphView)); - menu = new MenuPane(); - setRight(menu); - } + public SmartGraphDemoContainer(SmartGraphPanel graphView) { + setCenter(new ContentResizerPane(graphView)); + menu = new MenuPane(); + setRight(menu); + } - public MenuPane getMenu() { - return menu; - } + public MenuPane getMenu() { + return menu; + } } diff --git a/src/graphvisualizer/graph/AbstractPriorityQueue.java b/src/graphvisualizer/graph/AbstractPriorityQueue.java index 8310edd..ebd835f 100644 --- a/src/graphvisualizer/graph/AbstractPriorityQueue.java +++ b/src/graphvisualizer/graph/AbstractPriorityQueue.java @@ -2,50 +2,71 @@ import java.util.Comparator; -//An abstract base class to assist implementations of the PriorityQueue interface -public abstract class AbstractPriorityQueue implements PriorityQueue{ - - //start of nested PQEntry class - protected static class PQEntry implements Entry { - private K k; - private V v; - public PQEntry(K key, V value){ - k = key; - v = value; - } - //methods of the Entry interface - public K getKey() {return k;} - public V getValue() {return v;} - //utilities not exposed as part of the Entry interface - protected void setKey(K key) {k=key;} - protected void setValue(V value) {v=value;} +// An abstract base class to assist implementations of the PriorityQueue interface +public abstract class AbstractPriorityQueue implements PriorityQueue { + + // start of nested PQEntry class + protected static class PQEntry implements Entry { + private K k; + private V v; + + public PQEntry(K key, V value) { + k = key; + v = value; + } + + // methods of the Entry interface + public K getKey() { + return k; } - //end of nested PQEntry class - - //instance variable for an AbstractPriorityQueue - - //The comparator defining the ordering of keys in the priority queue - protected Comparator comp; - //Creates an empty priority queue using the given comparator to order keys - protected AbstractPriorityQueue(Comparator c) {comp=c;} - //Creates an empty priority queue based on the natural ordering of its keys - protected AbstractPriorityQueue() {this(new DefaultComparator());} - //Method for comparing two entries according to key - protected int compare(Entry a, Entry b){ - return comp.compare(a.getKey(),b.getKey()); + + public V getValue() { + return v; + } + + // utilities not exposed as part of the Entry interface + protected void setKey(K key) { + k = key; } - //Determines whether a key is valid - protected boolean checkKey(K key) throws IllegalArgumentException { - try{ - return(comp.compare(key,key)==0); //see if key can be compared to itself - } catch (ClassCastException e) { - throw new IllegalArgumentException("Incompatible key"); - } + protected void setValue(V value) { + v = value; } + } - //Tests whether the priority queue is empty - public boolean isEmpty() { - return size()==0; + // end of nested PQEntry class + + // instance variable for an AbstractPriorityQueue + + // The comparator defining the ordering of keys in the priority queue + protected Comparator comp; + + // Creates an empty priority queue using the given comparator to order keys + protected AbstractPriorityQueue(Comparator c) { + comp = c; + } + + // Creates an empty priority queue based on the natural ordering of its keys + protected AbstractPriorityQueue() { + this(new DefaultComparator()); + } + + // Method for comparing two entries according to key + protected int compare(Entry a, Entry b) { + return comp.compare(a.getKey(), b.getKey()); + } + + // Determines whether a key is valid + protected boolean checkKey(K key) throws IllegalArgumentException { + try { + return (comp.compare(key, key) == 0); // see if key can be compared to itself + } catch (ClassCastException e) { + throw new IllegalArgumentException("Incompatible key"); } -} \ No newline at end of file + } + + // Tests whether the priority queue is empty + public boolean isEmpty() { + return size() == 0; + } +} diff --git a/src/graphvisualizer/graph/AdaptablePriorityQueue.java b/src/graphvisualizer/graph/AdaptablePriorityQueue.java index bbf2d58..f2f4400 100644 --- a/src/graphvisualizer/graph/AdaptablePriorityQueue.java +++ b/src/graphvisualizer/graph/AdaptablePriorityQueue.java @@ -1,7 +1,8 @@ package graphvisualizer.graph; -//Interface for AdaptablePriorityQueue +// Interface for AdaptablePriorityQueue public interface AdaptablePriorityQueue { - void remove (Entry entry); //Removes the given entry from the priority queue - void replaceKey (Entry entry, K key); //Replaces the key of an entry -} \ No newline at end of file + void remove(Entry entry); // Removes the given entry from the priority queue + + void replaceKey(Entry entry, K key); // Replaces the key of an entry +} diff --git a/src/graphvisualizer/graph/AdjacencyMapDigraph.java b/src/graphvisualizer/graph/AdjacencyMapDigraph.java index a6da97d..dc9d723 100644 --- a/src/graphvisualizer/graph/AdjacencyMapDigraph.java +++ b/src/graphvisualizer/graph/AdjacencyMapDigraph.java @@ -3,348 +3,353 @@ import java.util.*; /** - * An adjacency map structure for a directed graph. A double map structure is used - * to represent the directed graph. The vertices are stored in the a map. The outgoing - * and incoming edges are stored in two maps of the corresponding vertex. The double - * map structure can be used since the vertices added are unique i.e. with unique - * String labels. This structure provides similar performance to an - * adjacency matrix where the {@link #getEdge(Vertex u, Vertex v)} method can achieve - * O(1) by performing lookup on the first and second map respectively. + * An adjacency map structure for a directed graph. A double map structure is used to represent the + * directed graph. The vertices are stored in the a map. The outgoing and incoming edges are stored + * in two maps of the corresponding vertex. The double map structure can be used since the vertices + * added are unique i.e. with unique String labels. This structure provides similar + * performance to an adjacency matrix where the {@link #getEdge(Vertex u, Vertex v)} method can + * achieve O(1) by performing lookup on the first and second map respectively. * * @param Vertex type * @param Edge type */ public class AdjacencyMapDigraph implements Graph { - /** - * Concrete implementation of {@link Vertex}. A {@link DVertex} object stores - * a {@link V} element and its edges. Edges are implemented as {@link LinkedHashMap} - * to provide O(1) lookup and also maintain the insertion order. - */ - private class DVertex implements Vertex { - private V element; - private Map, Edge> outgoingEdges, incomingEdges; - - public DVertex(V element) { - this.element = element; - outgoingEdges = new LinkedHashMap<>(); - incomingEdges = new LinkedHashMap<>(); - } - - @Override - public V element() { - return element; - } - - public Map, Edge> getOutgoingEdges() { - return outgoingEdges; - } - - public Map, Edge> getIncomingEdges() { - return incomingEdges; - } - - @Override - public String toString() { - return "Vertex{" + element + "}"; - } - - /* - 2 DVertex objects are equals if their elements are equal. - Override hashCode() if override equals() - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DVertex vertex = (DVertex) o; - return element.equals(vertex.element); - } - - @Override - public int hashCode() { - return Objects.hash(element); - } - } - - /** - * Concrete implementation of {@link Edge}. A {@link DEdge} object stores - * an {@link E} element and its {@link V} end vertices. - */ - private class DEdge implements Edge { - private E element; - private Vertex[] endVertices; - - public DEdge(Vertex u, Vertex v, E element) { - this.element = element; - this.endVertices = (Vertex[]) new Vertex[]{u, v}; - } - - @Override - public E element() { - return element; - } - - @Override - public Vertex[] vertices() { - return endVertices; - } - - @Override - public String toString() { - return "Edge from " + endVertices[0] + " to " + endVertices[1] + " with weight of " + element; - } - - /* - 2 DEdge objects are equals if their end vertices elements are equal. - Override hashCode() if override equals() - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DEdge edge = (DEdge) o; - return Arrays.equals(endVertices, edge.endVertices); - } - - @Override - public int hashCode() { - return Arrays.hashCode(endVertices); - } + /** + * Concrete implementation of {@link Vertex}. A {@link DVertex} object stores a {@link V} element + * and its edges. Edges are implemented as {@link LinkedHashMap} to provide O(1) lookup and also + * maintain the insertion order. + */ + private class DVertex implements Vertex { + private V element; + private Map, Edge> outgoingEdges, incomingEdges; + + public DVertex(V element) { + this.element = element; + outgoingEdges = new LinkedHashMap<>(); + incomingEdges = new LinkedHashMap<>(); } - private Map> vertices; - private Set> edges; - - /* - LinkedHashMap is used to provide a map interface and maintain the insertion order of - vertices and elements. This also provides a faster iteration in the Java for-each loop - than HashMap. - */ - public AdjacencyMapDigraph() { - this.vertices = new LinkedHashMap<>(); - this.edges = new LinkedHashSet<>(); + @Override + public V element() { + return element; } - public synchronized void clear() { - vertices.clear(); - edges.clear(); + public Map, Edge> getOutgoingEdges() { + return outgoingEdges; } - @Override - public int numVertices() { - return vertices.size(); + public Map, Edge> getIncomingEdges() { + return incomingEdges; } @Override - public int numEdges() { - return edges.size(); + public String toString() { + return "Vertex{" + element + "}"; } /* - synchronized methods are used to prevent thread interference since the graph visualization is - run using a non-javafx thread according to the author of JavaFX SmartGraph library. + 2 DVertex objects are equals if their elements are equal. + Override hashCode() if override equals() */ @Override - public synchronized Collection> vertices() { - return vertices.values(); - } - - @Override - public synchronized Collection> edges() { - return edges; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DVertex vertex = (DVertex) o; + return element.equals(vertex.element); } @Override - public synchronized Collection> incomingEdges(Vertex v) throws InvalidVertexException { - DVertex vertex = validateVertex(v); - return vertex.getIncomingEdges().values(); + public int hashCode() { + return Objects.hash(element); } - - @Override - public synchronized Collection> outgoingEdges(Vertex v) throws InvalidVertexException { - DVertex vertex = validateVertex(v); - return vertex.getOutgoingEdges().values(); + } + + /** + * Concrete implementation of {@link Edge}. A {@link DEdge} object stores an {@link E} element and + * its {@link V} end vertices. + */ + private class DEdge implements Edge { + private E element; + private Vertex[] endVertices; + + public DEdge(Vertex u, Vertex v, E element) { + this.element = element; + this.endVertices = (Vertex[]) new Vertex[] {u, v}; } @Override - public synchronized Vertex opposite(Vertex v, Edge e) throws InvalidVertexException, InvalidEdgeException { - DVertex vertex = validateVertex(v); - DEdge edge = validateEdge(e); - Vertex[] endVertices = edge.vertices(); - - if(endVertices[0].equals(vertex)) { - return endVertices[1]; - } - else if (endVertices[1].equals(vertex)) { - return endVertices[0]; - } - else { - throw new InvalidEdgeException("v is not incident to this edge."); - } + public E element() { + return element; } @Override - public synchronized Vertex insertVertex(V element) throws InvalidVertexException { - if (vertices.containsKey(element)) { - throw new InvalidVertexException("A vertex with this element already exists."); - } - else { - DVertex vertex = new DVertex(element); - vertices.put(element, vertex); - return vertex; - } + public Vertex[] vertices() { + return endVertices; } @Override - public synchronized Edge getEdge(Vertex u, Vertex v) throws InvalidVertexException { - DVertex startVertex = validateVertex(u); - return startVertex.getOutgoingEdges().get(v); + public String toString() { + return "Edge from " + endVertices[0] + " to " + endVertices[1] + " with weight of " + element; } + /* + 2 DEdge objects are equals if their end vertices elements are equal. + Override hashCode() if override equals() + */ @Override - public synchronized Edge insertEdge(Vertex u, Vertex v, E element) throws InvalidVertexException, InvalidEdgeException { - if(getEdge(u, v) == null) { - DVertex startVertex = validateVertex(u); - DVertex endVertex = validateVertex(v); - DEdge edge = new DEdge(startVertex, endVertex, element); - edges.add(edge); - startVertex.getOutgoingEdges().put(endVertex, edge); - endVertex.getIncomingEdges().put(startVertex, edge); - return edge; - } - else { - throw new InvalidEdgeException("Edge from u to v exists."); - } + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DEdge edge = (DEdge) o; + return Arrays.equals(endVertices, edge.endVertices); } @Override - public synchronized Edge insertEdge(V uElement, V vElement, E eElement) throws InvalidVertexException, InvalidEdgeException { - DVertex startVertex = validateVertex(vertices.get(uElement)); - DVertex endVertex = validateVertex(vertices.get(vElement)); - - if(getEdge(startVertex, endVertex) == null) { - DEdge edge = new DEdge(startVertex, endVertex, eElement); - edges.add(edge); - startVertex.getOutgoingEdges().put(endVertex, edge); - endVertex.getIncomingEdges().put(startVertex, edge); - return edge; - } - else { - throw new InvalidEdgeException("Edge from u to v exists."); - } + public int hashCode() { + return Arrays.hashCode(endVertices); } + } + + private Map> vertices; + private Set> edges; + + /* + LinkedHashMap is used to provide a map interface and maintain the insertion order of + vertices and elements. This also provides a faster iteration in the Java for-each loop + than HashMap. + */ + public AdjacencyMapDigraph() { + this.vertices = new LinkedHashMap<>(); + this.edges = new LinkedHashSet<>(); + } + + public synchronized void clear() { + vertices.clear(); + edges.clear(); + } + + @Override + public int numVertices() { + return vertices.size(); + } + + @Override + public int numEdges() { + return edges.size(); + } + + /* + synchronized methods are used to prevent thread interference since the graph visualization is + run using a non-javafx thread according to the author of JavaFX SmartGraph library. + */ + @Override + public synchronized Collection> vertices() { + return vertices.values(); + } + + @Override + public synchronized Collection> edges() { + return edges; + } + + @Override + public synchronized Collection> incomingEdges(Vertex v) + throws InvalidVertexException { + DVertex vertex = validateVertex(v); + return vertex.getIncomingEdges().values(); + } + + @Override + public synchronized Collection> outgoingEdges(Vertex v) + throws InvalidVertexException { + DVertex vertex = validateVertex(v); + return vertex.getOutgoingEdges().values(); + } + + @Override + public synchronized Vertex opposite(Vertex v, Edge e) + throws InvalidVertexException, InvalidEdgeException { + DVertex vertex = validateVertex(v); + DEdge edge = validateEdge(e); + Vertex[] endVertices = edge.vertices(); + + if (endVertices[0].equals(vertex)) { + return endVertices[1]; + } else if (endVertices[1].equals(vertex)) { + return endVertices[0]; + } else { + throw new InvalidEdgeException("v is not incident to this edge."); + } + } + + @Override + public synchronized Vertex insertVertex(V element) throws InvalidVertexException { + if (vertices.containsKey(element)) { + throw new InvalidVertexException("A vertex with this element already exists."); + } else { + DVertex vertex = new DVertex(element); + vertices.put(element, vertex); + return vertex; + } + } + + @Override + public synchronized Edge getEdge(Vertex u, Vertex v) throws InvalidVertexException { + DVertex startVertex = validateVertex(u); + return startVertex.getOutgoingEdges().get(v); + } + + @Override + public synchronized Edge insertEdge(Vertex u, Vertex v, E element) + throws InvalidVertexException, InvalidEdgeException { + if (getEdge(u, v) == null) { + DVertex startVertex = validateVertex(u); + DVertex endVertex = validateVertex(v); + DEdge edge = new DEdge(startVertex, endVertex, element); + edges.add(edge); + startVertex.getOutgoingEdges().put(endVertex, edge); + endVertex.getIncomingEdges().put(startVertex, edge); + return edge; + } else { + throw new InvalidEdgeException("Edge from u to v exists."); + } + } + + @Override + public synchronized Edge insertEdge(V uElement, V vElement, E eElement) + throws InvalidVertexException, InvalidEdgeException { + DVertex startVertex = validateVertex(vertices.get(uElement)); + DVertex endVertex = validateVertex(vertices.get(vElement)); + + if (getEdge(startVertex, endVertex) == null) { + DEdge edge = new DEdge(startVertex, endVertex, eElement); + edges.add(edge); + startVertex.getOutgoingEdges().put(endVertex, edge); + endVertex.getIncomingEdges().put(startVertex, edge); + return edge; + } else { + throw new InvalidEdgeException("Edge from u to v exists."); + } + } - @Override - public synchronized V removeVertex(Vertex v) throws InvalidVertexException { - DVertex vertex = validateVertex(v); - List> removedEdges = new LinkedList<>(); - - removedEdges.addAll(vertex.getIncomingEdges().values()); - removedEdges.addAll(vertex.getOutgoingEdges().values()); + @Override + public synchronized V removeVertex(Vertex v) throws InvalidVertexException { + DVertex vertex = validateVertex(v); + List> removedEdges = new LinkedList<>(); - for (Edge edge : removedEdges) { - removeEdge(edge); - } + removedEdges.addAll(vertex.getIncomingEdges().values()); + removedEdges.addAll(vertex.getOutgoingEdges().values()); - V element = v.element(); - vertices.remove(v.element()); - return element; + for (Edge edge : removedEdges) { + removeEdge(edge); } - @Override - public synchronized E removeEdge(Edge e) throws InvalidEdgeException { - DEdge edge = validateEdge(e); - Vertex[] endVertices = edge.vertices(); - DVertex startVertex = validateVertex(endVertices[0]); - DVertex endVertex = validateVertex(endVertices[1]); - - startVertex.getOutgoingEdges().remove(endVertex); - endVertex.getIncomingEdges().remove(startVertex); - E element = edge.element(); - edges.remove(edge); - return element; + V element = v.element(); + vertices.remove(v.element()); + return element; + } + + @Override + public synchronized E removeEdge(Edge e) throws InvalidEdgeException { + DEdge edge = validateEdge(e); + Vertex[] endVertices = edge.vertices(); + DVertex startVertex = validateVertex(endVertices[0]); + DVertex endVertex = validateVertex(endVertices[1]); + + startVertex.getOutgoingEdges().remove(endVertex); + endVertex.getIncomingEdges().remove(startVertex); + E element = edge.element(); + edges.remove(edge); + return element; + } + + public synchronized String generateRandomEdge(E randomElement) { + StringBuilder sb = new StringBuilder(); + if (numEdges() + == numVertices() * (numVertices() - 1)) // maximum no. of edges in digraph is n(n - 1) + return sb.append("Graph has maximum number of edges.\n").toString(); + + Random random; + List randomVertices = new ArrayList<>(vertices.keySet()); + V randomVertex; + DVertex startVertex, endVertex; + + for (; ; ) { // continue to generate a new random edge between random vertices if invalid + random = new Random(); + randomVertex = randomVertices.get(random.nextInt(randomVertices.size())); + startVertex = validateVertex(vertices.get(randomVertex)); + + random = new Random(); + randomVertex = randomVertices.get(random.nextInt(randomVertices.size())); + endVertex = validateVertex(vertices.get(randomVertex)); + + if (startVertex.equals(endVertex)) // self-loop is not allowed, retry + continue; + else if (getEdge(startVertex, endVertex) + == null) { // a random edge from u to v does not exist + return sb.append(insertEdge(startVertex, endVertex, randomElement)) + .append(" is generated.\n") + .toString(); + } else if (getEdge(endVertex, startVertex) + == null) { // a random edge from v to u does not exist + return sb.append(insertEdge(endVertex, startVertex, randomElement)) + .append(" is generated.\n") + .toString(); + } // random edges exist between u and v, retry } + } - public synchronized String generateRandomEdge(E randomElement) { - StringBuilder sb = new StringBuilder(); - if(numEdges() == numVertices()*(numVertices() - 1)) //maximum no. of edges in digraph is n(n - 1) - return sb.append("Graph has maximum number of edges.\n").toString(); - - Random random; - List randomVertices = new ArrayList<>(vertices.keySet()); - V randomVertex; - DVertex startVertex, endVertex; - - for ( ; ;) { //continue to generate a new random edge between random vertices if invalid - random = new Random(); - randomVertex = randomVertices.get(random.nextInt(randomVertices.size())); - startVertex = validateVertex(vertices.get(randomVertex)); - - random = new Random(); - randomVertex = randomVertices.get(random.nextInt(randomVertices.size())); - endVertex = validateVertex(vertices.get(randomVertex)); - - if (startVertex.equals(endVertex)) //self-loop is not allowed, retry - continue; - else if (getEdge(startVertex, endVertex) == null) { //a random edge from u to v does not exist - return sb.append(insertEdge(startVertex, endVertex, randomElement)).append(" is generated.\n").toString(); - } - else if (getEdge(endVertex, startVertex) == null) { //a random edge from v to u does not exist - return sb.append(insertEdge(endVertex, startVertex, randomElement)).append(" is generated.\n").toString(); - } //random edges exist between u and v, retry - } + /* validate that this vertex belongs to the graph */ + private DVertex validateVertex(Vertex v) throws InvalidVertexException { + if (v == null) throw new InvalidVertexException("Null vertex."); + + DVertex vertex; + + try { + vertex = (DVertex) v; + } catch (ClassCastException e) { + throw new InvalidVertexException("Not a vertex."); } - /* validate that this vertex belongs to the graph */ - private DVertex validateVertex(Vertex v) throws InvalidVertexException { - if(v == null) throw new InvalidVertexException("Null vertex."); - - DVertex vertex; - - try { - vertex = (DVertex) v; - } catch (ClassCastException e) { - throw new InvalidVertexException("Not a vertex."); - } - - if (!vertices.containsKey(vertex.element)) { - throw new InvalidVertexException("Vertex does not belong to this graph."); - } - return vertex; + if (!vertices.containsKey(vertex.element)) { + throw new InvalidVertexException("Vertex does not belong to this graph."); } + return vertex; + } + + /* validate that this edge belongs to the graph */ + private DEdge validateEdge(Edge e) throws InvalidEdgeException { + if (e == null) throw new InvalidEdgeException("Null edge."); + + DEdge edge; - /* validate that this edge belongs to the graph */ - private DEdge validateEdge(Edge e) throws InvalidEdgeException { - if(e == null) throw new InvalidEdgeException("Null edge."); - - DEdge edge; - - try { - edge = (DEdge) e; - } catch (ClassCastException ex) { - throw new InvalidVertexException("Not an adge."); - } - - if (!edges.contains(edge)) { - throw new InvalidEdgeException("Edge does not belong to this graph."); - } - return edge; + try { + edge = (DEdge) e; + } catch (ClassCastException ex) { + throw new InvalidVertexException("Not an adge."); } - @Override - public String toString() { - StringBuilder sb = new StringBuilder( - String.format("[Graph with %d vertices and %d edges]\n", numVertices(), numEdges()) - ); - - sb.append("--- Vertices: \n"); - for (Vertex v : vertices.values()) { - sb.append("\t").append(v.toString()).append("\n"); - } - sb.append("\n--- Edges: \n"); - for (Edge e : edges) { - sb.append("\t").append(e.toString()).append("\n"); - } - return sb.append("\n").toString(); + if (!edges.contains(edge)) { + throw new InvalidEdgeException("Edge does not belong to this graph."); + } + return edge; + } + + @Override + public String toString() { + StringBuilder sb = + new StringBuilder( + String.format("[Graph with %d vertices and %d edges]\n", numVertices(), numEdges())); + + sb.append("--- Vertices: \n"); + for (Vertex v : vertices.values()) { + sb.append("\t").append(v.toString()).append("\n"); + } + sb.append("\n--- Edges: \n"); + for (Edge e : edges) { + sb.append("\t").append(e.toString()).append("\n"); } -} \ No newline at end of file + return sb.append("\n").toString(); + } +} diff --git a/src/graphvisualizer/graph/DefaultComparator.java b/src/graphvisualizer/graph/DefaultComparator.java index 5fc83c7..17bce65 100644 --- a/src/graphvisualizer/graph/DefaultComparator.java +++ b/src/graphvisualizer/graph/DefaultComparator.java @@ -3,7 +3,7 @@ import java.util.Comparator; public class DefaultComparator implements Comparator { - public int compare(E a, E b) throws ClassCastException { - return ((Comparable) a).compareTo(b); - } + public int compare(E a, E b) throws ClassCastException { + return ((Comparable) a).compareTo(b); + } } diff --git a/src/graphvisualizer/graph/Edge.java b/src/graphvisualizer/graph/Edge.java index 5fe90a5..55e49bf 100644 --- a/src/graphvisualizer/graph/Edge.java +++ b/src/graphvisualizer/graph/Edge.java @@ -1,8 +1,9 @@ package graphvisualizer.graph; public interface Edge { - /* return element stored in edge */ - E element(); - /* return 2 end vertices connected by edge */ - Vertex[] vertices(); + /* return element stored in edge */ + E element(); + + /* return 2 end vertices connected by edge */ + Vertex[] vertices(); } diff --git a/src/graphvisualizer/graph/Entry.java b/src/graphvisualizer/graph/Entry.java index bf8da96..5c6b3b2 100644 --- a/src/graphvisualizer/graph/Entry.java +++ b/src/graphvisualizer/graph/Entry.java @@ -1,8 +1,9 @@ package graphvisualizer.graph; -//Interface for a key-value pair -public interface Entry { +// Interface for a key-value pair +public interface Entry { - K getKey(); //return the key stored in this entry - V getValue(); //return the value stored in this entry + K getKey(); // return the key stored in this entry + + V getValue(); // return the value stored in this entry } diff --git a/src/graphvisualizer/graph/Graph.java b/src/graphvisualizer/graph/Graph.java index 4497e63..2943295 100644 --- a/src/graphvisualizer/graph/Graph.java +++ b/src/graphvisualizer/graph/Graph.java @@ -3,30 +3,44 @@ import java.util.Collection; public interface Graph { - /* return total number of vertices */ - int numVertices(); - /* return total number of edges */ - int numEdges(); - /* return iteration of all vertices */ - Collection> vertices(); - /* return iteration of all edges */ - Collection> edges(); - /* return iteration of all incoming edges */ - Collection> incomingEdges(Vertex v) throws InvalidVertexException; - /* return iteration of all outgoing edges */ - Collection> outgoingEdges(Vertex v) throws InvalidVertexException; - /* return opposite vertex */ - Vertex opposite(Vertex v, Edge e) throws InvalidVertexException, InvalidEdgeException; - /* create and return a new vertex */ - Vertex insertVertex(V element) throws InvalidVertexException; - /* return a new edge from u to v or null if not exists */ - Edge getEdge(Vertex u, Vertex v) throws InvalidVertexException, InvalidEdgeException; - /* create and return a new edge using vertex instance */ - Edge insertEdge(Vertex u, Vertex v, E element) throws InvalidVertexException, InvalidEdgeException; - /* create and return a new edge using vertex element */ - Edge insertEdge(V uElement, V vElement, E eElement) throws InvalidVertexException, InvalidEdgeException; - /* remove a vertex and all incident edges */ - V removeVertex(Vertex v) throws InvalidVertexException; - /* remove an edge */ - E removeEdge(Edge e) throws InvalidEdgeException; -} \ No newline at end of file + /* return total number of vertices */ + int numVertices(); + + /* return total number of edges */ + int numEdges(); + + /* return iteration of all vertices */ + Collection> vertices(); + + /* return iteration of all edges */ + Collection> edges(); + + /* return iteration of all incoming edges */ + Collection> incomingEdges(Vertex v) throws InvalidVertexException; + + /* return iteration of all outgoing edges */ + Collection> outgoingEdges(Vertex v) throws InvalidVertexException; + + /* return opposite vertex */ + Vertex opposite(Vertex v, Edge e) throws InvalidVertexException, InvalidEdgeException; + + /* create and return a new vertex */ + Vertex insertVertex(V element) throws InvalidVertexException; + + /* return a new edge from u to v or null if not exists */ + Edge getEdge(Vertex u, Vertex v) throws InvalidVertexException, InvalidEdgeException; + + /* create and return a new edge using vertex instance */ + Edge insertEdge(Vertex u, Vertex v, E element) + throws InvalidVertexException, InvalidEdgeException; + + /* create and return a new edge using vertex element */ + Edge insertEdge(V uElement, V vElement, E eElement) + throws InvalidVertexException, InvalidEdgeException; + + /* remove a vertex and all incident edges */ + V removeVertex(Vertex v) throws InvalidVertexException; + + /* remove an edge */ + E removeEdge(Edge e) throws InvalidEdgeException; +} diff --git a/src/graphvisualizer/graph/HeapAdaptablePriorityQueue.java b/src/graphvisualizer/graph/HeapAdaptablePriorityQueue.java index d347f69..4eefd86 100644 --- a/src/graphvisualizer/graph/HeapAdaptablePriorityQueue.java +++ b/src/graphvisualizer/graph/HeapAdaptablePriorityQueue.java @@ -2,78 +2,88 @@ import java.util.Comparator; -//An implementation of an adaptable priority queue using an array-based heap -public class HeapAdaptablePriorityQueue extends HeapPriorityQueue implements AdaptablePriorityQueue { +// An implementation of an adaptable priority queue using an array-based heap +public class HeapAdaptablePriorityQueue extends HeapPriorityQueue + implements AdaptablePriorityQueue { - //nested Adaptable PQEntry class - protected static class AdaptablePQEntry extends PQEntry{ - private int index; - public AdaptablePQEntry(K key, V value, int j){ - super(key, value); - index=j; - } - public int getIndex(){return index;} - public void setIndex(int j) {index=j;} - } //end of nested Adaptable PQEntry class + // nested Adaptable PQEntry class + protected static class AdaptablePQEntry extends PQEntry { + private int index; - //Creates an empty adaptable priority queue using natural ordering of keys - public HeapAdaptablePriorityQueue(){super();} - //Creates an empty adaptable priority queue using the given comparator - public HeapAdaptablePriorityQueue(Comparator comp) { super(comp);} - - //Validates an entry to ensure it is location-aware - protected AdaptablePQEntry validate(Entry entry) throws IllegalArgumentException{ - if (!(entry instanceof AdaptablePQEntry)) - throw new IllegalArgumentException("Invalid Entry"); - AdaptablePQEntry locator = (AdaptablePQEntry) entry; - int j = locator.getIndex(); - if (j>=heap.size()||heap.get(j)!=locator) - throw new IllegalArgumentException("Invalid Entry"); - return locator; + public AdaptablePQEntry(K key, V value, int j) { + super(key, value); + index = j; } - //Exchanges the entries at indices i and j of the array list - protected void swap (int i, int j){ - super.swap(i,j); //perform the swap - ((AdaptablePQEntry) heap.get(i)).setIndex(i); //reset entry's index - ((AdaptablePQEntry) heap.get(j)).setIndex(j); //reset entry's index + public int getIndex() { + return index; } - //Restores the heap property by moving the entry at index j upward/downward - protected void bubble (int j){ - if (j > 0 && compare(heap.get(j),heap.get(parent(j))) < 0) - upheap(j); - else - downheap(j); + public void setIndex(int j) { + index = j; } + } // end of nested Adaptable PQEntry class - //Inserts a key-value pair and returns the entry created - public Entry insert (K key, V value) throws IllegalArgumentException{ - checkKey(key); - Entry newest = new AdaptablePQEntry<>(key,value,heap.size()); - heap.add(newest); //add to the end of list - upheap(heap.size()-1); //upheap newly added entry - return newest; - } + // Creates an empty adaptable priority queue using natural ordering of keys + public HeapAdaptablePriorityQueue() { + super(); + } - //Removes the given entry from the priority queue - public void remove (Entry entry) throws IllegalArgumentException{ - AdaptablePQEntry locator = validate(entry); - int j = locator.getIndex(); - if (j == heap.size()-1) //entry is at last position - heap.remove(heap.size()-1); //so just remove it - else { - swap(j, heap.size()-1); //swap entry to last position - heap.remove(heap.size()-1); //then remove it - bubble(j); //and fix entry displayed by the swap - } - } + // Creates an empty adaptable priority queue using the given comparator + public HeapAdaptablePriorityQueue(Comparator comp) { + super(comp); + } + + // Validates an entry to ensure it is location-aware + protected AdaptablePQEntry validate(Entry entry) throws IllegalArgumentException { + if (!(entry instanceof AdaptablePQEntry)) throw new IllegalArgumentException("Invalid Entry"); + AdaptablePQEntry locator = (AdaptablePQEntry) entry; + int j = locator.getIndex(); + if (j >= heap.size() || heap.get(j) != locator) + throw new IllegalArgumentException("Invalid Entry"); + return locator; + } - //Replaces the key of an entry - public void replaceKey (Entry entry, K key) throws IllegalArgumentException{ - AdaptablePQEntry locator = validate(entry); - checkKey(key); - locator.setKey(key); //method inherited from PQEntry - bubble(locator.getIndex()); //with new key, may need to move entry + // Exchanges the entries at indices i and j of the array list + protected void swap(int i, int j) { + super.swap(i, j); // perform the swap + ((AdaptablePQEntry) heap.get(i)).setIndex(i); // reset entry's index + ((AdaptablePQEntry) heap.get(j)).setIndex(j); // reset entry's index + } + + // Restores the heap property by moving the entry at index j upward/downward + protected void bubble(int j) { + if (j > 0 && compare(heap.get(j), heap.get(parent(j))) < 0) upheap(j); + else downheap(j); + } + + // Inserts a key-value pair and returns the entry created + public Entry insert(K key, V value) throws IllegalArgumentException { + checkKey(key); + Entry newest = new AdaptablePQEntry<>(key, value, heap.size()); + heap.add(newest); // add to the end of list + upheap(heap.size() - 1); // upheap newly added entry + return newest; + } + + // Removes the given entry from the priority queue + public void remove(Entry entry) throws IllegalArgumentException { + AdaptablePQEntry locator = validate(entry); + int j = locator.getIndex(); + if (j == heap.size() - 1) // entry is at last position + heap.remove(heap.size() - 1); // so just remove it + else { + swap(j, heap.size() - 1); // swap entry to last position + heap.remove(heap.size() - 1); // then remove it + bubble(j); // and fix entry displayed by the swap } + } + + // Replaces the key of an entry + public void replaceKey(Entry entry, K key) throws IllegalArgumentException { + AdaptablePQEntry locator = validate(entry); + checkKey(key); + locator.setKey(key); // method inherited from PQEntry + bubble(locator.getIndex()); // with new key, may need to move entry + } } diff --git a/src/graphvisualizer/graph/HeapPriorityQueue.java b/src/graphvisualizer/graph/HeapPriorityQueue.java index 923a6b2..e5d9613 100644 --- a/src/graphvisualizer/graph/HeapPriorityQueue.java +++ b/src/graphvisualizer/graph/HeapPriorityQueue.java @@ -3,96 +3,105 @@ import java.util.ArrayList; import java.util.Comparator; -//An implementation of a priority queue using an array-based heap -public class HeapPriorityQueue extends AbstractPriorityQueue{ - //primary collection of priority queue entries - protected ArrayList> heap = new ArrayList<>(); - //create an empty priority queue based on the natural ordering of its keys - public HeapPriorityQueue(){super();} - //create an empty priority queue using the given comparator to order keys - public HeapPriorityQueue(Comparator comp){super(comp);} - - //protected utilities - protected int parent(int j){ - return (j-1)/2; - } //truncating division - protected int left (int j){ - return 2*j+1; - } - protected int right (int j){ - return 2*j+2; - } - protected boolean hasLeft(int j){ - return left(j) < heap.size(); - } - protected boolean hasRight(int j){ - return right(j) < heap.size(); - } +// An implementation of a priority queue using an array-based heap +public class HeapPriorityQueue extends AbstractPriorityQueue { + // primary collection of priority queue entries + protected ArrayList> heap = new ArrayList<>(); - //Exchanges the entries at indices i and j of the array list - protected void swap(int i, int j){ - Entry temp = heap.get(i); - heap.set(i, heap.get(j)); - heap.set(j, temp); - } + // create an empty priority queue based on the natural ordering of its keys + public HeapPriorityQueue() { + super(); + } - //Moves the entry at index j higher, if necessary, to restore the heap property - protected void upheap (int j){ - while (j>0){ //continue until reaching root (or break statement) - int p = parent(j); - if (compare(heap.get(j), heap.get(p)) >= 0) break; //heap property verified - swap(j, p); - j = p; //continue from the parent's location - } - } + // create an empty priority queue using the given comparator to order keys + public HeapPriorityQueue(Comparator comp) { + super(comp); + } - //Moves the entry at index j lower, if necessary, to restore the heap property - protected void downheap (int j){ - while (hasLeft(j)){ //continue to bottom (or break statement) - int leftIndex = left(j); - int smallChildIndex = leftIndex; //although right may be smaller - if (hasRight(j)){ - int rightIndex = right(j); - if (compare(heap.get(leftIndex), heap.get(rightIndex)) > 0) - smallChildIndex = rightIndex; //right child is smaller - } - if (compare(heap.get(smallChildIndex), heap.get(j)) >= 0) - break; //heap property has been restored - swap(j, smallChildIndex); - j = smallChildIndex; //continue at position of the child - } - } + // protected utilities + protected int parent(int j) { + return (j - 1) / 2; + } // truncating division - //Returns the number of items in the priority queue - public int size(){ - return heap.size(); - } + protected int left(int j) { + return 2 * j + 1; + } - //Returns (but does not remove) an entry with minimal key (if any) - public Entry min() { - if (heap.isEmpty()) - return null; - return heap.get(0); + protected int right(int j) { + return 2 * j + 2; + } + + protected boolean hasLeft(int j) { + return left(j) < heap.size(); + } + + protected boolean hasRight(int j) { + return right(j) < heap.size(); + } + + // Exchanges the entries at indices i and j of the array list + protected void swap(int i, int j) { + Entry temp = heap.get(i); + heap.set(i, heap.get(j)); + heap.set(j, temp); + } + + // Moves the entry at index j higher, if necessary, to restore the heap property + protected void upheap(int j) { + while (j > 0) { // continue until reaching root (or break statement) + int p = parent(j); + if (compare(heap.get(j), heap.get(p)) >= 0) break; // heap property verified + swap(j, p); + j = p; // continue from the parent's location } + } - //Inserts a key-value pair and returns the entry created - public Entry insert(K key, V value) throws IllegalArgumentException { - checkKey(key); //auxiliary key-checking method (could throw exception) - Entry newest = new PQEntry<>(key, value); - heap.add(newest); //add to the end of the list - upheap(heap.size()-1); //upheap newly added entry - return newest; + // Moves the entry at index j lower, if necessary, to restore the heap property + protected void downheap(int j) { + while (hasLeft(j)) { // continue to bottom (or break statement) + int leftIndex = left(j); + int smallChildIndex = leftIndex; // although right may be smaller + if (hasRight(j)) { + int rightIndex = right(j); + if (compare(heap.get(leftIndex), heap.get(rightIndex)) > 0) + smallChildIndex = rightIndex; // right child is smaller + } + if (compare(heap.get(smallChildIndex), heap.get(j)) >= 0) + break; // heap property has been restored + swap(j, smallChildIndex); + j = smallChildIndex; // continue at position of the child } + } + + // Returns the number of items in the priority queue + public int size() { + return heap.size(); + } + + // Returns (but does not remove) an entry with minimal key (if any) + public Entry min() { + if (heap.isEmpty()) return null; + return heap.get(0); + } + + // Inserts a key-value pair and returns the entry created + public Entry insert(K key, V value) throws IllegalArgumentException { + checkKey(key); // auxiliary key-checking method (could throw exception) + Entry newest = new PQEntry<>(key, value); + heap.add(newest); // add to the end of the list + upheap(heap.size() - 1); // upheap newly added entry + return newest; + } - //Removes and returns an entry with minimal key (if any) - public Entry removeMin() { - if (heap.isEmpty()) { - return null; - } - Entry answer = heap.get(0); - swap(0,heap.size()-1); - heap.remove(heap.size()-1); - downheap(0); - return answer; + // Removes and returns an entry with minimal key (if any) + public Entry removeMin() { + if (heap.isEmpty()) { + return null; } + Entry answer = heap.get(0); + swap(0, heap.size() - 1); + heap.remove(heap.size() - 1); + downheap(0); + return answer; + } } diff --git a/src/graphvisualizer/graph/InvalidEdgeException.java b/src/graphvisualizer/graph/InvalidEdgeException.java index f7d4bd6..c7cfacf 100644 --- a/src/graphvisualizer/graph/InvalidEdgeException.java +++ b/src/graphvisualizer/graph/InvalidEdgeException.java @@ -1,11 +1,11 @@ package graphvisualizer.graph; public class InvalidEdgeException extends RuntimeException { - public InvalidEdgeException() { - super("The edge is invalid or does not belong to this graph."); - } + public InvalidEdgeException() { + super("The edge is invalid or does not belong to this graph."); + } - public InvalidEdgeException(String string) { - super(string); - } + public InvalidEdgeException(String string) { + super(string); + } } diff --git a/src/graphvisualizer/graph/InvalidVertexException.java b/src/graphvisualizer/graph/InvalidVertexException.java index c38d191..ea598e8 100644 --- a/src/graphvisualizer/graph/InvalidVertexException.java +++ b/src/graphvisualizer/graph/InvalidVertexException.java @@ -1,11 +1,11 @@ package graphvisualizer.graph; public class InvalidVertexException extends RuntimeException { - public InvalidVertexException() { - super("The vertex is invalid or does not belong to this graph."); - } + public InvalidVertexException() { + super("The vertex is invalid or does not belong to this graph."); + } - public InvalidVertexException(String string) { - super(string); - } + public InvalidVertexException(String string) { + super(string); + } } diff --git a/src/graphvisualizer/graph/PriorityQueue.java b/src/graphvisualizer/graph/PriorityQueue.java index de09ca0..98a9d1c 100644 --- a/src/graphvisualizer/graph/PriorityQueue.java +++ b/src/graphvisualizer/graph/PriorityQueue.java @@ -1,10 +1,14 @@ package graphvisualizer.graph; -//Interface for the priority queue ADT -public interface PriorityQueue { - int size(); - boolean isEmpty(); - Entry insert(K key, V value) throws IllegalArgumentException; - Entry min(); - Entry removeMin(); +// Interface for the priority queue ADT +public interface PriorityQueue { + int size(); + + boolean isEmpty(); + + Entry insert(K key, V value) throws IllegalArgumentException; + + Entry min(); + + Entry removeMin(); } diff --git a/src/graphvisualizer/graph/Vertex.java b/src/graphvisualizer/graph/Vertex.java index 2046c29..74f5539 100644 --- a/src/graphvisualizer/graph/Vertex.java +++ b/src/graphvisualizer/graph/Vertex.java @@ -1,6 +1,6 @@ package graphvisualizer.graph; public interface Vertex { - /* return element stored in vertex */ - V element(); + /* return element stored in vertex */ + V element(); } diff --git a/src/graphvisualizer/graphalgorithms/CycleDetection.java b/src/graphvisualizer/graphalgorithms/CycleDetection.java index 1e0e26a..ee12499 100644 --- a/src/graphvisualizer/graphalgorithms/CycleDetection.java +++ b/src/graphvisualizer/graphalgorithms/CycleDetection.java @@ -7,118 +7,151 @@ import java.util.*; /** - * A cycle detection algorithm that can be used on a directed graph - * that is strongly connected or not strongly connected with random - * generation of directed edges. + * A cycle detection algorithm that can be used on a directed graph that is strongly connected or + * not strongly connected with random generation of directed edges. */ public class CycleDetection { - /* - These 2 variables are declared as global variables since it's hard to - pass primitive datatype by reference as they are immutable. The only - way to pass them by reference without using other datatype in external - libraries is to create a class to wrap the primitive datatype, however, - since this is not our primary concern in detecting a cycle, both variables - are merely declared as global variables. - */ - private static boolean isCyclic; - private static int cycleCount; + /* + These 2 variables are declared as global variables since it's hard to + pass primitive datatype by reference as they are immutable. The only + way to pass them by reference without using other datatype in external + libraries is to create a class to wrap the primitive datatype, however, + since this is not our primary concern in detecting a cycle, both variables + are merely declared as global variables. + */ + private static boolean isCyclic; + private static int cycleCount; - /** - * Construct a visualization of cycle detection algorithm in a directed graph (strongly connected/not - * strongly connected) referenced by digraph. Random directed edges will be generated until - * a cycle is found. - * - * @param digraph Directed graph - * @param graphView Graph visualization object - * @return A general description of the process and result of the cycle detection algorithm - */ - public static String start(AdjacencyMapDigraph digraph, SmartGraphPanel graphView) { - LinkedList> vertices = new LinkedList<>(); - Set> visitedVertices = new HashSet<>(); - Set> onStackVertices = new HashSet<>(); - Map, Vertex> parentsOfVertices = new HashMap<>(); - LinkedList>> foundCycles = new LinkedList<>(); - isCyclic = false; - cycleCount = 0; - StringBuilder sb = new StringBuilder(); - StringBuilder buf = new StringBuilder(); + /** + * Construct a visualization of cycle detection algorithm in a directed graph (strongly + * connected/not strongly connected) referenced by digraph. Random directed edges + * will be generated until a cycle is found. + * + * @param digraph Directed graph + * @param graphView Graph visualization object + * @return A general description of the process and result of the cycle detection algorithm + */ + public static String start( + AdjacencyMapDigraph digraph, SmartGraphPanel graphView) { + LinkedList> vertices = new LinkedList<>(); + Set> visitedVertices = new HashSet<>(); + Set> onStackVertices = new HashSet<>(); + Map, Vertex> parentsOfVertices = new HashMap<>(); + LinkedList>> foundCycles = new LinkedList<>(); + isCyclic = false; + cycleCount = 0; + StringBuilder sb = new StringBuilder(); + StringBuilder buf = new StringBuilder(); - while(!isCyclic) { - vertices.addAll(digraph.vertices()); + while (!isCyclic) { + vertices.addAll(digraph.vertices()); - while (!vertices.isEmpty()) { //continue perform DFS if there are unvisited vertices, e.g. when there are several strongly connected components - checkCycle(digraph, vertices.remove(), vertices, visitedVertices, onStackVertices, parentsOfVertices, foundCycles, buf, graphView); - visitedVertices.clear(); parentsOfVertices.clear(); - } - if(!isCyclic) { - sb.append(digraph.generateRandomEdge(new Random().nextInt(20) + 1)); //generate a random edge - } - } - return sb.append(String.format("\n[Cycle Detection] [%d cycle(s) found.]", cycleCount)).append(buf).append("\n").toString(); + while (!vertices + .isEmpty()) { // continue perform DFS if there are unvisited vertices, e.g. when there are + // several strongly connected components + checkCycle( + digraph, + vertices.remove(), + vertices, + visitedVertices, + onStackVertices, + parentsOfVertices, + foundCycles, + buf, + graphView); + visitedVertices.clear(); + parentsOfVertices.clear(); + } + if (!isCyclic) { + sb.append( + digraph.generateRandomEdge(new Random().nextInt(20) + 1)); // generate a random edge + } } + return sb.append(String.format("\n[Cycle Detection] [%d cycle(s) found.]", cycleCount)) + .append(buf) + .append("\n") + .toString(); + } - /** - * A DFS method to detect the existence of a cycle in the directed graph. Random directed edges will be generated until - * a cycle is found. This should be called repeatedly by start(AdjacencyMapDigraph, SmartGraphPanel). - * - * @param digraph Directed graph - * @param startVertex Starting vertex of a DFS search - * @param vertices List of vertices of the directed graph - * @param visitedVertices Set of visited vertices - * @param onStackVertices Set of vertices on the recursion stack - * @param parentsOfVertices Map that maps visited vertices to their parents - * @param foundCycles List of found cycles - * @param buf StringBuilder object to print the resulting cycles - * @param graphView Graph visualization object - */ - private static void checkCycle(AdjacencyMapDigraph digraph, Vertex startVertex, LinkedList> vertices, - Set> visitedVertices, Set> onStackVertices, Map, Vertex> parentsOfVertices, - LinkedList>> foundCycles, StringBuilder buf, SmartGraphPanel graphView) - { - vertices.remove(startVertex); - visitedVertices.add(startVertex); - onStackVertices.add(startVertex); + /** + * A DFS method to detect the existence of a cycle in the directed graph. Random directed edges + * will be generated until a cycle is found. This should be called repeatedly by + * start(AdjacencyMapDigraph, SmartGraphPanel). + * + * @param digraph Directed graph + * @param startVertex Starting vertex of a DFS search + * @param vertices List of vertices of the directed graph + * @param visitedVertices Set of visited vertices + * @param onStackVertices Set of vertices on the recursion stack + * @param parentsOfVertices Map that maps visited vertices to their parents + * @param foundCycles List of found cycles + * @param buf StringBuilder object to print the resulting cycles + * @param graphView Graph visualization object + */ + private static void checkCycle( + AdjacencyMapDigraph digraph, + Vertex startVertex, + LinkedList> vertices, + Set> visitedVertices, + Set> onStackVertices, + Map, Vertex> parentsOfVertices, + LinkedList>> foundCycles, + StringBuilder buf, + SmartGraphPanel graphView) { + vertices.remove(startVertex); + visitedVertices.add(startVertex); + onStackVertices.add(startVertex); - for (Edge edge : digraph.outgoingEdges(startVertex)) { - Vertex vertex = digraph.opposite(startVertex, edge); //obtain the child of the vertex + for (Edge edge : digraph.outgoingEdges(startVertex)) { + Vertex vertex = digraph.opposite(startVertex, edge); // obtain the child of the vertex - if(!visitedVertices.contains(vertex)) { - parentsOfVertices.put(vertex, startVertex); //store the tree edge connecting the vertex - checkCycle(digraph, vertex, vertices, visitedVertices, onStackVertices, parentsOfVertices, foundCycles, buf, graphView); //recursively perform DFS search - } - else if (onStackVertices.contains(vertex)) { - Vertex vertexInCycle; - Stack> cycleStack = new Stack<>(); - boolean isRepeated = false; - isCyclic = true; + if (!visitedVertices.contains(vertex)) { + parentsOfVertices.put(vertex, startVertex); // store the tree edge connecting the vertex + checkCycle( + digraph, + vertex, + vertices, + visitedVertices, + onStackVertices, + parentsOfVertices, + foundCycles, + buf, + graphView); // recursively perform DFS search + } else if (onStackVertices.contains(vertex)) { + Vertex vertexInCycle; + Stack> cycleStack = new Stack<>(); + boolean isRepeated = false; + isCyclic = true; - for (vertexInCycle = startVertex; !vertexInCycle.equals(vertex); vertexInCycle = parentsOfVertices.get(vertexInCycle)) { - cycleStack.push(vertexInCycle); - } - cycleStack.push(vertex); - Set> newCycle = new HashSet<>(cycleStack); + for (vertexInCycle = startVertex; + !vertexInCycle.equals(vertex); + vertexInCycle = parentsOfVertices.get(vertexInCycle)) { + cycleStack.push(vertexInCycle); + } + cycleStack.push(vertex); + Set> newCycle = new HashSet<>(cycleStack); - for (Set> foundCycle : foundCycles) { - if (newCycle.equals(foundCycle)) { //Check if this is a repeated cycle found before - isRepeated = true; - break; - } - } - if(!isRepeated) { //Not a repeated cycle found before - ++cycleCount; - foundCycles.add(newCycle); - buf.append("\nCycle of length ").append(cycleStack.size()).append(": "); + for (Set> foundCycle : foundCycles) { + if (newCycle.equals(foundCycle)) { // Check if this is a repeated cycle found before + isRepeated = true; + break; + } + } + if (!isRepeated) { // Not a repeated cycle found before + ++cycleCount; + foundCycles.add(newCycle); + buf.append("\nCycle of length ").append(cycleStack.size()).append(": "); - while(!cycleStack.isEmpty()) { - vertexInCycle = cycleStack.pop(); - graphView.getStylableVertex(vertexInCycle).setStyleClass("highlightedVertex"); - graphView.update(); - buf.append(vertexInCycle).append(" --> "); - } - buf.append(vertex).append("\n"); - } - } + while (!cycleStack.isEmpty()) { + vertexInCycle = cycleStack.pop(); + graphView.getStylableVertex(vertexInCycle).setStyleClass("highlightedVertex"); + graphView.update(); + buf.append(vertexInCycle).append(" --> "); + } + buf.append(vertex).append("\n"); } - onStackVertices.remove(startVertex); + } } + onStackVertices.remove(startVertex); + } } diff --git a/src/graphvisualizer/graphalgorithms/Main.java b/src/graphvisualizer/graphalgorithms/Main.java index 3ad8f19..4a96d66 100644 --- a/src/graphvisualizer/graphalgorithms/Main.java +++ b/src/graphvisualizer/graphalgorithms/Main.java @@ -1,174 +1,195 @@ package graphvisualizer.graphalgorithms; import graphvisualizer.containers.MenuPane; +import graphvisualizer.containers.SmartGraphDemoContainer; +import graphvisualizer.graph.AdjacencyMapDigraph; +import graphvisualizer.graph.Graph; import graphvisualizer.graph.Vertex; import graphvisualizer.graphview.*; -import graphvisualizer.graph.Graph; +import java.util.LinkedList; +import java.util.Queue; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.StageStyle; -import graphvisualizer.containers.SmartGraphDemoContainer; -import graphvisualizer.graph.AdjacencyMapDigraph; - -import java.util.LinkedList; -import java.util.Queue; public class Main extends Application { - private static int selectedVerticesCount = 0; - private static Vertex startVertex; - private static Vertex endVertex; - - @Override - public void start(Stage ignored) { - final AdjacencyMapDigraph defaultDigraph = new AdjacencyMapDigraph<>(); - createDefaultDigraph(defaultDigraph); - SmartPlacementStrategy strategy = new SmartCircularSortedPlacementStrategy(); - SmartGraphPanel graphView = new SmartGraphPanel<>(defaultDigraph, strategy); - SmartGraphDemoContainer smartGraphDemoContainer = new SmartGraphDemoContainer(graphView); - MenuPane menu = smartGraphDemoContainer.getMenu(); - final Queue addedVertices = new LinkedList<>(); - createAddedVertices(addedVertices); - - Scene scene = new Scene(smartGraphDemoContainer, 1024, 768); - Stage stage = new Stage(StageStyle.DECORATED); - stage.setOpacity(0.0); - stage.setTitle("Graph Algorithms Visualization"); - stage.setScene(scene); - stage.setMaximized(true); - stage.show(); - stage.setOpacity(1.0); - graphView.init(); - graphView.setAutomaticLayout(true); - - menu.getStatusBox().setText("[Graph Algorithms Visualization]\n1. Click the buttons above to start the graph algorithms.\n" + - "2. Double click to select the vertices and the edges.\n3. Click \"ADD VERTEX\" to add up to 5 additional vertices.\n" + - "4. Click \"RESET\" to reset the default graph.\n\n" + defaultDigraph); - - graphView.setVertexDoubleClickAction(graphVertex -> { - switch (selectedVerticesCount) { - case 0: - for (Vertex vertex : defaultDigraph.vertices()) { - graphView.getStylableVertex(vertex).setStyleClass("vertex"); - } - startVertex = graphVertex.getUnderlyingVertex(); - graphVertex.setStyleClass("selectedVertex"); - ++selectedVerticesCount; - menu.getStatusBox().appendText(graphVertex.getUnderlyingVertex() + " is selected as starting vertex.\n\n"); - break; - - case 1: - if(!graphVertex.getUnderlyingVertex().equals(startVertex)) { - endVertex = graphVertex.getUnderlyingVertex(); - graphVertex.setStyleClass("selectedVertex"); - ++selectedVerticesCount; - menu.getStatusBox().appendText(graphVertex.getUnderlyingVertex() + " is selected as ending vertex.\n\n"); - } - break; - - case 2: - if(!graphVertex.getUnderlyingVertex().equals(startVertex) && !graphVertex.getUnderlyingVertex().equals(endVertex)) { - graphView.getStylableVertex(startVertex).setStyleClass("vertex"); - startVertex = endVertex; - endVertex = graphVertex.getUnderlyingVertex(); - graphVertex.setStyleClass("selectedVertex"); - menu.getStatusBox().appendText(startVertex + " is selected as new starting vertex.\n\n"); - menu.getStatusBox().appendText(endVertex + " is selected as new ending vertex.\n\n"); - } - default: - } + private static int selectedVerticesCount = 0; + private static Vertex startVertex; + private static Vertex endVertex; + + @Override + public void start(Stage ignored) { + final AdjacencyMapDigraph defaultDigraph = new AdjacencyMapDigraph<>(); + createDefaultDigraph(defaultDigraph); + SmartPlacementStrategy strategy = new SmartCircularSortedPlacementStrategy(); + SmartGraphPanel graphView = new SmartGraphPanel<>(defaultDigraph, strategy); + SmartGraphDemoContainer smartGraphDemoContainer = new SmartGraphDemoContainer(graphView); + MenuPane menu = smartGraphDemoContainer.getMenu(); + final Queue addedVertices = new LinkedList<>(); + createAddedVertices(addedVertices); + + Scene scene = new Scene(smartGraphDemoContainer, 1024, 768); + Stage stage = new Stage(StageStyle.DECORATED); + stage.setOpacity(0.0); + stage.setTitle("Graph Algorithms Visualization"); + stage.setScene(scene); + stage.setMaximized(true); + stage.show(); + stage.setOpacity(1.0); + graphView.init(); + graphView.setAutomaticLayout(true); + + menu.getStatusBox() + .setText( + "[Graph Algorithms Visualization]\n" + + "1. Click the buttons above to start the graph algorithms.\n" + + "2. Double click to select the vertices and the edges.\n" + + "3. Click \"ADD VERTEX\" to add up to 5 additional vertices.\n" + + "4. Click \"RESET\" to reset the default graph.\n\n" + + defaultDigraph); + + graphView.setVertexDoubleClickAction( + graphVertex -> { + switch (selectedVerticesCount) { + case 0: + for (Vertex vertex : defaultDigraph.vertices()) { + graphView.getStylableVertex(vertex).setStyleClass("vertex"); + } + startVertex = graphVertex.getUnderlyingVertex(); + graphVertex.setStyleClass("selectedVertex"); + ++selectedVerticesCount; + menu.getStatusBox() + .appendText( + graphVertex.getUnderlyingVertex() + " is selected as starting vertex.\n\n"); + break; + + case 1: + if (!graphVertex.getUnderlyingVertex().equals(startVertex)) { + endVertex = graphVertex.getUnderlyingVertex(); + graphVertex.setStyleClass("selectedVertex"); + ++selectedVerticesCount; + menu.getStatusBox() + .appendText( + graphVertex.getUnderlyingVertex() + " is selected as ending vertex.\n\n"); + } + break; + + case 2: + if (!graphVertex.getUnderlyingVertex().equals(startVertex) + && !graphVertex.getUnderlyingVertex().equals(endVertex)) { + graphView.getStylableVertex(startVertex).setStyleClass("vertex"); + startVertex = endVertex; + endVertex = graphVertex.getUnderlyingVertex(); + graphVertex.setStyleClass("selectedVertex"); + menu.getStatusBox() + .appendText(startVertex + " is selected as new starting vertex.\n\n"); + menu.getStatusBox() + .appendText(endVertex + " is selected as new ending vertex.\n\n"); + } + default: + } }); - graphView.setEdgeDoubleClickAction(graphEdge -> menu.getStatusBox().appendText(graphEdge.getUnderlyingEdge() + " is selected.\n\n")); + graphView.setEdgeDoubleClickAction( + graphEdge -> + menu.getStatusBox().appendText(graphEdge.getUnderlyingEdge() + " is selected.\n\n")); + + menu.setStrongConnectivityButtonAction( + event -> { + for (Vertex vertex : defaultDigraph.vertices()) { + graphView.getStylableVertex(vertex).setStyleClass("vertex"); + } + menu.getStatusBox().appendText(StrongConnectivity.start(defaultDigraph, graphView)); + selectedVerticesCount = 0; + startVertex = endVertex = null; + }); - menu.setStrongConnectivityButtonAction(event -> { - for (Vertex vertex : defaultDigraph.vertices()) { - graphView.getStylableVertex(vertex).setStyleClass("vertex"); - } - menu.getStatusBox().appendText(StrongConnectivity.start(defaultDigraph, graphView)); - selectedVerticesCount = 0; - startVertex = endVertex = null; + menu.setCycleDetectionButtonAction( + event -> { + for (Vertex vertex : defaultDigraph.vertices()) { + graphView.getStylableVertex(vertex).setStyleClass("vertex"); + } + menu.getStatusBox().appendText(CycleDetection.start(defaultDigraph, graphView)); + selectedVerticesCount = 0; + startVertex = endVertex = null; }); - menu.setCycleDetectionButtonAction(event -> { - for (Vertex vertex : defaultDigraph.vertices()) { - graphView.getStylableVertex(vertex).setStyleClass("vertex"); - } - menu.getStatusBox().appendText(CycleDetection.start(defaultDigraph, graphView)); + menu.setShortestPathButtonAction( + event -> { + if (startVertex != null && endVertex != null) { + menu.getStatusBox() + .appendText(ShortestPath.start(defaultDigraph, graphView, startVertex, endVertex)); selectedVerticesCount = 0; startVertex = endVertex = null; + } else { + menu.getStatusBox() + .appendText( + "Please select two vertices to compute the shortest path between them.\n\n"); + } }); - menu.setShortestPathButtonAction(event -> { - if(startVertex != null && endVertex != null) { - menu.getStatusBox().appendText(ShortestPath.start(defaultDigraph, graphView, startVertex, endVertex)); - selectedVerticesCount = 0; - startVertex = endVertex = null; - } - else{ - menu.getStatusBox().appendText("Please select two vertices to compute the shortest path between them.\n\n"); - } - }); - - menu.setAddVertexButtonAction(event -> { - if (!addedVertices.isEmpty()) { - String addedVertex = addedVertices.remove(); - defaultDigraph.insertVertex(addedVertex); - graphView.update(); - menu.getStatusBox().appendText("Vertex{" + addedVertex + "} has been added.\n\n"); - } - else { - menu.getStatusBox().appendText("No additional vertices will be added.\n\n"); - } + menu.setAddVertexButtonAction( + event -> { + if (!addedVertices.isEmpty()) { + String addedVertex = addedVertices.remove(); + defaultDigraph.insertVertex(addedVertex); + graphView.update(); + menu.getStatusBox().appendText("Vertex{" + addedVertex + "} has been added.\n\n"); + } else { + menu.getStatusBox().appendText("No additional vertices will be added.\n\n"); + } }); - menu.setResetButtonAction(event -> { - resetDefaultDigraph(defaultDigraph, graphView); - createAddedVertices(addedVertices); - selectedVerticesCount = 0; - startVertex = endVertex = null; - menu.getStatusBox().appendText("Graph has been reset.\n\n" + defaultDigraph); + menu.setResetButtonAction( + event -> { + resetDefaultDigraph(defaultDigraph, graphView); + createAddedVertices(addedVertices); + selectedVerticesCount = 0; + startVertex = endVertex = null; + menu.getStatusBox().appendText("Graph has been reset.\n\n" + defaultDigraph); }); - } - - public static void main(String[] args) { - launch(args); - } - - private void createDefaultDigraph(Graph defaultDigraph) { - defaultDigraph.insertVertex("AU"); - defaultDigraph.insertVertex("BE"); - defaultDigraph.insertVertex("DK"); - defaultDigraph.insertVertex("EG"); - defaultDigraph.insertVertex("HK"); - defaultDigraph.insertEdge("AU", "HK", 6); - defaultDigraph.insertEdge("AU", "EG", 12); - defaultDigraph.insertEdge("DK", "BE", 1); - defaultDigraph.insertEdge("DK", "EG", 4); - defaultDigraph.insertEdge("HK", "BE", 9); - } - - private void resetDefaultDigraph(Graph defaultDigraph, SmartGraphPanel graphView) { - ((AdjacencyMapDigraph) defaultDigraph).clear(); - graphView.getStylableVertex(defaultDigraph.insertVertex("AU")).setStyleClass("vertex"); - graphView.getStylableVertex(defaultDigraph.insertVertex("BE")).setStyleClass("vertex"); - graphView.getStylableVertex(defaultDigraph.insertVertex("DK")).setStyleClass("vertex"); - graphView.getStylableVertex(defaultDigraph.insertVertex("EG")).setStyleClass("vertex"); - graphView.getStylableVertex(defaultDigraph.insertVertex("HK")).setStyleClass("vertex"); - defaultDigraph.insertEdge("AU", "HK", 6); - defaultDigraph.insertEdge("AU", "EG", 12); - defaultDigraph.insertEdge("DK", "BE", 1); - defaultDigraph.insertEdge("DK", "EG", 4); - defaultDigraph.insertEdge("HK", "BE", 9); - graphView.update(); - } - - private void createAddedVertices(Queue addVertices) { - addVertices.clear(); - addVertices.add("JS"); - addVertices.add("MN"); - addVertices.add("PQ"); - addVertices.add("WX"); - addVertices.add("YZ"); - } -} \ No newline at end of file + } + + public static void main(String[] args) { + launch(args); + } + + private void createDefaultDigraph(Graph defaultDigraph) { + defaultDigraph.insertVertex("AU"); + defaultDigraph.insertVertex("BE"); + defaultDigraph.insertVertex("DK"); + defaultDigraph.insertVertex("EG"); + defaultDigraph.insertVertex("HK"); + defaultDigraph.insertEdge("AU", "HK", 6); + defaultDigraph.insertEdge("AU", "EG", 12); + defaultDigraph.insertEdge("DK", "BE", 1); + defaultDigraph.insertEdge("DK", "EG", 4); + defaultDigraph.insertEdge("HK", "BE", 9); + } + + private void resetDefaultDigraph( + Graph defaultDigraph, SmartGraphPanel graphView) { + ((AdjacencyMapDigraph) defaultDigraph).clear(); + graphView.getStylableVertex(defaultDigraph.insertVertex("AU")).setStyleClass("vertex"); + graphView.getStylableVertex(defaultDigraph.insertVertex("BE")).setStyleClass("vertex"); + graphView.getStylableVertex(defaultDigraph.insertVertex("DK")).setStyleClass("vertex"); + graphView.getStylableVertex(defaultDigraph.insertVertex("EG")).setStyleClass("vertex"); + graphView.getStylableVertex(defaultDigraph.insertVertex("HK")).setStyleClass("vertex"); + defaultDigraph.insertEdge("AU", "HK", 6); + defaultDigraph.insertEdge("AU", "EG", 12); + defaultDigraph.insertEdge("DK", "BE", 1); + defaultDigraph.insertEdge("DK", "EG", 4); + defaultDigraph.insertEdge("HK", "BE", 9); + graphView.update(); + } + + private void createAddedVertices(Queue addVertices) { + addVertices.clear(); + addVertices.add("JS"); + addVertices.add("MN"); + addVertices.add("PQ"); + addVertices.add("WX"); + addVertices.add("YZ"); + } +} diff --git a/src/graphvisualizer/graphalgorithms/ShortestPath.java b/src/graphvisualizer/graphalgorithms/ShortestPath.java index 6a7adc4..9c0fb31 100644 --- a/src/graphvisualizer/graphalgorithms/ShortestPath.java +++ b/src/graphvisualizer/graphalgorithms/ShortestPath.java @@ -1,159 +1,178 @@ package graphvisualizer.graphalgorithms; -import graphvisualizer.graph.Edge; import graphvisualizer.graph.*; +import graphvisualizer.graph.Edge; import graphvisualizer.graphview.SmartGraphPanel; import java.util.*; /** - * A shortest path algorithm which implements Dijkstra’s algorithm - * that can be used on a directed graph that is either strongly connected - * or not strongly connected with random generation of directed edges + * A shortest path algorithm which implements Dijkstra’s algorithm that can be used on a directed + * graph that is either strongly connected or not strongly connected with random generation of + * directed edges */ - public class ShortestPath { - /** - * Construct a visualization of shortest path algorithm in a directed graph which is - * either strongly connected not strongly connected referenced by digraph. - * Random directed edges will be generated until a path exists between the starting vertex - * and ending vertex - * - * @param digraph Directed graph - * @param graphView Graph visualization object - * @param startVertex Starting vertex of the shortest path algorithm - * @param endVertex Ending vertex of the shortest path algorithm - * @return The weight count from the starting vertex to ending vertex of the Dijkstra’s algorithm - */ - - public static String start(AdjacencyMapDigraph digraph, SmartGraphPanel graphView, - Vertex startVertex, Vertex endVertex) { - LinkedHashMap, Integer> d = new LinkedHashMap<>(); - int[] weight = {1}; - StringBuilder sb = new StringBuilder(); - - //Generate random edges between random vertices until the path exists - while (!dijkstra(digraph, startVertex, endVertex, d, weight)) { - //generate a random edge - sb.append(digraph.generateRandomEdge(new Random().nextInt(20) + 1)); - } - - return sb.append("\n[Shortest Path]\n\n").append(generatePath(digraph, startVertex, endVertex, d, graphView)).append("\nWeight count from "). - append(startVertex).append(" to ").append(endVertex).append(" is ").append(weight[0]).append(".\n").toString(); + /** + * Construct a visualization of shortest path algorithm in a directed graph which is either + * strongly connected not strongly connected referenced by digraph. Random directed + * edges will be generated until a path exists between the starting vertex and ending vertex + * + * @param digraph Directed graph + * @param graphView Graph visualization object + * @param startVertex Starting vertex of the shortest path algorithm + * @param endVertex Ending vertex of the shortest path algorithm + * @return The weight count from the starting vertex to ending vertex of the Dijkstra’s algorithm + */ + public static String start( + AdjacencyMapDigraph digraph, + SmartGraphPanel graphView, + Vertex startVertex, + Vertex endVertex) { + LinkedHashMap, Integer> d = new LinkedHashMap<>(); + int[] weight = {1}; + StringBuilder sb = new StringBuilder(); + + // Generate random edges between random vertices until the path exists + while (!dijkstra(digraph, startVertex, endVertex, d, weight)) { + // generate a random edge + sb.append(digraph.generateRandomEdge(new Random().nextInt(20) + 1)); } - - /** - * An implementation of Dijkstra’s algorithm to determine and pass the weight of shortest path between - * the starting vertex and the ending vertex through array weight. This is also to detect whether a path - * exists between the starting and ending vertices and return boolean value. - * This should be called repeatedly by start(AdjacencyMapDigraph, SmartGraphPanel, Vertex, Vertex). - * - * @param digraph Directed graph - * @param startVertex Starting vertex of the shortest path algorithm - * @param endVertex Ending vertex of the shortest path algorithm - * @param d Map to store the distance/weight of each vertex from the starting vertex - * @param weight The weight count of the shortest path - */ - - private static boolean dijkstra(AdjacencyMapDigraph digraph, Vertex startVertex, Vertex endVertex, - LinkedHashMap , Integer> d, int[] weight) { - - Map, Integer> cloud = new LinkedHashMap<>(); - HeapAdaptablePriorityQueue> pq = new HeapAdaptablePriorityQueue<>(); - Map, Entry>> pqTokens = new LinkedHashMap<>(); - - //for each vertex of the graph, add an entry to the priority queue with the source having distance 0 and all others having infinite distance - for (Vertex v : digraph.vertices()) { - if (v.equals(startVertex)) { - d.put(v, 0); - } else { - d.put(v, Integer.MAX_VALUE); - } - //save the entry for future updates - pqTokens.put(v, pq.insert(d.get(v), v)); - } - - //Add all the reachable vertices to the Map cloud - while (!pq.isEmpty()) { - Entry> entry = pq.removeMin(); - int key = entry.getKey(); - Vertex u = entry.getValue(); - cloud.put(u, key); //the actual distance to u - pqTokens.remove(u); //remove u from pq - - for (Edge edge : digraph.outgoingEdges(u)) { - Vertex v = digraph.opposite(u, edge); - - if (cloud.get(v) == null) { - //perform the relaxation step on edge (u,v) - int wgt = edge.element(); - if ((d.get(u) + wgt) < d.get(v)) { //check if there is any better/shorter path to v - d.put(v, (d.get(u) + wgt)); //update the distance in Map d - pq.replaceKey(pqTokens.get(v), d.get(v)); //update the pq entry - } - } - } - } - weight[0] = cloud.get(endVertex); //Store the weight of shortest path in array weight - - //check if there is any path can be reach by starting vertex to ending vertex and return a boolean value - if (cloud.get(endVertex) >= Integer.MAX_VALUE) { - return false; - } else return Integer.signum(cloud.get(endVertex)) != -1; + return sb.append("\n[Shortest Path]\n\n") + .append(generatePath(digraph, startVertex, endVertex, d, graphView)) + .append("\nWeight count from ") + .append(startVertex) + .append(" to ") + .append(endVertex) + .append(" is ") + .append(weight[0]) + .append(".\n") + .toString(); + } + + /** + * An implementation of Dijkstra’s algorithm to determine and pass the weight of shortest path + * between the starting vertex and the ending vertex through array weight. This is also to detect + * whether a path exists between the starting and ending vertices and return boolean value. This + * should be called repeatedly by + * start(AdjacencyMapDigraph, SmartGraphPanel, Vertex, Vertex). + * + * @param digraph Directed graph + * @param startVertex Starting vertex of the shortest path algorithm + * @param endVertex Ending vertex of the shortest path algorithm + * @param d Map to store the distance/weight of each vertex from the starting vertex + * @param weight The weight count of the shortest path + */ + private static boolean dijkstra( + AdjacencyMapDigraph digraph, + Vertex startVertex, + Vertex endVertex, + LinkedHashMap, Integer> d, + int[] weight) { + + Map, Integer> cloud = new LinkedHashMap<>(); + HeapAdaptablePriorityQueue> pq = new HeapAdaptablePriorityQueue<>(); + Map, Entry>> pqTokens = new LinkedHashMap<>(); + + // for each vertex of the graph, add an entry to the priority queue with the source having + // distance 0 and all others having infinite distance + for (Vertex v : digraph.vertices()) { + if (v.equals(startVertex)) { + d.put(v, 0); + } else { + d.put(v, Integer.MAX_VALUE); + } + // save the entry for future updates + pqTokens.put(v, pq.insert(d.get(v), v)); } - /** - * Reconstruct a shortest-path tree rooted at starting vertex, the tree is represented as a map - * from each reachable vertex other than the starting vertex to the edge that is used to reach - * a vertex from its parent u in the tree. Then, this method return the String value of the vertex or - * vertices on the path from starting vertex to ending vertex. This should be called repeatedly by - * start(AdjacencyMapDigraph, SmartGraphPanel, Vertex, Vertex). - * - * @param digraph Directed graph - * @param startVertex Starting vertex of the shortest path algorithm - * @param endVertex Ending vertex of the shortest path algorithm - * @param d Map to store the distance/weight of each vertex from the starting vertex - * @param graphView Graph visualization object - */ - - private static String generatePath (AdjacencyMapDigraph digraph, Vertex startVertex, Vertex endVertex, - LinkedHashMap, Integer> d, SmartGraphPanel graphView) { - Map, Edge> tree = new LinkedHashMap<>(); - Map, Vertex> parentsOfVertices = new HashMap<>(); - StringBuilder writtenPath = new StringBuilder(); - - for (Vertex vertex : d.keySet()) { - if (vertex != startVertex) { - for (Edge edge : digraph.incomingEdges(vertex)) { //consider the incoming edges - Vertex u = digraph.opposite(vertex, edge); - int wgt = edge.element(); - if (d.get(vertex) == d.get(u) + wgt) { - tree.put(vertex, edge); //The vertices and edges are stored - parentsOfVertices.put(vertex, u); //The parents of vertices are stored - } - } - } + // Add all the reachable vertices to the Map cloud + while (!pq.isEmpty()) { + Entry> entry = pq.removeMin(); + int key = entry.getKey(); + Vertex u = entry.getValue(); + cloud.put(u, key); // the actual distance to u + pqTokens.remove(u); // remove u from pq + + for (Edge edge : digraph.outgoingEdges(u)) { + Vertex v = digraph.opposite(u, edge); + + if (cloud.get(v) == null) { + // perform the relaxation step on edge (u,v) + int wgt = edge.element(); + if ((d.get(u) + wgt) < d.get(v)) { // check if there is any better/shorter path to v + d.put(v, (d.get(u) + wgt)); // update the distance in Map d + pq.replaceKey(pqTokens.get(v), d.get(v)); // update the pq entry + } } - Stack> path = new Stack<>(); //set of vertices on the path - Vertex vertexInPath; - - //push the vertex or vertices on path into the stack - for (vertexInPath = endVertex; !vertexInPath.equals(startVertex); vertexInPath = parentsOfVertices.get(vertexInPath)) { - path.push(vertexInPath); + } + } + weight[0] = cloud.get(endVertex); // Store the weight of shortest path in array weight + + // check if there is any path can be reach by starting vertex to ending vertex and return a + // boolean value + if (cloud.get(endVertex) >= Integer.MAX_VALUE) { + return false; + } else return Integer.signum(cloud.get(endVertex)) != -1; + } + + /** + * Reconstruct a shortest-path tree rooted at starting vertex, the tree is represented as a map + * from each reachable vertex other than the starting vertex to the edge that is used to reach a + * vertex from its parent u in the tree. Then, this method return the String value of the vertex + * or vertices on the path from starting vertex to ending vertex. This should be called repeatedly + * by start(AdjacencyMapDigraph, SmartGraphPanel, Vertex, Vertex). + * + * @param digraph Directed graph + * @param startVertex Starting vertex of the shortest path algorithm + * @param endVertex Ending vertex of the shortest path algorithm + * @param d Map to store the distance/weight of each vertex from the starting vertex + * @param graphView Graph visualization object + */ + private static String generatePath( + AdjacencyMapDigraph digraph, + Vertex startVertex, + Vertex endVertex, + LinkedHashMap, Integer> d, + SmartGraphPanel graphView) { + Map, Edge> tree = new LinkedHashMap<>(); + Map, Vertex> parentsOfVertices = new HashMap<>(); + StringBuilder writtenPath = new StringBuilder(); + + for (Vertex vertex : d.keySet()) { + if (vertex != startVertex) { + for (Edge edge : + digraph.incomingEdges(vertex)) { // consider the incoming edges + Vertex u = digraph.opposite(vertex, edge); + int wgt = edge.element(); + if (d.get(vertex) == d.get(u) + wgt) { + tree.put(vertex, edge); // The vertices and edges are stored + parentsOfVertices.put(vertex, u); // The parents of vertices are stored + } } + } + } + Stack> path = new Stack<>(); // set of vertices on the path + Vertex vertexInPath; + + // push the vertex or vertices on path into the stack + for (vertexInPath = endVertex; + !vertexInPath.equals(startVertex); + vertexInPath = parentsOfVertices.get(vertexInPath)) { + path.push(vertexInPath); + } - //push the starting vertex into the stack - path.push(startVertex); + // push the starting vertex into the stack + path.push(startVertex); - //Set the style class of the vertex or vertices on path and update the graphview - while(!path.isEmpty()) { - vertexInPath = path.pop(); - graphView.getStylableVertex(vertexInPath).setStyleClass("highlightedVertex"); - graphView.update(); + // Set the style class of the vertex or vertices on path and update the graphview + while (!path.isEmpty()) { + vertexInPath = path.pop(); + graphView.getStylableVertex(vertexInPath).setStyleClass("highlightedVertex"); + graphView.update(); - if(!vertexInPath.equals(startVertex)) - writtenPath.append(tree.get(vertexInPath)).append("\n"); - } - return writtenPath.toString(); + if (!vertexInPath.equals(startVertex)) + writtenPath.append(tree.get(vertexInPath)).append("\n"); } + return writtenPath.toString(); + } } diff --git a/src/graphvisualizer/graphalgorithms/StrongConnectivity.java b/src/graphvisualizer/graphalgorithms/StrongConnectivity.java index 8bba1b8..ced4888 100644 --- a/src/graphvisualizer/graphalgorithms/StrongConnectivity.java +++ b/src/graphvisualizer/graphalgorithms/StrongConnectivity.java @@ -1,142 +1,147 @@ package graphvisualizer.graphalgorithms; +import static java.lang.Integer.min; + import graphvisualizer.graph.AdjacencyMapDigraph; import graphvisualizer.graph.Edge; import graphvisualizer.graph.Vertex; import graphvisualizer.graphview.SmartGraphPanel; import java.util.*; -import static java.lang.Integer.min; - /** - * This class is used to determine the strong connectivity of a directed graph. - * This method {@link #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} implements DFS - * to determine strong connectivity. - * New edges are generated when the graph is not strongly connected until it is strongly connected. + * This class is used to determine the strong connectivity of a directed graph. This method {@link + * #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} implements DFS to + * determine strong connectivity. New edges are generated when the graph is not strongly connected + * until it is strongly connected. */ public class StrongConnectivity { - private static final int UNVISITED = -1; - - private static HashMap,Integer> ref ; - private static LinkedList> verticesList; - private static StringBuilder sb; - private static Deque stack; - private static int id; - private static int sccCount; - private static int[] ids, low; - private static boolean[] onStack; - - /** - * Generate a visualization for the DFS algorithm for a directed graph. - * This method calls the DFS algorithm {@link #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} - * to determine the connectivity. - * This method will generate a new edge when the graph is not strongly connected. - * - * @param digraph Directed graph - * @param graphView Graph visualization object - * @return description of edges that are newly added, result of the DFS algorithm {@link #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} - */ - public static String start(AdjacencyMapDigraph digraph, SmartGraphPanel graphView) { - ref = new HashMap,Integer>(); - setRef(digraph, ref); - - boolean isStronglyConnected = false; - Vertex obj; - verticesList = new LinkedList<>(digraph.vertices()); - StringBuilder sb = new StringBuilder(); - - while(!isStronglyConnected) { - init(digraph); - while (!verticesList.isEmpty()){ - obj = verticesList.remove(); - if (ids[ref.get(obj)]==UNVISITED){ - checkStronglyConnected(digraph,obj, graphView); - } - } - - isStronglyConnected = sccCount == 1; - - if (isStronglyConnected) { - sb.append("\n[Strong Connectivity] [Graph is strongly connected!]\n"); - break; - } - else { - sb.append(digraph.generateRandomEdge(new Random().nextInt(20) + 1)); - verticesList.addAll(digraph.vertices()); - } + private static final int UNVISITED = -1; + + private static HashMap, Integer> ref; + private static LinkedList> verticesList; + private static StringBuilder sb; + private static Deque stack; + private static int id; + private static int sccCount; + private static int[] ids, low; + private static boolean[] onStack; + + /** + * Generate a visualization for the DFS algorithm for a directed graph. This method calls the DFS + * algorithm {@link #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} to + * determine the connectivity. This method will generate a new edge when the graph is not strongly + * connected. + * + * @param digraph Directed graph + * @param graphView Graph visualization object + * @return description of edges that are newly added, result of the DFS algorithm {@link + * #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} + */ + public static String start( + AdjacencyMapDigraph digraph, SmartGraphPanel graphView) { + ref = new HashMap, Integer>(); + setRef(digraph, ref); + + boolean isStronglyConnected = false; + Vertex obj; + verticesList = new LinkedList<>(digraph.vertices()); + StringBuilder sb = new StringBuilder(); + + while (!isStronglyConnected) { + init(digraph); + while (!verticesList.isEmpty()) { + obj = verticesList.remove(); + if (ids[ref.get(obj)] == UNVISITED) { + checkStronglyConnected(digraph, obj, graphView); } - return sb.append(digraph).toString(); - } - - /** - * This method is a DFS algorithm used to determine strong connectivity of a directed graph. - * This method is repeatedly called by {@link #start(AdjacencyMapDigraph, SmartGraphPanel)} - * - * @param digraph Directed graph - * @param startVertex The starting vertex that will be used for DFS - * @param graphView Graph visualization object - */ - private static void checkStronglyConnected(AdjacencyMapDigraph digraph, Vertex startVertex, SmartGraphPanel graphView) { - int at = ref.get(startVertex); - - stack.push(at); - onStack[at] = true; - ids[at] = low [at] = id++; - - graphView.getStylableVertex(startVertex).setStyleClass("highlightedVertex"); - graphView.update(); + } - for (Edge edge : digraph.outgoingEdges(startVertex)) { - Vertex vertex = digraph.opposite(startVertex, edge); + isStronglyConnected = sccCount == 1; - if (ids[ref.get(vertex)]==UNVISITED) - checkStronglyConnected(digraph, vertex, graphView); - - if (onStack[ref.get(vertex)]) low[at] = min(low[at], low[ref.get(vertex)]); - } - - if (ids[at]==low[at]){ - for (int node = stack.pop(); ; node=stack.pop()){ - onStack[node] = false; - low[node] = ids[at]; - if (node==at) break; - } - sccCount++; - } + if (isStronglyConnected) { + sb.append("\n[Strong Connectivity] [Graph is strongly connected!]\n"); + break; + } else { + sb.append(digraph.generateRandomEdge(new Random().nextInt(20) + 1)); + verticesList.addAll(digraph.vertices()); + } } - - /** - * This method is used to initialize the hashmap named 'ref'. - * The hashmap is used as a index reference of the vertex when accessing to arrays. - * - * @param digraph Directed graph - * @param ref A hashmap that stores the vertex as key and Integer as value. - */ - public static void setRef(AdjacencyMapDigraph digraph, HashMap, Integer> ref) { - verticesList = new LinkedList<>(digraph.vertices()); - int count=0; - - while (!verticesList.isEmpty()){ - ref.put(verticesList.remove(),count); - count++; - } + return sb.append(digraph).toString(); + } + + /** + * This method is a DFS algorithm used to determine strong connectivity of a directed graph. This + * method is repeatedly called by {@link #start(AdjacencyMapDigraph, SmartGraphPanel)} + * + * @param digraph Directed graph + * @param startVertex The starting vertex that will be used for DFS + * @param graphView Graph visualization object + */ + private static void checkStronglyConnected( + AdjacencyMapDigraph digraph, + Vertex startVertex, + SmartGraphPanel graphView) { + int at = ref.get(startVertex); + + stack.push(at); + onStack[at] = true; + ids[at] = low[at] = id++; + + graphView.getStylableVertex(startVertex).setStyleClass("highlightedVertex"); + graphView.update(); + + for (Edge edge : digraph.outgoingEdges(startVertex)) { + Vertex vertex = digraph.opposite(startVertex, edge); + + if (ids[ref.get(vertex)] == UNVISITED) checkStronglyConnected(digraph, vertex, graphView); + + if (onStack[ref.get(vertex)]) low[at] = min(low[at], low[ref.get(vertex)]); } - /** - * Initialize the members of the class before {@link #checkStronglyConnected(AdjacencyMapDigraph, Vertex, SmartGraphPanel)} is executed. - * The members can be found declared as global variables of this class. - * - * @param digraph Directed graph - */ - private static void init(AdjacencyMapDigraph digraph){ - int graphSize = digraph.numVertices(); - - id = 0; - ids = new int[graphSize]; - low = new int[graphSize]; - onStack = new boolean[graphSize]; - stack = new ArrayDeque<>(); - Arrays.fill(ids, UNVISITED); - sccCount=0; + if (ids[at] == low[at]) { + for (int node = stack.pop(); ; node = stack.pop()) { + onStack[node] = false; + low[node] = ids[at]; + if (node == at) break; + } + sccCount++; + } + } + + /** + * This method is used to initialize the hashmap named 'ref'. The hashmap is used as a index + * reference of the vertex when accessing to arrays. + * + * @param digraph Directed graph + * @param ref A hashmap that stores the vertex as key and Integer as value. + */ + public static void setRef( + AdjacencyMapDigraph digraph, HashMap, Integer> ref) { + verticesList = new LinkedList<>(digraph.vertices()); + int count = 0; + + while (!verticesList.isEmpty()) { + ref.put(verticesList.remove(), count); + count++; } + } + + /** + * Initialize the members of the class before {@link #checkStronglyConnected(AdjacencyMapDigraph, + * Vertex, SmartGraphPanel)} is executed. The members can be found declared as global variables of + * this class. + * + * @param digraph Directed graph + */ + private static void init(AdjacencyMapDigraph digraph) { + int graphSize = digraph.numVertices(); + + id = 0; + ids = new int[graphSize]; + low = new int[graphSize]; + onStack = new boolean[graphSize]; + stack = new ArrayDeque<>(); + Arrays.fill(ids, UNVISITED); + sccCount = 0; + } } diff --git a/src/graphvisualizer/graphview/SmartArrow.java b/src/graphvisualizer/graphview/SmartArrow.java index 6a6262a..8b2e1d4 100644 --- a/src/graphvisualizer/graphview/SmartArrow.java +++ b/src/graphvisualizer/graphview/SmartArrow.java @@ -29,26 +29,25 @@ /** * A shape of an arrow to be attached to a {@link SmartGraphEdge}. - * + * * @author brunomnsilva */ public class SmartArrow extends Path implements SmartStylableNode { - - public SmartArrow() { - - /* Create this arrow shape */ - getElements().add(new MoveTo(0, 0)); - getElements().add(new LineTo(-5, 5)); - getElements().add(new MoveTo(0, 0)); - getElements().add(new LineTo(-5, -5)); - - /* Add the corresponding css class */ - getStyleClass().add("arrow"); - } - @Override - public void setStyleClass(String cssClass) { - getStyleClass().add(cssClass); - } - + public SmartArrow() { + + /* Create this arrow shape */ + getElements().add(new MoveTo(0, 0)); + getElements().add(new LineTo(-5, 5)); + getElements().add(new MoveTo(0, 0)); + getElements().add(new LineTo(-5, -5)); + + /* Add the corresponding css class */ + getStyleClass().add("arrow"); + } + + @Override + public void setStyleClass(String cssClass) { + getStyleClass().add(cssClass); + } } diff --git a/src/graphvisualizer/graphview/SmartCircularSortedPlacementStrategy.java b/src/graphvisualizer/graphview/SmartCircularSortedPlacementStrategy.java index 60f95e3..d7f1a18 100644 --- a/src/graphvisualizer/graphview/SmartCircularSortedPlacementStrategy.java +++ b/src/graphvisualizer/graphview/SmartCircularSortedPlacementStrategy.java @@ -23,67 +23,71 @@ */ package graphvisualizer.graphview; +import graphvisualizer.graph.Graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import javafx.geometry.Point2D; -import graphvisualizer.graph.Graph; /** - * Places vertices around a circle, ordered by the underlying - * vertices {@code element.toString() value}. - * + * Places vertices around a circle, ordered by the underlying vertices {@code element.toString() + * value}. + * * @see SmartPlacementStrategy - * * @author brunomnsilva */ public class SmartCircularSortedPlacementStrategy implements SmartPlacementStrategy { - @Override - public void place(double width, double height, Graph theGraph, Collection> vertices) { - Point2D center = new Point2D(width / 2, height / 2.5); - int N = vertices.size(); - double angleIncrement = -360f / N; - - //place first vertice north position, others in clockwise manner - boolean first = true; - Point2D p = null; - for (SmartGraphVertex vertex : sort(vertices)) { - - if (first) { - //verifiy smaller width and height. - if(width > height) - p = new Point2D(center.getX(), - center.getY() - height / 2 + vertex.getRadius() * 2); - else - p = new Point2D(center.getX(), - center.getY() - width / 2 + vertex.getRadius() * 2); - - - first = false; - } else { - p = UtilitiesPoint2D.rotate(p, center, angleIncrement); - } + @Override + public void place( + double width, + double height, + Graph theGraph, + Collection> vertices) { + Point2D center = new Point2D(width / 2, height / 2.5); + int N = vertices.size(); + double angleIncrement = -360f / N; + + // place first vertice north position, others in clockwise manner + boolean first = true; + Point2D p = null; + for (SmartGraphVertex vertex : sort(vertices)) { + + if (first) { + // verifiy smaller width and height. + if (width > height) + p = new Point2D(center.getX(), center.getY() - height / 2 + vertex.getRadius() * 2); + else p = new Point2D(center.getX(), center.getY() - width / 2 + vertex.getRadius() * 2); + + first = false; + } else { + p = UtilitiesPoint2D.rotate(p, center, angleIncrement); + } - vertex.setPosition(p.getX(), p.getY()); - - } + vertex.setPosition(p.getX(), p.getY()); } - - protected Collection> sort(Collection> vertices) { - - List> list = new ArrayList<>(); - list.addAll(vertices); - - Collections.sort(list, new Comparator>() { - @Override - public int compare(SmartGraphVertex t, SmartGraphVertex t1) { - return t.getUnderlyingVertex().element().toString().compareToIgnoreCase(t1.getUnderlyingVertex().element().toString()); - } + } + + protected Collection> sort( + Collection> vertices) { + + List> list = new ArrayList<>(); + list.addAll(vertices); + + Collections.sort( + list, + new Comparator>() { + @Override + public int compare(SmartGraphVertex t, SmartGraphVertex t1) { + return t.getUnderlyingVertex() + .element() + .toString() + .compareToIgnoreCase(t1.getUnderlyingVertex().element().toString()); + } }); - - return list; - } + + return list; + } } diff --git a/src/graphvisualizer/graphview/SmartGraphEdge.java b/src/graphvisualizer/graphview/SmartGraphEdge.java index f783819..4750376 100644 --- a/src/graphvisualizer/graphview/SmartGraphEdge.java +++ b/src/graphvisualizer/graphview/SmartGraphEdge.java @@ -27,27 +27,23 @@ import graphvisualizer.graph.Vertex; /** - * A graph edge visually connects two {@link Vertex} of type V. - *
- * Concrete edge implementations used by {@link SmartGraphPanel} should - * implement this interface as this type is the only one exposed to the user. - * + * A graph edge visually connects two {@link Vertex} of type V.
+ * Concrete edge implementations used by {@link SmartGraphPanel} should implement this interface as + * this type is the only one exposed to the user. + * * @param Type stored in the underlying edge * @param Type of connecting vertex - * * @see Vertex * @see SmartGraphPanel - * * @author brunomnsilva */ public interface SmartGraphEdge extends SmartStylableNode { - - /** - * Returns the underlying (stored reference) graph edge. - * - * @return edge reference - * - * @see SmartGraphPanel - */ - public Edge getUnderlyingEdge(); + + /** + * Returns the underlying (stored reference) graph edge. + * + * @return edge reference + * @see SmartGraphPanel + */ + public Edge getUnderlyingEdge(); } diff --git a/src/graphvisualizer/graphview/SmartGraphEdgeBase.java b/src/graphvisualizer/graphview/SmartGraphEdgeBase.java index 7a80bc9..0a460b5 100644 --- a/src/graphvisualizer/graphview/SmartGraphEdgeBase.java +++ b/src/graphvisualizer/graphview/SmartGraphEdgeBase.java @@ -24,35 +24,31 @@ package graphvisualizer.graphview; /** - * Used as a super-type for all different concrete edge implementations by - * {@link SmartGraphPanel}, e.g., line and curves. - *
+ * Used as a super-type for all different concrete edge implementations by {@link SmartGraphPanel}, + * e.g., line and curves.
* An edge can have an attached arrow. - * + * * @param Type stored in the underlying edge * @param Type of connecting vertex - * * @see SmartArrow * @see SmartGraphEdge * @see SmartLabelledNode * @see SmartGraphPanel - * * @author brunomnsilva */ public interface SmartGraphEdgeBase extends SmartGraphEdge, SmartLabelledNode { - - /** - * Attaches a {@link SmartArrow} to this edge, binding its position/rotation. - * - * @param arrow arrow to attach - */ - public void attachArrow(SmartArrow arrow); - - /** - * Returns the attached {@link SmartArrow}, if any. - * - * @return reference of the attached arrow; null if none. - */ - public SmartArrow getAttachedArrow(); - + + /** + * Attaches a {@link SmartArrow} to this edge, binding its position/rotation. + * + * @param arrow arrow to attach + */ + public void attachArrow(SmartArrow arrow); + + /** + * Returns the attached {@link SmartArrow}, if any. + * + * @return reference of the attached arrow; null if none. + */ + public SmartArrow getAttachedArrow(); } diff --git a/src/graphvisualizer/graphview/SmartGraphEdgeCurve.java b/src/graphvisualizer/graphview/SmartGraphEdgeCurve.java index 16cff9f..2f3d8c5 100644 --- a/src/graphvisualizer/graphview/SmartGraphEdgeCurve.java +++ b/src/graphvisualizer/graphview/SmartGraphEdgeCurve.java @@ -23,187 +23,206 @@ */ package graphvisualizer.graphview; +import graphvisualizer.graph.Edge; import javafx.beans.value.ObservableValue; import javafx.geometry.Point2D; import javafx.scene.shape.CubicCurve; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; -import graphvisualizer.graph.Edge; /** - * Concrete implementation of a curved edge. - *
- * The edge binds its start point to the outbound - * {@link SmartGraphVertexNode} center and its end point to the - * inbound {@link SmartGraphVertexNode} center. As such, the curve - * is updated automatically as the vertices move. - *
- * Given there can be several curved edges connecting two vertices, when calling - * the constructor {@link #SmartGraphEdgeCurve(graphvisualizer.graph.Edge, - * graphvisualizer.graphview.SmartGraphVertexNode, - * graphvisualizer.graphview.SmartGraphVertexNode, int) } the edgeIndex - * can be specified as to create non-overlaping curves. + * Concrete implementation of a curved edge.
+ * The edge binds its start point to the outbound {@link SmartGraphVertexNode} center + * and its end point to the inbound {@link SmartGraphVertexNode} center. As such, the + * curve is updated automatically as the vertices move.
+ * Given there can be several curved edges connecting two vertices, when calling the constructor + * {@link #SmartGraphEdgeCurve(graphvisualizer.graph.Edge, + * graphvisualizer.graphview.SmartGraphVertexNode, graphvisualizer.graphview.SmartGraphVertexNode, + * int) } the edgeIndex can be specified as to create non-overlaping curves. * * @param Type stored in the underlying edge * @param Type of connecting vertex - * * @author brunomnsilva */ public class SmartGraphEdgeCurve extends CubicCurve implements SmartGraphEdgeBase { - private static final double MAX_EDGE_CURVE_ANGLE = 20; + private static final double MAX_EDGE_CURVE_ANGLE = 20; - private final Edge underlyingEdge; + private final Edge underlyingEdge; - private final SmartGraphVertexNode inbound; - private final SmartGraphVertexNode outbound; + private final SmartGraphVertexNode inbound; + private final SmartGraphVertexNode outbound; - private SmartLabel attachedLabel = null; - private SmartArrow attachedArrow = null; + private SmartLabel attachedLabel = null; + private SmartArrow attachedArrow = null; - private double randomAngleFactor = 0; + private double randomAngleFactor = 0; - public SmartGraphEdgeCurve(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { - this(edge, inbound, outbound, 0); - } + public SmartGraphEdgeCurve( + Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { + this(edge, inbound, outbound, 0); + } - public SmartGraphEdgeCurve(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound, int edgeIndex) { - this.inbound = inbound; - this.outbound = outbound; + public SmartGraphEdgeCurve( + Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound, int edgeIndex) { + this.inbound = inbound; + this.outbound = outbound; - this.underlyingEdge = edge; + this.underlyingEdge = edge; - getStyleClass().add("edge"); + getStyleClass().add("edge"); - //bind start and end positions to vertices centers through properties - this.startXProperty().bind(outbound.centerXProperty()); - this.startYProperty().bind(outbound.centerYProperty()); - this.endXProperty().bind(inbound.centerXProperty()); - this.endYProperty().bind(inbound.centerYProperty()); + // bind start and end positions to vertices centers through properties + this.startXProperty().bind(outbound.centerXProperty()); + this.startYProperty().bind(outbound.centerYProperty()); + this.endXProperty().bind(inbound.centerXProperty()); + this.endYProperty().bind(inbound.centerYProperty()); - //TODO: improve this solution taking into account even indices, etc. - randomAngleFactor = edgeIndex == 0 ? 0 : 1.0 / edgeIndex; //Math.random(); + // TODO: improve this solution taking into account even indices, etc. + randomAngleFactor = edgeIndex == 0 ? 0 : 1.0 / edgeIndex; // Math.random(); - //update(); - enableListeners(); - } + // update(); + enableListeners(); + } - @Override - public void setStyleClass(String cssClass) { - getStyleClass().clear(); - setStyle(null); - getStyleClass().add(cssClass); - } - - private void update() { - if (inbound == outbound) { - /* Make a loop using the control points proportional to the vertex radius */ - - //TODO: take into account several "self-loops" with randomAngleFactor - double midpointX1 = outbound.getCenterX() - inbound.getRadius() * 5; - double midpointY1 = outbound.getCenterY() - inbound.getRadius() * 2; - - double midpointX2 = outbound.getCenterX() + inbound.getRadius() * 5; - double midpointY2 = outbound.getCenterY() - inbound.getRadius() * 2; - - setControlX1(midpointX1); - setControlY1(midpointY1); - setControlX2(midpointX2); - setControlY2(midpointY2); - - } else { - /* Make a curved edge. The curve is proportional to the distance */ - double midpointX = (outbound.getCenterX() + inbound.getCenterX()) / 2; - double midpointY = (outbound.getCenterY() + inbound.getCenterY()) / 2; - - Point2D midpoint = new Point2D(midpointX, midpointY); - - Point2D startpoint = new Point2D(inbound.getCenterX(), inbound.getCenterY()); - Point2D endpoint = new Point2D(outbound.getCenterX(), outbound.getCenterY()); - - //TODO: improvement lower max_angle_placement according to distance between vertices - double angle = MAX_EDGE_CURVE_ANGLE; - - double distance = startpoint.distance(endpoint); - - //TODO: remove "magic number" 1500 and provide a distance function for the - //decreasing angle with distance - angle = angle - (distance / 1500 * angle); - - midpoint = UtilitiesPoint2D.rotate(midpoint, - startpoint, - (-angle) + randomAngleFactor * (angle - (-angle))); - - setControlX1(midpoint.getX()); - setControlY1(midpoint.getY()); - setControlX2(midpoint.getX()); - setControlY2(midpoint.getY()); - } + @Override + public void setStyleClass(String cssClass) { + getStyleClass().clear(); + setStyle(null); + getStyleClass().add(cssClass); + } - } + private void update() { + if (inbound == outbound) { + /* Make a loop using the control points proportional to the vertex radius */ - /* - With a curved edge we need to continuously update the control points. - TODO: Maybe we can achieve this solely with bindings. - */ - private void enableListeners() { - this.startXProperty().addListener((ObservableValue ov, Number t, Number t1) -> { - update(); - }); - this.startYProperty().addListener((ObservableValue ov, Number t, Number t1) -> { - update(); - }); - this.endXProperty().addListener((ObservableValue ov, Number t, Number t1) -> { - update(); - }); - this.endYProperty().addListener((ObservableValue ov, Number t, Number t1) -> { - update(); - }); - } + // TODO: take into account several "self-loops" with randomAngleFactor + double midpointX1 = outbound.getCenterX() - inbound.getRadius() * 5; + double midpointY1 = outbound.getCenterY() - inbound.getRadius() * 2; - @Override - public void attachLabel(SmartLabel label) { - this.attachedLabel = label; - label.xProperty().bind(controlX1Property().add(controlX2Property()).divide(2).subtract(label.getLayoutBounds().getWidth() / 2)); - label.yProperty().bind(controlY1Property().add(controlY2Property()).divide(2).add(label.getLayoutBounds().getHeight() / 2)); - } + double midpointX2 = outbound.getCenterX() + inbound.getRadius() * 5; + double midpointY2 = outbound.getCenterY() - inbound.getRadius() * 2; - @Override - public SmartLabel getAttachedLabel() { - return attachedLabel; - } + setControlX1(midpointX1); + setControlY1(midpointY1); + setControlX2(midpointX2); + setControlY2(midpointY2); - @Override - public Edge getUnderlyingEdge() { - return underlyingEdge; - } + } else { + /* Make a curved edge. The curve is proportional to the distance */ + double midpointX = (outbound.getCenterX() + inbound.getCenterX()) / 2; + double midpointY = (outbound.getCenterY() + inbound.getCenterY()) / 2; - @Override - public void attachArrow(SmartArrow arrow) { - this.attachedArrow = arrow; + Point2D midpoint = new Point2D(midpointX, midpointY); - /* attach arrow to line's endpoint */ - arrow.translateXProperty().bind(endXProperty()); - arrow.translateYProperty().bind(endYProperty()); + Point2D startpoint = new Point2D(inbound.getCenterX(), inbound.getCenterY()); + Point2D endpoint = new Point2D(outbound.getCenterX(), outbound.getCenterY()); - /* rotate arrow around itself based on this line's angle */ - Rotate rotation = new Rotate(); - rotation.pivotXProperty().bind(translateXProperty()); - rotation.pivotYProperty().bind(translateYProperty()); - rotation.angleProperty().bind(UtilitiesBindings.toDegrees( - UtilitiesBindings.atan2(endYProperty().subtract(controlY2Property()), - endXProperty().subtract(controlX2Property())) - )); + // TODO: improvement lower max_angle_placement according to distance between vertices + double angle = MAX_EDGE_CURVE_ANGLE; - arrow.getTransforms().add(rotation); + double distance = startpoint.distance(endpoint); - /* add translation transform to put the arrow touching the circle's bounds */ - Translate t = new Translate(-outbound.getRadius(), 0); - arrow.getTransforms().add(t); - } + // TODO: remove "magic number" 1500 and provide a distance function for the + // decreasing angle with distance + angle = angle - (distance / 1500 * angle); + + midpoint = + UtilitiesPoint2D.rotate( + midpoint, startpoint, (-angle) + randomAngleFactor * (angle - (-angle))); - @Override - public SmartArrow getAttachedArrow() { - return this.attachedArrow; + setControlX1(midpoint.getX()); + setControlY1(midpoint.getY()); + setControlX2(midpoint.getX()); + setControlY2(midpoint.getY()); } + } + + /* + With a curved edge we need to continuously update the control points. + TODO: Maybe we can achieve this solely with bindings. + */ + private void enableListeners() { + this.startXProperty() + .addListener( + (ObservableValue ov, Number t, Number t1) -> { + update(); + }); + this.startYProperty() + .addListener( + (ObservableValue ov, Number t, Number t1) -> { + update(); + }); + this.endXProperty() + .addListener( + (ObservableValue ov, Number t, Number t1) -> { + update(); + }); + this.endYProperty() + .addListener( + (ObservableValue ov, Number t, Number t1) -> { + update(); + }); + } + + @Override + public void attachLabel(SmartLabel label) { + this.attachedLabel = label; + label + .xProperty() + .bind( + controlX1Property() + .add(controlX2Property()) + .divide(2) + .subtract(label.getLayoutBounds().getWidth() / 2)); + label + .yProperty() + .bind( + controlY1Property() + .add(controlY2Property()) + .divide(2) + .add(label.getLayoutBounds().getHeight() / 2)); + } + + @Override + public SmartLabel getAttachedLabel() { + return attachedLabel; + } + + @Override + public Edge getUnderlyingEdge() { + return underlyingEdge; + } + + @Override + public void attachArrow(SmartArrow arrow) { + this.attachedArrow = arrow; + + /* attach arrow to line's endpoint */ + arrow.translateXProperty().bind(endXProperty()); + arrow.translateYProperty().bind(endYProperty()); + + /* rotate arrow around itself based on this line's angle */ + Rotate rotation = new Rotate(); + rotation.pivotXProperty().bind(translateXProperty()); + rotation.pivotYProperty().bind(translateYProperty()); + rotation + .angleProperty() + .bind( + UtilitiesBindings.toDegrees( + UtilitiesBindings.atan2( + endYProperty().subtract(controlY2Property()), + endXProperty().subtract(controlX2Property())))); + + arrow.getTransforms().add(rotation); + + /* add translation transform to put the arrow touching the circle's bounds */ + Translate t = new Translate(-outbound.getRadius(), 0); + arrow.getTransforms().add(t); + } + + @Override + public SmartArrow getAttachedArrow() { + return this.attachedArrow; + } } diff --git a/src/graphvisualizer/graphview/SmartGraphEdgeLine.java b/src/graphvisualizer/graphview/SmartGraphEdgeLine.java index e480cee..924170d 100644 --- a/src/graphvisualizer/graphview/SmartGraphEdgeLine.java +++ b/src/graphvisualizer/graphview/SmartGraphEdgeLine.java @@ -23,102 +23,113 @@ */ package graphvisualizer.graphview; +import graphvisualizer.graph.Edge; import javafx.scene.shape.Line; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; -import graphvisualizer.graph.Edge; - /** * Implementation of a straight line edge. - * + * * @param Type stored in the underlying edge * @param Type of connecting vertex - * * @author brunomnsilva */ public class SmartGraphEdgeLine extends Line implements SmartGraphEdgeBase { - - private final Edge underlyingEdge; - - private final SmartGraphVertexNode inbound; - private final SmartGraphVertexNode outbound; - - private SmartLabel attachedLabel = null; - private SmartArrow attachedArrow = null; - - public SmartGraphEdgeLine(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { - if( inbound == null || outbound == null) { - throw new IllegalArgumentException("Cannot connect null vertices."); - } - - this.inbound = inbound; - this.outbound = outbound; - - this.underlyingEdge = edge; - - getStyleClass().add("edge"); - - //bind start and end positions to vertices centers through properties - this.startXProperty().bind(outbound.centerXProperty()); - this.startYProperty().bind(outbound.centerYProperty()); - this.endXProperty().bind(inbound.centerXProperty()); - this.endYProperty().bind(inbound.centerYProperty()); - } - - @Override - public void setStyleClass(String cssClass) { - getStyleClass().clear(); - setStyle(null); - getStyleClass().add(cssClass); - } - - @Override - public void attachLabel(SmartLabel label) { - this.attachedLabel = label; - label.xProperty().bind(startXProperty().add(endXProperty()).divide(2).subtract(label.getLayoutBounds().getWidth() / 2)); - label.yProperty().bind(startYProperty().add(endYProperty()).divide(2).add(label.getLayoutBounds().getHeight() / 1.5)); - } + private final Edge underlyingEdge; - @Override - public SmartLabel getAttachedLabel() { - return attachedLabel; - } + private final SmartGraphVertexNode inbound; + private final SmartGraphVertexNode outbound; - @Override - public Edge getUnderlyingEdge() { - return underlyingEdge; - } + private SmartLabel attachedLabel = null; + private SmartArrow attachedArrow = null; - @Override - public void attachArrow(SmartArrow arrow) { - this.attachedArrow = arrow; - - /* attach arrow to line's endpoint */ - arrow.translateXProperty().bind(endXProperty()); - arrow.translateYProperty().bind(endYProperty()); - - /* rotate arrow around itself based on this line's angle */ - Rotate rotation = new Rotate(); - rotation.pivotXProperty().bind(translateXProperty()); - rotation.pivotYProperty().bind(translateYProperty()); - rotation.angleProperty().bind( UtilitiesBindings.toDegrees( - UtilitiesBindings.atan2( endYProperty().subtract(startYProperty()), - endXProperty().subtract(startXProperty())) - )); - - arrow.getTransforms().add(rotation); - - /* add translation transform to put the arrow touching the circle's bounds */ - Translate t = new Translate(- outbound.getRadius(), 0); - arrow.getTransforms().add(t); - + public SmartGraphEdgeLine( + Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { + if (inbound == null || outbound == null) { + throw new IllegalArgumentException("Cannot connect null vertices."); } - @Override - public SmartArrow getAttachedArrow() { - return this.attachedArrow; - } - + this.inbound = inbound; + this.outbound = outbound; + + this.underlyingEdge = edge; + + getStyleClass().add("edge"); + + // bind start and end positions to vertices centers through properties + this.startXProperty().bind(outbound.centerXProperty()); + this.startYProperty().bind(outbound.centerYProperty()); + this.endXProperty().bind(inbound.centerXProperty()); + this.endYProperty().bind(inbound.centerYProperty()); + } + + @Override + public void setStyleClass(String cssClass) { + getStyleClass().clear(); + setStyle(null); + getStyleClass().add(cssClass); + } + + @Override + public void attachLabel(SmartLabel label) { + this.attachedLabel = label; + label + .xProperty() + .bind( + startXProperty() + .add(endXProperty()) + .divide(2) + .subtract(label.getLayoutBounds().getWidth() / 2)); + label + .yProperty() + .bind( + startYProperty() + .add(endYProperty()) + .divide(2) + .add(label.getLayoutBounds().getHeight() / 1.5)); + } + + @Override + public SmartLabel getAttachedLabel() { + return attachedLabel; + } + + @Override + public Edge getUnderlyingEdge() { + return underlyingEdge; + } + + @Override + public void attachArrow(SmartArrow arrow) { + this.attachedArrow = arrow; + + /* attach arrow to line's endpoint */ + arrow.translateXProperty().bind(endXProperty()); + arrow.translateYProperty().bind(endYProperty()); + + /* rotate arrow around itself based on this line's angle */ + Rotate rotation = new Rotate(); + rotation.pivotXProperty().bind(translateXProperty()); + rotation.pivotYProperty().bind(translateYProperty()); + rotation + .angleProperty() + .bind( + UtilitiesBindings.toDegrees( + UtilitiesBindings.atan2( + endYProperty().subtract(startYProperty()), + endXProperty().subtract(startXProperty())))); + + arrow.getTransforms().add(rotation); + + /* add translation transform to put the arrow touching the circle's bounds */ + Translate t = new Translate(-outbound.getRadius(), 0); + arrow.getTransforms().add(t); + } + + @Override + public SmartArrow getAttachedArrow() { + return this.attachedArrow; + } } diff --git a/src/graphvisualizer/graphview/SmartGraphPanel.java b/src/graphvisualizer/graphview/SmartGraphPanel.java index 1f619ae..b82a48b 100644 --- a/src/graphvisualizer/graphview/SmartGraphPanel.java +++ b/src/graphvisualizer/graphview/SmartGraphPanel.java @@ -1,4 +1,4 @@ -/* +/* * The MIT License * * Copyright 2019 brunomnsilva@gmail.com. @@ -23,9 +23,20 @@ */ package graphvisualizer.graphview; +import static graphvisualizer.graphview.UtilitiesJavaFX.pick; +import static graphvisualizer.graphview.UtilitiesPoint2D.attractiveForce; +import static graphvisualizer.graphview.UtilitiesPoint2D.repellingForce; + +import graphvisualizer.graph.Edge; +import graphvisualizer.graph.Graph; +import graphvisualizer.graph.Vertex; import java.io.File; import java.net.MalformedURLException; +import java.net.URI; import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,1013 +46,1029 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.BoundingBox; import javafx.geometry.Bounds; +import javafx.geometry.Point2D; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.control.Tooltip; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.text.Text; -import javafx.geometry.Point2D; -import javafx.scene.Scene; -import graphvisualizer.graph.Graph; -import graphvisualizer.graph.Vertex; -import graphvisualizer.graph.Edge; -import static graphvisualizer.graphview.UtilitiesJavaFX.pick; -import static graphvisualizer.graphview.UtilitiesPoint2D.attractiveForce; -import static graphvisualizer.graphview.UtilitiesPoint2D.repellingForce; -import java.net.URI; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; /** - * JavaFX {@link Pane} that is capable of plotting a {@link Graph}. - *
- * Be sure to call {@link #init() } after the Stage is displayed. - *
- * Whenever changes to the underlying graph are made, you should call - * {@link #update()} to force the rendering of any new elements and, also, the - * removal of others, if applicable. - *
- * Vertices can be dragged by the user, if configured to do so. Consequently, - * any connected edges will also adjust automatically to the new vertex positioning. + * JavaFX {@link Pane} that is capable of plotting a {@link Graph}.
+ * Be sure to call {@link #init() } after the Stage is displayed.
+ * Whenever changes to the underlying graph are made, you should call {@link #update()} to force the + * rendering of any new elements and, also, the removal of others, if applicable.
+ * Vertices can be dragged by the user, if configured to do so. Consequently, any connected edges + * will also adjust automatically to the new vertex positioning. * * @param Type of element stored at a vertex * @param Type of element stored at an edge - * * @author brunomnsilva */ public class SmartGraphPanel extends Pane { - /* - CONFIGURATION PROPERTIES - */ - private final SmartGraphProperties graphProperties; - - /* - INTERNAL DATA STRUCTURE - */ - private final Graph theGraph; - private final SmartPlacementStrategy placementStrategy; - private final Map, SmartGraphVertexNode> vertexNodes; - private final Map, SmartGraphEdgeBase> edgeNodes; - private final Map, Integer> placedEdges = new HashMap<>(); - private boolean initialized = false; - private final boolean edgesWithArrows; - /* - INTERACTION WITH VERTICES AND EDGES - */ - private Consumer> vertexClickConsumer = null; - private Consumer> edgeClickConsumer = null; - - /* - AUTOMATIC LAYOUT RELATED ATTRIBUTES - */ - public final BooleanProperty automaticLayoutProperty; - private AnimationTimer timer; - private final double repulsionForce; - private final double attractionForce; - private final double attractionScale; - - /** - * Constructs a visualization of the graph referenced by - * theGraph, using default properties and default random - * placement of vertices. - * - * @param theGraph underlying graph - * - * @see Graph - */ - public SmartGraphPanel(Graph theGraph) { - this(theGraph, new SmartGraphProperties(), null); - } - - /** - * Constructs a visualization of the graph referenced by - * theGraph, using custom properties and default random - * placement of vertices. - * - * @param theGraph underlying graph - * @param properties custom properties - */ - public SmartGraphPanel(Graph theGraph, SmartGraphProperties properties) { - this(theGraph, properties, null); - } - - /** - * Constructs a visualization of the graph referenced by - * theGraph, using default properties and custom placement of - * vertices. - * - * @param theGraph underlying graph - * @param placementStrategy placement strategy, null for default - */ - public SmartGraphPanel(Graph theGraph, SmartPlacementStrategy placementStrategy) { - this(theGraph, null, placementStrategy); - } - - /** - * Constructs a visualization of the graph referenced by - * theGraph, using custom properties and custom placement of - * vertices. - * - * @param theGraph underlying graph - * @param properties custom properties, null for default - * @param placementStrategy placement strategy, null for default - */ - public SmartGraphPanel(Graph theGraph, SmartGraphProperties properties, - SmartPlacementStrategy placementStrategy) { - - this(theGraph, properties, placementStrategy, null); + /* + CONFIGURATION PROPERTIES + */ + private final SmartGraphProperties graphProperties; + + /* + INTERNAL DATA STRUCTURE + */ + private final Graph theGraph; + private final SmartPlacementStrategy placementStrategy; + private final Map, SmartGraphVertexNode> vertexNodes; + private final Map, SmartGraphEdgeBase> edgeNodes; + private final Map, Integer> placedEdges = new HashMap<>(); + private boolean initialized = false; + private final boolean edgesWithArrows; + /* + INTERACTION WITH VERTICES AND EDGES + */ + private Consumer> vertexClickConsumer = null; + private Consumer> edgeClickConsumer = null; + + /* + AUTOMATIC LAYOUT RELATED ATTRIBUTES + */ + public final BooleanProperty automaticLayoutProperty; + private AnimationTimer timer; + private final double repulsionForce; + private final double attractionForce; + private final double attractionScale; + + /** + * Constructs a visualization of the graph referenced by theGraph, using default + * properties and default random placement of vertices. + * + * @param theGraph underlying graph + * @see Graph + */ + public SmartGraphPanel(Graph theGraph) { + this(theGraph, new SmartGraphProperties(), null); + } + + /** + * Constructs a visualization of the graph referenced by theGraph, using custom + * properties and default random placement of vertices. + * + * @param theGraph underlying graph + * @param properties custom properties + */ + public SmartGraphPanel(Graph theGraph, SmartGraphProperties properties) { + this(theGraph, properties, null); + } + + /** + * Constructs a visualization of the graph referenced by theGraph, using default + * properties and custom placement of vertices. + * + * @param theGraph underlying graph + * @param placementStrategy placement strategy, null for default + */ + public SmartGraphPanel(Graph theGraph, SmartPlacementStrategy placementStrategy) { + this(theGraph, null, placementStrategy); + } + + /** + * Constructs a visualization of the graph referenced by theGraph, using custom + * properties and custom placement of vertices. + * + * @param theGraph underlying graph + * @param properties custom properties, null for default + * @param placementStrategy placement strategy, null for default + */ + public SmartGraphPanel( + Graph theGraph, + SmartGraphProperties properties, + SmartPlacementStrategy placementStrategy) { + + this(theGraph, properties, placementStrategy, null); + } + + /** + * Constructs a visualization of the graph referenced by theGraph, using custom + * properties and custom placement of vertices. + * + * @param theGraph underlying graph + * @param properties custom properties, null for default + * @param placementStrategy placement strategy, null for default + * @param cssFile alternative css file, instead of default 'smartgraph.css' + */ + public SmartGraphPanel( + Graph theGraph, + SmartGraphProperties properties, + SmartPlacementStrategy placementStrategy, + URI cssFile) { + + if (theGraph == null) { + throw new IllegalArgumentException("The graph cannot be null."); } - - /** - * Constructs a visualization of the graph referenced by - * theGraph, using custom properties and custom placement of - * vertices. - * - * @param theGraph underlying graph - * @param properties custom properties, null for default - * @param placementStrategy placement strategy, null for default - * @param cssFile alternative css file, instead of default 'smartgraph.css' - */ - public SmartGraphPanel(Graph theGraph, SmartGraphProperties properties, - SmartPlacementStrategy placementStrategy, URI cssFile) { - - if (theGraph == null) { - throw new IllegalArgumentException("The graph cannot be null."); - } - this.theGraph = theGraph; - this.graphProperties = properties != null ? properties : new SmartGraphProperties(); - this.placementStrategy = placementStrategy != null ? placementStrategy : new SmartRandomPlacementStrategy(); + this.theGraph = theGraph; + this.graphProperties = properties != null ? properties : new SmartGraphProperties(); + this.placementStrategy = + placementStrategy != null ? placementStrategy : new SmartRandomPlacementStrategy(); - this.edgesWithArrows = this.graphProperties.getUseEdgeArrow(); + this.edgesWithArrows = this.graphProperties.getUseEdgeArrow(); - this.repulsionForce = this.graphProperties.getRepulsionForce(); - this.attractionForce = this.graphProperties.getAttractionForce(); - this.attractionScale = this.graphProperties.getAttractionScale(); + this.repulsionForce = this.graphProperties.getRepulsionForce(); + this.attractionForce = this.graphProperties.getAttractionForce(); + this.attractionScale = this.graphProperties.getAttractionScale(); - vertexNodes = new HashMap<>(); - edgeNodes = new HashMap<>(); + vertexNodes = new HashMap<>(); + edgeNodes = new HashMap<>(); - //set stylesheet and class - loadStylesheet(cssFile); + // set stylesheet and class + loadStylesheet(cssFile); - initNodes(); + initNodes(); - enableDoubleClickListener(); + enableDoubleClickListener(); - //automatic layout initializations - timer = new AnimationTimer() { + // automatic layout initializations + timer = + new AnimationTimer() { - @Override - public void handle(long now) { - runLayoutIteration(); - } + @Override + public void handle(long now) { + runLayoutIteration(); + } }; - - this.automaticLayoutProperty = new SimpleBooleanProperty(false); - this.automaticLayoutProperty.addListener((observable, oldValue, newValue) -> { - if (newValue) { - timer.start(); - } else { - timer.stop(); - } - }); - - } - - private synchronized void runLayoutIteration() { - for (int i = 0; i < 20; i++) { - resetForces(); - computeForces(); - updateForces(); - } - applyForces(); - } - - /** - * Runs the initial current vertex placement strategy. - *

- * This method should only be called once during the lifetime of the object - * and only after the underlying {@link Scene} is displayed. - * - * Further required updates should be performed through the {@link #update() - * } method. - * - * @throws IllegalStateException The exception is thrown if: (1) the Scene - * is not yet displayed; (2) It has zero width and/or height, and; (3) If - * this method was already called. - */ - public void init() throws IllegalStateException { - if (this.getScene() == null) { - throw new IllegalStateException("You must call this method after the instance was added to a scene."); - } else if (this.getWidth() == 0 || this.getHeight() == 0) { - throw new IllegalStateException("The layout for this panel has zero width and/or height"); - } else if (this.initialized) { - throw new IllegalStateException("Already initialized. Use update() method instead."); - } - - if (placementStrategy != null) { - // call strategy to place the vertices in their initial locations - placementStrategy.place(this.widthProperty().doubleValue(), - this.heightProperty().doubleValue(), - this.theGraph, - this.vertexNodes.values()); - } else { - //apply random placement - new SmartRandomPlacementStrategy().place(this.widthProperty().doubleValue(), - this.heightProperty().doubleValue(), - this.theGraph, - this.vertexNodes.values()); - //start automatic layout + this.automaticLayoutProperty = new SimpleBooleanProperty(false); + this.automaticLayoutProperty.addListener( + (observable, oldValue, newValue) -> { + if (newValue) { timer.start(); - } + } else { + timer.stop(); + } + }); + } - this.initialized = true; + private synchronized void runLayoutIteration() { + for (int i = 0; i < 20; i++) { + resetForces(); + computeForces(); + updateForces(); } - - /** - * Returns the property used to toggle the automatic layout of vertices. - * - * @return automatic layout property - */ - public BooleanProperty automaticLayoutProperty() { - return this.automaticLayoutProperty; + applyForces(); + } + + /** + * Runs the initial current vertex placement strategy. + * + *

This method should only be called once during the lifetime of the object and only after the + * underlying {@link Scene} is displayed. + * + *

Further required updates should be performed through the {@link #update() } method. + * + * @throws IllegalStateException The exception is thrown if: (1) the Scene is not yet displayed; + * (2) It has zero width and/or height, and; (3) If this method was already called. + */ + public void init() throws IllegalStateException { + if (this.getScene() == null) { + throw new IllegalStateException( + "You must call this method after the instance was added to a scene."); + } else if (this.getWidth() == 0 || this.getHeight() == 0) { + throw new IllegalStateException("The layout for this panel has zero width and/or height"); + } else if (this.initialized) { + throw new IllegalStateException("Already initialized. Use update() method instead."); } - - /** - * Toggle the automatic layout of vertices. - * - * @param value true if enabling; false, otherwise - */ - public void setAutomaticLayout(boolean value) { - automaticLayoutProperty.set(value); + + if (placementStrategy != null) { + // call strategy to place the vertices in their initial locations + placementStrategy.place( + this.widthProperty().doubleValue(), + this.heightProperty().doubleValue(), + this.theGraph, + this.vertexNodes.values()); + } else { + // apply random placement + new SmartRandomPlacementStrategy() + .place( + this.widthProperty().doubleValue(), + this.heightProperty().doubleValue(), + this.theGraph, + this.vertexNodes.values()); + + // start automatic layout + timer.start(); } - /** - * Forces a refresh of the visualization based on current state of the - * underlying graph, immediately returning to the caller. - * - * This method invokes the refresh in the graphical - * thread through Platform.runLater(), so its not guaranteed that the visualization is in sync - * immediately after this method finishes. That is, this method - * immediately returns to the caller without waiting for the update to the - * visualization. - *

- * New vertices will be added close to adjacent ones or randomly for - * isolated vertices. - */ - public void update() { - if (this.getScene() == null) { - throw new IllegalStateException("You must call this method after the instance was added to a scene."); - } + this.initialized = true; + } + + /** + * Returns the property used to toggle the automatic layout of vertices. + * + * @return automatic layout property + */ + public BooleanProperty automaticLayoutProperty() { + return this.automaticLayoutProperty; + } + + /** + * Toggle the automatic layout of vertices. + * + * @param value true if enabling; false, otherwise + */ + public void setAutomaticLayout(boolean value) { + automaticLayoutProperty.set(value); + } + + /** + * Forces a refresh of the visualization based on current state of the underlying graph, + * immediately returning to the caller. + * + *

This method invokes the refresh in the graphical thread through Platform.runLater(), so its + * not guaranteed that the visualization is in sync immediately after this method finishes. That + * is, this method immediately returns to the caller without waiting for the update to the + * visualization. + * + *

New vertices will be added close to adjacent ones or randomly for isolated vertices. + */ + public void update() { + if (this.getScene() == null) { + throw new IllegalStateException( + "You must call this method after the instance was added to a scene."); + } - if (!this.initialized) { - throw new IllegalStateException("You must call init() method before any updates."); - } + if (!this.initialized) { + throw new IllegalStateException("You must call init() method before any updates."); + } - //this will be called from a non-javafx thread, so this must be guaranteed to run of the graphics thread - Platform.runLater(() -> { - updateNodes(); + // this will be called from a non-javafx thread, so this must be guaranteed to run of the + // graphics thread + Platform.runLater( + () -> { + updateNodes(); }); + } + + /** + * Forces a refresh of the visualization based on current state of the underlying graph and waits + * for completion of the update. + * + *

Use this variant only when necessary, e.g., need to style an element immediately after + * adding it to the underlying graph. Otherwise, use {@link #update() } instead for performance + * sake. + * + *

New vertices will be added close to adjacent ones or randomly for isolated vertices. + */ + public void updateAndWait() { + if (this.getScene() == null) { + throw new IllegalStateException( + "You must call this method after the instance was added to a scene."); + } + if (!this.initialized) { + throw new IllegalStateException("You must call init() method before any updates."); } - - /** - * Forces a refresh of the visualization based on current state of the - * underlying graph and waits for completion of the update. - * - * Use this variant only when necessary, e.g., need to style an element - * immediately after adding it to the underlying graph. Otherwise, use - * {@link #update() } instead for performance sake. - *

- * New vertices will be added close to adjacent ones or randomly for - * isolated vertices. - */ - public void updateAndWait() { - if (this.getScene() == null) { - throw new IllegalStateException("You must call this method after the instance was added to a scene."); - } - if (!this.initialized) { - throw new IllegalStateException("You must call init() method before any updates."); - } - - final FutureTask update = new FutureTask(new Callable() { - @Override - public Boolean call() throws Exception { + final FutureTask update = + new FutureTask( + new Callable() { + @Override + public Boolean call() throws Exception { updateNodes(); return true; - } - }); - - //this will be called from a non-javafx thread, so this must be guaranteed to run of the graphics thread - Platform.runLater(update); - - try { - //wait for completion - update.get(); - } catch (InterruptedException | ExecutionException ex) { - Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); - } - + } + }); + + // this will be called from a non-javafx thread, so this must be guaranteed to run of the + // graphics thread + Platform.runLater(update); + + try { + // wait for completion + update.get(); + } catch (InterruptedException | ExecutionException ex) { + Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); } - - private synchronized void updateNodes() { - removeNodes(); - insertNodes(); - updateLabels(); + } + + private synchronized void updateNodes() { + removeNodes(); + insertNodes(); + updateLabels(); + } + + /* + INTERACTION WITH VERTICES AND EDGES + */ + /** + * Sets the action that should be performed when a vertex is double clicked. + * + * @param action action to be performed + */ + public void setVertexDoubleClickAction(Consumer> action) { + this.vertexClickConsumer = action; + } + + /** + * Sets the action that should be performed when an edge is double clicked. + * + * @param action action to be performed + */ + public void setEdgeDoubleClickAction(Consumer> action) { + this.edgeClickConsumer = action; + } + + /* + NODES CREATION/UPDATES + */ + private void initNodes() { + + /* create vertex graphical representations */ + for (Vertex vertex : listOfVertices()) { + SmartGraphVertexNode vertexAnchor = + new SmartGraphVertexNode( + vertex, + 0, + 0, + graphProperties.getVertexRadius(), + graphProperties.getVertexAllowUserMove()); + + vertexNodes.put(vertex, vertexAnchor); } - /* - INTERACTION WITH VERTICES AND EDGES - */ - /** - * Sets the action that should be performed when a vertex is double clicked. - * - * @param action action to be performed - */ - public void setVertexDoubleClickAction(Consumer> action) { - this.vertexClickConsumer = action; - } + /* create edges graphical representations between existing vertices */ + // this is used to guarantee that no duplicate edges are ever inserted + List> edgesToPlace = listOfEdges(); - /** - * Sets the action that should be performed when an edge is double clicked. - * - * @param action action to be performed - */ - public void setEdgeDoubleClickAction(Consumer> action) { - this.edgeClickConsumer = action; - } + for (Vertex vertex : vertexNodes.keySet()) { - /* - NODES CREATION/UPDATES - */ - private void initNodes() { + Iterable> incidentEdges = theGraph.incomingEdges(vertex); - /* create vertex graphical representations */ - for (Vertex vertex : listOfVertices()) { - SmartGraphVertexNode vertexAnchor = new SmartGraphVertexNode(vertex, 0, 0, - graphProperties.getVertexRadius(), graphProperties.getVertexAllowUserMove()); + for (Edge edge : incidentEdges) { - vertexNodes.put(vertex, vertexAnchor); + // if already plotted, ignore edge. + if (!edgesToPlace.contains(edge)) { + continue; } - /* create edges graphical representations between existing vertices */ - //this is used to guarantee that no duplicate edges are ever inserted - List> edgesToPlace = listOfEdges(); + Vertex oppositeVertex = theGraph.opposite(vertex, edge); - for (Vertex vertex : vertexNodes.keySet()) { + SmartGraphVertexNode graphVertexIn = vertexNodes.get(vertex); + SmartGraphVertexNode graphVertexOppositeOut = vertexNodes.get(oppositeVertex); - Iterable> incidentEdges = theGraph.incomingEdges(vertex); + graphVertexIn.addAdjacentVertex(graphVertexOppositeOut); + graphVertexOppositeOut.addAdjacentVertex(graphVertexIn); - for (Edge edge : incidentEdges) { + SmartGraphEdgeBase graphEdge = createEdge(edge, graphVertexIn, graphVertexOppositeOut); - //if already plotted, ignore edge. - if (!edgesToPlace.contains(edge)) { - continue; - } - - Vertex oppositeVertex = theGraph.opposite(vertex, edge); - - SmartGraphVertexNode graphVertexIn = vertexNodes.get(vertex); - SmartGraphVertexNode graphVertexOppositeOut = vertexNodes.get(oppositeVertex); - - graphVertexIn.addAdjacentVertex(graphVertexOppositeOut); - graphVertexOppositeOut.addAdjacentVertex(graphVertexIn); - - SmartGraphEdgeBase graphEdge = createEdge(edge, graphVertexIn, graphVertexOppositeOut); - - /* Track Edges already placed */ - addEdge(graphEdge, edge); - - if (this.edgesWithArrows) { - SmartArrow arrow = new SmartArrow(); - graphEdge.attachArrow(arrow); - this.getChildren().add(arrow); - } - - edgesToPlace.remove(edge); - } + /* Track Edges already placed */ + addEdge(graphEdge, edge); + if (this.edgesWithArrows) { + SmartArrow arrow = new SmartArrow(); + graphEdge.attachArrow(arrow); + this.getChildren().add(arrow); } - /* place vertices above lines */ - for (Vertex vertex : vertexNodes.keySet()) { - SmartGraphVertexNode v = vertexNodes.get(vertex); - - addVertex(v); - } + edgesToPlace.remove(edge); + } } - private SmartGraphEdgeBase createEdge(Edge edge, SmartGraphVertexNode graphVertexInbound, SmartGraphVertexNode graphVertexOutbound) { - /* - Even if edges are later removed, the corresponding index remains the same. Otherwise, we would have to - regenerate the appropriate edges. - */ - int edgeIndex = 0; - Integer counter = placedEdges.get(new Tuple(graphVertexInbound, graphVertexOutbound)); - - if (counter != null) { - edgeIndex = counter; - } - - SmartGraphEdgeBase graphEdge; - graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, 4); + /* place vertices above lines */ + for (Vertex vertex : vertexNodes.keySet()) { + SmartGraphVertexNode v = vertexNodes.get(vertex); - /* - if (getTotalEdgesBetween(graphVertexInbound.getUnderlyingVertex(), graphVertexOutbound.getUnderlyingVertex()) >= 1 - || graphVertexInbound == graphVertexOutbound) { - graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, new Random().nextInt(30) + 1); - graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, 30); - } else { - graphEdge = new SmartGraphEdgeLine<>(edge, graphVertexInbound, graphVertexOutbound); - } - */ - - placedEdges.put(new Tuple(graphVertexInbound, graphVertexOutbound), ++edgeIndex); - - return graphEdge; + addVertex(v); } + } - private void addVertex(SmartGraphVertexNode v) { - this.getChildren().add(v); - - String labelText = (v.getUnderlyingVertex().element() != null) ? - v.getUnderlyingVertex().element().toString() : - ""; - - if (graphProperties.getUseVertexTooltip()) { - Tooltip t = new Tooltip(labelText); - Tooltip.install(v, t); - } - - if (graphProperties.getUseVertexLabel()) { - SmartLabel label = new SmartLabel(labelText); + private SmartGraphEdgeBase createEdge( + Edge edge, + SmartGraphVertexNode graphVertexInbound, + SmartGraphVertexNode graphVertexOutbound) { + /* + Even if edges are later removed, the corresponding index remains the same. Otherwise, we would have to + regenerate the appropriate edges. + */ + int edgeIndex = 0; + Integer counter = placedEdges.get(new Tuple(graphVertexInbound, graphVertexOutbound)); - label.getStyleClass().add("vertex-label"); - this.getChildren().add(label); - v.attachLabel(label); - } + if (counter != null) { + edgeIndex = counter; } - private void addEdge(SmartGraphEdgeBase e, Edge edge) { - //edges to the back - this.getChildren().add(0, (Node) e); - edgeNodes.put(edge, e); - - String labelText = (edge.element() != null) ? - edge.element().toString() : - ""; - - if (graphProperties.getUseEdgeTooltip()) { - Tooltip t = new Tooltip(labelText); - Tooltip.install((Node) e, t); - } - - if (graphProperties.getUseEdgeLabel()) { - SmartLabel label = new SmartLabel(labelText); + SmartGraphEdgeBase graphEdge; + graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, 4); - label.getStyleClass().add("edge-label"); - this.getChildren().add(label); - e.attachLabel(label); - } + /* + if (getTotalEdgesBetween(graphVertexInbound.getUnderlyingVertex(), graphVertexOutbound.getUnderlyingVertex()) >= 1 + || graphVertexInbound == graphVertexOutbound) { + graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, new Random().nextInt(30) + 1); + graphEdge = new SmartGraphEdgeCurve(edge, graphVertexInbound, graphVertexOutbound, 30); + } else { + graphEdge = new SmartGraphEdgeLine<>(edge, graphVertexInbound, graphVertexOutbound); } + */ - private void insertNodes() { - Collection> unplottedVertices = unplottedVertices(); - - List> newVertices = null; - - Bounds bounds = getPlotBounds(); - double mx = bounds.getMinX() + bounds.getWidth() / 2.0; - double my = bounds.getMinY() + bounds.getHeight() / 2.0; - - if (!unplottedVertices.isEmpty()) { - - newVertices = new LinkedList<>(); - - for (Vertex vertex : unplottedVertices) { - //create node - //Place new nodes in the vicinity of existing adjacent ones; - //Place them in the middle of the plot, otherwise. - double x, y; - Collection> incidentEdges = theGraph.incomingEdges(vertex); - if (incidentEdges.isEmpty()) { - /* not (yet) connected, put in the middle of the plot */ - x = mx; - y = my; - } else { - Edge firstEdge = incidentEdges.iterator().next(); - Vertex opposite = theGraph.opposite(vertex, firstEdge); - SmartGraphVertexNode existing = vertexNodes.get(opposite); - - if(existing == null) { - /* - Updates may be coming too fast and we can get out of sync. - The opposite vertex exists in the (di)graph, but we have not yet - created it for the panel. Therefore, its position is unknown, - so place the vertex representation in the middle. - */ - x = mx; - y = my; - } else { - /* TODO: fix -- the placing point can be set out of bounds*/ - Point2D p = UtilitiesPoint2D.rotate(existing.getPosition().add(50.0, 50.0), - existing.getPosition(), Math.random() * 360); - - x = p.getX(); - y = p.getY(); - } - } - - SmartGraphVertexNode newVertex = new SmartGraphVertexNode<>(vertex, - x, y, graphProperties.getVertexRadius(), graphProperties.getVertexAllowUserMove()); - - //track new nodes - newVertices.add(newVertex); - //add to global mapping - vertexNodes.put(vertex, newVertex); - } + placedEdges.put(new Tuple(graphVertexInbound, graphVertexOutbound), ++edgeIndex); - } + return graphEdge; + } - Collection> unplottedEdges = unplottedEdges(); - if (!unplottedEdges.isEmpty()) { - for (Edge edge : unplottedEdges) { + private void addVertex(SmartGraphVertexNode v) { + this.getChildren().add(v); - Vertex[] vertices = edge.vertices(); - Vertex u = vertices[0]; //oubound if digraph, by javadoc requirement - Vertex v = vertices[1]; //inbound if digraph, by javadoc requirement + String labelText = + (v.getUnderlyingVertex().element() != null) + ? v.getUnderlyingVertex().element().toString() + : ""; - SmartGraphVertexNode graphVertexOut = vertexNodes.get(u); - SmartGraphVertexNode graphVertexIn = vertexNodes.get(v); + if (graphProperties.getUseVertexTooltip()) { + Tooltip t = new Tooltip(labelText); + Tooltip.install(v, t); + } - /* - Updates may be coming too fast and we can get out of sync. - Skip and wait for another update call, since they will surely - be coming at this pace. - */ - if(graphVertexIn == null || graphVertexOut == null) { - continue; - } - - graphVertexOut.addAdjacentVertex(graphVertexIn); - graphVertexIn.addAdjacentVertex(graphVertexOut); + if (graphProperties.getUseVertexLabel()) { + SmartLabel label = new SmartLabel(labelText); - SmartGraphEdgeBase graphEdge = createEdge(edge, graphVertexIn, graphVertexOut); + label.getStyleClass().add("vertex-label"); + this.getChildren().add(label); + v.attachLabel(label); + } + } - if (this.edgesWithArrows) { - SmartArrow arrow = new SmartArrow(); - graphEdge.attachArrow(arrow); - this.getChildren().add(arrow); - } + private void addEdge(SmartGraphEdgeBase e, Edge edge) { + // edges to the back + this.getChildren().add(0, (Node) e); + edgeNodes.put(edge, e); - addEdge(graphEdge, edge); + String labelText = (edge.element() != null) ? edge.element().toString() : ""; - } - } + if (graphProperties.getUseEdgeTooltip()) { + Tooltip t = new Tooltip(labelText); + Tooltip.install((Node) e, t); + } - if (newVertices != null) { - for (SmartGraphVertexNode v : newVertices) { - addVertex(v); - } - } + if (graphProperties.getUseEdgeLabel()) { + SmartLabel label = new SmartLabel(labelText); + label.getStyleClass().add("edge-label"); + this.getChildren().add(label); + e.attachLabel(label); } + } - private void removeNodes() { - Collection> removedVertices = removedVertices(); - Collection values = new LinkedList<>(edgeNodes.values()); + private void insertNodes() { + Collection> unplottedVertices = unplottedVertices(); - Set> verticesToRemove = new HashSet<>(); - Set edgesToRemove = new HashSet<>(); + List> newVertices = null; - //filter vertices to remove and their adjacent edges - for (Vertex v : removedVertices) { + Bounds bounds = getPlotBounds(); + double mx = bounds.getMinX() + bounds.getWidth() / 2.0; + double my = bounds.getMinY() + bounds.getHeight() / 2.0; - for (SmartGraphEdgeBase edge : values) { - Vertex[] vertices = edge.getUnderlyingEdge().vertices(); + if (!unplottedVertices.isEmpty()) { - if (vertices[0] == v || vertices[1] == v) { - edgesToRemove.add(edge); - } - } + newVertices = new LinkedList<>(); - SmartGraphVertexNode get = vertexNodes.get(v); - verticesToRemove.add(get); + for (Vertex vertex : unplottedVertices) { + // create node + // Place new nodes in the vicinity of existing adjacent ones; + // Place them in the middle of the plot, otherwise. + double x, y; + Collection> incidentEdges = theGraph.incomingEdges(vertex); + if (incidentEdges.isEmpty()) { + /* not (yet) connected, put in the middle of the plot */ + x = mx; + y = my; + } else { + Edge firstEdge = incidentEdges.iterator().next(); + Vertex opposite = theGraph.opposite(vertex, firstEdge); + SmartGraphVertexNode existing = vertexNodes.get(opposite); + + if (existing == null) { + /* + Updates may be coming too fast and we can get out of sync. + The opposite vertex exists in the (di)graph, but we have not yet + created it for the panel. Therefore, its position is unknown, + so place the vertex representation in the middle. + */ + x = mx; + y = my; + } else { + /* TODO: fix -- the placing point can be set out of bounds*/ + Point2D p = + UtilitiesPoint2D.rotate( + existing.getPosition().add(50.0, 50.0), + existing.getPosition(), + Math.random() * 360); + + x = p.getX(); + y = p.getY(); + } } - //permanently remove edges - for (SmartGraphEdgeBase e : edgesToRemove) { - edgeNodes.remove(e.getUnderlyingEdge()); - removeEdge(e); - } + SmartGraphVertexNode newVertex = + new SmartGraphVertexNode<>( + vertex, + x, + y, + graphProperties.getVertexRadius(), + graphProperties.getVertexAllowUserMove()); + + // track new nodes + newVertices.add(newVertex); + // add to global mapping + vertexNodes.put(vertex, newVertex); + } + } - //permanently remove vertices - for (SmartGraphVertexNode v : verticesToRemove) { - vertexNodes.remove(v.getUnderlyingVertex()); - removeVertice(v); - } - - //permanently remove remaining edges that were removed from the underlying graph - Collection> removedEdges = removedEdges(); - for (Edge e : removedEdges) { - SmartGraphEdgeBase edgeToRemove = edgeNodes.get(e); - edgeNodes.remove(e); - removeEdge(edgeToRemove); - } + Collection> unplottedEdges = unplottedEdges(); + if (!unplottedEdges.isEmpty()) { + for (Edge edge : unplottedEdges) { - //remove adjacencies from remaining vertices - for (SmartGraphVertexNode v : vertexNodes.values()) { - v.removeAdjacentVertices(verticesToRemove); - } - } + Vertex[] vertices = edge.vertices(); + Vertex u = vertices[0]; // oubound if digraph, by javadoc requirement + Vertex v = vertices[1]; // inbound if digraph, by javadoc requirement - private void removeEdge(SmartGraphEdgeBase e) { - getChildren().remove((Node) e); + SmartGraphVertexNode graphVertexOut = vertexNodes.get(u); + SmartGraphVertexNode graphVertexIn = vertexNodes.get(v); - SmartArrow attachedArrow = e.getAttachedArrow(); - if (attachedArrow != null) { - getChildren().remove(attachedArrow); + /* + Updates may be coming too fast and we can get out of sync. + Skip and wait for another update call, since they will surely + be coming at this pace. + */ + if (graphVertexIn == null || graphVertexOut == null) { + continue; } - Text attachedLabel = e.getAttachedLabel(); - if (attachedLabel != null) { - getChildren().remove(attachedLabel); - } - } + graphVertexOut.addAdjacentVertex(graphVertexIn); + graphVertexIn.addAdjacentVertex(graphVertexOut); - private void removeVertice(SmartGraphVertexNode v) { - getChildren().remove(v); + SmartGraphEdgeBase graphEdge = createEdge(edge, graphVertexIn, graphVertexOut); - Text attachedLabel = v.getAttachedLabel(); - if (attachedLabel != null) { - getChildren().remove(attachedLabel); + if (this.edgesWithArrows) { + SmartArrow arrow = new SmartArrow(); + graphEdge.attachArrow(arrow); + this.getChildren().add(arrow); } - } - /** - * Updates node's labels - */ - private void updateLabels() { - theGraph.vertices().forEach((v) -> { - SmartGraphVertexNode vertexNode = vertexNodes.get(v); - if (vertexNode != null) { - SmartLabel label = vertexNode.getAttachedLabel(); - if(label != null) { - label.setText(v.element() != null ? v.element().toString() : ""); - } - - } - }); - - theGraph.edges().forEach((e) -> { - SmartGraphEdgeBase edgeNode = edgeNodes.get(e); - if (edgeNode != null) { - SmartLabel label = edgeNode.getAttachedLabel(); - if (label != null) { - label.setText(e.element() != null ? e.element().toString() : ""); - } - } - }); + addEdge(graphEdge, edge); + } } - - /** - * Computes the bounding box from all displayed vertices. - * - * @return bounding box - */ - private Bounds getPlotBounds() { - double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, - maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE; - - if(vertexNodes.size() == 0) return new BoundingBox(0, 0, getWidth(), getHeight()); - - for (SmartGraphVertexNode v : vertexNodes.values()) { - minX = Math.min(minX, v.getCenterX()); - minY = Math.min(minY, v.getCenterY()); - maxX = Math.max(maxX, v.getCenterX()); - maxY = Math.max(maxY, v.getCenterY()); - } - return new BoundingBox(minX, minY, maxX - minX, maxY - minY); + if (newVertices != null) { + for (SmartGraphVertexNode v : newVertices) { + addVertex(v); + } } + } + private void removeNodes() { + Collection> removedVertices = removedVertices(); + Collection values = new LinkedList<>(edgeNodes.values()); - /* - * AUTOMATIC LAYOUT - */ - private void computeForces() { - for (SmartGraphVertexNode v : vertexNodes.values()) { - for (SmartGraphVertexNode other : vertexNodes.values()) { - if (v == other) { - continue; //NOP - } - - //double k = Math.sqrt(getWidth() * getHeight() / graphVertexMap.size()); - Point2D repellingForce = repellingForce(v.getUpdatedPosition(), other.getUpdatedPosition(), this.repulsionForce); + Set> verticesToRemove = new HashSet<>(); + Set edgesToRemove = new HashSet<>(); - double deltaForceX = 0, deltaForceY = 0; + // filter vertices to remove and their adjacent edges + for (Vertex v : removedVertices) { - //compute attractive and reppeling forces - //opt to use internal areAdjacent check, because a vertex can be removed from - //the underlying graph before we have the chance to remove it from our - //internal data structure - if (areAdjacent(v, other)) { + for (SmartGraphEdgeBase edge : values) { + Vertex[] vertices = edge.getUnderlyingEdge().vertices(); - Point2D attractiveForce = attractiveForce(v.getUpdatedPosition(), other.getUpdatedPosition(), - vertexNodes.size(), this.attractionForce, this.attractionScale); - - deltaForceX = attractiveForce.getX() + repellingForce.getX(); - deltaForceY = attractiveForce.getY() + repellingForce.getY(); - } else { - deltaForceX = repellingForce.getX(); - deltaForceY = repellingForce.getY(); - } - - v.addForceVector(deltaForceX, deltaForceY); - } + if (vertices[0] == v || vertices[1] == v) { + edgesToRemove.add(edge); } - } + } - private boolean areAdjacent(SmartGraphVertexNode v, SmartGraphVertexNode u) { - return v.isAdjacentTo(u); + SmartGraphVertexNode get = vertexNodes.get(v); + verticesToRemove.add(get); } - private void updateForces() { - vertexNodes.values().forEach((v) -> { - v.updateDelta(); - }); + // permanently remove edges + for (SmartGraphEdgeBase e : edgesToRemove) { + edgeNodes.remove(e.getUnderlyingEdge()); + removeEdge(e); } - private void applyForces() { - vertexNodes.values().forEach((v) -> { - v.moveFromForces(); - }); + // permanently remove vertices + for (SmartGraphVertexNode v : verticesToRemove) { + vertexNodes.remove(v.getUnderlyingVertex()); + removeVertice(v); } - private void resetForces() { - vertexNodes.values().forEach((v) -> { - v.resetForces(); - }); + // permanently remove remaining edges that were removed from the underlying graph + Collection> removedEdges = removedEdges(); + for (Edge e : removedEdges) { + SmartGraphEdgeBase edgeToRemove = edgeNodes.get(e); + edgeNodes.remove(e); + removeEdge(edgeToRemove); } - private int getTotalEdgesBetween(Vertex v, Vertex u) { - //TODO: It may be necessary to adjust this method if you use another Graph - //variant, e.g., Digraph (directed graph) - int count = 0; - for (Edge edge : theGraph.edges()) { - if (edge.vertices()[0] == v && edge.vertices()[1] == u - || edge.vertices()[0] == u && edge.vertices()[1] == v) { - count++; - } - } - return count; + // remove adjacencies from remaining vertices + for (SmartGraphVertexNode v : vertexNodes.values()) { + v.removeAdjacentVertices(verticesToRemove); } + } - private List> listOfEdges() { - List> list = new LinkedList<>(); - for (Edge edge : theGraph.edges()) { - list.add(edge); - } - return list; - } + private void removeEdge(SmartGraphEdgeBase e) { + getChildren().remove((Node) e); - private List> listOfVertices() { - List> list = new LinkedList<>(); - for (Vertex vertex : theGraph.vertices()) { - list.add(vertex); - } - return list; + SmartArrow attachedArrow = e.getAttachedArrow(); + if (attachedArrow != null) { + getChildren().remove(attachedArrow); } - /** - * Computes the vertex collection of the underlying graph that are not - * currently being displayed. - * - * @return collection of vertices - */ - private Collection> unplottedVertices() { - List> unplotted = new LinkedList<>(); - - for (Vertex v : theGraph.vertices()) { - if (!vertexNodes.containsKey(v)) { - unplotted.add(v); - } - } - - return unplotted; + Text attachedLabel = e.getAttachedLabel(); + if (attachedLabel != null) { + getChildren().remove(attachedLabel); } + } - /** - * Computes the collection for vertices that are currently being displayed but do - * not longer exist in the underlying graph. - * - * @return collection of vertices - */ - private Collection> removedVertices() { - List> removed = new LinkedList<>(); + private void removeVertice(SmartGraphVertexNode v) { + getChildren().remove(v); - Collection> graphVertices = theGraph.vertices(); - Collection> plotted = vertexNodes.values(); + Text attachedLabel = v.getAttachedLabel(); + if (attachedLabel != null) { + getChildren().remove(attachedLabel); + } + } + + /** Updates node's labels */ + private void updateLabels() { + theGraph + .vertices() + .forEach( + (v) -> { + SmartGraphVertexNode vertexNode = vertexNodes.get(v); + if (vertexNode != null) { + SmartLabel label = vertexNode.getAttachedLabel(); + if (label != null) { + label.setText(v.element() != null ? v.element().toString() : ""); + } + } + }); + + theGraph + .edges() + .forEach( + (e) -> { + SmartGraphEdgeBase edgeNode = edgeNodes.get(e); + if (edgeNode != null) { + SmartLabel label = edgeNode.getAttachedLabel(); + if (label != null) { + label.setText(e.element() != null ? e.element().toString() : ""); + } + } + }); + } + + /** + * Computes the bounding box from all displayed vertices. + * + * @return bounding box + */ + private Bounds getPlotBounds() { + double minX = Double.MAX_VALUE, + minY = Double.MAX_VALUE, + maxX = Double.MIN_VALUE, + maxY = Double.MIN_VALUE; + + if (vertexNodes.size() == 0) return new BoundingBox(0, 0, getWidth(), getHeight()); + + for (SmartGraphVertexNode v : vertexNodes.values()) { + minX = Math.min(minX, v.getCenterX()); + minY = Math.min(minY, v.getCenterY()); + maxX = Math.max(maxX, v.getCenterX()); + maxY = Math.max(maxY, v.getCenterY()); + } - for (SmartGraphVertexNode v : plotted) { - if (!graphVertices.contains(v.getUnderlyingVertex())) { - removed.add(v.getUnderlyingVertex()); - } + return new BoundingBox(minX, minY, maxX - minX, maxY - minY); + } + + /* + * AUTOMATIC LAYOUT + */ + private void computeForces() { + for (SmartGraphVertexNode v : vertexNodes.values()) { + for (SmartGraphVertexNode other : vertexNodes.values()) { + if (v == other) { + continue; // NOP } - return removed; - } - - /** - * Computes the collection for edges that are currently being displayed but do - * not longer exist in the underlying graph. - * - * @return collection of edges - */ - private Collection> removedEdges() { - List> removed = new LinkedList<>(); - - Collection> graphEdges = theGraph.edges(); - Collection plotted = edgeNodes.values(); + // double k = Math.sqrt(getWidth() * getHeight() / graphVertexMap.size()); + Point2D repellingForce = + repellingForce(v.getUpdatedPosition(), other.getUpdatedPosition(), this.repulsionForce); - for (SmartGraphEdgeBase e : plotted) { - if (!graphEdges.contains(e.getUnderlyingEdge())) { - removed.add(e.getUnderlyingEdge()); - } - } + double deltaForceX = 0, deltaForceY = 0; - return removed; - } + // compute attractive and reppeling forces + // opt to use internal areAdjacent check, because a vertex can be removed from + // the underlying graph before we have the chance to remove it from our + // internal data structure + if (areAdjacent(v, other)) { - /** - * Computes the edge collection of the underlying graph that are not - * currently being displayed. - * - * @return collection of edges - */ - private Collection> unplottedEdges() { - List> unplotted = new LinkedList<>(); + Point2D attractiveForce = + attractiveForce( + v.getUpdatedPosition(), + other.getUpdatedPosition(), + vertexNodes.size(), + this.attractionForce, + this.attractionScale); - for (Edge e : theGraph.edges()) { - if (!edgeNodes.containsKey(e)) { - unplotted.add(e); - } + deltaForceX = attractiveForce.getX() + repellingForce.getX(); + deltaForceY = attractiveForce.getY() + repellingForce.getY(); + } else { + deltaForceX = repellingForce.getX(); + deltaForceY = repellingForce.getY(); } - return unplotted; + v.addForceVector(deltaForceX, deltaForceY); + } } - - /** - * Returns the associated stylable element with a graph vertex. - * - * @param v underlying vertex - * @return stylable element - */ - public SmartStylableNode getStylableVertex(Vertex v) { - return vertexNodes.get(v); + } + + private boolean areAdjacent(SmartGraphVertexNode v, SmartGraphVertexNode u) { + return v.isAdjacentTo(u); + } + + private void updateForces() { + vertexNodes + .values() + .forEach( + (v) -> { + v.updateDelta(); + }); + } + + private void applyForces() { + vertexNodes + .values() + .forEach( + (v) -> { + v.moveFromForces(); + }); + } + + private void resetForces() { + vertexNodes + .values() + .forEach( + (v) -> { + v.resetForces(); + }); + } + + private int getTotalEdgesBetween(Vertex v, Vertex u) { + // TODO: It may be necessary to adjust this method if you use another Graph + // variant, e.g., Digraph (directed graph) + int count = 0; + for (Edge edge : theGraph.edges()) { + if (edge.vertices()[0] == v && edge.vertices()[1] == u + || edge.vertices()[0] == u && edge.vertices()[1] == v) { + count++; + } } + return count; + } - /** - * Returns the associated stylable element with a graph vertex. - * - * @param vertexElement underlying vertex's element - * @return stylable element - */ - public SmartStylableNode getStylableVertex(V vertexElement) { - for (Vertex v : vertexNodes.keySet()) { - if (v.element().equals(vertexElement)) { - return vertexNodes.get(v); - } - } - return null; + private List> listOfEdges() { + List> list = new LinkedList<>(); + for (Edge edge : theGraph.edges()) { + list.add(edge); } + return list; + } - /** - * Returns the associated stylable element with a graph edge. - * - * @param edge underlying graph edge - * @return stylable element - */ - public SmartStylableNode getStylableEdge(Edge edge) { - return edgeNodes.get(edge); + private List> listOfVertices() { + List> list = new LinkedList<>(); + for (Vertex vertex : theGraph.vertices()) { + list.add(vertex); } - - /** - * Returns the associated stylable element with a graph edge. - * - * @param edgeElement underlying graph edge's element - * @return stylable element - */ - public SmartStylableNode getStylableEdge(E edgeElement) { - for (Edge e : edgeNodes.keySet()) { - if (e.element().equals(edgeElement)) { - return edgeNodes.get(e); - } - } - return null; + return list; + } + + /** + * Computes the vertex collection of the underlying graph that are not currently being displayed. + * + * @return collection of vertices + */ + private Collection> unplottedVertices() { + List> unplotted = new LinkedList<>(); + + for (Vertex v : theGraph.vertices()) { + if (!vertexNodes.containsKey(v)) { + unplotted.add(v); + } } - /** - * Loads the stylesheet and applies the .graph class to this panel. - */ - private void loadStylesheet(URI cssFile) { - try { - String css; - if( cssFile != null ) { - css = cssFile.toURL().toExternalForm(); - } else { - File f = new File("smartgraph.css"); - css = f.toURI().toURL().toExternalForm(); - } + return unplotted; + } + + /** + * Computes the collection for vertices that are currently being displayed but do not longer exist + * in the underlying graph. + * + * @return collection of vertices + */ + private Collection> removedVertices() { + List> removed = new LinkedList<>(); + + Collection> graphVertices = theGraph.vertices(); + Collection> plotted = vertexNodes.values(); + + for (SmartGraphVertexNode v : plotted) { + if (!graphVertices.contains(v.getUnderlyingVertex())) { + removed.add(v.getUnderlyingVertex()); + } + } - getStylesheets().add(css); - this.getStyleClass().add("graph"); - } catch (MalformedURLException ex) { - Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); - } + return removed; + } + + /** + * Computes the collection for edges that are currently being displayed but do not longer exist in + * the underlying graph. + * + * @return collection of edges + */ + private Collection> removedEdges() { + List> removed = new LinkedList<>(); + + Collection> graphEdges = theGraph.edges(); + Collection plotted = edgeNodes.values(); + + for (SmartGraphEdgeBase e : plotted) { + if (!graphEdges.contains(e.getUnderlyingEdge())) { + removed.add(e.getUnderlyingEdge()); + } } - /** - * Enables the double click action on this pane. - * - * This method identifies the node that was clicked and, if any, calls the - * appropriate consumer, i.e., vertex or edge consumers. - */ - private void enableDoubleClickListener() { - setOnMouseClicked((MouseEvent mouseEvent) -> { - if (mouseEvent.getButton().equals(MouseButton.PRIMARY)) { - if (mouseEvent.getClickCount() == 2) { - //no need to continue otherwise - if (vertexClickConsumer == null && edgeClickConsumer == null) { - return; - } - - Node node = pick(SmartGraphPanel.this, mouseEvent.getSceneX(), mouseEvent.getSceneY()); - if (node == null) { - return; - } - - if (node instanceof SmartGraphVertex) { - SmartGraphVertex v = (SmartGraphVertex) node; - vertexClickConsumer.accept(v); - } else if (node instanceof SmartGraphEdge) { - SmartGraphEdge e = (SmartGraphEdge) node; - edgeClickConsumer.accept(e); - } + return removed; + } + + /** + * Computes the edge collection of the underlying graph that are not currently being displayed. + * + * @return collection of edges + */ + private Collection> unplottedEdges() { + List> unplotted = new LinkedList<>(); + + for (Edge e : theGraph.edges()) { + if (!edgeNodes.containsKey(e)) { + unplotted.add(e); + } + } - } + return unplotted; + } + + /** + * Returns the associated stylable element with a graph vertex. + * + * @param v underlying vertex + * @return stylable element + */ + public SmartStylableNode getStylableVertex(Vertex v) { + return vertexNodes.get(v); + } + + /** + * Returns the associated stylable element with a graph vertex. + * + * @param vertexElement underlying vertex's element + * @return stylable element + */ + public SmartStylableNode getStylableVertex(V vertexElement) { + for (Vertex v : vertexNodes.keySet()) { + if (v.element().equals(vertexElement)) { + return vertexNodes.get(v); + } + } + return null; + } + + /** + * Returns the associated stylable element with a graph edge. + * + * @param edge underlying graph edge + * @return stylable element + */ + public SmartStylableNode getStylableEdge(Edge edge) { + return edgeNodes.get(edge); + } + + /** + * Returns the associated stylable element with a graph edge. + * + * @param edgeElement underlying graph edge's element + * @return stylable element + */ + public SmartStylableNode getStylableEdge(E edgeElement) { + for (Edge e : edgeNodes.keySet()) { + if (e.element().equals(edgeElement)) { + return edgeNodes.get(e); + } + } + return null; + } + + /** Loads the stylesheet and applies the .graph class to this panel. */ + private void loadStylesheet(URI cssFile) { + try { + String css; + if (cssFile != null) { + css = cssFile.toURL().toExternalForm(); + } else { + File f = new File("smartgraph.css"); + css = f.toURI().toURL().toExternalForm(); + } + + getStylesheets().add(css); + this.getStyleClass().add("graph"); + } catch (MalformedURLException ex) { + Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + + /** + * Enables the double click action on this pane. + * + *

This method identifies the node that was clicked and, if any, calls the appropriate + * consumer, i.e., vertex or edge consumers. + */ + private void enableDoubleClickListener() { + setOnMouseClicked( + (MouseEvent mouseEvent) -> { + if (mouseEvent.getButton().equals(MouseButton.PRIMARY)) { + if (mouseEvent.getClickCount() == 2) { + // no need to continue otherwise + if (vertexClickConsumer == null && edgeClickConsumer == null) { + return; + } + + Node node = + pick(SmartGraphPanel.this, mouseEvent.getSceneX(), mouseEvent.getSceneY()); + if (node == null) { + return; + } + + if (node instanceof SmartGraphVertex) { + SmartGraphVertex v = (SmartGraphVertex) node; + vertexClickConsumer.accept(v); + } else if (node instanceof SmartGraphEdge) { + SmartGraphEdge e = (SmartGraphEdge) node; + edgeClickConsumer.accept(e); + } } + } }); - } - - /** - * Represents a tuple in Java. - * - * @param the type of the tuple - */ - private class Tuple { + } - private final T first; - private final T second; + /** + * Represents a tuple in Java. + * + * @param the type of the tuple + */ + private class Tuple { - public Tuple(T first, T second) { - this.first = first; - this.second = second; - } + private final T first; + private final T second; - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.first); - hash = 29 * hash + Objects.hashCode(this.second); - return hash; - } + public Tuple(T first, T second) { + this.first = first; + this.second = second; + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Tuple other = (Tuple) obj; - if (!Objects.equals(this.first, other.first)) { - return false; - } - if (!Objects.equals(this.second, other.second)) { - return false; - } - return true; - } + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.first); + hash = 29 * hash + Objects.hashCode(this.second); + return hash; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Tuple other = (Tuple) obj; + if (!Objects.equals(this.first, other.first)) { + return false; + } + if (!Objects.equals(this.second, other.second)) { + return false; + } + return true; + } + } } diff --git a/src/graphvisualizer/graphview/SmartGraphProperties.java b/src/graphvisualizer/graphview/SmartGraphProperties.java index d09ba59..25157c4 100644 --- a/src/graphvisualizer/graphview/SmartGraphProperties.java +++ b/src/graphvisualizer/graphview/SmartGraphProperties.java @@ -31,202 +31,193 @@ import java.util.logging.Logger; /** - * Properties used by {@link SmartGraphPanel}. Default file is given by - * the {@link #DEFAULT_FILE} property. + * Properties used by {@link SmartGraphPanel}. Default file is given by the {@link #DEFAULT_FILE} + * property. * * @see SmartGraphPanel * @see SmartGraphVertex * @see SmartGraphEdge - * * @author brunomnsilva */ public class SmartGraphProperties { - private static final boolean DEFAULT_VERTEX_ALLOW_USER_MOVE = true; - private static final String PROPERTY_VERTEX_ALLOW_USER_MOVE = "vertex.allow-user-move"; - - private static final double DEFAULT_VERTEX_RADIUS = 5; - private static final String PROPERTY_VERTEX_RADIUS = "vertex.radius"; - - private static final boolean DEFAULT_VERTEX_USE_TOOLTIP = true; - private static final String PROPERTY_VERTEX_USE_TOOLTIP = "vertex.tooltip"; - - private static final boolean DEFAULT_VERTEX_USE_LABEL = false; - private static final String PROPERTY_VERTEX_USE_LABEL = "vertex.label"; - - private static final boolean DEFAULT_EDGE_USE_TOOLTIP = true; - private static final String PROPERTY_EDGE_USE_TOOLTIP = "edge.tooltip"; - - private static final boolean DEFAULT_EDGE_USE_LABEL = true; - private static final String PROPERTY_EDGE_USE_LABEL = "edge.label"; - - private static final boolean DEFAULT_EDGE_USE_ARROW = true; - private static final String PROPERTY_EDGE_USE_ARROW = "edge.arrow"; - - private static final double DEFAULT_REPULSION_FORCE = 1000; - private static final String PROPERTY_REPULSION_FORCE = "layout.repulsive-force"; - - private static final double DEFAULT_ATTRACTION_FORCE = 20; - private static final String PROPERTY_ATTRACTION_FORCE = "layout.attraction-force"; - - private static final double DEFAULT_ATTRACTION_SCALE = 1; - private static final String PROPERTY_ATTRACTION_SCALE = "layout.attraction-scale"; - - private static final String DEFAULT_FILE = "smartgraph.properties"; - private Properties properties; - - /** - * Uses default properties file. - */ - public SmartGraphProperties() { - properties = new Properties(); - - try { - properties.load(new FileInputStream(DEFAULT_FILE)); - } catch (IOException ex) { - String msg = String.format("The default %s was not found. Using default values.", DEFAULT_FILE); - Logger.getLogger(SmartGraphProperties.class.getName()).log(Level.WARNING, msg); - } - } - - /** - * Reads properties from the desired input stream. - * - * @param inputStream input stream from where to read the properties - */ - public SmartGraphProperties(InputStream inputStream) { - properties = new Properties(); - try { - properties.load(inputStream); - } catch (IOException ex) { - String msg = "The file provided by the input stream does not exist. Using default values."; - Logger.getLogger(SmartGraphProperties.class.getName()).log(Level.WARNING, msg); - } - } - - /** - * Returns a property that indicates whether a vertex can be moved freely - * by the user. - * - * @return corresponding property value - */ - public boolean getVertexAllowUserMove() { - return getBooleanProperty(PROPERTY_VERTEX_ALLOW_USER_MOVE, DEFAULT_VERTEX_ALLOW_USER_MOVE); - } - - /** - * Returns a property that indicates the radius of each vertex. - * - * @return corresponding property value - */ - public double getVertexRadius() { - return getDoubleProperty(PROPERTY_VERTEX_RADIUS, DEFAULT_VERTEX_RADIUS); - } - - /** - * Returns a property that indicates the repulsion force to use in the - * automatic force-based layout. - * - * @return corresponding property value - */ - public double getRepulsionForce() { - return getDoubleProperty(PROPERTY_REPULSION_FORCE, DEFAULT_REPULSION_FORCE); - } - - /** - * Returns a property that indicates the attraction force to use in the - * automatic force-based layout. - * - * @return corresponding property value - */ - public double getAttractionForce() { - return getDoubleProperty(PROPERTY_ATTRACTION_FORCE, DEFAULT_ATTRACTION_FORCE); - } - - /** - * Returns a property that indicates the attraction scale to use in the - * automatic force-based layout. - * - * @return corresponding property value - */ - public double getAttractionScale() { - return getDoubleProperty(PROPERTY_ATTRACTION_SCALE, DEFAULT_ATTRACTION_SCALE); - } - - /** - * Returns a property that indicates whether a vertex has a tooltip installed. - * - * @return corresponding property value - */ - public boolean getUseVertexTooltip() { - return getBooleanProperty(PROPERTY_VERTEX_USE_TOOLTIP, DEFAULT_VERTEX_USE_TOOLTIP); - } - - /** - * Returns a property that indicates whether a vertex has a {@link SmartLabel} - * attached to it. - * - * @return corresponding property value - */ - public boolean getUseVertexLabel() { - return getBooleanProperty(PROPERTY_VERTEX_USE_LABEL, DEFAULT_VERTEX_USE_LABEL); - } - - /** - * Returns a property that indicates whether an edge has a tooltip installed. - * - * @return corresponding property value - */ - public boolean getUseEdgeTooltip() { - return getBooleanProperty(PROPERTY_EDGE_USE_TOOLTIP, DEFAULT_EDGE_USE_TOOLTIP); - } - - /** - * Returns a property that indicates whether an edge has a {@link SmartLabel} - * attached to it. - * - * @return corresponding property value - */ - public boolean getUseEdgeLabel() { - return getBooleanProperty(PROPERTY_EDGE_USE_LABEL, DEFAULT_EDGE_USE_LABEL); - } - - /** - * Returns a property that indicates whether a {@link SmartArrow} should be - * attached to an edge. - * - * @return corresponding property value - */ - public boolean getUseEdgeArrow() { - return getBooleanProperty(PROPERTY_EDGE_USE_ARROW, DEFAULT_EDGE_USE_ARROW); + private static final boolean DEFAULT_VERTEX_ALLOW_USER_MOVE = true; + private static final String PROPERTY_VERTEX_ALLOW_USER_MOVE = "vertex.allow-user-move"; + + private static final double DEFAULT_VERTEX_RADIUS = 5; + private static final String PROPERTY_VERTEX_RADIUS = "vertex.radius"; + + private static final boolean DEFAULT_VERTEX_USE_TOOLTIP = true; + private static final String PROPERTY_VERTEX_USE_TOOLTIP = "vertex.tooltip"; + + private static final boolean DEFAULT_VERTEX_USE_LABEL = false; + private static final String PROPERTY_VERTEX_USE_LABEL = "vertex.label"; + + private static final boolean DEFAULT_EDGE_USE_TOOLTIP = true; + private static final String PROPERTY_EDGE_USE_TOOLTIP = "edge.tooltip"; + + private static final boolean DEFAULT_EDGE_USE_LABEL = true; + private static final String PROPERTY_EDGE_USE_LABEL = "edge.label"; + + private static final boolean DEFAULT_EDGE_USE_ARROW = true; + private static final String PROPERTY_EDGE_USE_ARROW = "edge.arrow"; + + private static final double DEFAULT_REPULSION_FORCE = 1000; + private static final String PROPERTY_REPULSION_FORCE = "layout.repulsive-force"; + + private static final double DEFAULT_ATTRACTION_FORCE = 20; + private static final String PROPERTY_ATTRACTION_FORCE = "layout.attraction-force"; + + private static final double DEFAULT_ATTRACTION_SCALE = 1; + private static final String PROPERTY_ATTRACTION_SCALE = "layout.attraction-scale"; + + private static final String DEFAULT_FILE = "smartgraph.properties"; + private Properties properties; + + /** Uses default properties file. */ + public SmartGraphProperties() { + properties = new Properties(); + + try { + properties.load(new FileInputStream(DEFAULT_FILE)); + } catch (IOException ex) { + String msg = + String.format("The default %s was not found. Using default values.", DEFAULT_FILE); + Logger.getLogger(SmartGraphProperties.class.getName()).log(Level.WARNING, msg); } - - - private double getDoubleProperty(String propertyName, double defaultValue) { - String p = properties.getProperty(propertyName, Double.toString(defaultValue)); - try { - return Double.valueOf(p); - } catch (NumberFormatException e) { - System.err.printf("Error in reading property %s: %s", propertyName, e.getMessage()); - return defaultValue; - } - + } + + /** + * Reads properties from the desired input stream. + * + * @param inputStream input stream from where to read the properties + */ + public SmartGraphProperties(InputStream inputStream) { + properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException ex) { + String msg = "The file provided by the input stream does not exist. Using default values."; + Logger.getLogger(SmartGraphProperties.class.getName()).log(Level.WARNING, msg); } - - private boolean getBooleanProperty(String propertyName, boolean defaultValue) { - String p = properties.getProperty(propertyName, Boolean.toString(defaultValue)); - try { - return Boolean.valueOf(p); - } catch (NumberFormatException e) { - System.err.printf("Error in reading property %s: %s", propertyName, e.getMessage()); - return defaultValue; - } + } + + /** + * Returns a property that indicates whether a vertex can be moved freely by the user. + * + * @return corresponding property value + */ + public boolean getVertexAllowUserMove() { + return getBooleanProperty(PROPERTY_VERTEX_ALLOW_USER_MOVE, DEFAULT_VERTEX_ALLOW_USER_MOVE); + } + + /** + * Returns a property that indicates the radius of each vertex. + * + * @return corresponding property value + */ + public double getVertexRadius() { + return getDoubleProperty(PROPERTY_VERTEX_RADIUS, DEFAULT_VERTEX_RADIUS); + } + + /** + * Returns a property that indicates the repulsion force to use in the automatic force-based + * layout. + * + * @return corresponding property value + */ + public double getRepulsionForce() { + return getDoubleProperty(PROPERTY_REPULSION_FORCE, DEFAULT_REPULSION_FORCE); + } + + /** + * Returns a property that indicates the attraction force to use in the automatic force-based + * layout. + * + * @return corresponding property value + */ + public double getAttractionForce() { + return getDoubleProperty(PROPERTY_ATTRACTION_FORCE, DEFAULT_ATTRACTION_FORCE); + } + + /** + * Returns a property that indicates the attraction scale to use in the automatic force-based + * layout. + * + * @return corresponding property value + */ + public double getAttractionScale() { + return getDoubleProperty(PROPERTY_ATTRACTION_SCALE, DEFAULT_ATTRACTION_SCALE); + } + + /** + * Returns a property that indicates whether a vertex has a tooltip installed. + * + * @return corresponding property value + */ + public boolean getUseVertexTooltip() { + return getBooleanProperty(PROPERTY_VERTEX_USE_TOOLTIP, DEFAULT_VERTEX_USE_TOOLTIP); + } + + /** + * Returns a property that indicates whether a vertex has a {@link SmartLabel} attached to it. + * + * @return corresponding property value + */ + public boolean getUseVertexLabel() { + return getBooleanProperty(PROPERTY_VERTEX_USE_LABEL, DEFAULT_VERTEX_USE_LABEL); + } + + /** + * Returns a property that indicates whether an edge has a tooltip installed. + * + * @return corresponding property value + */ + public boolean getUseEdgeTooltip() { + return getBooleanProperty(PROPERTY_EDGE_USE_TOOLTIP, DEFAULT_EDGE_USE_TOOLTIP); + } + + /** + * Returns a property that indicates whether an edge has a {@link SmartLabel} attached to it. + * + * @return corresponding property value + */ + public boolean getUseEdgeLabel() { + return getBooleanProperty(PROPERTY_EDGE_USE_LABEL, DEFAULT_EDGE_USE_LABEL); + } + + /** + * Returns a property that indicates whether a {@link SmartArrow} should be attached to an edge. + * + * @return corresponding property value + */ + public boolean getUseEdgeArrow() { + return getBooleanProperty(PROPERTY_EDGE_USE_ARROW, DEFAULT_EDGE_USE_ARROW); + } + + private double getDoubleProperty(String propertyName, double defaultValue) { + String p = properties.getProperty(propertyName, Double.toString(defaultValue)); + try { + return Double.valueOf(p); + } catch (NumberFormatException e) { + System.err.printf("Error in reading property %s: %s", propertyName, e.getMessage()); + return defaultValue; } - - - public static void main(String[] args) { - SmartGraphProperties props = new SmartGraphProperties(); - System.out.println("Prop vertex radius: " + props.getVertexRadius()); - System.out.println("Prop vertex use label: " + props.getUseVertexLabel()); + } + + private boolean getBooleanProperty(String propertyName, boolean defaultValue) { + String p = properties.getProperty(propertyName, Boolean.toString(defaultValue)); + try { + return Boolean.valueOf(p); + } catch (NumberFormatException e) { + System.err.printf("Error in reading property %s: %s", propertyName, e.getMessage()); + return defaultValue; } + } + + public static void main(String[] args) { + SmartGraphProperties props = new SmartGraphProperties(); + System.out.println("Prop vertex radius: " + props.getVertexRadius()); + System.out.println("Prop vertex use label: " + props.getUseVertexLabel()); + } } diff --git a/src/graphvisualizer/graphview/SmartGraphVertex.java b/src/graphvisualizer/graphview/SmartGraphVertex.java index 5aed289..c78ecbe 100644 --- a/src/graphvisualizer/graphview/SmartGraphVertex.java +++ b/src/graphvisualizer/graphview/SmartGraphVertex.java @@ -28,53 +28,50 @@ /** * Abstracts the internal representation and behavior of a visualized graph vertex. - * + * * @param Type stored in the underlying vertex - * * @see SmartGraphPanel - * * @author brunomnsilva */ public interface SmartGraphVertex extends SmartStylableNode { - - /** - * Returns the underlying (stored reference) graph vertex. - * - * @return vertex reference - * - * @see Graph - */ - public Vertex getUnderlyingVertex(); - - /** - * Sets the position of this vertex in panel coordinates. - * - * Apart from its usage in the {@link SmartGraphPanel}, this method - * should only be called when implementing {@link SmartPlacementStrategy}. - * - * @param x x-coordinate for the vertex - * @param y y-coordinate for the vertex - */ - public void setPosition(double x, double y); - - /** - * Return the center x-coordinate of this vertex in panel coordinates. - * - * @return x-coordinate of the vertex - */ - public double getPositionCenterX(); - - /** - * Return the center y-coordinate of this vertex in panel coordinates. - * - * @return y-coordinate of the vertex - */ - public double getPositionCenterY(); - - /** - * Returns the circle radius used to represent this vertex. - * - * @return circle radius - */ - public double getRadius(); + + /** + * Returns the underlying (stored reference) graph vertex. + * + * @return vertex reference + * @see Graph + */ + public Vertex getUnderlyingVertex(); + + /** + * Sets the position of this vertex in panel coordinates. + * + *

Apart from its usage in the {@link SmartGraphPanel}, this method should only be called when + * implementing {@link SmartPlacementStrategy}. + * + * @param x x-coordinate for the vertex + * @param y y-coordinate for the vertex + */ + public void setPosition(double x, double y); + + /** + * Return the center x-coordinate of this vertex in panel coordinates. + * + * @return x-coordinate of the vertex + */ + public double getPositionCenterX(); + + /** + * Return the center y-coordinate of this vertex in panel coordinates. + * + * @return y-coordinate of the vertex + */ + public double getPositionCenterY(); + + /** + * Returns the circle radius used to represent this vertex. + * + * @return circle radius + */ + public double getRadius(); } diff --git a/src/graphvisualizer/graphview/SmartGraphVertexNode.java b/src/graphvisualizer/graphview/SmartGraphVertexNode.java index 8cc1d1f..50d3b2c 100644 --- a/src/graphvisualizer/graphview/SmartGraphVertexNode.java +++ b/src/graphvisualizer/graphview/SmartGraphVertexNode.java @@ -1,4 +1,4 @@ -/* +/* * The MIT License * * Copyright 2018 brunomnsilva@gmail.com. @@ -23,6 +23,7 @@ */ package graphvisualizer.graphview; +import graphvisualizer.graph.Vertex; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -30,323 +31,304 @@ import javafx.scene.Cursor; import javafx.scene.input.MouseEvent; import javafx.scene.shape.Circle; -import graphvisualizer.graph.Vertex; /** - * Internal implementation of a graph vertex for the {@link SmartGraphPanel} - * class. - *
- * Visually it depicts a vertex as a circle, extending from {@link Circle}. - *
- * The vertex internally deals with mouse drag events that visually move - * it in the {@link SmartGraphPanel} when displayed, if parameterized to do so. - * - * + * Internal implementation of a graph vertex for the {@link SmartGraphPanel} class.
+ * Visually it depicts a vertex as a circle, extending from {@link Circle}.
+ * The vertex internally deals with mouse drag events that visually move it in the {@link + * SmartGraphPanel} when displayed, if parameterized to do so. * * @param the type of the underlying vertex - * * @see SmartGraphPanel - * * @author brunomnsilva */ -public class SmartGraphVertexNode extends Circle implements SmartGraphVertex, SmartLabelledNode { - - private final Vertex underlyingVertex; - /* Critical for performance, so we don't rely on the efficiency of the Graph.areAdjacent method */ - private final Set> adjacentVertices; - - private SmartLabel attachedLabel = null; - private boolean isDragging = false; - - /* - Automatic layout functionality members - */ - private final PointVector forceVector = new PointVector(0, 0); - private final PointVector updatedPosition = new PointVector(0, 0); - - /** - * Constructor which sets the instance attributes. - * - * @param v the underlying vertex - * @param x initial x position on the parent pane - * @param y initial y position on the parent pane - * @param radius radius of this vertex representation, i.e., a circle - * @param allowMove should the vertex be draggable with the mouse - */ - public SmartGraphVertexNode(Vertex v, double x, double y, double radius, boolean allowMove) { - super(x, y, radius); - - this.underlyingVertex = v; - this.attachedLabel = null; - this.isDragging = false; - - this.adjacentVertices = new HashSet<>(); - - getStyleClass().add("vertex"); - - if (allowMove) { - enableDrag(); - } - } - - @Override - public void setStyleClass(String cssClass) { - getStyleClass().clear(); - setStyle(null); - getStyleClass().add(cssClass); - } - - /** - * Adds a vertex to the internal list of adjacent vertices. - * - * @param v vertex to add - */ - public void addAdjacentVertex(SmartGraphVertexNode v) { - this.adjacentVertices.add(v); - } - - /** - * Removes a vertex from the internal list of adjacent vertices. - * - * @param v vertex to remove - * @return true if v existed; false otherwise. - */ - public boolean removeAdjacentVertex(SmartGraphVertexNode v) { - return this.adjacentVertices.remove(v); - } - - /** - * Removes a collection of vertices from the internal list of adjacent - * vertices. - * - * @param col collection of vertices - * @return true if any vertex was effectively removed - */ - public boolean removeAdjacentVertices(Collection> col) { - return this.adjacentVertices.removeAll(col); - } - - /** - * Checks whether v is adjacent this instance. - * - * @param v vertex to check - * @return true if adjacent; false otherwise - */ - public boolean isAdjacentTo(SmartGraphVertexNode v) { - return this.adjacentVertices.contains(v); +public class SmartGraphVertexNode extends Circle + implements SmartGraphVertex, SmartLabelledNode { + + private final Vertex underlyingVertex; + /* Critical for performance, so we don't rely on the efficiency of the Graph.areAdjacent method */ + private final Set> adjacentVertices; + + private SmartLabel attachedLabel = null; + private boolean isDragging = false; + + /* + Automatic layout functionality members + */ + private final PointVector forceVector = new PointVector(0, 0); + private final PointVector updatedPosition = new PointVector(0, 0); + + /** + * Constructor which sets the instance attributes. + * + * @param v the underlying vertex + * @param x initial x position on the parent pane + * @param y initial y position on the parent pane + * @param radius radius of this vertex representation, i.e., a circle + * @param allowMove should the vertex be draggable with the mouse + */ + public SmartGraphVertexNode(Vertex v, double x, double y, double radius, boolean allowMove) { + super(x, y, radius); + + this.underlyingVertex = v; + this.attachedLabel = null; + this.isDragging = false; + + this.adjacentVertices = new HashSet<>(); + + getStyleClass().add("vertex"); + + if (allowMove) { + enableDrag(); } - - /** - * Returns the current position of the instance in pixels. - * - * @return the x,y coordinates in pixels - */ - public Point2D getPosition() { - return new Point2D(getCenterX(), getCenterY()); + } + + @Override + public void setStyleClass(String cssClass) { + getStyleClass().clear(); + setStyle(null); + getStyleClass().add(cssClass); + } + + /** + * Adds a vertex to the internal list of adjacent vertices. + * + * @param v vertex to add + */ + public void addAdjacentVertex(SmartGraphVertexNode v) { + this.adjacentVertices.add(v); + } + + /** + * Removes a vertex from the internal list of adjacent vertices. + * + * @param v vertex to remove + * @return true if v existed; false otherwise. + */ + public boolean removeAdjacentVertex(SmartGraphVertexNode v) { + return this.adjacentVertices.remove(v); + } + + /** + * Removes a collection of vertices from the internal list of adjacent vertices. + * + * @param col collection of vertices + * @return true if any vertex was effectively removed + */ + public boolean removeAdjacentVertices(Collection> col) { + return this.adjacentVertices.removeAll(col); + } + + /** + * Checks whether v is adjacent this instance. + * + * @param v vertex to check + * @return true if adjacent; false otherwise + */ + public boolean isAdjacentTo(SmartGraphVertexNode v) { + return this.adjacentVertices.contains(v); + } + + /** + * Returns the current position of the instance in pixels. + * + * @return the x,y coordinates in pixels + */ + public Point2D getPosition() { + return new Point2D(getCenterX(), getCenterY()); + } + + /** + * Sets the position of the instance in pixels. + * + * @param x x coordinate + * @param y y coordinate + */ + @Override + public void setPosition(double x, double y) { + if (isDragging) { + return; } - /** - * Sets the position of the instance in pixels. - * - * @param x x coordinate - * @param y y coordinate - */ - @Override - public void setPosition(double x, double y) { - if (isDragging) { - return; - } - - setCenterX(x); - setCenterY(y); - } - - @Override - public double getPositionCenterX() { - return getCenterX(); - } - - @Override - public double getPositionCenterY() { - return getCenterY(); - } - - - /** - * Sets the position of the instance in pixels. - * - * @param p coordinates - */ - public void setPosition(Point2D p) { - setPosition(p.getX(), p.getY()); - } - - /** - * Resets the current computed external force vector. - * - */ - public void resetForces() { - forceVector.x = forceVector.y = 0; - updatedPosition.x = getCenterX(); - updatedPosition.y = getCenterY(); - } - - /** - * Adds the vector represented by (x,y) to the current external - * force vector. - * - * @param x x-component of the force vector - * @param y y-component of the force vector - * - */ - public void addForceVector(double x, double y) { - forceVector.x += x; - forceVector.y += y; - } - - /** - * Returns the current external force vector. - * - * @return force vector - */ - public Point2D getForceVector() { - return new Point2D(forceVector.x, forceVector.y); - } - - /** - * Returns the future position of the vertex. - * - * @return future position - */ - public Point2D getUpdatedPosition() { - return new Point2D(updatedPosition.x, updatedPosition.y); - } - - /** - * Updates the future position according to the current internal force - * vector. - * - * see SmartGraphPanel#updateForces() - */ - public void updateDelta() { - updatedPosition.x = updatedPosition.x /* + speed*/ + forceVector.x; - updatedPosition.y = updatedPosition.y + forceVector.y; - } - - /** - * Moves the vertex position to the computed future position. - *

- * Moves are constrained within the parent pane dimensions. - * - * see SmartGraphPanel#applyForces() - */ - public void moveFromForces() { - - //limit movement to parent bounds - double height = getParent().getLayoutBounds().getHeight(); - double width = getParent().getLayoutBounds().getWidth(); - - updatedPosition.x = boundCenterCoordinate(updatedPosition.x, 0, width); - updatedPosition.y = boundCenterCoordinate(updatedPosition.y, 0, height); - - setPosition(updatedPosition.x, updatedPosition.y); - } - - /** - * Make a node movable by dragging it around with the mouse primary button. - */ - private void enableDrag() { - final PointVector dragDelta = new PointVector(0, 0); - - setOnMousePressed((MouseEvent mouseEvent) -> { - if (mouseEvent.isPrimaryButtonDown()) { - // record a delta distance for the drag and drop operation. - dragDelta.x = getCenterX() - mouseEvent.getX(); - dragDelta.y = getCenterY() - mouseEvent.getY(); - getScene().setCursor(Cursor.MOVE); - isDragging = true; - - mouseEvent.consume(); - } - - }); - - setOnMouseReleased((MouseEvent mouseEvent) -> { - getScene().setCursor(Cursor.HAND); - isDragging = false; + setCenterX(x); + setCenterY(y); + } + + @Override + public double getPositionCenterX() { + return getCenterX(); + } + + @Override + public double getPositionCenterY() { + return getCenterY(); + } + + /** + * Sets the position of the instance in pixels. + * + * @param p coordinates + */ + public void setPosition(Point2D p) { + setPosition(p.getX(), p.getY()); + } + + /** Resets the current computed external force vector. */ + public void resetForces() { + forceVector.x = forceVector.y = 0; + updatedPosition.x = getCenterX(); + updatedPosition.y = getCenterY(); + } + + /** + * Adds the vector represented by (x,y) to the current external force vector. + * + * @param x x-component of the force vector + * @param y y-component of the force vector + */ + public void addForceVector(double x, double y) { + forceVector.x += x; + forceVector.y += y; + } + + /** + * Returns the current external force vector. + * + * @return force vector + */ + public Point2D getForceVector() { + return new Point2D(forceVector.x, forceVector.y); + } + + /** + * Returns the future position of the vertex. + * + * @return future position + */ + public Point2D getUpdatedPosition() { + return new Point2D(updatedPosition.x, updatedPosition.y); + } + + /** + * Updates the future position according to the current internal force vector. + * + *

see SmartGraphPanel#updateForces() + */ + public void updateDelta() { + updatedPosition.x = updatedPosition.x /* + speed*/ + forceVector.x; + updatedPosition.y = updatedPosition.y + forceVector.y; + } + + /** + * Moves the vertex position to the computed future position. + * + *

Moves are constrained within the parent pane dimensions. + * + *

see SmartGraphPanel#applyForces() + */ + public void moveFromForces() { + + // limit movement to parent bounds + double height = getParent().getLayoutBounds().getHeight(); + double width = getParent().getLayoutBounds().getWidth(); + + updatedPosition.x = boundCenterCoordinate(updatedPosition.x, 0, width); + updatedPosition.y = boundCenterCoordinate(updatedPosition.y, 0, height); + + setPosition(updatedPosition.x, updatedPosition.y); + } + + /** Make a node movable by dragging it around with the mouse primary button. */ + private void enableDrag() { + final PointVector dragDelta = new PointVector(0, 0); + + setOnMousePressed( + (MouseEvent mouseEvent) -> { + if (mouseEvent.isPrimaryButtonDown()) { + // record a delta distance for the drag and drop operation. + dragDelta.x = getCenterX() - mouseEvent.getX(); + dragDelta.y = getCenterY() - mouseEvent.getY(); + getScene().setCursor(Cursor.MOVE); + isDragging = true; mouseEvent.consume(); + } }); - setOnMouseDragged((MouseEvent mouseEvent) -> { - if (mouseEvent.isPrimaryButtonDown()) { - double newX = mouseEvent.getX() + dragDelta.x; - double x = boundCenterCoordinate(newX, 0, getParent().getLayoutBounds().getWidth()); - setCenterX(x); - - double newY = mouseEvent.getY() + dragDelta.y; - double y = boundCenterCoordinate(newY, 0, getParent().getLayoutBounds().getHeight()); - setCenterY(y); - mouseEvent.consume(); - } + setOnMouseReleased( + (MouseEvent mouseEvent) -> { + getScene().setCursor(Cursor.HAND); + isDragging = false; + mouseEvent.consume(); }); - setOnMouseEntered((MouseEvent mouseEvent) -> { - if (!mouseEvent.isPrimaryButtonDown()) { - getScene().setCursor(Cursor.HAND); - } + setOnMouseDragged( + (MouseEvent mouseEvent) -> { + if (mouseEvent.isPrimaryButtonDown()) { + double newX = mouseEvent.getX() + dragDelta.x; + double x = boundCenterCoordinate(newX, 0, getParent().getLayoutBounds().getWidth()); + setCenterX(x); + double newY = mouseEvent.getY() + dragDelta.y; + double y = boundCenterCoordinate(newY, 0, getParent().getLayoutBounds().getHeight()); + setCenterY(y); + mouseEvent.consume(); + } }); - setOnMouseExited((MouseEvent mouseEvent) -> { - if (!mouseEvent.isPrimaryButtonDown()) { - getScene().setCursor(Cursor.DEFAULT); - } + setOnMouseEntered( + (MouseEvent mouseEvent) -> { + if (!mouseEvent.isPrimaryButtonDown()) { + getScene().setCursor(Cursor.HAND); + } + }); + setOnMouseExited( + (MouseEvent mouseEvent) -> { + if (!mouseEvent.isPrimaryButtonDown()) { + getScene().setCursor(Cursor.DEFAULT); + } }); - } + } - private double boundCenterCoordinate(double value, double min, double max) { - double radius = getRadius(); + private double boundCenterCoordinate(double value, double min, double max) { + double radius = getRadius(); - if (value < min + radius) { - return min + radius; - } else if (value > max - radius) { - return max - radius; - } else { - return value; - } + if (value < min + radius) { + return min + radius; + } else if (value > max - radius) { + return max - radius; + } else { + return value; } + } - @Override - public void attachLabel(SmartLabel label) { - this.attachedLabel = label; - label.xProperty().bind(centerXProperty().subtract(label.getLayoutBounds().getWidth())); - label.yProperty().bind(centerYProperty().add(label.getLayoutBounds().getHeight() / 2.0)); - } + @Override + public void attachLabel(SmartLabel label) { + this.attachedLabel = label; + label.xProperty().bind(centerXProperty().subtract(label.getLayoutBounds().getWidth())); + label.yProperty().bind(centerYProperty().add(label.getLayoutBounds().getHeight() / 2.0)); + } - @Override - public SmartLabel getAttachedLabel() { - return attachedLabel; - } + @Override + public SmartLabel getAttachedLabel() { + return attachedLabel; + } - @Override - public Vertex getUnderlyingVertex() { - return underlyingVertex; - } + @Override + public Vertex getUnderlyingVertex() { + return underlyingVertex; + } - /** - * Internal representation of a 2D point or vector for quick access to its - * attributes. - */ - private class PointVector { + /** Internal representation of a 2D point or vector for quick access to its attributes. */ + private class PointVector { - double x, y; + double x, y; - public PointVector(double x, double y) { - this.x = x; - this.y = y; - } + public PointVector(double x, double y) { + this.x = x; + this.y = y; } + } } diff --git a/src/graphvisualizer/graphview/SmartLabel.java b/src/graphvisualizer/graphview/SmartLabel.java index 856cea2..beb135e 100644 --- a/src/graphvisualizer/graphview/SmartLabel.java +++ b/src/graphvisualizer/graphview/SmartLabel.java @@ -26,29 +26,25 @@ import javafx.scene.text.Text; /** - * A label contains text and can be attached to any {@link SmartLabelledNode}. - *
- * This class extends from {@link Text} and is allowed any corresponding - * css formatting. - * + * A label contains text and can be attached to any {@link SmartLabelledNode}.
+ * This class extends from {@link Text} and is allowed any corresponding css formatting. + * * @author Bruno Silva */ public class SmartLabel extends Text implements SmartStylableNode { - public SmartLabel() { - } + public SmartLabel() {} + + public SmartLabel(String text) { + super(text); + } - public SmartLabel(String text) { - super(text); - } + public SmartLabel(double x, double y, String text) { + super(x, y, text); + } - public SmartLabel(double x, double y, String text) { - super(x, y, text); - } - - @Override - public void setStyleClass(String cssClass) { - getStyleClass().add(cssClass); - } - + @Override + public void setStyleClass(String cssClass) { + getStyleClass().add(cssClass); + } } diff --git a/src/graphvisualizer/graphview/SmartLabelledNode.java b/src/graphvisualizer/graphview/SmartLabelledNode.java index 6112cc0..87daa26 100644 --- a/src/graphvisualizer/graphview/SmartLabelledNode.java +++ b/src/graphvisualizer/graphview/SmartLabelledNode.java @@ -25,23 +25,22 @@ /** * A node to which a {@link SmartLabel} can be attached. - * + * * @author brunomnsilva */ public interface SmartLabelledNode { - - /** - * Own and bind the label position to the desired position. - * - * @param label text label node - */ - public void attachLabel(SmartLabel label); - - /** - * Returns the attached text label, if any. - * - * @return the text label reference or null if no label is attached - */ - public SmartLabel getAttachedLabel(); - + + /** + * Own and bind the label position to the desired position. + * + * @param label text label node + */ + public void attachLabel(SmartLabel label); + + /** + * Returns the attached text label, if any. + * + * @return the text label reference or null if no label is attached + */ + public SmartLabel getAttachedLabel(); } diff --git a/src/graphvisualizer/graphview/SmartPlacementStrategy.java b/src/graphvisualizer/graphview/SmartPlacementStrategy.java index 1d5e219..5ed9acf 100644 --- a/src/graphvisualizer/graphview/SmartPlacementStrategy.java +++ b/src/graphvisualizer/graphview/SmartPlacementStrategy.java @@ -23,34 +23,33 @@ */ package graphvisualizer.graphview; -import java.util.Collection; import graphvisualizer.graph.Graph; +import java.util.Collection; /** - * Contains the method that should be implemented when creating new vertex placement - * strategies. - * + * Contains the method that should be implemented when creating new vertex placement strategies. + * * @author brunomnsilva */ public interface SmartPlacementStrategy { - /** - * Implementations of placement strategies must implement this interface. - * - * Should use the {@link SmartGraphVertex#setPosition(double, double) } - * method to place individual vertices. - * - * - * @param Generic type for element stored at vertices. - * @param Generic type for element stored at edges. - * @param width Width of the area in which to place the vertices. - * @param height Height of the area in which to place the vertices. - * @param theGraph Reference to the {@link Graph} containing the graph model. - * Can use methods to check for additional information - * pertaining the model. - * - * @param vertices Collection of {@link SmartGraphVertex} to place. - * - */ - public void place(double width, double height, Graph theGraph, Collection> vertices); + /** + * Implementations of placement strategies must implement this interface. + * + *

Should use the {@link SmartGraphVertex#setPosition(double, double) } method to place + * individual vertices. + * + * @param Generic type for element stored at vertices. + * @param Generic type for element stored at edges. + * @param width Width of the area in which to place the vertices. + * @param height Height of the area in which to place the vertices. + * @param theGraph Reference to the {@link Graph} containing the graph model. Can use methods to + * check for additional information pertaining the model. + * @param vertices Collection of {@link SmartGraphVertex} to place. + */ + public void place( + double width, + double height, + Graph theGraph, + Collection> vertices); } diff --git a/src/graphvisualizer/graphview/SmartRandomPlacementStrategy.java b/src/graphvisualizer/graphview/SmartRandomPlacementStrategy.java index a53ceb3..c07a311 100644 --- a/src/graphvisualizer/graphview/SmartRandomPlacementStrategy.java +++ b/src/graphvisualizer/graphview/SmartRandomPlacementStrategy.java @@ -23,32 +23,33 @@ */ package graphvisualizer.graphview; +import graphvisualizer.graph.Graph; import java.util.Collection; import java.util.Random; -import graphvisualizer.graph.Graph; /** * Scatters the vertices randomly. - * + * * @see SmartPlacementStrategy - * * @author brunomnsilva */ public class SmartRandomPlacementStrategy implements SmartPlacementStrategy { - @Override - public void place(double width, double height, Graph theGraph, Collection> vertices) { - - Random rand = new Random(); + @Override + public void place( + double width, + double height, + Graph theGraph, + Collection> vertices) { + + Random rand = new Random(); + + for (SmartGraphVertex vertex : vertices) { + + double x = rand.nextDouble() * width; + double y = rand.nextDouble() * height; - for (SmartGraphVertex vertex : vertices) { - - double x = rand.nextDouble() * width; - double y = rand.nextDouble() * height; - - vertex.setPosition(x, y); - - } + vertex.setPosition(x, y); } - + } } diff --git a/src/graphvisualizer/graphview/SmartStylableNode.java b/src/graphvisualizer/graphview/SmartStylableNode.java index 490446f..b6baa33 100644 --- a/src/graphvisualizer/graphview/SmartStylableNode.java +++ b/src/graphvisualizer/graphview/SmartStylableNode.java @@ -24,38 +24,35 @@ package graphvisualizer.graphview; /** - * A stylable node can have its css properties changed at runtime. - *
- * All Java FX nodes used by {@link SmartGraphPanel} to represent graph entities - * should implement this interface. - * + * A stylable node can have its css properties changed at runtime.
+ * All Java FX nodes used by {@link SmartGraphPanel} to represent graph entities should implement + * this interface. + * * @see SmartGraphPanel - * * @author brunomnsilva */ -public interface SmartStylableNode { - - /** - * Applies cumulatively the css styles to the node. - * - * Note that JavaFX styles are cumulative. - * - * @param css styles - */ - public void setStyle(String css); - - /** - * Applies the CSS styling defined in class selector cssClass. - * - * The cssClass string must not contain a preceding dot, e.g., - * "myClass" instead of ".myClass". - * - * The CSS Class must be defined in smartpgraph.css file. - * - * The expected behavior is to remove all current styling before - * applying the class css. - * - * @param cssClass name of the CSS class. - */ - public void setStyleClass(String cssClass); +public interface SmartStylableNode { + + /** + * Applies cumulatively the css styles to the node. + * + *

Note that JavaFX styles are cumulative. + * + * @param css styles + */ + public void setStyle(String css); + + /** + * Applies the CSS styling defined in class selector cssClass. + * + *

The cssClass string must not contain a preceding dot, e.g., "myClass" instead + * of ".myClass". + * + *

The CSS Class must be defined in smartpgraph.css file. + * + *

The expected behavior is to remove all current styling before applying the class css. + * + * @param cssClass name of the CSS class. + */ + public void setStyleClass(String cssClass); } diff --git a/src/graphvisualizer/graphview/UtilitiesBindings.java b/src/graphvisualizer/graphview/UtilitiesBindings.java index c7304da..f742f22 100644 --- a/src/graphvisualizer/graphview/UtilitiesBindings.java +++ b/src/graphvisualizer/graphview/UtilitiesBindings.java @@ -24,38 +24,37 @@ package graphvisualizer.graphview; import static javafx.beans.binding.Bindings.createDoubleBinding; + import javafx.beans.binding.DoubleBinding; import javafx.beans.value.ObservableDoubleValue; /** * Some {@link Math} operations implemented as bindings. - * + * * @author brunomnsilva */ public class UtilitiesBindings { - - /** - * Binding for {@link java.lang.Math#atan2(double, double)} - * - * @param y the ordinate coordinate - * @param x the abscissa coordinate - * @return the theta component of the point - * (rtheta) - * in polar coordinates that corresponds to the point - * (xy) in Cartesian coordinates. - */ - public static DoubleBinding atan2(final ObservableDoubleValue y, final ObservableDoubleValue x) { - return createDoubleBinding(() -> Math.atan2(y.get(), x.get()), y, x); - } - - /** - * Binding for {@link java.lang.Math#toDegrees(double)} - * - * @param angrad an angle, in radians - * @return the measurement of the angle {@code angrad} - * in degrees. - */ - public static DoubleBinding toDegrees(final ObservableDoubleValue angrad) { - return createDoubleBinding(() -> Math.toDegrees(angrad.get()), angrad); - } + + /** + * Binding for {@link java.lang.Math#atan2(double, double)} + * + * @param y the ordinate coordinate + * @param x the abscissa coordinate + * @return the theta component of the point (rtheta) in polar + * coordinates that corresponds to the point (xy) in Cartesian + * coordinates. + */ + public static DoubleBinding atan2(final ObservableDoubleValue y, final ObservableDoubleValue x) { + return createDoubleBinding(() -> Math.atan2(y.get(), x.get()), y, x); + } + + /** + * Binding for {@link java.lang.Math#toDegrees(double)} + * + * @param angrad an angle, in radians + * @return the measurement of the angle {@code angrad} in degrees. + */ + public static DoubleBinding toDegrees(final ObservableDoubleValue angrad) { + return createDoubleBinding(() -> Math.toDegrees(angrad.get()), angrad); + } } diff --git a/src/graphvisualizer/graphview/UtilitiesJavaFX.java b/src/graphvisualizer/graphview/UtilitiesJavaFX.java index 096efa8..8ff47cd 100644 --- a/src/graphvisualizer/graphview/UtilitiesJavaFX.java +++ b/src/graphvisualizer/graphview/UtilitiesJavaFX.java @@ -7,53 +7,51 @@ /** * Utility methods for JavaFX. - * + * * @author brunomnsilva */ public class UtilitiesJavaFX { - /** - * Determines the closest node that resides in the x,y scene position, if any. - *
- * Obtained from: http://fxexperience.com/2016/01/node-picking-in-javafx/ - * - * @param node parent node - * @param sceneX x-coordinate of picking point - * @param sceneY y-coordinate of picking point - * - * @return topmost node containing (sceneX, sceneY) point - */ - public static Node pick(Node node, double sceneX, double sceneY) { - Point2D p = node.sceneToLocal(sceneX, sceneY, true /* rootScene */); + /** + * Determines the closest node that resides in the x,y scene position, if any.
+ * Obtained from: http://fxexperience.com/2016/01/node-picking-in-javafx/ + * + * @param node parent node + * @param sceneX x-coordinate of picking point + * @param sceneY y-coordinate of picking point + * @return topmost node containing (sceneX, sceneY) point + */ + public static Node pick(Node node, double sceneX, double sceneY) { + Point2D p = node.sceneToLocal(sceneX, sceneY, true /* rootScene */); - // check if the given node has the point inside it, or else we drop out - if (!node.contains(p)) { - return null; - } - - // at this point we know that _at least_ the given node is a valid - // answer to the given point, so we will return that if we don't find - // a better child option - if (node instanceof Parent) { - // we iterate through all children in reverse order, and stop when we find a match. - // We do this as we know the elements at the end of the list have a higher - // z-order, and are therefore the better match, compared to children that - // might also intersect (but that would be underneath the element). - Node bestMatchingChild = null; - List children = ((Parent) node).getChildrenUnmodifiable(); - for (int i = children.size() - 1; i >= 0; i--) { - Node child = children.get(i); - p = child.sceneToLocal(sceneX, sceneY, true /* rootScene */); - if (child.isVisible() && !child.isMouseTransparent() && child.contains(p)) { - bestMatchingChild = child; - break; - } - } + // check if the given node has the point inside it, or else we drop out + if (!node.contains(p)) { + return null; + } - if (bestMatchingChild != null) { - return pick(bestMatchingChild, sceneX, sceneY); - } + // at this point we know that _at least_ the given node is a valid + // answer to the given point, so we will return that if we don't find + // a better child option + if (node instanceof Parent) { + // we iterate through all children in reverse order, and stop when we find a match. + // We do this as we know the elements at the end of the list have a higher + // z-order, and are therefore the better match, compared to children that + // might also intersect (but that would be underneath the element). + Node bestMatchingChild = null; + List children = ((Parent) node).getChildrenUnmodifiable(); + for (int i = children.size() - 1; i >= 0; i--) { + Node child = children.get(i); + p = child.sceneToLocal(sceneX, sceneY, true /* rootScene */); + if (child.isVisible() && !child.isMouseTransparent() && child.contains(p)) { + bestMatchingChild = child; + break; } + } - return node; + if (bestMatchingChild != null) { + return pick(bestMatchingChild, sceneX, sceneY); + } } + + return node; + } } diff --git a/src/graphvisualizer/graphview/UtilitiesPoint2D.java b/src/graphvisualizer/graphview/UtilitiesPoint2D.java index 70af5d1..e892ed8 100644 --- a/src/graphvisualizer/graphview/UtilitiesPoint2D.java +++ b/src/graphvisualizer/graphview/UtilitiesPoint2D.java @@ -1,4 +1,4 @@ -/* +/* * The MIT License * * Copyright 2019 brunomnsilva@gmail.com. @@ -26,112 +26,111 @@ import javafx.geometry.Point2D; /** - * Class with utility methods for Point2D instances and force-based layout - * computations. - * + * Class with utility methods for Point2D instances and force-based layout computations. + * * @author brunomnsilva */ public class UtilitiesPoint2D { - - /** - * Rotate a point around a pivot point by a specific degrees amount - * @param point point to rotate - * @param pivot pivot point - * @param angle_degrees rotation degrees - * @return rotated point - */ - public static Point2D rotate(final Point2D point, final Point2D pivot, double angle_degrees) { - double angle = Math.toRadians(angle_degrees); //angle_degrees * (Math.PI/180); //to radians - - double sin = Math.sin(angle); - double cos = Math.cos(angle); - - //translate to origin - Point2D result = point.subtract(pivot); - - // rotate point - Point2D rotatedOrigin = new Point2D( - result.getX() * cos - result.getY() * sin, - result.getX() * sin + result.getY() * cos); - - // translate point back - result = rotatedOrigin.add(pivot); - - return result; - } - - /** - * Computes the vector of the attractive force between two nodes. - * - * @param from Coordinates of the first node - * @param to Coordinates of the second node - * @param globalCount Global number of nodes - * @param force Force factor to be used - * @param scale Scale factor to be used - * - * @return Computed force vector - */ - public static Point2D attractiveForce(Point2D from, Point2D to, int globalCount, double force, double scale) { - - double distance = from.distance(to); - - Point2D vec = to.subtract(from).normalize(); - - double factor = attractiveFunction(distance, globalCount, force, scale); - return vec.multiply(factor); - } - /** - * Computes the value of the scalar attractive force function based on - * the given distance of a group of nodes. - * - * @param distance Distance between two nodes - * @param globalCount Global number of nodes - * @return Computed attractive force - */ - static double attractiveFunction(double distance, int globalCount, double force, double scale) { - if (distance < 1) { - distance = 1; - } - - //the attractive strenght grows logarithmically with distance - - //return force * Math.log(distance / scale) * (1.0 / (1.0 * numVertices)); - //return force * Math.log(distance / numVertices) * (1 / scale); - - return force * Math.log(distance / scale) * 0.1;// * (1.0 / (1.0 * numVertices)); - } + /** + * Rotate a point around a pivot point by a specific degrees amount + * + * @param point point to rotate + * @param pivot pivot point + * @param angle_degrees rotation degrees + * @return rotated point + */ + public static Point2D rotate(final Point2D point, final Point2D pivot, double angle_degrees) { + double angle = Math.toRadians(angle_degrees); // angle_degrees * (Math.PI/180); //to radians - /** - * Computes the vector of the repelling force between two node. - * - * @param from Coordinates of the first node - * @param to Coordinates of the second node - * @param scale Scale factor to be used - * @return Computed force vector - */ - public static Point2D repellingForce(Point2D from, Point2D to, double scale) { - double distance = from.distance(to); - - Point2D vec = to.subtract(from).normalize(); - - double factor = -repellingFunction(distance, scale); - - return vec.multiply(factor); - } + double sin = Math.sin(angle); + double cos = Math.cos(angle); + + // translate to origin + Point2D result = point.subtract(pivot); + + // rotate point + Point2D rotatedOrigin = + new Point2D( + result.getX() * cos - result.getY() * sin, result.getX() * sin + result.getY() * cos); + + // translate point back + result = rotatedOrigin.add(pivot); + + return result; + } + + /** + * Computes the vector of the attractive force between two nodes. + * + * @param from Coordinates of the first node + * @param to Coordinates of the second node + * @param globalCount Global number of nodes + * @param force Force factor to be used + * @param scale Scale factor to be used + * @return Computed force vector + */ + public static Point2D attractiveForce( + Point2D from, Point2D to, int globalCount, double force, double scale) { + + double distance = from.distance(to); - /** - * Computes the value of the scalar repelling force function based on - * the given distance of two nodes. - * - * @param distance Distance between two nodes - * @return Computed repelling force - */ - static double repellingFunction(double distance, double scale) { - if (distance < 1) { - distance = 1; - } - return scale / (distance*distance); + Point2D vec = to.subtract(from).normalize(); + + double factor = attractiveFunction(distance, globalCount, force, scale); + return vec.multiply(factor); + } + + /** + * Computes the value of the scalar attractive force function based on the given distance of a + * group of nodes. + * + * @param distance Distance between two nodes + * @param globalCount Global number of nodes + * @return Computed attractive force + */ + static double attractiveFunction(double distance, int globalCount, double force, double scale) { + if (distance < 1) { + distance = 1; } + // the attractive strenght grows logarithmically with distance + + // return force * Math.log(distance / scale) * (1.0 / (1.0 * numVertices)); + // return force * Math.log(distance / numVertices) * (1 / scale); + + return force * Math.log(distance / scale) * 0.1; // * (1.0 / (1.0 * numVertices)); + } + + /** + * Computes the vector of the repelling force between two node. + * + * @param from Coordinates of the first node + * @param to Coordinates of the second node + * @param scale Scale factor to be used + * @return Computed force vector + */ + public static Point2D repellingForce(Point2D from, Point2D to, double scale) { + double distance = from.distance(to); + + Point2D vec = to.subtract(from).normalize(); + + double factor = -repellingFunction(distance, scale); + + return vec.multiply(factor); + } + + /** + * Computes the value of the scalar repelling force function based on the given distance of two + * nodes. + * + * @param distance Distance between two nodes + * @return Computed repelling force + */ + static double repellingFunction(double distance, double scale) { + if (distance < 1) { + distance = 1; + } + return scale / (distance * distance); + } }