diff --git a/pom.xml b/pom.xml index c82e3af..773de23 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ junit junit - 4.12 + 4.13.1 compile @@ -168,6 +168,12 @@ test + + org.openjfx + javafx-swing + 11-ea+24 + + @@ -180,7 +186,7 @@ 11 11 11 - com.todense.application.Starter2 + com.todense.application.StarterInvoker @@ -191,7 +197,7 @@ - com.todense.application.Starter2 + com.todense.application.StarterInvoker diff --git a/src/main/java/com/todense/application/Starter.java b/src/main/java/com/todense/application/Starter.java index 3e038ca..116de99 100644 --- a/src/main/java/com/todense/application/Starter.java +++ b/src/main/java/com/todense/application/Starter.java @@ -16,6 +16,7 @@ public class Starter extends MvvmfxGuiceApplication { @Override public void startMvvmfx(Stage stage) { + stage.setTitle("OmniGraph"); final ViewTuple viewTuple = FluentViewLoader.fxmlView(MainView.class).load(); final Parent root = viewTuple.getView(); diff --git a/src/main/java/com/todense/application/Starter2.java b/src/main/java/com/todense/application/StarterInvoker.java similarity index 54% rename from src/main/java/com/todense/application/Starter2.java rename to src/main/java/com/todense/application/StarterInvoker.java index f328553..b032c6b 100644 --- a/src/main/java/com/todense/application/Starter2.java +++ b/src/main/java/com/todense/application/StarterInvoker.java @@ -1,7 +1,8 @@ package com.todense.application; -public class Starter2 { +public class StarterInvoker { + // This class exist because of some bug with maven javafx plugin public static void main(String[] args) { Starter.main(args); } diff --git a/src/main/java/com/todense/model/EdgeList.java b/src/main/java/com/todense/model/EdgeList.java index 7d1c31c..2b67567 100644 --- a/src/main/java/com/todense/model/EdgeList.java +++ b/src/main/java/com/todense/model/EdgeList.java @@ -48,7 +48,6 @@ public Edge getEdge(Node n, Node m){ String id = n.getID() < m.getID() ? n.getID()+"-"+m.getID() : m.getID()+"-"+n.getID(); - assert edgeMap.get(id) != null : "No edge with id: "+id; return edgeMap.get(id); } diff --git a/src/main/java/com/todense/model/graph/Graph.java b/src/main/java/com/todense/model/graph/Graph.java index 3269c01..64d71dc 100644 --- a/src/main/java/com/todense/model/graph/Graph.java +++ b/src/main/java/com/todense/model/graph/Graph.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; public class Graph { @@ -44,7 +45,6 @@ public Node addNode(Point2D pt, int id){ return n; } - public Edge addEdge(Node n, Node m){ assert !edges.isEdgeBetween(n, m): "Edge "+edges.getEdge(n, m).toString()+" already exist!"; @@ -67,13 +67,47 @@ public void removeEdge(Edge e){ edges.remove(e); } - public void removeAllEdges(){ - edges.clear(); - for (Node node : nodes) { - node.getNeighbours().clear(); + public void removeEdges(List nodes){ + applyToAllPairOfNodes(nodes, (n, m) -> { + if(edges.isEdgeBetween(n, m)){ + removeEdge(n, m); + } + }); + } + + public void applyToAllPairOfNodes(List nodes, BiConsumer consumer){ + for (int i = 0; i < nodes.size(); i++) { + for (int j = i+1; j < nodes.size(); j++) { + consumer.accept(nodes.get(i), nodes.get(j)); + } + } + } + + public void applyToAllConnectedPairOfNodes(List nodes, BiConsumer consumer){ + for (int i = 0; i < nodes.size(); i++) { + for (int j = i+1; j < nodes.size(); j++) { + Node n = nodes.get(i); + Node m = nodes.get(j); + Edge e = getEdge(n, m); + if(e != null){ + consumer.accept(nodes.get(i), nodes.get(j)); + } + } } } + public void applyToAllConnectedPairOfNodes(BiConsumer consumer){ + applyToAllConnectedPairOfNodes(nodes, consumer); + } + + public void applyToAllPairOfNodes(BiConsumer consumer){ + applyToAllPairOfNodes(nodes, consumer); + } + + public void removeEdges(){ + this.removeEdges(this.nodes); + } + public void removeNode(Node n){ //decrement indexes for (int i = n.getIndex() + 1; i < nodes.size(); i++) { diff --git a/src/main/java/com/todense/util/Util.java b/src/main/java/com/todense/util/Util.java index 154c48f..ccce751 100644 --- a/src/main/java/com/todense/util/Util.java +++ b/src/main/java/com/todense/util/Util.java @@ -36,8 +36,7 @@ public static boolean isDouble(String strNum) { } //calculates rgb values of a color with given opacity over a background color - public static Color getFaintColor(Color color, Color backgroundColor) { - double opacity = 0.3; + public static Color getFaintColor(Color color, Color backgroundColor, double opacity) { int r = (int) (backgroundColor.getRed() * 255 + (color.getRed() * 255 - backgroundColor.getRed() * 255) * opacity); int g = (int) (backgroundColor.getGreen() * 255 + (color.getGreen()* 255 - backgroundColor.getGreen()* 255) * opacity); int b = (int) (backgroundColor.getBlue() * 255 + (color.getBlue()* 255 - backgroundColor.getBlue()* 255) * opacity); @@ -45,6 +44,10 @@ public static Color getFaintColor(Color color, Color backgroundColor) { return Color.rgb(r, g, b); } + public static Color getFaintColor(Color color, Color backgroundColor) { + return getFaintColor(color, backgroundColor, 0.3); + } + public static void bindSliderAndTextField(Slider slider, TextField textField, String pattern){ StringProperty sp = textField.textProperty(); DoubleProperty dp = slider.valueProperty(); diff --git a/src/main/java/com/todense/view/AlgorithmView.java b/src/main/java/com/todense/view/AlgorithmView.java index d40cfff..1b6f2d3 100644 --- a/src/main/java/com/todense/view/AlgorithmView.java +++ b/src/main/java/com/todense/view/AlgorithmView.java @@ -57,12 +57,12 @@ public void initialize(){ @FXML private void startAlgorithmAction() { - viewModel.start(); + viewModel.startTask(); } @FXML private void stopAlgorithmAction() { - viewModel.stop(); + viewModel.stopTask(); } } diff --git a/src/main/java/com/todense/view/AntsView.java b/src/main/java/com/todense/view/AntsView.java index 8f7dec8..3a4c76c 100644 --- a/src/main/java/com/todense/view/AntsView.java +++ b/src/main/java/com/todense/view/AntsView.java @@ -186,12 +186,12 @@ private void scaleIncrementAction() { @FXML private void startAction() { - viewModel.startAlgorithm(); + viewModel.startTask(); } @FXML private void stopAction() { - viewModel.stopAlgorithm(); + viewModel.stopTask(); } } diff --git a/src/main/java/com/todense/view/CanvasView.java b/src/main/java/com/todense/view/CanvasView.java index 099aa35..c7ffc99 100644 --- a/src/main/java/com/todense/view/CanvasView.java +++ b/src/main/java/com/todense/view/CanvasView.java @@ -10,6 +10,7 @@ import javafx.scene.Cursor; import javafx.scene.canvas.Canvas; import javafx.scene.layout.Pane; +import javafx.scene.shape.StrokeLineCap; public class CanvasView implements FxmlView { @@ -49,10 +50,15 @@ public void initialize(){ canvas.setOnScroll(viewModel.getMouseHandler()::onMouseScroll); canvas.setOnMouseExited(viewModel.getMouseHandler()::onMouseExited); + canvas.getGraphicsContext2D().setLineCap(StrokeLineCap.BUTT); + Platform.runLater(() -> { viewModel.setCanvasNode(canvas); viewModel.getPopOverManager().setContext(context); }); } + + + } diff --git a/src/main/java/com/todense/view/GraphView.java b/src/main/java/com/todense/view/GraphView.java index fa29a8e..19905ba 100644 --- a/src/main/java/com/todense/view/GraphView.java +++ b/src/main/java/com/todense/view/GraphView.java @@ -11,16 +11,18 @@ import javafx.scene.control.ChoiceBox; import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; +import javafx.scene.layout.VBox; import org.controlsfx.control.ToggleSwitch; public class GraphView implements FxmlView { - @FXML private Label nodeSizeLabel, edgeWidthLabel; - @FXML private JFXSlider nodeSizeSlider, edgeWidthSlider; + @FXML private Label nodeSizeLabel, edgeWidthLabel, edgeWidthDecayLabel, edgeOpacityDecayLabel; + @FXML private JFXSlider nodeSizeSlider, edgeWidthSlider, edgeWidthDecaySlider, edgeOpacityDecaySlider; @FXML private ColorPicker nodeColorPicker, edgeColorPicker, labelColorPicker, weightColorPicker; @FXML private ChoiceBox nodeLabelChoiceBox; @FXML private ChoiceBox edgeWeightChoiceBox; - @FXML private ToggleSwitch nodeBorderToggleSwitch, edgeVisibilityToggleSwitch; + @FXML private ToggleSwitch nodeBorderToggleSwitch, edgeVisibilityToggleSwitch, widthDecayToggleSwitch, opacityDecayToggleSwitch; + @FXML private VBox edgeWidthDecayVBox, edgeWidthDecayStrengthVBox, edgeOpacityDecayVBox, edgeOpacityDecayStrengthVBox; @InjectViewModel GraphViewModel viewModel; @@ -35,8 +37,32 @@ public void initialize(){ edgeWeightChoiceBox.valueProperty().bindBidirectional(viewModel.edgeWeightModeProperty()); nodeSizeSlider.valueProperty().bindBidirectional(viewModel.nodeSizeProperty()); edgeWidthSlider.valueProperty().bindBidirectional(viewModel.edgeWidthProperty()); + edgeWidthDecaySlider.valueProperty().bindBidirectional(viewModel.edgeWidthDecayProperty()); + edgeOpacityDecaySlider.valueProperty().bindBidirectional(viewModel.edgeOpacityDecayProperty()); nodeBorderToggleSwitch.selectedProperty().bindBidirectional(viewModel.nodeBorderProperty()); edgeVisibilityToggleSwitch.selectedProperty().bindBidirectional(viewModel.edgeVisibilityProperty()); + widthDecayToggleSwitch.selectedProperty().bindBidirectional(viewModel.edgeWidthDecayOnProperty()); + opacityDecayToggleSwitch.selectedProperty().bindBidirectional(viewModel.edgeOpacityDecayOnProperty()); + + widthDecayToggleSwitch.selectedProperty().addListener((obs, oldVal, newVal) -> { + if(newVal){ + edgeWidthDecayVBox.getChildren().add(edgeWidthDecayStrengthVBox); + }else{ + edgeWidthDecayVBox.getChildren().remove(edgeWidthDecayStrengthVBox); + } + }); + + opacityDecayToggleSwitch.selectedProperty().addListener((obs, oldVal, newVal) -> { + if(newVal){ + edgeOpacityDecayVBox.getChildren().add(edgeOpacityDecayStrengthVBox); + }else{ + edgeOpacityDecayVBox.getChildren().remove(edgeOpacityDecayStrengthVBox); + } + }); + + edgeWidthDecayVBox.getChildren().remove(edgeWidthDecayStrengthVBox); + edgeOpacityDecayVBox.getChildren().remove(edgeOpacityDecayStrengthVBox); + nodeSizeLabel.textProperty().bind(Bindings.createStringBinding(() -> String.format("%.1f", nodeSizeSlider.getValue()), nodeSizeSlider.valueProperty())); @@ -45,6 +71,12 @@ public void initialize(){ edgeWidthLabel.textProperty().bind(Bindings.createStringBinding(()-> String.format("%.2f", edgeWidthSlider.getValue()), edgeWidthSlider.valueProperty())); + edgeWidthDecayLabel.textProperty().bind(Bindings.createStringBinding(()-> + String.format("%.4f", edgeWidthDecaySlider.getValue()), edgeWidthDecaySlider.valueProperty())); + + edgeOpacityDecayLabel.textProperty().bind(Bindings.createStringBinding(()-> + String.format("%.4f", edgeOpacityDecaySlider.getValue()), edgeOpacityDecaySlider.valueProperty())); + nodeLabelChoiceBox.getItems().addAll(NodeLabelMode.values()); edgeWeightChoiceBox.getItems().addAll(EdgeWeightMode.values()); diff --git a/src/main/java/com/todense/view/LayoutView.java b/src/main/java/com/todense/view/LayoutView.java index de70d9c..799aea6 100644 --- a/src/main/java/com/todense/view/LayoutView.java +++ b/src/main/java/com/todense/view/LayoutView.java @@ -14,6 +14,7 @@ import javafx.scene.control.ChoiceBox; import javafx.scene.control.Slider; import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.util.StringConverter; import javafx.util.converter.NumberStringConverter; @@ -23,10 +24,11 @@ public class LayoutView implements FxmlView { @FXML private TextField stepTextField, toleranceTextField, optDistTextField, pullStrengthTextField, coolingFactorTextField; @FXML private Slider toleranceSlider, stepSlider, coolingFactorSlider, pullStrengthSlider; - @FXML private JFXSlider optDistSlider; - @FXML private ToggleSwitch pullToggleSwitch, coolingToggleSwitch, multilevelToggleSwitch, barnesHutToggleSwitch; + @FXML private JFXSlider optDistSlider, smoothnessSlider; + @FXML private ToggleSwitch pullToggleSwitch, coolingToggleSwitch, multilevelToggleSwitch, barnesHutToggleSwitch, smoothToggleSwitch; @FXML private Button startButton; @FXML private VBox coolingVBox, pullVBox; + @FXML private HBox smoothnessHBox; @FXML private ChoiceBox longRangeChoiceBox; @InjectViewModel @@ -38,11 +40,13 @@ public void initialize(){ optDistSlider.valueProperty().bindBidirectional(viewModel.optDistProperty()); coolingFactorSlider.valueProperty().bindBidirectional(viewModel.coolingStrengthProperty()); pullStrengthSlider.valueProperty().bindBidirectional(viewModel.centerPullStrengthProperty()); + smoothnessSlider.valueProperty().bindBidirectional(viewModel.smoothnessProperty()); pullToggleSwitch.selectedProperty().bindBidirectional(viewModel.centerPullOnProperty()); coolingToggleSwitch.selectedProperty().bindBidirectional(viewModel.coolingOnProperty()); multilevelToggleSwitch.selectedProperty().bindBidirectional(viewModel.multilevelOnProperty()); barnesHutToggleSwitch.selectedProperty().bindBidirectional(viewModel.barnesHutOnProperty()); + smoothToggleSwitch.selectedProperty().bindBidirectional(viewModel.smoothnessOnProperty()); bindSliderAndTextField(optDistSlider, optDistTextField); bindSliderAndTextField(stepSlider, stepTextField); @@ -52,6 +56,7 @@ public void initialize(){ pullVBox.disableProperty().bind(pullToggleSwitch.selectedProperty().not()); coolingVBox.disableProperty().bind(coolingToggleSwitch.selectedProperty().not()); + smoothnessHBox.disableProperty().bind(smoothToggleSwitch.selectedProperty().not()); longRangeChoiceBox.valueProperty().bindBidirectional(viewModel.longRangeForceProperty()); longRangeChoiceBox.getItems().addAll(LongRangeForce.values()); @@ -75,14 +80,20 @@ private StringBinding createIntBinding(DoubleProperty property){ String.valueOf(property.getValue().intValue()), property); } + @FXML private void dynamicLayoutAction() { - viewModel.start(); + viewModel.startTask(); } @FXML private void stopAlgorithmAction() { - viewModel.stop(); + viewModel.stopTask(); + } + + @FXML + private void randomLayoutAction(){ + viewModel.randomLayout(); } } diff --git a/src/main/java/com/todense/view/MainView.java b/src/main/java/com/todense/view/MainView.java index 8e0972b..d901adc 100644 --- a/src/main/java/com/todense/view/MainView.java +++ b/src/main/java/com/todense/view/MainView.java @@ -195,7 +195,7 @@ private void adjustAction() { @FXML private void stopAction(){ - viewModel.stop(); + viewModel.stopAll(); } @FXML diff --git a/src/main/java/com/todense/view/NodePopOverView.java b/src/main/java/com/todense/view/NodePopOverView.java index 5031974..06cab1d 100644 --- a/src/main/java/com/todense/view/NodePopOverView.java +++ b/src/main/java/com/todense/view/NodePopOverView.java @@ -6,6 +6,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ColorPicker; +import javafx.scene.control.Slider; import javafx.scene.control.TextField; import javafx.scene.layout.VBox; @@ -16,6 +17,8 @@ public class NodePopOverView implements FxmlView { @FXML private TextField nodeLabelTextField; @FXML private Button startNodeButton; @FXML private Button goalNodeButton; + @FXML private VBox rotationVBox; + @FXML private Slider rotationSlider; @InjectViewModel @@ -24,8 +27,9 @@ public class NodePopOverView implements FxmlView { public void initialize(){ this.editNodeColorPicker.valueProperty().bindBidirectional(viewModel.nodeColorProperty()); this.nodeLabelTextField.textProperty().bindBidirectional(viewModel.labelProperty()); + this.rotationSlider.valueProperty().bindBidirectional(viewModel.rotationProperty()); - //remove buttons if more than one node is selected + //remove components if more than one node is selected viewModel.subscribe("MULTIPLE", (key, payload) -> { nodeVBox.getChildren().remove(startNodeButton); nodeVBox.getChildren().remove(goalNodeButton); diff --git a/src/main/java/com/todense/view/OperationsView.java b/src/main/java/com/todense/view/OperationsView.java index b81112a..099eb3e 100644 --- a/src/main/java/com/todense/view/OperationsView.java +++ b/src/main/java/com/todense/view/OperationsView.java @@ -4,12 +4,19 @@ import de.saxsys.mvvmfx.FxmlView; import de.saxsys.mvvmfx.InjectViewModel; import javafx.fxml.FXML; +import org.controlsfx.control.ToggleSwitch; public class OperationsView implements FxmlView { @InjectViewModel OperationsViewModel viewModel; + @FXML private ToggleSwitch editSubgraphToggleSwitch; + + public void initialize(){ + editSubgraphToggleSwitch.selectedProperty().bindBidirectional(viewModel.editSubgraphProperty()); + } + @FXML private void pathAction() { viewModel.createPath(); } diff --git a/src/main/java/com/todense/viewmodel/AlgorithmViewModel.java b/src/main/java/com/todense/viewmodel/AlgorithmViewModel.java index 0bb618b..6a59575 100644 --- a/src/main/java/com/todense/viewmodel/AlgorithmViewModel.java +++ b/src/main/java/com/todense/viewmodel/AlgorithmViewModel.java @@ -5,6 +5,7 @@ import com.todense.model.graph.Node; import com.todense.viewmodel.algorithm.Algorithm; import com.todense.viewmodel.algorithm.AlgorithmTask; +import com.todense.viewmodel.algorithm.AlgorithmTaskManager; import com.todense.viewmodel.algorithm.task.*; import com.todense.viewmodel.canvas.DisplayMode; import com.todense.viewmodel.graph.GraphManager; @@ -22,7 +23,7 @@ import javax.inject.Inject; -public class AlgorithmViewModel implements ViewModel { +public class AlgorithmViewModel extends AlgorithmTaskManager implements ViewModel { @Inject NotificationCenter notificationCenter; @@ -41,8 +42,6 @@ public class AlgorithmViewModel implements ViewModel { private GraphManager graphManager; private BooleanProperty connectivityChecksProperty = new SimpleBooleanProperty(true); - - private AlgorithmTask task; private double startTime; public void initialize(){ @@ -63,22 +62,20 @@ public void initialize(){ }); notificationCenter.subscribe(GraphViewModel.NEW_GRAPH_REQUEST, (key, payload) -> { - Platform.runLater(this::stop); + Platform.runLater(this::stopTask); startNodeProperty().set(null); goalNodeProperty().set(null); }); + + super.initialize(taskScope, canvasScope, notificationCenter); } - public void start(){ + @Override + protected AlgorithmTask createAlgorithmTask() { Graph g = graphManager.getGraph(); - AlgorithmTask currentTask = taskScope.getTask(); - - if((currentTask != null && currentTask.isRunning()) || g.getNodes().size() == 0) return; - graphScope.displayModeProperty().set(DisplayMode.ALGORITHMIC); - graphManager.resetGraph(); if(algorithmScope.getStartNode() == null){ @@ -94,39 +91,19 @@ public void start(){ boolean customWeight = graphScope.getEdgeWeightMode().equals(EdgeWeightMode.CUSTOM); + AlgorithmTask task = null; + switch (getAlgorithm()){ case BFS: task = new BFSTask(startNode, g); break; case DFS: task = new DFSTask(startNode, g); break; case PRIM: task = new PrimTask(startNode, g, customWeight); break; case KRUSKAL: task = new KruskalTask(g, customWeight); break; case DIJKSTRA: task = new DijkstraTask(startNode, goalNode, g, customWeight); break; - case HCSEARCH: task = new HCSearchTask(startNode, g, connectivityChecksProperty.get()); break; + case HCSEARCH: task = new HamiltonianCycleSearchTask(startNode, g, connectivityChecksProperty.get()); break; case ASTAR: task = new AStarTask(startNode, goalNode, g, customWeight); break; } - taskScope.setTask(task); - - task.setOnSucceeded(workerStateEvent -> notificationCenter.publish(MainViewModel.TASK_FINISHED, - getAlgorithm(), - System.currentTimeMillis()- startTime, - task.getResultMessage())); - - task.setOnCancelled(workerStateEvent -> - notificationCenter.publish(MainViewModel.TASK_CANCELLED, getAlgorithm())); - - task.setPainter(canvasScope.getPainter()); - - notificationCenter.publish(MainViewModel.TASK_STARTED, getAlgorithm().toString()); - - startTime = System.currentTimeMillis(); - Thread thread = new Thread(task); - thread.start(); - } - - public void stop(){ - if(task != null){ - task.cancel(); - } + return task; } @@ -153,4 +130,5 @@ public ObjectProperty goalNodeProperty() { public BooleanProperty showingEndpointsProperty() { return algorithmScope.showingEndpointsProperty(); } + } diff --git a/src/main/java/com/todense/viewmodel/AnimationViewModel.java b/src/main/java/com/todense/viewmodel/AnimationViewModel.java index a52c362..0dc3e48 100644 --- a/src/main/java/com/todense/viewmodel/AnimationViewModel.java +++ b/src/main/java/com/todense/viewmodel/AnimationViewModel.java @@ -15,7 +15,6 @@ public void nextStep() { nextStepProperty().setValue(true); } - public IntegerProperty stepTimeProperty() { return animationScope.stepTimeProperty(); } diff --git a/src/main/java/com/todense/viewmodel/AntsViewModel.java b/src/main/java/com/todense/viewmodel/AntsViewModel.java index d384ee7..55f4742 100644 --- a/src/main/java/com/todense/viewmodel/AntsViewModel.java +++ b/src/main/java/com/todense/viewmodel/AntsViewModel.java @@ -1,32 +1,31 @@ package com.todense.viewmodel; import com.todense.model.graph.Graph; -import com.todense.viewmodel.algorithm.AlgorithmTask; +import com.todense.viewmodel.algorithm.AlgorithmTaskManager; import com.todense.viewmodel.ants.*; import com.todense.viewmodel.canvas.DisplayMode; -import com.todense.viewmodel.canvas.drawlayer.layers.AntsDrawLayer; -import com.todense.viewmodel.scope.AntsScope; -import com.todense.viewmodel.scope.CanvasScope; -import com.todense.viewmodel.scope.GraphScope; -import com.todense.viewmodel.scope.TaskScope; +import com.todense.viewmodel.scope.*; import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; -import javafx.application.Platform; -import javafx.beans.property.*; -import javafx.concurrent.WorkerStateEvent; -import javafx.event.EventHandler; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; import javafx.scene.paint.Color; import javax.inject.Inject; import java.text.DateFormat; import java.text.SimpleDateFormat; -public class AntsViewModel implements ViewModel { +public class AntsViewModel extends AlgorithmTaskManager implements ViewModel { @InjectScope CanvasScope canvasScope; + @InjectScope + AlgorithmScope algorithmScope; + @InjectScope AntsScope antsScope; @@ -39,68 +38,54 @@ public class AntsViewModel implements ViewModel { @Inject NotificationCenter notificationCenter; - private AntColonyAlgorithmTask algorithmTask; - DateFormat dateFormat = new SimpleDateFormat("mm:ss:SSS"); - private long startTime; - public void initialize(){ - AntsDrawLayer antsDrawLayer = new AntsDrawLayer(antsScope, graphScope); - Platform.runLater(() -> canvasScope.getPainter().addDrawLayer(antsDrawLayer)); - } + //AntsDrawLayer antsDrawLayer = new AntsDrawLayer(antsScope, graphScope); + //Platform.runLater(() -> canvasScope.getPainter().addDrawLayer(antsDrawLayer)); - public void startAlgorithm(){ - AlgorithmTask currentTask = taskScope.getTask(); + super.initialize(taskScope, canvasScope, notificationCenter); + } - if(currentTask != null && currentTask.isRunning()) return; + @Override + protected AntColonyAlgorithmTask createAlgorithmTask() { Graph graph = graphScope.getGraphManager().getGraph(); - if(graph.getNodes().size() < 3) return; + if(graph.getNodes().size() < 3){ + throw new RuntimeException("Graph must have at least 3 nodes"); + } graphScope.getGraphManager().createCompleteGraph(); - startTime = System.currentTimeMillis(); - notificationCenter.publish(MainViewModel.TASK_STARTED, algorithmProperty().get().toString()); + //notificationCenter.publish(MainViewModel.TASK_STARTED, algorithmProperty().get().toString()); graphScope.displayModeProperty().set(DisplayMode.ANT_COLONY); + AntColonyAlgorithmTask task = null; + switch (antsScope.algorithmProperty().get()){ case ACS: - algorithmTask = new AntColonySystemTask(graph, antsScope); + task = new AntColonySystemTask(graph, antsScope, algorithmScope); break; case AS: - algorithmTask = new AntSystemTask(graph, antsScope); + task = new AntSystemTask(graph, antsScope, algorithmScope); break; case MMAS: - algorithmTask = new MaxMinAntSystemTask(graph, antsScope); + task = new MaxMinAntSystemTask(graph, antsScope, algorithmScope); break; case RANK_AS: - algorithmTask = new RankedAntSystem(graph, antsScope); + task = new RankedAntSystem(graph, antsScope, algorithmScope); break; } - this.algorithmTask.setPainter(canvasScope.getPainter()); - this.algorithmTask.bestSolutionLengthProperty().addListener((obs, oldVal, newVal) -> + + task.setPainter(canvasScope.getPainter()); + AntColonyAlgorithmTask finalTask = task; + task.bestSolutionLengthProperty().addListener((obs, oldVal, newVal) -> notificationCenter.publish(MainViewModel.WRITE, "Best Length: " + String.format("%.2f", newVal.doubleValue()) + - " found in "+ dateFormat.format(System.currentTimeMillis()-startTime)+ - " after "+ algorithmTask.getIterationCounter()+ " iterations")); - taskScope.setTask(this.algorithmTask); + " found in "+ dateFormat.format(System.currentTimeMillis() - super.getStartTime())+ + " after "+ finalTask.getIterationCounter()+ " iterations")); - EventHandler finishHandler = workerStateEvent -> - notificationCenter.publish(MainViewModel.TASK_FINISHED, - algorithmProperty().get().toString(), - System.currentTimeMillis() - this.algorithmTask.getStartTime(), - ""); - - this.algorithmTask.setOnSucceeded(finishHandler); - this.algorithmTask.setOnCancelled(finishHandler); - this.algorithmTask.run(); - } - - public void stopAlgorithm(){ - if(algorithmTask != null && algorithmTask.isRunning()){ - algorithmTask.cancel(); - } + return task; } @@ -167,5 +152,4 @@ public IntegerProperty neighbourhoodSizeProperty() { public IntegerProperty rankSizeProperty() { return antsScope.rankSizeProperty(); } - } diff --git a/src/main/java/com/todense/viewmodel/CanvasViewModel.java b/src/main/java/com/todense/viewmodel/CanvasViewModel.java index 751fedc..de7debf 100644 --- a/src/main/java/com/todense/viewmodel/CanvasViewModel.java +++ b/src/main/java/com/todense/viewmodel/CanvasViewModel.java @@ -37,6 +37,9 @@ public class CanvasViewModel implements ViewModel { @InjectScope AnimationScope animationScope; + @InjectScope + AntsScope antsScope; + @InjectScope KeysScope keysScope; @@ -47,7 +50,6 @@ public class CanvasViewModel implements ViewModel { NotificationCenter notificationCenter; private MouseHandler mouseHandler; - private PopOverManager popOverManager; public void initialize(){ @@ -70,7 +72,7 @@ public void initialize(){ Platform.runLater(() ->{ LowerDrawLayer lowerDrawLayer = new LowerDrawLayer(inputScope, graphScope); - UpperDrawLayer upperDrawLayer = new UpperDrawLayer(graphScope, inputScope, canvasScope, backgroundScope, algorithmScope); + UpperDrawLayer upperDrawLayer = new UpperDrawLayer(graphScope, inputScope, canvasScope, backgroundScope, algorithmScope, antsScope); painter.addDrawLayer(lowerDrawLayer); painter.addDrawLayer(upperDrawLayer); diff --git a/src/main/java/com/todense/viewmodel/GraphViewModel.java b/src/main/java/com/todense/viewmodel/GraphViewModel.java index b0929f0..33f32b9 100644 --- a/src/main/java/com/todense/viewmodel/GraphViewModel.java +++ b/src/main/java/com/todense/viewmodel/GraphViewModel.java @@ -84,6 +84,10 @@ public void initialize(){ edgeWeightModeProperty().addListener(listener); nodeBorderProperty().addListener(listener); edgeVisibilityProperty().addListener(listener); + edgeWidthDecayProperty().addListener(listener); + edgeOpacityDecayProperty().addListener(listener); + edgeWidthDecayOnProperty().addListener(listener); + edgeOpacityDecayOnProperty().addListener(listener); } public void applyColorToNodes() { @@ -100,6 +104,7 @@ public void applyColorToEdges() { notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); } + public DoubleProperty nodeSizeProperty() { return graphScope.nodeSizeProperty(); } @@ -139,4 +144,20 @@ public BooleanProperty nodeBorderProperty() { public BooleanProperty edgeVisibilityProperty() { return graphScope.edgeVisibilityProperty(); } + + public DoubleProperty edgeWidthDecayProperty() { + return graphScope.edgeWidthDecayProperty(); + } + + public BooleanProperty edgeWidthDecayOnProperty(){ + return graphScope.edgeWidthDecayOnProperty(); + } + + public DoubleProperty edgeOpacityDecayProperty() { + return graphScope.edgeOpacityDecayProperty(); + } + + public BooleanProperty edgeOpacityDecayOnProperty() { + return graphScope.edgeOpacityDecayOnProperty(); + } } diff --git a/src/main/java/com/todense/viewmodel/LayoutViewModel.java b/src/main/java/com/todense/viewmodel/LayoutViewModel.java index b28d93e..29c7ae3 100644 --- a/src/main/java/com/todense/viewmodel/LayoutViewModel.java +++ b/src/main/java/com/todense/viewmodel/LayoutViewModel.java @@ -1,8 +1,12 @@ package com.todense.viewmodel; +import com.todense.model.graph.Node; import com.todense.viewmodel.algorithm.AlgorithmTask; +import com.todense.viewmodel.algorithm.AlgorithmTaskManager; import com.todense.viewmodel.algorithm.task.ForceDirectedLayoutTask; import com.todense.viewmodel.layout.LongRangeForce; +import com.todense.viewmodel.random.Generator; +import com.todense.viewmodel.random.arrangement.generators.RandomCirclePointGenerator; import com.todense.viewmodel.scope.CanvasScope; import com.todense.viewmodel.scope.GraphScope; import com.todense.viewmodel.scope.TaskScope; @@ -10,19 +14,20 @@ import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; import javafx.beans.property.*; -import javafx.concurrent.WorkerStateEvent; -import javafx.event.EventHandler; import javafx.geometry.Point2D; import javax.inject.Inject; +import java.util.HashMap; -public class LayoutViewModel implements ViewModel { +public class LayoutViewModel extends AlgorithmTaskManager implements ViewModel { private IntegerProperty optDistProperty = new SimpleIntegerProperty(30); + private DoubleProperty smoothnessProperty = new SimpleDoubleProperty(0.9); private DoubleProperty stepProperty = new SimpleDoubleProperty(5d); private DoubleProperty toleranceProperty = new SimpleDoubleProperty(0.01); private BooleanProperty coolingOnProperty = new SimpleBooleanProperty(true); private BooleanProperty barnesHutOnProperty = new SimpleBooleanProperty(true); + private BooleanProperty smoothnessOnProperty = new SimpleBooleanProperty(false); private DoubleProperty coolingStrengthProperty = new SimpleDoubleProperty(0.02); private BooleanProperty centerPullOnProperty = new SimpleBooleanProperty(true); private BooleanProperty multilevelOnProperty = new SimpleBooleanProperty(false); @@ -41,43 +46,36 @@ public class LayoutViewModel implements ViewModel { @Inject NotificationCenter notificationCenter; - private AlgorithmTask task; + private HashMap nodeSmoothedPositionMap; + public void initialize(){ - notificationCenter.subscribe("LAYOUT", (key, payload) -> start()); + notificationCenter.subscribe("LAYOUT", (key, payload) -> super.startTask()); + super.initialize(taskScope, canvasScope, notificationCenter); } - public void start(){ - AlgorithmTask currentTask = taskScope.getTask(); - - if(currentTask != null && currentTask.isRunning()) return; - - task = new ForceDirectedLayoutTask(graphScope.getGraphManager(), - this,new Point2D(canvasScope.getCanvasWidth()/2, - canvasScope.getCanvasHeight()/2)); - task.setPainter(canvasScope.getPainter()); - taskScope.setTask(task); - - EventHandler handler = workerStateEvent -> { - notificationCenter.publish(MainViewModel.TASK_FINISHED, - "Force-Directed Layout", - System.currentTimeMillis() - task.getStartTime(), - ""); - }; + @Override + public AlgorithmTask createAlgorithmTask() { + return new ForceDirectedLayoutTask(graphScope.getGraphManager(), this, canvasScope.getCanvasCenter()); + } - task.setOnSucceeded(handler); - task.setOnCancelled(handler); - notificationCenter.publish(MainViewModel.TASK_STARTED, "Force-Directed Layout"); - new Thread(task).start(); + @Override + public void startTask() { + nodeSmoothedPositionMap = new HashMap<>(); + graphScope.setNodePositionFunction(node -> nodeSmoothedPositionMap.get(node) != null ? nodeSmoothedPositionMap.get(node) : node.getPos()); + super.startTask(); } - public void stop(){ - if(task != null && task.isRunning()){ - task.cancel(); + public void randomLayout() { + double height = canvasScope.getCanvasHeight() * 0.9; + Point2D canvasCenter = new Point2D(canvasScope.getCanvasWidth()/2, canvasScope.getCanvasHeight()/2); + Generator generator = new RandomCirclePointGenerator(height/2, canvasCenter); + for(Node n: graphScope.getGraphManager().getGraph().getNodes()){ + n.setPos(generator.next()); } + notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); } - public int getOptDist() { return optDistProperty.get(); } @@ -157,4 +155,25 @@ public LongRangeForce getLongRangeForce() { public ObjectProperty longRangeForceProperty() { return longRangeForceProperty; } + + public double getSmoothness() { + return smoothnessProperty.get(); + } + + public DoubleProperty smoothnessProperty() { + return smoothnessProperty; + } + + public boolean isSmoothnessOn() { + return smoothnessOnProperty.get(); + } + + public BooleanProperty smoothnessOnProperty() { + return smoothnessOnProperty; + } + + public HashMap getNodeSmoothedPositionMap() { + return nodeSmoothedPositionMap; + } + } diff --git a/src/main/java/com/todense/viewmodel/MainViewModel.java b/src/main/java/com/todense/viewmodel/MainViewModel.java index c315035..183a2f4 100644 --- a/src/main/java/com/todense/viewmodel/MainViewModel.java +++ b/src/main/java/com/todense/viewmodel/MainViewModel.java @@ -83,6 +83,7 @@ public void initialize(){ notificationCenter.subscribe(TASK_CANCELLED, (key, payload) ->{ writeInfo(""); write(payload[0]+ " cancelled"); + graphScope.setNodePositionFunction(GraphScope.NODE_ORDINARY_POSITION_FUNCTION); workingProperty.set(false); taskRunningProperty.set(false); }); @@ -102,12 +103,13 @@ public void initialize(){ write((String) payload[2]); // result message } writeInfo(""); + graphScope.setNodePositionFunction(GraphScope.NODE_ORDINARY_POSITION_FUNCTION); workingProperty.set(false); taskRunningProperty.set(false); }); notificationCenter.subscribe(THREAD_STARTED, (key, payload) -> { - stop(); + stopAll(); writeInfo((String) payload[0]); workingProperty.set(true); }); @@ -155,8 +157,18 @@ public void openGraph(File file) { canvasScope.getCanvasHeight() * 0.9); break; } assert graphReader != null; - Graph openedGraph = graphReader.readGraph(file); + Graph openedGraph = null; + try{ + openedGraph = graphReader.readGraph(file); + } catch (RuntimeException e){ + if (e.getMessage() != null){ + notificationCenter.publish(MainViewModel.WRITE,"ERROR: "+e.getMessage()); + }else{ + notificationCenter.publish(MainViewModel.WRITE, "Error: File is corrupted"); + } + e.printStackTrace(); + } if(openedGraph != null){ notificationCenter.publish(GraphViewModel.NEW_GRAPH_REQUEST, openedGraph); notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); @@ -165,8 +177,8 @@ public void openGraph(File file) { } - public void stop() { - taskScope.stop(); + public void stopAll() { + taskScope.stopTask(); } public void setKeyInput(Scene scene){ @@ -198,11 +210,12 @@ public void setKeyInput(Scene scene){ public void write(String s){ Platform.runLater(() -> { - String text = textProperty.get() - + "\n"+"["+timeFormatter.format(System.currentTimeMillis())+"]"+" "+s; + String message = "["+timeFormatter.format(System.currentTimeMillis())+"]"+" "+s; + String text = textProperty.get() + "\n"+message; textProperty.setValue(text); - System.out.println(text); + System.out.println(message); }); + } public void writeInfo(String s){ @@ -228,6 +241,7 @@ public void adjustCameraToGraph() { notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); } + public ObjectProperty textProperty() { return textProperty; } diff --git a/src/main/java/com/todense/viewmodel/NodePopOverViewModel.java b/src/main/java/com/todense/viewmodel/NodePopOverViewModel.java index 2907cc5..7127139 100644 --- a/src/main/java/com/todense/viewmodel/NodePopOverViewModel.java +++ b/src/main/java/com/todense/viewmodel/NodePopOverViewModel.java @@ -5,7 +5,9 @@ import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.paint.Color; @@ -16,7 +18,7 @@ public class NodePopOverViewModel implements ViewModel { private ObjectProperty nodeColorProperty = new SimpleObjectProperty<>(Color.WHITE); private ObjectProperty labelProperty = new SimpleObjectProperty<>(""); - + private DoubleProperty rotationProperty = new SimpleDoubleProperty(0d); private List nodes; @InjectScope @@ -25,7 +27,7 @@ public class NodePopOverViewModel implements ViewModel { @Inject NotificationCenter notificationCenter; - public void bindToNodes(List nodes){ + public void bindToNodes(Node clickedNode, List nodes){ this.nodes = nodes; this.nodeColorProperty.addListener((obs, oldVal, newVal) -> { nodes.forEach((node -> node.setColor(nodeColorProperty.get()))); @@ -35,6 +37,14 @@ public void bindToNodes(List nodes){ nodes.forEach(node -> node.setLabelText(labelProperty.get())); notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); }); + this.rotationProperty.addListener((obs, oldVal, newVal) -> { + var GM = graphScope.getGraphManager(); + double angle = Math.toRadians(newVal.doubleValue() - oldVal.doubleValue()); + for(Node n : nodes){ + GM.rotateNode(n, clickedNode.getPos(), angle); + } + notificationCenter.publish(CanvasViewModel.REPAINT_REQUEST); + }); //remove buttons if more than one node is selected if(nodes.size() > 1){ @@ -81,4 +91,11 @@ public ObjectProperty labelProperty() { return labelProperty; } + public double getRotationProperty() { + return rotationProperty.get(); + } + + public DoubleProperty rotationProperty() { + return rotationProperty; + } } diff --git a/src/main/java/com/todense/viewmodel/OperationsViewModel.java b/src/main/java/com/todense/viewmodel/OperationsViewModel.java index 6b31e01..26275cd 100644 --- a/src/main/java/com/todense/viewmodel/OperationsViewModel.java +++ b/src/main/java/com/todense/viewmodel/OperationsViewModel.java @@ -1,13 +1,17 @@ package com.todense.viewmodel; +import com.todense.model.graph.Node; import com.todense.viewmodel.graph.GraphManager; import com.todense.viewmodel.scope.GraphScope; import com.todense.viewmodel.scope.TaskScope; import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javax.inject.Inject; +import java.util.List; public class OperationsViewModel implements ViewModel { @@ -20,6 +24,8 @@ public class OperationsViewModel implements ViewModel { @Inject NotificationCenter notificationCenter; + private BooleanProperty editSubgraphProperty = new SimpleBooleanProperty(false); + GraphManager graphManager; public void initialize(){ @@ -27,25 +33,40 @@ public void initialize(){ } public void createPath() { - notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createPath()); - + notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createPath(getEditedNodes())); } + + public void createCompleteGraph() { - notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createCompleteGraph()); + notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createCompleteGraph(getEditedNodes())); } public void createComplementGraph() { - notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createComplementGraph()); + notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.createComplementGraph(getEditedNodes())); } public void subdivideEdges() { - notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.subdivideEdges()); + notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.subdivideEdges(getEditedNodes())); } public void deleteEdges() { - notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.deleteEdges()); + notificationCenter.publish(MainViewModel.GRAPH_EDIT_REQUEST, (Runnable) () -> graphManager.deleteEdges(getEditedNodes())); + } + + public boolean isEditSubgraphOn() { + return editSubgraphProperty.get(); + } + + public BooleanProperty editSubgraphProperty() { + return editSubgraphProperty; + } + + private List getEditedNodes() { + if(isEditSubgraphOn()) + return graphManager.getSelectedNodes(); + else return graphManager.getGraph().getNodes(); } } diff --git a/src/main/java/com/todense/viewmodel/PropertiesViewModel.java b/src/main/java/com/todense/viewmodel/PropertiesViewModel.java index 1212025..aabe704 100644 --- a/src/main/java/com/todense/viewmodel/PropertiesViewModel.java +++ b/src/main/java/com/todense/viewmodel/PropertiesViewModel.java @@ -72,7 +72,7 @@ public void start(){ calculate(); notificationCenter.publish(MainViewModel.THREAD_FINISHED, "Calculated Properties"); }); - taskScope.setThread(propertiesThread); + //taskScope.setThread(propertiesThread); propertiesThread.start(); } } diff --git a/src/main/java/com/todense/viewmodel/RandomGeneratorViewModel.java b/src/main/java/com/todense/viewmodel/RandomGeneratorViewModel.java index 358ab03..2236a63 100644 --- a/src/main/java/com/todense/viewmodel/RandomGeneratorViewModel.java +++ b/src/main/java/com/todense/viewmodel/RandomGeneratorViewModel.java @@ -1,18 +1,20 @@ package com.todense.viewmodel; import com.todense.model.graph.Graph; +import com.todense.model.graph.Node; import com.todense.viewmodel.random.Generator; import com.todense.viewmodel.random.GeneratorModel; import com.todense.viewmodel.random.RandomEdgeGenerator; import com.todense.viewmodel.random.RandomGraphGenerator; +import com.todense.viewmodel.random.arrangement.NodeArrangement; import com.todense.viewmodel.random.arrangement.generators.CircularPointGenerator; import com.todense.viewmodel.random.arrangement.generators.RandomCirclePointGenerator; -import com.todense.viewmodel.random.arrangement.NodeArrangement; import com.todense.viewmodel.random.arrangement.generators.RandomSquarePointGenerator; import com.todense.viewmodel.random.generators.BarabasiAlbertGenerator; import com.todense.viewmodel.random.generators.ErdosRenyiGenerator; import com.todense.viewmodel.random.generators.GeometricGenerator; import com.todense.viewmodel.scope.CanvasScope; +import com.todense.viewmodel.scope.GraphScope; import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; @@ -50,6 +52,9 @@ public class RandomGeneratorViewModel implements ViewModel { @InjectScope CanvasScope canvasScope; + @InjectScope + GraphScope graphScope; + public void initialize(){ notificationCenter.subscribe("RANDOM", (key, payload) -> generate()); } @@ -106,7 +111,6 @@ private void generateGraph(){ throw new IllegalStateException("Unexpected value: " + generatorProperty.get()); } - try{ Graph randomGraph = RandomGraphGenerator.generateGraph(nodeCountProperty.get(), pointGenerator, edgeGenerator, minDist); notificationCenter.publish(GraphViewModel.NEW_GRAPH_REQUEST, randomGraph); @@ -122,6 +126,8 @@ public void generate(){ thread.start(); } + + public ObjectProperty generatorProperty() { return generatorProperty; } diff --git a/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTask.java b/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTask.java index 1af6297..3957472 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTask.java @@ -6,6 +6,8 @@ public abstract class AlgorithmTask extends Task{ + protected String algorithmName = "Unnamed Algorithm"; + protected Graph graph; protected String resultMessage = ""; protected Painter painter; @@ -71,7 +73,6 @@ public void setPainter(Painter painter){ } - public String getResultMessage() { return resultMessage; } @@ -95,4 +96,8 @@ public double getResult() { public void setResult(double result) { this.result = result; } + + public String getAlgorithmName() { + return algorithmName; + } } diff --git a/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTaskManager.java b/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTaskManager.java new file mode 100644 index 0000000..9e07f46 --- /dev/null +++ b/src/main/java/com/todense/viewmodel/algorithm/AlgorithmTaskManager.java @@ -0,0 +1,66 @@ +package com.todense.viewmodel.algorithm; + +import com.todense.viewmodel.MainViewModel; +import com.todense.viewmodel.scope.CanvasScope; +import com.todense.viewmodel.scope.TaskScope; +import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; +import javafx.concurrent.WorkerStateEvent; +import javafx.event.EventHandler; + +public abstract class AlgorithmTaskManager { + + private AlgorithmTask task; + private TaskScope taskScope; + private CanvasScope canvasScope; + private NotificationCenter notificationCenter; + + private long startTime; + + + public void initialize(TaskScope taskScope, CanvasScope canvasScope, NotificationCenter notificationCenter){ + this.taskScope = taskScope; + this.canvasScope = canvasScope; + this.notificationCenter = notificationCenter; + + } + + public void startTask() { + if (!taskScope.isDone()) + return; + + task = createAlgorithmTask(); + + task.setPainter(canvasScope.getPainter()); + + EventHandler handler = workerStateEvent -> { + notificationCenter.publish(MainViewModel.TASK_FINISHED, + task.getAlgorithmName(), + System.currentTimeMillis() - task.getStartTime(), + ""); + }; + + task.setOnSucceeded(workerStateEvent -> notificationCenter.publish(MainViewModel.TASK_FINISHED, + task.getAlgorithmName(), + System.currentTimeMillis() - startTime, + task.getResultMessage())); + + task.setOnCancelled(workerStateEvent -> + notificationCenter.publish(MainViewModel.TASK_CANCELLED, task.getAlgorithmName())); + + startTime = System.currentTimeMillis(); + notificationCenter.publish(MainViewModel.TASK_STARTED, task.getAlgorithmName()); + taskScope.start(task); + } + + public void stopTask(){ + if(task != null && task.isRunning()){ + task.cancel(); + } + } + + protected abstract AlgorithmTask createAlgorithmTask(); + + public long getStartTime() { + return startTime; + } +} diff --git a/src/main/java/com/todense/viewmodel/algorithm/TestTask.java b/src/main/java/com/todense/viewmodel/algorithm/TestTask.java deleted file mode 100644 index fcedf50..0000000 --- a/src/main/java/com/todense/viewmodel/algorithm/TestTask.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.todense.viewmodel.algorithm; - -import javafx.concurrent.Task; - -public class TestTask extends Task { - - - @Override - protected Void call() throws Exception { - return null; - } - - @Override - protected void running() { - super.running(); - } -} diff --git a/src/main/java/com/todense/viewmodel/algorithm/WalkingAgent.java b/src/main/java/com/todense/viewmodel/algorithm/WalkingAgent.java new file mode 100644 index 0000000..8a1d8ff --- /dev/null +++ b/src/main/java/com/todense/viewmodel/algorithm/WalkingAgent.java @@ -0,0 +1,26 @@ +package com.todense.viewmodel.algorithm; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class WalkingAgent { + + DoubleProperty x = new SimpleDoubleProperty(); + DoubleProperty y = new SimpleDoubleProperty(); + + public double getX() { + return x.get(); + } + + public DoubleProperty xProperty() { + return x; + } + + public double getY() { + return y.get(); + } + + public DoubleProperty yProperty() { + return y; + } +} diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/AStarTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/AStarTask.java index 7062dd5..547b952 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/AStarTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/AStarTask.java @@ -23,6 +23,7 @@ public AStarTask(Node startNode, Node goalNode, Graph graph, boolean customWeigh super(graph, customWeight); this.startNode = startNode; this.goalNode = goalNode; + super.algorithmName = "A* Shortest Path"; } @Override @@ -31,6 +32,8 @@ public void perform() throws InterruptedException { result = super.pathLength; } + + @Override protected void init(){ int n = graph.getNodes().size(); diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/BFSTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/BFSTask.java index 33bb685..ca93400 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/BFSTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/BFSTask.java @@ -14,6 +14,7 @@ public class BFSTask extends AlgorithmTask { public BFSTask(Node startNode, Graph graph){ super(graph); this.startNode = startNode; + super.algorithmName = "BFS"; } @Override diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/DFSTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/DFSTask.java index f5f153f..7c44773 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/DFSTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/DFSTask.java @@ -15,6 +15,7 @@ public class DFSTask extends AlgorithmTask { public DFSTask(Node startNode, Graph graph){ super(graph); this.startNode = startNode; + super.algorithmName = "DFS"; } @Override diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/DijkstraTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/DijkstraTask.java index 481d3b2..bcec110 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/DijkstraTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/DijkstraTask.java @@ -21,6 +21,7 @@ public DijkstraTask(Node startNode, Node goalNode, Graph graph, boolean customWe super(graph, customWeight); this.startNode = startNode; this.goalNode = goalNode; + super.algorithmName = "Dijkstra's Algorithm"; } @Override diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/ForceDirectedLayoutTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/ForceDirectedLayoutTask.java index 1d1d55a..08b4952 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/ForceDirectedLayoutTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/ForceDirectedLayoutTask.java @@ -12,9 +12,7 @@ import com.todense.viewmodel.layout.barnesHut.QuadTree; import javafx.geometry.Point2D; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Stack; +import java.util.*; public class ForceDirectedLayoutTask extends AlgorithmTask { @@ -22,6 +20,8 @@ public class ForceDirectedLayoutTask extends AlgorithmTask { private LayoutViewModel layoutVM; private int coolingCounter = 0; + private double smoothness; + private boolean smoothModeOn; double energy = Double.POSITIVE_INFINITY; @@ -41,6 +41,7 @@ public class ForceDirectedLayoutTask extends AlgorithmTask { private double repulsiveStrength; private final double repulsiveness = 1d; + private int counter = 0; private QuadTree quadTree; @@ -48,6 +49,8 @@ public class ForceDirectedLayoutTask extends AlgorithmTask { private LongRangeForce longRangeForce; private Point2D center; + private Random rnd = new Random(); + public ForceDirectedLayoutTask(GraphManager graphManager, LayoutViewModel layoutVM, Point2D center){ super(graphManager.getGraph()); this.graphManager = graphManager; @@ -59,30 +62,20 @@ public ForceDirectedLayoutTask(GraphManager graphManager, LayoutViewModel layout this.tolerance = layoutVM.getTolerance(); this.longRangeForce = layoutVM.getLongRangeForce(); this.repulsiveStrength = getRepulsiveStrength(repulsiveness, optDist, longRangeForce.getExponent() + 1); + this.smoothness = layoutVM.getSmoothness(); + this.smoothModeOn = layoutVM.isSmoothnessOn(); + super.algorithmName = "Force Directed Layout"; } @Override - public void perform() throws InterruptedException { - + public void perform() { if(layoutVM.isMultilevelOn()){ - GraphCoarsener graphCoarsener = new GraphCoarsener(graphManager); - graphCoarsener.initGraphSequence(); - - while(!graphCoarsener.maxLevelReached()){ - graphCoarsener.coarsen(); - painter.sleep(); - } - optDist = optDist * Math.pow(gamma, graphCoarsener.getGraphSequence().size()-1); - - while(graphCoarsener.getGraphSequence().size() > 1){ - optDist = optDist/gamma; - stepSize = initStep; - graphCoarsener.reconstruct(0.3 * optDist); - painter.sleep(); - forceDirectedLayout(graphCoarsener.getGraphSequence().peek()); - } + multilevelForceDirectedLayout(graph); }else{ - forceDirectedLayout(graph); + try { + forceDirectedLayout(graph); + } catch (InterruptedException ignored) { + } } } @@ -90,20 +83,51 @@ public void perform() throws InterruptedException { protected void onFinished() { } + void init(Graph graph){ - int nodeCount = graph.getNodes().size(); + int nodeCount = graph.getOrder(); repulsiveForces = new double[nodeCount][nodeCount]; attractiveForces = new double[nodeCount][nodeCount]; nodeEnergies = new double[nodeCount]; distances = new double[nodeCount][nodeCount]; + + var smoothedNodePositionMap = layoutVM.getNodeSmoothedPositionMap(); + for (Node n: graph.getNodes()){ + smoothedNodePositionMap.put(n, n.getPos()); + } } - public int counter = 0; + public void multilevelForceDirectedLayout(Graph graph){ + GraphCoarsener graphCoarsener = new GraphCoarsener(graphManager); + graphCoarsener.initGraphSequence(); + while(!graphCoarsener.maxLevelReached()){ + graphCoarsener.coarsen(); + try { + painter.sleep(); + } catch (InterruptedException e) { + if(graphManager.getGraph().getOrder() < graphCoarsener.getOriginalGraph().getOrder()){ + graphManager.setGraph(graphCoarsener.getOriginalGraph()); + } + } + } + optDist = optDist * Math.pow(gamma, graphCoarsener.getGraphSequence().size()-1); + while(graphCoarsener.getGraphSequence().size() > 1){ + optDist = optDist/gamma; + stepSize = initStep; + graphCoarsener.reconstruct(0.3 * optDist); + try { + painter.sleep(); + forceDirectedLayout(graphCoarsener.getGraphSequence().peek()); + } catch (InterruptedException e) { + if(graphManager.getGraph().getOrder() < graphCoarsener.getOriginalGraph().getOrder()){ + graphManager.setGraph(graphCoarsener.getOriginalGraph()); + } + } + } + } public void forceDirectedLayout(Graph graph) throws InterruptedException { - init(graph); - while(!super.isCancelled() && stepSize > tolerance * optDist) { counter++; @@ -149,6 +173,7 @@ public void forceDirectedLayout(Graph graph) throws InterruptedException { nodeForceMap = new HashMap<>(); + //apply forces to every node graph.getNodes().forEach(n ->{ Point2D force = new Point2D(0, 0); @@ -160,7 +185,7 @@ public void forceDirectedLayout(Graph graph) throws InterruptedException { for (Node m : graph.getNodes()) { double dist = getDistance(n, m); //if(dist > 0 && dist < 2 * (coarserLevel + 1) * optDist) { - if(dist > 0 ) { + if(dist > 0) { double rf = repulsiveForces[n.getIndex()][m.getIndex()]; force = force.add(m.getPos().subtract(n.getPos()).multiply(rf/dist)); } @@ -184,10 +209,22 @@ public void forceDirectedLayout(Graph graph) throws InterruptedException { nodeEnergies[n.getIndex()] = Math.pow(force.getX(), 2) + Math.pow(force.getY(), 2); - nodeForceMap.put(n, force.normalize().multiply(stepSize)); + nodeForceMap.put(n, force.normalize().multiply(stepSize).add(new Point2D(rnd.nextGaussian()/5, rnd.nextGaussian()/5))); }); - nodeForceMap.forEach((node, force) -> node.setPos(node.getPos().add(force))); + + nodeForceMap.forEach((node, force) -> { + Point2D updatedPos = node.getPos().add(force); + if(layoutVM.isSmoothnessOn()){ + double smoothness = layoutVM.getSmoothness(); + Point2D smoothedPos = updatedPos.multiply(1-smoothness).add(layoutVM.getNodeSmoothedPositionMap().get(node).multiply(smoothness)); + layoutVM.getNodeSmoothedPositionMap().replace(node, smoothedPos); + } + else{ + layoutVM.getNodeSmoothedPositionMap().replace(node, updatedPos); + } + node.setPos(updatedPos); + }); Arrays.stream(nodeEnergies).forEach(e -> energy += e); @@ -195,12 +232,25 @@ public void forceDirectedLayout(Graph graph) throws InterruptedException { if(layoutVM.isCoolingOn()) { stepSize = updateStepLength(energy, prevEnergy); + }else{ + stepSize = layoutVM.getStep(); + } + } + + //transition between smoothed and real position + if(smoothModeOn){ + for(int i = 0; i < 20; i++){ + for(Node node: graph.getNodes()){ + var currentPos = layoutVM.getNodeSmoothedPositionMap().get(node); + layoutVM.getNodeSmoothedPositionMap().replace(node, node.getPos().add(currentPos.subtract(node.getPos()).multiply(0.8))); + } + painter.sleep(10); } } - super.repaint(); + painter.repaint(); } - private Point2D calcBarnesHutRepulsiveForce(Node node, double strength ,QuadTree quadTree){ + private Point2D calcBarnesHutRepulsiveForce(Node node, double strength, QuadTree quadTree){ Point2D repulsiveForce = new Point2D(0, 0); Stack cellStack = new Stack<>(); cellStack.add(quadTree.getRoot()); diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/HCSearchTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/HamiltonianCycleSearchTask.java similarity index 92% rename from src/main/java/com/todense/viewmodel/algorithm/task/HCSearchTask.java rename to src/main/java/com/todense/viewmodel/algorithm/task/HamiltonianCycleSearchTask.java index b6ff765..b3bb6ee 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/HCSearchTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/HamiltonianCycleSearchTask.java @@ -10,7 +10,7 @@ import java.util.Stack; -public class HCSearchTask extends AlgorithmTask { +public class HamiltonianCycleSearchTask extends AlgorithmTask { ArrayList cycle; boolean[] visitedDFS; @@ -20,10 +20,11 @@ public class HCSearchTask extends AlgorithmTask { private Node startNode; private boolean checkingConnectivity; - public HCSearchTask(Node startNode, Graph graph, boolean checkingConnectivity){ + public HamiltonianCycleSearchTask(Node startNode, Graph graph, boolean checkingConnectivity){ super(graph); this.startNode = startNode; this.checkingConnectivity = checkingConnectivity; + super.algorithmName = "Hamiltonian Cycle Search"; } @Override @@ -56,7 +57,7 @@ boolean HCSearch(Node startNode) throws InterruptedException { Stack nodeStack = new Stack<>(); - //push node twice : first time for removing from a cycle when backtracking, second time for adding node to a cycle + //push node twice: first time for removing from a cycle when backtracking, second time for adding node to a cycle nodeStack.push(startNode); nodeStack.push(startNode); diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/KruskalTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/KruskalTask.java index 1c046c8..5f35cbe 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/KruskalTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/KruskalTask.java @@ -19,6 +19,7 @@ public class KruskalTask extends WeightedAlgorithmTask { public KruskalTask(Graph graph, boolean customWeight) { super(graph, customWeight); + super.algorithmName = "Kruskal's algorithm"; } @Override diff --git a/src/main/java/com/todense/viewmodel/algorithm/task/PrimTask.java b/src/main/java/com/todense/viewmodel/algorithm/task/PrimTask.java index f475524..01ffa49 100644 --- a/src/main/java/com/todense/viewmodel/algorithm/task/PrimTask.java +++ b/src/main/java/com/todense/viewmodel/algorithm/task/PrimTask.java @@ -22,6 +22,7 @@ public class PrimTask extends WeightedAlgorithmTask { public PrimTask(Node startNode, Graph graph, boolean customWeight) { super(graph, customWeight); this.startNode = startNode; + super.algorithmName = "Prim's algorithm"; } diff --git a/src/main/java/com/todense/viewmodel/ants/Ant.java b/src/main/java/com/todense/viewmodel/ants/Ant.java index 976920c..3caa6c3 100644 --- a/src/main/java/com/todense/viewmodel/ants/Ant.java +++ b/src/main/java/com/todense/viewmodel/ants/Ant.java @@ -1,12 +1,11 @@ package com.todense.viewmodel.ants; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; +import com.todense.viewmodel.algorithm.WalkingAgent; import java.util.ArrayList; -public class Ant{ +public class Ant extends WalkingAgent { private double cycleLength = 0; @@ -19,10 +18,6 @@ public class Ant{ private int goal; private int previous; - - private DoubleProperty x = new SimpleDoubleProperty(); - private DoubleProperty y = new SimpleDoubleProperty(); - public Ant(int start){ this.start = start; } @@ -43,22 +38,6 @@ public void setCycleLength(double cycleLength) { this.cycleLength = cycleLength; } - public double getX() { - return x.get(); - } - - public DoubleProperty xProperty() { - return x; - } - - public double getY() { - return y.get(); - } - - public DoubleProperty yProperty() { - return y; - } - public int getGoal() { return goal; } diff --git a/src/main/java/com/todense/viewmodel/ants/AntColonyAlgorithmTask.java b/src/main/java/com/todense/viewmodel/ants/AntColonyAlgorithmTask.java index 2b0b31f..2e03725 100644 --- a/src/main/java/com/todense/viewmodel/ants/AntColonyAlgorithmTask.java +++ b/src/main/java/com/todense/viewmodel/ants/AntColonyAlgorithmTask.java @@ -4,6 +4,7 @@ import com.todense.model.graph.Graph; import com.todense.model.graph.Node; import com.todense.viewmodel.algorithm.AlgorithmTask; +import com.todense.viewmodel.scope.AlgorithmScope; import com.todense.viewmodel.scope.AntsScope; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -25,6 +26,7 @@ public abstract class AntColonyAlgorithmTask extends AlgorithmTask { final AntsScope antsScope; + final AlgorithmScope algorithmScope; private DoubleProperty bestSolutionLength = new SimpleDoubleProperty(Double.POSITIVE_INFINITY); //global best cycle length @@ -50,9 +52,10 @@ public abstract class AntColonyAlgorithmTask extends AlgorithmTask { private Random rnd = new Random(); private final Object lock = new Object(); - public AntColonyAlgorithmTask(Graph graph, AntsScope antsScope){ + public AntColonyAlgorithmTask(Graph graph, AntsScope antsScope, AlgorithmScope algorithmScope){ super(graph); this.antsScope = antsScope; + this.algorithmScope = algorithmScope; this.graphOrder = graph.getOrder(); antsScope.getAnts().clear(); @@ -64,7 +67,7 @@ public AntColonyAlgorithmTask(Graph graph, AntsScope antsScope){ antsScope.setGbCycle(new ArrayList<>()); antsScope.setPheromones(new double[graphOrder][graphOrder]); - + algorithmScope.setWalkingAgents(antsScope.getAnts()); } @Override @@ -76,7 +79,6 @@ public void perform() { @Override protected void onFinished() { - } protected double getInitialPheromoneLevel(){ @@ -165,14 +167,12 @@ else if(antsScope.isWith2Opt()){ protected void moveAnts() { AtomicBoolean working = new AtomicBoolean(true); - for (Ant ant : antsScope.getAnts()) { ant.setPrevious(ant.getStart()); moveAnt(ant); } if(!super.isConnectedToUI()) return; - if(painter.isAnimationOn() && antsScope.isAntsAnimationOn()){ ParallelTransition transition = new ParallelTransition(); for (Ant ant : antsScope.getAnts()) { @@ -190,6 +190,7 @@ protected void moveAnts() { try { lock.wait(); } catch (InterruptedException e) { + working.set(false); transition.stop(); antsScope.getAnts().clear(); } @@ -221,7 +222,6 @@ protected void moveAnt(Ant ant){ else{ ant.setStart(ant.getGoal()); } - } protected int getRandomNeighbour(Ant ant, List availableNeighbours) throws MathArithmeticException { @@ -389,10 +389,8 @@ private double inPheromoneRange(double value) { } private Timeline antMoveTimeline(Ant ant) { - Node start = graph.getNodes().get(ant.getPrevious()); Node goal = graph.getNodes().get(ant.getGoal()); - return new Timeline( new KeyFrame(Duration.millis(0), new KeyValue(ant.xProperty(), start.getPos().getX()), diff --git a/src/main/java/com/todense/viewmodel/ants/AntColonySystemTask.java b/src/main/java/com/todense/viewmodel/ants/AntColonySystemTask.java index 707d272..fae52a3 100644 --- a/src/main/java/com/todense/viewmodel/ants/AntColonySystemTask.java +++ b/src/main/java/com/todense/viewmodel/ants/AntColonySystemTask.java @@ -2,6 +2,7 @@ import com.todense.model.graph.Graph; import com.todense.model.graph.Node; +import com.todense.viewmodel.scope.AlgorithmScope; import com.todense.viewmodel.scope.AntsScope; import org.apache.commons.math3.exception.MathArithmeticException; @@ -12,8 +13,9 @@ public class AntColonySystemTask extends AntColonyAlgorithmTask { private Random rnd = new Random(); - public AntColonySystemTask(Graph graph, AntsScope antsScope) { - super(graph, antsScope); + public AntColonySystemTask(Graph graph, AntsScope antsScope, AlgorithmScope algorithmScope) { + super(graph, antsScope, algorithmScope); + super.algorithmName = "Ant Colony System"; } @Override diff --git a/src/main/java/com/todense/viewmodel/ants/AntSystemTask.java b/src/main/java/com/todense/viewmodel/ants/AntSystemTask.java index 4c5c3d4..2373c8a 100644 --- a/src/main/java/com/todense/viewmodel/ants/AntSystemTask.java +++ b/src/main/java/com/todense/viewmodel/ants/AntSystemTask.java @@ -1,12 +1,14 @@ package com.todense.viewmodel.ants; import com.todense.model.graph.Graph; +import com.todense.viewmodel.scope.AlgorithmScope; import com.todense.viewmodel.scope.AntsScope; public class AntSystemTask extends AntColonyAlgorithmTask { - public AntSystemTask(Graph graph, AntsScope antsScope) { - super(graph, antsScope); + public AntSystemTask(Graph graph, AntsScope antsScope, AlgorithmScope algorithmScope) { + super(graph, antsScope, algorithmScope); + super.algorithmName = "Ant System"; } @Override diff --git a/src/main/java/com/todense/viewmodel/ants/MaxMinAntSystemTask.java b/src/main/java/com/todense/viewmodel/ants/MaxMinAntSystemTask.java index b993a2b..68bed71 100644 --- a/src/main/java/com/todense/viewmodel/ants/MaxMinAntSystemTask.java +++ b/src/main/java/com/todense/viewmodel/ants/MaxMinAntSystemTask.java @@ -1,13 +1,15 @@ package com.todense.viewmodel.ants; import com.todense.model.graph.Graph; +import com.todense.viewmodel.scope.AlgorithmScope; import com.todense.viewmodel.scope.AntsScope; public class MaxMinAntSystemTask extends AntColonyAlgorithmTask { - public MaxMinAntSystemTask(Graph graph, AntsScope antsScope) { - super(graph, antsScope); + public MaxMinAntSystemTask(Graph graph, AntsScope antsScope, AlgorithmScope algorithmScope) { + super(graph, antsScope, algorithmScope); + super.algorithmName = "Max-Min Ant System"; } @Override diff --git a/src/main/java/com/todense/viewmodel/ants/RankedAntSystem.java b/src/main/java/com/todense/viewmodel/ants/RankedAntSystem.java index c0c4a65..2f482b2 100644 --- a/src/main/java/com/todense/viewmodel/ants/RankedAntSystem.java +++ b/src/main/java/com/todense/viewmodel/ants/RankedAntSystem.java @@ -1,14 +1,16 @@ package com.todense.viewmodel.ants; import com.todense.model.graph.Graph; +import com.todense.viewmodel.scope.AlgorithmScope; import com.todense.viewmodel.scope.AntsScope; import java.util.Comparator; public class RankedAntSystem extends AntColonyAlgorithmTask { - public RankedAntSystem(Graph graph, AntsScope antsScope) { - super(graph, antsScope); + public RankedAntSystem(Graph graph, AntsScope antsScope, AlgorithmScope algorithmScope) { + super(graph, antsScope, algorithmScope); + super.algorithmName = "Ranked Ant System"; } @Override diff --git a/src/main/java/com/todense/viewmodel/canvas/Camera.java b/src/main/java/com/todense/viewmodel/canvas/Camera.java index ada3791..a1f7364 100644 --- a/src/main/java/com/todense/viewmodel/canvas/Camera.java +++ b/src/main/java/com/todense/viewmodel/canvas/Camera.java @@ -43,7 +43,6 @@ public void adjustToGraph(Graph graph, double canvasWidth, double canvasHeight, affine.appendTranslation(translation.getX(), translation.getY()); affine.appendScale(1/d, 1/d, center); - } public void translate(Point2D delta) { @@ -51,7 +50,7 @@ public void translate(Point2D delta) { affine.appendTranslation(scaledDelta.getX(), scaledDelta.getY()); } - public Point2D inverse(Point2D point) { + public Point2D inverse(Point2D point) { try { return affine.inverseTransform(point); } catch (NonInvertibleTransformException e) { diff --git a/src/main/java/com/todense/viewmodel/canvas/MouseHandler.java b/src/main/java/com/todense/viewmodel/canvas/MouseHandler.java index 0dbc1ee..edf4932 100644 --- a/src/main/java/com/todense/viewmodel/canvas/MouseHandler.java +++ b/src/main/java/com/todense/viewmodel/canvas/MouseHandler.java @@ -114,6 +114,9 @@ else if(clickedEdge != null){ } public void onMouseClicked(MouseEvent event) { + + if(inputScope.isEditLocked()) return; + if(event.getButton() == MouseButton.PRIMARY && !dragging) { // LEFT MOUSE BUTTON if(!pressedKeys.contains(KeyCode.SHIFT)){ if(clickedNode == null && clickedEdge == null){ // click on background @@ -130,11 +133,13 @@ else if(pressedKeys.contains(KeyCode.CONTROL)){ if(pressedKeys.contains(KeyCode.C)){ graphScope.getGraphManager().copySelectedSubgraph(); } - if(clickedNode != null){ - reverseSelection(clickedNode); - } else{ - reverseSelection(clickedEdge); + if(clickedNode != null){ + reverseSelection(clickedNode); + } + else{ + reverseSelection(clickedEdge); + } } } } @@ -177,7 +182,7 @@ else if(!inputScope.isSelecting()){ releaseNode.setSelected(true); GM.getSelectedNodes().add(releaseNode); } - popOver = popOverManager.createNodePopOver(graphScope.getGraphManager(), + popOver = popOverManager.createNodePopOver(graphScope.getGraphManager(), releaseNode, GM.getSelectedNodes(), event.getScreenX(), event.getScreenY()); } else if (releaseEdge != null) { //edge popover if (!releaseEdge.isSelected()) { @@ -205,17 +210,18 @@ else if(!inputScope.isSelecting()){ } edgeStartNode = null; } + inputScope.getDummyEdgeStartNodes().clear(); painter.repaint(); } public void onMouseMoved(MouseEvent event){ - if(inputScope.isEditLocked()) return; - Point2D movePt = new Point2D(event.getX(), event.getY()); currentMousePt = movePt; + if(inputScope.isEditLocked()) return; + Node prevNode = hoverNode; hoverNode = getNodeFromPoint(movePt); @@ -261,7 +267,7 @@ public void onMouseDragged(MouseEvent event) { if(inputScope.isEditLocked()){ camera.translate(delta); - currentMousePt = new Point2D(event.getX(),event.getY()); + currentMousePt = new Point2D(event.getX(), event.getY()); painter.repaint(); return; } @@ -281,9 +287,15 @@ public void onMouseDragged(MouseEvent event) { camera.translate(delta); currentMousePt = new Point2D(event.getX(),event.getY()); } - else if(clickedNode != null) { + else if(clickedNode != null && !pressedKeys.contains(KeyCode.CONTROL)) { if (!clickedNode.isSelected()) { - GM.updateNodePosition(clickedNode, delta.multiply(1/ camera.getZoom())); + GM.updateNodePosition(clickedNode, delta.multiply(1/camera.getZoom())); + } + else if(pressedKeys.contains(KeyCode.A)){ + int clockwise = delta.getY() > 0? 1 : -1; + for (Node n : GM.getSelectedNodes()) { + GM.rotateNode(n, clickedNode.getPos(), clockwise * 0.02); + } } else { for (Node n : GM.getSelectedNodes()) { @@ -388,6 +400,9 @@ public void selectNodesInRect(Rectangle2D rect){ for(Node n: GM.getGraph().getNodes()){ n.setSelected(rect.contains(camera.transform(n.getPos()))); } + for(Edge e: GM.getGraph().getEdges()){ + e.setMarked(e.getN1().isSelected() && e.getN2().isSelected()); + } } private void reverseSelection(Node n){ @@ -398,6 +413,11 @@ private void reverseSelection(Node n){ n.setSelected(true); GM.getSelectedNodes().add(n); } + Graph g = GM.getGraph(); + for(Node m : n.getNeighbours()){ + Edge e = g.getEdge(n, m); + e.setMarked(e.getN1().isSelected() && e.getN2().isSelected()); + } } private void reverseSelection(Edge e){ @@ -419,6 +439,11 @@ public void hidePopOver(){ public void clearSelection(){ for(Edge edge : selectedEdges){ edge.setSelected(false); + //edge.setHighlighted(false); + //edge.setMarked(false); + } + for(Edge edge : GM.getGraph().getEdges()){ + edge.setMarked(false); } selectedEdges.clear(); for(Node node : GM.getSelectedNodes()){ diff --git a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/AntsDrawLayer.java b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/AntsDrawLayer.java deleted file mode 100644 index cb18889..0000000 --- a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/AntsDrawLayer.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.todense.viewmodel.canvas.drawlayer.layers; - -import com.todense.viewmodel.ants.Ant; -import com.todense.viewmodel.canvas.DisplayMode; -import com.todense.viewmodel.canvas.drawlayer.DrawLayer; -import com.todense.viewmodel.scope.AntsScope; -import com.todense.viewmodel.scope.GraphScope; -import javafx.scene.canvas.GraphicsContext; - -public class AntsDrawLayer implements DrawLayer { - - private AntsScope antsScope; - private GraphScope graphScope; - - public AntsDrawLayer(AntsScope antsScope, GraphScope graphScope){ - this.antsScope = antsScope; - this.graphScope = graphScope; - } - - @Override - public void draw(GraphicsContext gc) { - if(graphScope.getDisplayMode() == DisplayMode.ANT_COLONY) { - if (antsScope.isAntsAnimationOn()) { - gc.setFill(antsScope.getAntColor()); - double size = antsScope.getAntSize() * graphScope.getNodeSize(); - for (Ant ant : antsScope.getAnts()){ - gc.fillOval( - ant.getX() - size, - ant.getY() - size, - 2 * size, - 2 * size - ); - } - } - } - } - - @Override - public int getOrder() { - return 3; - } -} diff --git a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/BackgroundDrawLayer.java b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/BackgroundDrawLayer.java index e8914cd..bd90698 100644 --- a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/BackgroundDrawLayer.java +++ b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/BackgroundDrawLayer.java @@ -41,13 +41,18 @@ public void draw(GraphicsContext gc) { private void drawGrid(GraphicsContext gc, double width, double height){ double gap = backgroundScope.getGridGap(); + double screenGap = camera.getZoom() * gap; + double log2Floor = Math.floor(Math.log(screenGap/gap)/Math.log(2)); + + screenGap /= Math.pow(2, log2Floor); + gap = screenGap / camera.getZoom(); Point2D center = new Point2D(width/2, height/2); Point2D start = camera.inverse(new Point2D(0,0)); Point2D end = camera.inverse(new Point2D(width, height)); gc.setStroke(Color.grayRgb(backgroundScope.getGridBrightness())); - gc.setLineWidth(backgroundScope.getGridWidth()); + gc.setLineWidth(backgroundScope.getGridWidth()/ camera.getZoom()); //vertical lines int iMin = (int) ((start.getX() - center.getX())/gap); diff --git a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/GraphDrawLayer.java b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/GraphDrawLayer.java index e20a2a4..153bd0a 100644 --- a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/GraphDrawLayer.java +++ b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/GraphDrawLayer.java @@ -39,13 +39,15 @@ public void draw(GraphicsContext gc) { DisplayMode displayMode = graphScope.getDisplayMode(); double defaultEdgeWidth = graphScope.getEdgeWidth() * graphScope.getNodeSize(); double defaultNodeSize = graphScope.getNodeSize(); - boolean selecting = graphScope.getGraphManager().getSelectedNodes().size() > 0 || inputScope.isSelecting(); + boolean selecting = (inputScope.isSelecting() && graph.getNodes().stream().anyMatch(Node::isSelected)) || + !graphScope.getGraphManager().getSelectedNodes().isEmpty(); if(graphScope.areEdgesVisibile()){ graph.getEdges().stream().filter(e -> !isEdgePrimary(e) && e.isVisible()).forEach(e -> drawEdge(e, gc, defaultEdgeWidth, displayMode, selecting)); graph.getEdges().stream().filter(this::isEdgePrimary).forEach(e -> drawEdge(e, gc, defaultEdgeWidth, displayMode, selecting)); } + graph.getNodes().forEach(n -> drawNode(n, gc, defaultNodeSize, displayMode, selecting)); } @@ -59,24 +61,25 @@ public int getOrder() { return 3; } - private void drawNode(Node node, GraphicsContext gc, double defaultSize ,DisplayMode displayMode, boolean selecting){ + private void drawNode(Node node, GraphicsContext gc, double defaultSize , DisplayMode displayMode, boolean selecting){ double size = getNodeSize(node, defaultSize, displayMode); + Point2D pos = graphScope.getNodePositionFunction().apply(node); Color color = node.getColor() != null ? getNodeDisplayColor(node.getColor(), node, displayMode, selecting) : getNodeDisplayColor(graphScope.getNodeColor(), node, displayMode, selecting); gc.setFill(color); + if(graphScope.showingNodeBorder()){ - double width = graphScope.getEdgeWidth() * graphScope.getNodeSize(); - gc.fillOval(node.getPos().getX() - (size-width)/2, - node.getPos().getY() - (size-width)/2, + gc.fillOval(pos.getX() - (size-width)/2, + pos.getY() - (size-width)/2, size-width, size-width ); }else{ - gc.fillOval(node.getPos().getX() - size/2, node.getPos().getY() - size/2, size, size); + gc.fillOval(pos.getX() - size/2, pos.getY() - size/2, size, size); } if(graphScope.showingNodeBorder()) { @@ -84,8 +87,8 @@ private void drawNode(Node node, GraphicsContext gc, double defaultSize ,Display gc.setLineWidth(width); gc.setStroke(getNodeDisplayColor(graphScope.getEdgeColor(), node, displayMode, selecting)); gc.strokeOval( - node.getPos().getX() - (size-width)/2, - node.getPos().getY() - (size-width)/2, + pos.getX() - (size-width)/2, + pos.getY() - (size-width)/2, size - width, size - width ); @@ -114,33 +117,32 @@ else if(graphScope.getNodeLabelMode() == NodeLabelMode.CUSTOM){ } private void drawEdge(Edge edge, GraphicsContext gc, double defaultWidth, DisplayMode displayMode, boolean selecting){ - Point2D p1 = edge.getN1().getPos(); - Point2D p2 = edge.getN2().getPos(); + Point2D p1 = graphScope.getNodePositionFunction().apply(edge.getN1()); + Point2D p2 = graphScope.getNodePositionFunction().apply(edge.getN2()); double width = getEdgeWidth(edge, defaultWidth, displayMode); + if(width == 0) + return; - //makes line shorter when it is wider - Point2D correctionVector = p1.subtract(p2).normalize().multiply(width/2); - + // ant colony algorithm cycle marker if(edge.isMarked() && displayMode == DisplayMode.ANT_COLONY) { gc.setLineWidth(width + graphScope.getNodeSize() * 0.25); gc.setStroke(antsScope.getCycleColor()); - gc.strokeLine(p1.getX() - correctionVector.getX(), - p1.getY() - correctionVector.getY(), - p2.getX() + correctionVector.getX(), - p2.getY() + correctionVector.getY()); + gc.strokeLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()); } + if(displayMode == DisplayMode.ANT_COLONY && !antsScope.isShowingPheromones()) return; gc.setLineWidth(width); - gc.setStroke(getEdgeDisplayColor(edge, displayMode, selecting)); + Color edgeColor = getEdgeDisplayColor(edge, displayMode, selecting); - gc.strokeLine(p1.getX() - correctionVector.getX(), - p1.getY() - correctionVector.getY(), - p2.getX() + correctionVector.getX(), - p2.getY() + correctionVector.getY()); + if(edgeColor.equals(backgroundScope.getBackgroundColor())){ + return; + } + gc.setStroke(edgeColor); + gc.strokeLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()); if(graphScope.getEdgeWeightMode() != EdgeWeightMode.NONE){ if(graphScope.getEdgeWeightMode() == EdgeWeightMode.LENGTH){ @@ -193,7 +195,7 @@ private Color getNodeDisplayColor(Color color, Node node, DisplayMode displayMod return displayColor; } - private double getNodeSize(Node node, double defaultSize ,DisplayMode displayMode) { + private double getNodeSize(Node node, double defaultSize, DisplayMode displayMode) { if(node.isHighlighted()) { defaultSize = defaultSize * 1.05; @@ -212,7 +214,7 @@ private double getNodeSize(Node node, double defaultSize ,DisplayMode displayMod break; default: defaultSize = 0; } - return defaultSize; + return defaultSize * graphScope.getNodeScaleFunction().apply(node); } private Color getEdgeDisplayColor(Edge edge, DisplayMode displayMode, boolean selecting) { @@ -238,6 +240,16 @@ else if(edge.isHighlighted() || edge.isSelected() || edge.isMarked()){ break; default: displayColor = Color.PINK; } + + if(graphScope.isEdgeOpacityDecayOn()){ + double decay = getDecayedValue(edge, graphScope.getEdgeOpacityDecay(), graphScope.getNodeSize() * 2); + if(decay > 1){ + decay = 1; + } + displayColor = displayColor.deriveColor(0,1,1, decay); + } + + //return Util.getFaintColor(displayColor, backgroundScope.getBackgroundColor(), decay); return displayColor; } @@ -266,6 +278,21 @@ private double getEdgeWidth(Edge edge, double defaultWidth, DisplayMode displayM } break; } + + if(graphScope.isEdgeWidthDecayOn()){ + double decay = getDecayedValue(edge, graphScope.getEdgeWidthDecay(), 2*graphScope.getNodeSize()); + defaultWidth *= decay; + } + return Math.min(defaultWidth, graphScope.getNodeSize()); } + + private double getDecayedValue(Edge edge, double decay, double maximum){ + Point2D p1 = graphScope.getNodePositionFunction().apply(edge.getN1()); + Point2D p2 = graphScope.getNodePositionFunction().apply(edge.getN2()); + double exponent = decay*(p1.distance(p2)-maximum); + return 2 - 2 * (Math.pow(Math.E, exponent)/(1+Math.pow(Math.E, exponent))); + } + + } diff --git a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/LowerDrawLayer.java b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/LowerDrawLayer.java index b709104..aedd57b 100644 --- a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/LowerDrawLayer.java +++ b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/LowerDrawLayer.java @@ -31,7 +31,6 @@ private void drawDummyEdge(GraphicsContext gc, Color dummyColor){ gc.setFill(dummyColor); gc.setLineWidth(graphScope.getEdgeWidth() * graphScope.getNodeSize()); - double circleSize = graphScope.getNodeSize() * 0.5; Point2D endPoint = inputScope.getDummyEdgeEnd(); for(Node node : inputScope.getDummyEdgeStartNodes()){ diff --git a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/UpperDrawLayer.java b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/UpperDrawLayer.java index 4b83e85..6ed893d 100644 --- a/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/UpperDrawLayer.java +++ b/src/main/java/com/todense/viewmodel/canvas/drawlayer/layers/UpperDrawLayer.java @@ -1,6 +1,7 @@ package com.todense.viewmodel.canvas.drawlayer.layers; import com.todense.model.graph.Node; +import com.todense.viewmodel.algorithm.WalkingAgent; import com.todense.viewmodel.canvas.DisplayMode; import com.todense.viewmodel.canvas.drawlayer.DrawLayer; import com.todense.viewmodel.scope.*; @@ -17,17 +18,20 @@ public class UpperDrawLayer implements DrawLayer { private CanvasScope canvasScope; private BackgroundScope backgroundScope; private AlgorithmScope algorithmScope; + private AntsScope antsScope; public UpperDrawLayer(GraphScope graphScope, InputScope inputScope, CanvasScope canvasScope, BackgroundScope backgroundScope, - AlgorithmScope algorithmScope){ + AlgorithmScope algorithmScope, + AntsScope antsScope){ this.graphScope = graphScope; this.inputScope = inputScope; this.canvasScope = canvasScope; this.backgroundScope = backgroundScope; this.algorithmScope = algorithmScope; + this.antsScope = antsScope; } @@ -68,6 +72,8 @@ public void draw(GraphicsContext gc) { } /* + // quad tree debugger + Graph graph = graphScope.getGraphManager().getGraph(); if(graph.getNodes().size() < 2) return; @@ -110,6 +116,21 @@ public void draw(GraphicsContext gc) { */ } + if(graphScope.getDisplayMode() == DisplayMode.ANT_COLONY){ + if (antsScope.isAntsAnimationOn()) { + gc.setFill(antsScope.getAntColor()); + double size = antsScope.getAntSize() * graphScope.getNodeSize(); + for(WalkingAgent agent: algorithmScope.getWalkingAgents()) { + gc.fillOval( + agent.getX() - size, + agent.getY() - size, + 2 * size, + 2 * size + ); + } + } + } + gc.setTransform(new Affine()); //select rectangle @@ -142,7 +163,7 @@ private void drawSelectRect(GraphicsContext gc){ Color rectColor = backgroundScope.getBackgroundColor() .invert() .grayscale() - .deriveColor(0,1,1,0.4); + .deriveColor(0,1,1,0.2); Rectangle2D rect = inputScope.getSelectRect(); gc.setFill(rectColor); diff --git a/src/main/java/com/todense/viewmodel/file/format/ogr/OgrReader.java b/src/main/java/com/todense/viewmodel/file/format/ogr/OgrReader.java index 7067e9a..fc55dd7 100644 --- a/src/main/java/com/todense/viewmodel/file/format/ogr/OgrReader.java +++ b/src/main/java/com/todense/viewmodel/file/format/ogr/OgrReader.java @@ -33,6 +33,7 @@ public Graph readGraph(File file) { int edgeCount = Integer.parseInt(edgeCountLine[1]); Graph graph = new Graph(name); + for (int i = 0; i < nodeCount; i++) { scanner.nextInt(); Point2D pos = new Point2D(Double.parseDouble(scanner.next()), Double.parseDouble(scanner.next())); diff --git a/src/main/java/com/todense/viewmodel/graph/GraphManager.java b/src/main/java/com/todense/viewmodel/graph/GraphManager.java index ac3b133..4a7714e 100644 --- a/src/main/java/com/todense/viewmodel/graph/GraphManager.java +++ b/src/main/java/com/todense/viewmodel/graph/GraphManager.java @@ -36,43 +36,45 @@ public void resetGraph() { selectedNodes = new ArrayList<>(); } - public void createPath(){ - for(int i = 0; i < graph.getNodes().size()-1; i++) { - Node n1 = graph.getNodes().get(i); - Node n2 = graph.getNodes().get(i+1); + public void createPath(List nodes){ + for(int i = 0; i < nodes.size()-1; i++) { + Node n1 = nodes.get(i); + Node n2 = nodes.get(i+1); if(!isEdgeBetween(n1,n2)) { graph.addEdge(n1, n2); } } } + public void createPath(){ + this.createPath(this.graph.getNodes()); + } + + public void createCompleteGraph(List nodes){ + graph.applyToAllPairOfNodes(nodes, (n, m) -> { + if(!isEdgeBetween(n, m)) + graph.addEdge(n, m); + }); + } + public void createCompleteGraph(){ - for(int i = 0; i < graph.getNodes().size()-1; i++) { - for(int j = i + 1; j < graph.getNodes().size(); j++) { - Node n1 = graph.getNodes().get(i); - Node n2 = graph.getNodes().get(j); - if(!isEdgeBetween(n1,n2)) { - graph.addEdge(n1, n2); - } - } - } + this.createCompleteGraph(this.graph.getNodes()); + } + + public void createComplementGraph(List nodes) { + graph.applyToAllPairOfNodes(nodes, (n, m) -> { + if(!isEdgeBetween(n, m)) + graph.addEdge(n, m); + else + graph.removeEdge(n, m); + }); } public void createComplementGraph() { - for(int i = 0; i < graph.getNodes().size(); i++){ - Node n = graph.getNodes().get(i); - for (int j = i+1; j < graph.getNodes().size(); j++) { - Node m = graph.getNodes().get(j); - if(!isEdgeBetween(n, m)){ - graph.addEdge(n, m); - } - else{ - graph.removeEdge(n, m); - } - } - } + this.createComplementGraph(this.graph.getNodes()); } + public void subdivideEdge(Edge e){ graph.removeEdge(e); Node n = e.getN1(); @@ -83,18 +85,20 @@ public void subdivideEdge(Edge e){ graph.addEdge(m, k); } - public void subdivideEdges() { - List edgesCopy = new ArrayList<>(graph.getEdges()); - graph.removeAllEdges(); - - for (Edge edge : edgesCopy) { - Node n = edge.getN1(); - Node m = edge.getN2(); + public void subdivideEdges(List nodes) { + ArrayList nodesCopy = new ArrayList<>(nodes); + graph.applyToAllConnectedPairOfNodes(nodesCopy, (n, m) -> { + Edge e = graph.getEdge(n, m); Point2D midpoint = n.getPos().midpoint(m.getPos()); Node k = graph.addNode(midpoint); + graph.removeEdge(n, m); graph.addEdge(n, k); graph.addEdge(m, k); - } + }); + } + + public void subdivideEdges() { + this.subdivideEdges(this.graph.getNodes()); } public void contractEdge(Edge e) { @@ -123,7 +127,7 @@ public void addSubgraph(Point2D center){ } } - public Graph getSubgraphFromSelectedNodes(){ + public Graph getSelectedSubgraph(){ Graph subGraph = new Graph(); for (Node n : selectedNodes) { subGraph.addNode(n.getPos(), n.getColor()); @@ -139,11 +143,15 @@ public Graph getSubgraphFromSelectedNodes(){ } public void copySelectedSubgraph(){ - clipboardGraph = getSubgraphFromSelectedNodes(); + clipboardGraph = getSelectedSubgraph(); + } + + public void deleteEdges(List nodes) { + graph.removeEdges(nodes); } - public void deleteEdges() { - graph.removeAllEdges(); + public void deleteEdges(){ + this.deleteEdges(this.graph.getNodes()); } public boolean isEdgeBetween(Node n1, Node n2) { @@ -154,6 +162,13 @@ public void updateNodePosition(Node n, Point2D d){ n.setPos(n.getPos().add(d)); } + public void rotateNode(Node n, Point2D pivot, double angle){ + Point2D u = n.getPos().subtract(pivot); + double x = u.getX() * Math.cos(angle) - u.getY() * Math.sin(angle); + double y = u.getX() * Math.sin(angle) + u.getY() * Math.cos(angle); + n.setPos(new Point2D(x, y).add(pivot)); + } + private List selectedNodes = new ArrayList<>(); public List getSelectedNodes() { diff --git a/src/main/java/com/todense/viewmodel/layout/GraphCoarsener.java b/src/main/java/com/todense/viewmodel/layout/GraphCoarsener.java index 7900e61..ba8c670 100644 --- a/src/main/java/com/todense/viewmodel/layout/GraphCoarsener.java +++ b/src/main/java/com/todense/viewmodel/layout/GraphCoarsener.java @@ -13,6 +13,7 @@ public class GraphCoarsener { + private Graph originalGraph; private GraphManager graphManager; private Stack graphSequence = new Stack<>(); private Stack> nodeMaps = new Stack<>(); @@ -22,6 +23,7 @@ public class GraphCoarsener { public GraphCoarsener(GraphManager graphManager){ this.graphManager = graphManager; + this.originalGraph = graphManager.getGraph().copy(); } public void reconstruct(double variation) { @@ -105,4 +107,8 @@ public Stack getGraphSequence() { public boolean maxLevelReached() { return (graphSequence.peek().getEdges().size() <= 1 || reductionRate > 0.75); } + + public Graph getOriginalGraph() { + return originalGraph; + } } diff --git a/src/main/java/com/todense/viewmodel/popover/PopOverManager.java b/src/main/java/com/todense/viewmodel/popover/PopOverManager.java index eeeb5a5..f188463 100644 --- a/src/main/java/com/todense/viewmodel/popover/PopOverManager.java +++ b/src/main/java/com/todense/viewmodel/popover/PopOverManager.java @@ -23,12 +23,12 @@ public class PopOverManager { private javafx.scene.Node owner; private Context context; - public PopOver createNodePopOver(GraphManager graphManager, List nodes, double x, double y){ + public PopOver createNodePopOver(GraphManager graphManager, Node clickedNode, List nodes, double x, double y){ final ViewTuple viewTuple = FluentViewLoader.fxmlView(NodePopOverView.class).context(context).load(); PopOver popOver = new PopOver(viewTuple.getView()); - viewTuple.getViewModel().bindToNodes(nodes); + viewTuple.getViewModel().bindToNodes(clickedNode, nodes); popOver.animatedProperty().set(false); popOver.detachableProperty().set(false); popOver.show(owner, x, y); diff --git a/src/main/java/com/todense/viewmodel/scope/AlgorithmScope.java b/src/main/java/com/todense/viewmodel/scope/AlgorithmScope.java index 3a44215..a18e395 100644 --- a/src/main/java/com/todense/viewmodel/scope/AlgorithmScope.java +++ b/src/main/java/com/todense/viewmodel/scope/AlgorithmScope.java @@ -2,12 +2,16 @@ import com.todense.model.graph.Node; import com.todense.viewmodel.algorithm.Algorithm; +import com.todense.viewmodel.algorithm.WalkingAgent; import de.saxsys.mvvmfx.Scope; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; +import java.util.ArrayList; +import java.util.List; + public class AlgorithmScope implements Scope { private ObjectProperty startNodeProperty = new SimpleObjectProperty<>(); @@ -15,6 +19,8 @@ public class AlgorithmScope implements Scope { private ObjectProperty algorithmProperty = new SimpleObjectProperty<>(); private BooleanProperty showingEndpointsProperty = new SimpleBooleanProperty(true); + private List walkingAgents = new ArrayList<>(); + public ObjectProperty startNodeProperty() { return startNodeProperty; } @@ -54,4 +60,12 @@ public boolean isShowingEndpoints() { public BooleanProperty showingEndpointsProperty() { return showingEndpointsProperty; } + + public List getWalkingAgents() { + return walkingAgents; + } + + public void setWalkingAgents(List walkingAgents) { + this.walkingAgents = walkingAgents; + } } diff --git a/src/main/java/com/todense/viewmodel/scope/AntsScope.java b/src/main/java/com/todense/viewmodel/scope/AntsScope.java index 4ed3584..536dd0b 100644 --- a/src/main/java/com/todense/viewmodel/scope/AntsScope.java +++ b/src/main/java/com/todense/viewmodel/scope/AntsScope.java @@ -8,12 +8,13 @@ import javafx.scene.paint.Color; import java.util.ArrayList; +import java.util.List; public class AntsScope implements Scope { - private ArrayList ants = new ArrayList<>(); + private List ants = new ArrayList<>(); - ArrayList gbCycle = new ArrayList<>(); + ArrayList gbCycle = new ArrayList<>(); //globally best cycle private double[][] pheromones; @@ -57,7 +58,7 @@ public double getPheromone(int i, int j){ return pheromones[i][j]; } - public ArrayList getAnts() { + public List getAnts() { return ants; } diff --git a/src/main/java/com/todense/viewmodel/scope/CanvasScope.java b/src/main/java/com/todense/viewmodel/scope/CanvasScope.java index f564304..d1cabe0 100644 --- a/src/main/java/com/todense/viewmodel/scope/CanvasScope.java +++ b/src/main/java/com/todense/viewmodel/scope/CanvasScope.java @@ -7,6 +7,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Point2D; import javafx.scene.paint.Color; public class CanvasScope implements Scope { @@ -54,4 +55,9 @@ public Color getBorderColor() { public ObjectProperty borderColorProperty() { return borderColorProperty; } + + public Point2D getCanvasCenter(){ + return new Point2D(canvasWidthProperty.get()/2, canvasHeightProperty.get()/2); + } + } diff --git a/src/main/java/com/todense/viewmodel/scope/GraphScope.java b/src/main/java/com/todense/viewmodel/scope/GraphScope.java index cab2acd..abf9a37 100644 --- a/src/main/java/com/todense/viewmodel/scope/GraphScope.java +++ b/src/main/java/com/todense/viewmodel/scope/GraphScope.java @@ -2,21 +2,30 @@ import com.todense.model.EdgeWeightMode; import com.todense.model.NodeLabelMode; +import com.todense.model.graph.Node; import com.todense.viewmodel.canvas.DisplayMode; import com.todense.viewmodel.graph.GraphManager; import de.saxsys.mvvmfx.Scope; import javafx.beans.property.*; +import javafx.geometry.Point2D; import javafx.scene.paint.Color; +import java.util.function.Function; + public class GraphScope implements Scope { private final Color INITIAL_NODE_COLOR = Color.rgb(50,90,170); private final Color INITIAL_EDGE_COLOR = Color.rgb(120,160,200); + public static Function NODE_ORDINARY_POSITION_FUNCTION = Node::getPos; private DoubleProperty nodeSizeProperty = new SimpleDoubleProperty(30d); private DoubleProperty edgeWidthProperty = new SimpleDoubleProperty(0.15); + private DoubleProperty edgeWidthDecayProperty = new SimpleDoubleProperty(0.06); + private DoubleProperty edgeOpacityDecayProperty = new SimpleDoubleProperty(0.06); private BooleanProperty nodeBorderProperty = new SimpleBooleanProperty(false); private BooleanProperty edgeVisibilityProperty = new SimpleBooleanProperty(true); + private BooleanProperty edgeWidthDecayOnProperty = new SimpleBooleanProperty(false); + private BooleanProperty edgeOpacityDecayOnProperty = new SimpleBooleanProperty(false); private ObjectProperty nodeColorProperty = new SimpleObjectProperty<>(INITIAL_NODE_COLOR); private ObjectProperty edgeColorProperty = new SimpleObjectProperty<>(INITIAL_EDGE_COLOR); private ObjectProperty nodeLabelColorProperty = new SimpleObjectProperty<>(Color.WHITE); @@ -25,6 +34,9 @@ public class GraphScope implements Scope { private ObjectProperty edgeWeightModeProperty = new SimpleObjectProperty<>(EdgeWeightMode.NONE); private ObjectProperty displayModeProperty = new SimpleObjectProperty<>(DisplayMode.DEFAULT); + private Function nodeScaleFunction = node -> 1.0; + private Function nodeCustomPositionFunction = Node::getPos; + private GraphManager graphManager = new GraphManager(); public double getNodeSize() { @@ -118,4 +130,52 @@ public boolean areEdgesVisibile() { public BooleanProperty edgeVisibilityProperty() { return edgeVisibilityProperty; } + + public Function getNodeScaleFunction() { + return nodeScaleFunction; + } + + public void setNodeScaleFunction(Function nodeScaleFunction) { + this.nodeScaleFunction = nodeScaleFunction; + } + + public Function getNodePositionFunction() { + return nodeCustomPositionFunction; + } + + public void setNodePositionFunction(Function nodeCustomPositionFunction) { + this.nodeCustomPositionFunction = nodeCustomPositionFunction; + } + + public DoubleProperty edgeWidthDecayProperty() { + return edgeWidthDecayProperty; + } + + public double getEdgeWidthDecay(){ + return edgeWidthDecayProperty.get(); + } + + public boolean isEdgeWidthDecayOn() { + return edgeWidthDecayOnProperty.get(); + } + + public BooleanProperty edgeWidthDecayOnProperty() { + return edgeWidthDecayOnProperty; + } + + public double getEdgeOpacityDecay() { + return edgeOpacityDecayProperty.get(); + } + + public DoubleProperty edgeOpacityDecayProperty() { + return edgeOpacityDecayProperty; + } + + public boolean isEdgeOpacityDecayOn() { + return edgeOpacityDecayOnProperty.get(); + } + + public BooleanProperty edgeOpacityDecayOnProperty() { + return edgeOpacityDecayOnProperty; + } } diff --git a/src/main/java/com/todense/viewmodel/scope/TaskScope.java b/src/main/java/com/todense/viewmodel/scope/TaskScope.java index 4eb5b01..48e85d0 100644 --- a/src/main/java/com/todense/viewmodel/scope/TaskScope.java +++ b/src/main/java/com/todense/viewmodel/scope/TaskScope.java @@ -3,28 +3,28 @@ import com.todense.viewmodel.algorithm.AlgorithmTask; import de.saxsys.mvvmfx.Scope; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + public class TaskScope implements Scope { + private ExecutorService executor = Executors.newSingleThreadExecutor(); + private Future future; private AlgorithmTask task; - private Thread thread; - - public AlgorithmTask getTask() { - return task; - } - - public void setTask(AlgorithmTask task) { - if(task != null && task.isRunning()){ - task.cancel(); + public boolean start(AlgorithmTask task){ + if(isDone()){ + this.executor = Executors.newSingleThreadExecutor(); + this.future = executor.submit(task); + this.task = task; + return true; } - this.task = task; + return false; } - public void setThread(Thread thread){ - if(this.thread != null && this.thread.isAlive()){ - this.thread.interrupt(); - } - this.thread = thread; + public boolean isDone(){ + return this.future == null || this.future.isCancelled() || this.future.isDone(); } public void stopTask(){ @@ -33,16 +33,9 @@ public void stopTask(){ } } - public void stopThread(){ - if(thread != null && thread.isAlive()){ - thread.interrupt(); - System.out.println(thread.getState()); - } + public AlgorithmTask getTask() { + return task; } - public void stop(){ - stopTask(); - stopThread(); - } } diff --git a/src/main/resources/application.css b/src/main/resources/application.css index 910c4f8..df775fe 100644 --- a/src/main/resources/application.css +++ b/src/main/resources/application.css @@ -182,6 +182,10 @@ -fx-pref-height:3; } +.slider .axis { + -fx-tick-label-fill: fx-text-dark; +} + .slider .fill { -fx-padding: 0.083333em; /* 1 */ } diff --git a/src/main/resources/com/todense/view/BackgroundView.fxml b/src/main/resources/com/todense/view/BackgroundView.fxml index 4077740..b86e2f1 100644 --- a/src/main/resources/com/todense/view/BackgroundView.fxml +++ b/src/main/resources/com/todense/view/BackgroundView.fxml @@ -36,7 +36,7 @@ - + - +
- - + + diff --git a/src/main/resources/com/todense/view/NodePopOverView.fxml b/src/main/resources/com/todense/view/NodePopOverView.fxml index 922e725..3370589 100644 --- a/src/main/resources/com/todense/view/NodePopOverView.fxml +++ b/src/main/resources/com/todense/view/NodePopOverView.fxml @@ -4,12 +4,13 @@ + - + - diff --git a/src/main/resources/com/todense/view/RandomGeneratorView.fxml b/src/main/resources/com/todense/view/RandomGeneratorView.fxml index d681e9a..884d2cd 100644 --- a/src/main/resources/com/todense/view/RandomGeneratorView.fxml +++ b/src/main/resources/com/todense/view/RandomGeneratorView.fxml @@ -11,7 +11,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -44,6 +44,9 @@ + + +