From 9acd5941f29fd5ac995e1be4e0d468ef32df35b7 Mon Sep 17 00:00:00 2001 From: brunomnsilva Date: Tue, 19 Mar 2024 17:22:14 +0000 Subject: [PATCH] Version 2.0.0-rc1. Shapes (partially addresses #4, #34 and #37), sizes, providers, annotations and minor improvements. --- README.md | 30 +- pom.xml | 2 +- smartgraph.properties | 35 ++- .../com/brunomnsilva/smartgraph/Main.java | 8 +- .../brunomnsilva/smartgraph/example/City.java | 33 +- .../smartgraph/example/ExampleMain.java | 18 +- ...rceDirectedSpringSystemLayoutStrategy.java | 2 +- .../smartgraph/graphview/ShapeCircle.java | 88 ++++++ .../smartgraph/graphview/ShapeFactory.java | 71 +++++ .../graphview/ShapeRegularPolygon.java | 131 ++++++++ .../smartgraph/graphview/ShapeStar.java | 65 ++++ .../smartgraph/graphview/ShapeWithRadius.java | 81 +++++ .../smartgraph/graphview/SmartArrow.java | 5 + .../graphview/SmartGraphEdgeCurve.java | 25 +- .../graphview/SmartGraphEdgeLine.java | 19 +- .../smartgraph/graphview/SmartGraphPanel.java | 195 ++++++++++-- .../graphview/SmartGraphProperties.java | 44 ++- .../graphview/SmartGraphVertexNode.java | 281 ++++++++++++++---- .../smartgraph/graphview/SmartLabel.java | 59 +++- .../graphview/SmartLabelProvider.java | 43 +++ .../graphview/SmartLabelSource.java | 1 - .../graphview/SmartRadiusProvider.java | 43 +++ .../graphview/SmartRadiusSource.java | 47 +++ .../graphview/SmartShapeTypeProvider.java | 42 +++ .../graphview/SmartShapeTypeSource.java | 47 +++ .../graphview/SmartStylableNode.java | 4 +- .../smartgraph/graphview/SmartStyleProxy.java | 29 +- .../smartgraph/graphview/UtilitiesJavaFX.java | 2 +- version.properties | 2 +- 29 files changed, 1317 insertions(+), 135 deletions(-) create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeCircle.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeFactory.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeRegularPolygon.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeStar.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeWithRadius.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelProvider.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusProvider.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusSource.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeProvider.java create mode 100755 src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeSource.java diff --git a/README.md b/README.md index 1ff8728..bafc658 100644 --- a/README.md +++ b/README.md @@ -23,20 +23,27 @@ through a [force-directed algorithm](https://en.wikipedia.org/wiki/Force-directe ### What's new? -- (1.1.0) Automatic layout is now performed through an instantiated *strategy*. There are two available (but the pattern allows for the user to devise others): - - - `ForceDirectedSpringSystemLayoutStrategy`: this is the original implementation for the automatic placement, through a spring system; - - `ForceDirectedSpringGravityLayoutStrategy`: (**new**) this is a variant of the spring system implementation, but with a gravity pull towards the center of the panel. This is now the default strategy and has the advantage of not repelling isolated vertices and/or bipartite graphs to the edges of the panel. +This is a *release candidate* version, as it hasn't been thoroughly tested yet. Feedback is appreciated. -- (1.0.0) Package now available through [Maven Central](https://central.sonatype.com/namespace/com.brunomnsilva). The library seems stable, after dozens of college projects of my students have used it. Hence, the version was bumped to 1.0.0. +Although one full version number higher than the previous *stable* version (1.1.0), existing applications are expected to work with this library version without significant changes. -- (0.9.4) You can now annotate a method with `@SmartLabelSource` within a model class to provide the displayed label for a vertex/edge; see the example at `com.brunomnsilva.smartgraph.example`. If no annotation is present, then the `toString()` method is used to obtain the label's text. +:warning: The only exception is the necessary use of `SmartStylableNode.setStyleInline(...)` instead of `SmartStylableNode.setStyle(...)` to correctly apply inline styles to nodes (vertices and edges). Css classes are set the same way as before. -- (0.9.4) You can manually alter a vertex position on the panel at anytime, through `SmartGraphPanel.setVertexPosition(Vertex v)`; see the example at `com.brunomnsilva.smartgraph.example`. +- (2.0.0-rc1) Shapes, sizes, providers, annotations and minor improvements: -- (0.9.4) You can override specific default properties by using a *String* parameter to the `SmartGraphProperties` constructor; see the example at `com.brunomnsilva.smartgraph.example`. This is useful if you want to display visually different graphs within the same application. + - Different shapes can be used to represent vertices, namely circles, stars and regular polygons (from triangles to dodecagons); + - The default shape can be specified with the `vertex.shape` property in `smartgraph.properties` + - Can be set/changed at runtime through a `SmartShapeTypeProvider` or `SmartShapeTypeSource` annotation. -- (0.9.4) You can now style labels and arrows individually. + - The radius of the shape (enclosing circle) used to represent a vertex can be set/changed at runtime through a `SmartRadiusProvider` or `SmartRadiusSource` annotation. + + - Updated shapes and radii are only reflected in the visualization after calling `SmartGraphPanel.update()` or `SmartGraphPanel.updateAndWait()`. + + - Improvements: + - When dragging nodes, they will be kept within the panel's bounds. + - The look of curved edges has been improved. + +See the [wiki](https://github.com/brunomnsilva/JavaFXSmartGraph/wiki) for the complete changelist. ### Using the library @@ -216,7 +223,7 @@ graphView.setVertexDoubleClickAction(graphVertex -> { graphView.setEdgeDoubleClickAction(graphEdge -> { System.out.println("Edge contains element: " + graphEdge.getUnderlyingEdge().element()); //dynamically change the style, can also be done for a vertex - graphEdge.setStyle("-fx-stroke: black; -fx-stroke-width: 2;"); + graphEdge.setStyleInline("-fx-stroke: black; -fx-stroke-width: 2;"); }); ``` @@ -234,7 +241,8 @@ You can set the graph visualization properties in the `smartgraph.properties` fi # Vertex related configurations # vertex.allow-user-move = true -vertex.radius = 15 +vertex.radius = 15 +vertex.shape = circle vertex.tooltip = true vertex.label = false diff --git a/pom.xml b/pom.xml index c24e78a..712a631 100755 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ com.brunomnsilva smartgraph - 1.1.1 + 2.0.0-rc1 jar ${project.groupId}:${project.artifactId} diff --git a/smartgraph.properties b/smartgraph.properties index 12d1117..ccec7fe 100644 --- a/smartgraph.properties +++ b/smartgraph.properties @@ -25,14 +25,40 @@ ################################################################################ # javafxgraph.properties # -# ATTENTION: do not leave any trailing spaces after 'true' or 'false' values +# These properties can be used to override the default values, which are: +# +# vertex.allow-user-move = true (boolean) +# vertex.radius = 15 (in pixels) +# vertex.shape = circle (string) +# vertex.tooltip = true (boolean) +# vertex.label = true (boolean) +# edge.tooltip = true (boolean) +# edge.label = true (boolean) +# edge.arrow = true (boolean) +# edge.arrowsize = 5 (in pixels) +# +# Allowed/available vertex shape names (string without quotes) are: +# - circle +# - star +# - triangle +# - square +# - pentagon +# - hexagon +# - heptagon +# - octagon +# - nonagon +# - decagon +# - hendecagon +# - dodecagon # +# ATTENTION: do not leave any trailing spaces after 'true' or 'false' values ################################################################################ # Vertex related configurations # vertex.allow-user-move = true -vertex.radius = 15 +vertex.radius = 20 +vertex.shape = star vertex.tooltip = true vertex.label = true @@ -46,12 +72,13 @@ edge.arrow = true # size in pixels (side of a triangle); only for directed graphs edge.arrowsize = 5 -# Force-directed layout related configurations -# # Notice: deprecated since version 1.1.0 Force directed layout strategies are now # instantiated and can be swapped at runtime, per the Strategy design pattern. # The parameters are passed as arguments or one can use the default ones described # in the javadoc documentation. +# +# Force-directed layout related configurations +# # -- You should experiment with different values for your # -- particular problem, knowing that not all will achieve # -- a stable state diff --git a/src/main/java/com/brunomnsilva/smartgraph/Main.java b/src/main/java/com/brunomnsilva/smartgraph/Main.java index 7639cc5..bcb1a33 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/Main.java +++ b/src/main/java/com/brunomnsilva/smartgraph/Main.java @@ -64,7 +64,7 @@ public void start(Stage ignored) { This can be done at any time afterwards. */ if (g.numVertices() > 0) { - graphView.getStylableVertex("A").setStyle("-fx-fill: gold; -fx-stroke: brown;"); + graphView.getStylableVertex("A").setStyleInline("-fx-fill: gold; -fx-stroke: brown;"); } /* @@ -110,9 +110,9 @@ public void start(Stage ignored) { graphView.setEdgeDoubleClickAction(graphEdge -> { System.out.println("Edge contains element: " + graphEdge.getUnderlyingEdge().element()); //dynamically change the style when clicked - graphEdge.setStyle("-fx-stroke: black; -fx-stroke-width: 3;"); + graphEdge.setStyleInline("-fx-stroke: black; -fx-stroke-width: 3;"); - graphEdge.getStylableArrow().setStyle("-fx-stroke: black; -fx-stroke-width: 3;"); + graphEdge.getStylableArrow().setStyleInline("-fx-stroke: black; -fx-stroke-width: 3;"); //uncomment to see edges being removed after click //Edge underlyingEdge = graphEdge.getUnderlyingEdge(); @@ -250,7 +250,7 @@ private void continuously_test_adding_elements(Graph g, SmartGra //color new vertices SmartStylableNode stylableVertex = graphView.getStylableVertex(vertexId); if(stylableVertex != null) { - stylableVertex.setStyle("-fx-fill: orange;"); + stylableVertex.setStyleInline("-fx-fill: orange;"); } } else { Vertex existing1 = get_random_vertex(g); diff --git a/src/main/java/com/brunomnsilva/smartgraph/example/City.java b/src/main/java/com/brunomnsilva/smartgraph/example/City.java index fc94c00..2c36780 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/example/City.java +++ b/src/main/java/com/brunomnsilva/smartgraph/example/City.java @@ -25,6 +25,8 @@ package com.brunomnsilva.smartgraph.example; import com.brunomnsilva.smartgraph.graphview.SmartLabelSource; +import com.brunomnsilva.smartgraph.graphview.SmartRadiusSource; +import com.brunomnsilva.smartgraph.graphview.SmartShapeTypeSource; /** * A simple class to represent a city in an example usage of the library. @@ -53,7 +55,6 @@ public void setName(String name) { this.name = name; } - public float getPopulation() { return population; } @@ -67,5 +68,33 @@ public String toString() { return "City{" + "name=" + name + ", population=" + population + '}'; } - + @SmartShapeTypeSource + public String modelShape() { + if(this.name == "Tokyo") { + return "star"; + } + + return "circle"; + } + + @SmartRadiusSource + public Double modelRadius() { + return convertToLogScale(Double.valueOf(String.valueOf(this.population))); + } + + private static double convertToLogScale(double value) { + // Define input range + double minValue = 1; + double maxValue = 40; + + // Define output range + double minOutputValue = 15; + double maxOutputValue = 40; + + // Map the input value to the output range using logarithmic function + double mappedValue = (Math.log(value) - Math.log(minValue)) / (Math.log(maxValue) - Math.log(minValue)); + + // Map the mapped value to the output range + return minOutputValue + mappedValue * (maxOutputValue - minOutputValue); + } } diff --git a/src/main/java/com/brunomnsilva/smartgraph/example/ExampleMain.java b/src/main/java/com/brunomnsilva/smartgraph/example/ExampleMain.java index 6694fd7..35fe6da 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/example/ExampleMain.java +++ b/src/main/java/com/brunomnsilva/smartgraph/example/ExampleMain.java @@ -25,12 +25,13 @@ package com.brunomnsilva.smartgraph.example; import com.brunomnsilva.smartgraph.containers.SmartGraphDemoContainer; -import com.brunomnsilva.smartgraph.graph.Graph; -import com.brunomnsilva.smartgraph.graph.GraphEdgeList; +import com.brunomnsilva.smartgraph.graph.Digraph; +import com.brunomnsilva.smartgraph.graph.DigraphEdgeList; import com.brunomnsilva.smartgraph.graph.Vertex; import com.brunomnsilva.smartgraph.graphview.SmartCircularSortedPlacementStrategy; import com.brunomnsilva.smartgraph.graphview.SmartGraphPanel; import com.brunomnsilva.smartgraph.graphview.SmartGraphProperties; +import com.brunomnsilva.smartgraph.graphview.SmartGraphVertex; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -45,7 +46,7 @@ public class ExampleMain extends Application { @Override public void start(Stage ignored) { - Graph distances = new GraphEdgeList<>(); + Digraph distances = new DigraphEdgeList<>(); Vertex prague = distances.insertVertex(new City("Prague", 1.3f)); Vertex tokyo = distances.insertVertex(new City("Tokyo", 37.5f)); @@ -64,7 +65,7 @@ public void start(Stage ignored) { distances.insertEdge(beijing, london, new Distance(8132)); /* Only Java 15 allows for multi-line strings. */ - String customProps = "edge.label = true" + "\n" + "edge.arrow = false"; + String customProps = "edge.label = true" + "\n" + "edge.arrow = true"; SmartGraphProperties properties = new SmartGraphProperties(customProps); @@ -92,10 +93,15 @@ public void start(Stage ignored) { graphView.setVertexPosition(helsinky, 924, 100); graphView.setVertexPosition(london, 200, 668); graphView.setVertexPosition(prague, 824, 668); - graphView.setVertexPosition(tokyo, 512, 300); + graphView.setVertexPosition(tokyo, 512, 200); graphView.setVertexPosition(newYork, 512, 400); - graphView.getStylableLabel(tokyo).setStyle("-fx-stroke: red; -fx-fill: red;"); + graphView.getStylableLabel(tokyo).setStyleInline("-fx-stroke: green; -fx-fill: green;"); + + graphView.setVertexDoubleClickAction((SmartGraphVertex graphVertex) -> { + graphVertex.addStyleClass("myVertex"); + }); + } public static void main(String[] args) { diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ForceDirectedSpringSystemLayoutStrategy.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ForceDirectedSpringSystemLayoutStrategy.java index 01876c1..50fa55c 100755 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/ForceDirectedSpringSystemLayoutStrategy.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ForceDirectedSpringSystemLayoutStrategy.java @@ -97,7 +97,7 @@ protected Point2D computeForceBetween(SmartGraphVertexNode v, SmartGraphVerte Point2D vPosition = v.getUpdatedPosition(); Point2D wPosition = w.getUpdatedPosition(); - double distance = vPosition.distance(wPosition); + double distance = vPosition.distance(wPosition) - (v.getRadius() + w.getRadius()); Point2D forceDirection = wPosition.subtract(vPosition).normalize(); if (distance < 1) { diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeCircle.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeCircle.java new file mode 100755 index 0000000..3fb2e08 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeCircle.java @@ -0,0 +1,88 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +import javafx.beans.property.DoubleProperty; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Shape; + +/** + * This class represents a circle shape with a specified radius. + * + * @author brunomnsilva + */ +public class ShapeCircle implements ShapeWithRadius { + + private final Circle surrogate; + + /** + * Creates a circle shape. + * @param x the x-center coordinate + * @param y the y-center coordinate + * @param radius the radius of the circle + */ + public ShapeCircle(double x, double y, double radius) { + Args.requireNonNegative(x, "x"); + Args.requireNonNegative(y, "y"); + Args.requireNonNegative(radius, "radius"); + + this.surrogate = new Circle(x, y, radius); + } + + @Override + public Shape getShape() { + return surrogate; + } + + @Override + public DoubleProperty centerXProperty() { + return surrogate.centerXProperty(); + } + + @Override + public DoubleProperty centerYProperty() { + return surrogate.centerYProperty(); + } + + @Override + public DoubleProperty radiusProperty() { + return surrogate.radiusProperty(); + } + + @Override + public double getRadius() { + return surrogate.getRadius(); + } + + @Override + public void setRadius(double radius) { + Args.requireNonNegative(radius, "radius"); + + // Only update if different + if(Double.compare(this.getRadius(), radius) != 0) { + surrogate.setRadius(radius); + } + } +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeFactory.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeFactory.java new file mode 100755 index 0000000..0e98992 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeFactory.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +/** + * A factory class for creating instances of shapes with a specified center coordinates and radius. + * + * @author brunomnsilva + */ +public class ShapeFactory { + + /** + * Creates a new instance of a shape with the specified type, center coordinates, and radius. + * + * @param type The type of shape to create. Supported types are "star", "circle", "triangle", + * "square", "pentagon", "hexagon", "heptagon", "octagon", "nonagon", "decagon", + * "hendecagon", and "dodecagon". + * @param x The center X coordinate of the shape. + * @param y The center Y coordinate of the shape. + * @param radius The radius of the shape. + * @return An instance of a shape with the specified parameters. + * @throws IllegalArgumentException If the provided type is not recognized or if the center coordinates + * or radius are negative. + */ + public static ShapeWithRadius create(String type, double x, double y, double radius) { + Args.requireNonNegative(x, "x"); + Args.requireNonNegative(y, "y"); + Args.requireNonNegative(radius, "radius"); + + type = type.trim().toLowerCase(); + + switch(type) { + case "star": return new ShapeStar(x, y, radius); + case "circle": return new ShapeCircle(x, y, radius); + case "triangle": return new ShapeRegularPolygon(x, y, radius, 3); + case "square": return new ShapeRegularPolygon(x, y, radius, 4); + case "pentagon": return new ShapeRegularPolygon(x, y, radius, 5); + case "hexagon": return new ShapeRegularPolygon(x, y, radius, 6); + case "heptagon": return new ShapeRegularPolygon(x, y, radius, 7); + case "octagon": return new ShapeRegularPolygon(x, y, radius, 8); + case "nonagon": return new ShapeRegularPolygon(x, y, radius, 9); + case "decagon": return new ShapeRegularPolygon(x, y, radius, 10); + case "hendecagon": return new ShapeRegularPolygon(x, y, radius, 11); + case "dodecagon": return new ShapeRegularPolygon(x, y, radius, 12); + + default: throw new IllegalArgumentException("Invalid shape type. See javadoc for available shapes."); + } + } +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeRegularPolygon.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeRegularPolygon.java new file mode 100755 index 0000000..cd03232 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeRegularPolygon.java @@ -0,0 +1,131 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.scene.shape.Polygon; +import javafx.scene.shape.Shape; + +/** + * This class represents a regular polygon shape with a specified number of sides and radius. + * + * @author brunomnsilva + */ +public class ShapeRegularPolygon implements ShapeWithRadius { + + protected final int numberSides; + protected final DoubleProperty centerX, centerY; + protected final DoubleProperty radius; + + protected final Polygon surrogate; + + /** + * Creates a regular polygon shape with numberSides + * @param x the x-center coordinate + * @param y the y-center coordinate + * @param radius the radius of the enclosed circle + * @param numberSides the number of sides of the polygon + */ + public ShapeRegularPolygon(double x, double y, double radius, int numberSides) { + Args.requireNonNegative(x, "x"); + Args.requireNonNegative(y, "y"); + Args.requireNonNegative(radius, "radius"); + Args.requireGreaterThan(numberSides, "numberSides", 2); + + this.surrogate = new Polygon(); + + this.numberSides = numberSides; + + this.centerX = new SimpleDoubleProperty(x); + this.centerY = new SimpleDoubleProperty(y); + + this.centerX.addListener((observable, oldValue, newValue) -> updatePolygon()); + this.centerY.addListener((observable, oldValue, newValue) -> updatePolygon()); + + this.radius = new SimpleDoubleProperty( radius ); + this.radius.addListener((observable, oldValue, newValue) -> updatePolygon()); + + updatePolygon(); + } + + protected void updatePolygon() { + surrogate.getPoints().clear(); + + double cx = centerX.doubleValue(); + double cy = centerY.doubleValue(); + + double startAngle = Math.PI / (numberSides % 2 == 0 ? numberSides : 2); + + double radius = getRadius(); + + for (int i = 0; i < numberSides; i++) { + double angle = startAngle + 2 * Math.PI * i / numberSides; + double px = cx - radius * Math.cos(angle); + double py = cy - radius * Math.sin(angle); + surrogate.getPoints().addAll(px, py); + } + } + + /** + * Returns the number of sides of the polygon. + * @return the number of sides of the polygon + */ + public int getNumberSides() { + return numberSides; + } + + @Override + public Shape getShape() { + return this.surrogate; + } + + @Override + public DoubleProperty centerXProperty() { + return this.centerX; + } + + @Override + public DoubleProperty centerYProperty() { + return this.centerY; + } + + @Override + public DoubleProperty radiusProperty() { + return this.radius; + } + + @Override + public double getRadius() { + return this.radius.doubleValue(); + } + + @Override + public void setRadius(double radius) { + Args.requireNonNegative(radius, "radius"); + + this.radius.set(radius); + } +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeStar.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeStar.java new file mode 100755 index 0000000..182cabe --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeStar.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +/** + * This class represents a five-point star shape inscribed within a specified radius. + * + * @author brunomnsilva + */ +public class ShapeStar extends ShapeRegularPolygon { + + /** + * Creates a new star shape enclosed in a circle of radius. + * @param x the x-center coordinate + * @param y the y-center coordinate + * @param radius the radius of the enclosed circle + */ + public ShapeStar(double x, double y, double radius) { + super(x, y, radius, 10); + } + + @Override + protected void updatePolygon() { + surrogate.getPoints().clear(); + + double cx = centerX.doubleValue(); + double cy = centerY.doubleValue(); + + double startAngle = Math.PI / 2; + + double radius = getRadius(); + double innerRadius = radius / 2; + + for (int i = 0; i < numberSides; i++) { + double angle = startAngle + 2 * Math.PI * i / numberSides; + + double radiusToggle = (i % 2 == 0 ? radius : innerRadius); + double px = cx - radiusToggle * Math.cos(angle); + double py = cy - radiusToggle * Math.sin(angle); + surrogate.getPoints().addAll(px, py); + } + } +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeWithRadius.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeWithRadius.java new file mode 100755 index 0000000..1eb3a04 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/ShapeWithRadius.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +import javafx.beans.property.DoubleProperty; +import javafx.scene.shape.Shape; + +/** + * This interface represents a shape with a radius, providing methods to access and modify + * properties related to the center coordinates and radius of the shape. + * + * @param The type of the concrete underlying shape. + * + * @author brunomnsilva + */ +public interface ShapeWithRadius { + + /** + * Returns the shape instance associated with this object. + * + * @return The shape instance. + */ + Shape getShape(); + + /** + * Returns the property representing the center X coordinate of the shape. + * + * @return The property representing the center X coordinate. + */ + DoubleProperty centerXProperty(); + + /** + * Returns the property representing the center Y coordinate of the shape. + * + * @return The property representing the center Y coordinate. + */ + DoubleProperty centerYProperty(); + + /** + * Returns the property representing the radius of the shape. + * + * @return The property representing the radius of the shape. + */ + DoubleProperty radiusProperty(); + + /** + * Returns the radius of the shape. + * + * @return The radius of the shape. + */ + double getRadius(); + + /** + * Sets the radius of the shape to the specified value. + * + * @param radius The new radius value. + */ + void setRadius(double radius); +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartArrow.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartArrow.java index 6be79d4..2370438 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartArrow.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartArrow.java @@ -55,6 +55,11 @@ public SmartArrow(double size) { styleProxy.addStyleClass("arrow"); } + @Override + public void setStyleInline(String css) { + styleProxy.setStyleInline(css); + } + @Override public void setStyleClass(String cssClass) { styleProxy.setStyleClass(cssClass); diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeCurve.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeCurve.java index 6e6a789..7f26aea 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeCurve.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeCurve.java @@ -24,6 +24,7 @@ package com.brunomnsilva.smartgraph.graphview; import com.brunomnsilva.smartgraph.graph.Edge; +import javafx.beans.binding.Bindings; import javafx.geometry.Point2D; import javafx.scene.shape.CubicCurve; import javafx.scene.transform.Rotate; @@ -68,10 +69,25 @@ public class SmartGraphEdgeCurve extends CubicCurve implements SmartGraphE /* Styling proxy */ private final SmartStyleProxy styleProxy; + /** + * Constructs a SmartGraphEdgeCurve representing a curved edge between two SmartGraphVertexNodes. + * + * @param edge the edge associated with this curve + * @param inbound the inbound SmartGraphVertexNode + * @param outbound the outbound SmartGraphVertexNode + */ public SmartGraphEdgeCurve(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { this(edge, inbound, outbound, 0); } + /** + * Constructs a SmartGraphEdgeCurve representing an edge curve between two SmartGraphVertexNodes. + * + * @param edge the edge associated with this curve + * @param inbound the inbound SmartGraphVertexNode + * @param outbound the outbound SmartGraphVertexNode + * @param edgeIndex the edge index (>=0) + */ public SmartGraphEdgeCurve(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound, int edgeIndex) { this.inbound = inbound; this.outbound = outbound; @@ -190,8 +206,9 @@ private void enableListeners() { @Override public void attachLabel(SmartLabel label) { this.attachedLabel = label; - label.xProperty().bind(controlX1Property().add(controlX2Property()).divide(2).subtract(label.getLayoutBounds().getWidth() / 2)); - label.yProperty().bind(controlY1Property().add(controlY2Property()).divide(2).add(label.getLayoutBounds().getHeight() / 2)); + + label.xProperty().bind(controlX1Property().add(controlX2Property()).divide(2).subtract(Bindings.divide(label.layoutWidthProperty(),2))); + label.yProperty().bind(controlY1Property().add(controlY2Property()).divide(2).add(Bindings.divide(label.layoutHeightProperty(), 2))); } @Override @@ -224,7 +241,9 @@ public void attachArrow(SmartArrow arrow) { arrow.getTransforms().add(rotation); /* add translation transform to put the arrow touching the circle's bounds */ - Translate t = new Translate(-inbound.getRadius(), 0); + Translate t = new Translate(0, 0); + t.xProperty().bind( inbound.radiusProperty().negate() ); + arrow.getTransforms().add(t); } diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeLine.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeLine.java index dd10825..31db5ee 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeLine.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphEdgeLine.java @@ -24,6 +24,7 @@ package com.brunomnsilva.smartgraph.graphview; import com.brunomnsilva.smartgraph.graph.Edge; +import javafx.beans.binding.Bindings; import javafx.scene.shape.Line; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; @@ -49,7 +50,14 @@ public class SmartGraphEdgeLine extends Line implements SmartGraphEdgeBase /* Styling proxy */ private final SmartStyleProxy styleProxy; - + + /** + * Constructs a SmartGraphEdgeLine representing an edge between two SmartGraphVertexNodes. + * + * @param edge the edge associated with this line + * @param inbound the inbound SmartGraphVertexNode + * @param outbound the outbound SmartGraphVertexNode + */ public SmartGraphEdgeLine(Edge edge, SmartGraphVertexNode inbound, SmartGraphVertexNode outbound) { if( inbound == null || outbound == null) { throw new IllegalArgumentException("Cannot connect null vertices."); @@ -94,8 +102,9 @@ public boolean removeStyleClass(String cssClass) { @Override public void attachLabel(SmartLabel label) { this.attachedLabel = label; - label.xProperty().bind(startXProperty().add(endXProperty()).divide(2).subtract(label.getLayoutBounds().getWidth() / 2)); - label.yProperty().bind(startYProperty().add(endYProperty()).divide(2).add(label.getLayoutBounds().getHeight() / 1.5)); + + label.xProperty().bind(startXProperty().add(endXProperty()).divide(2).subtract(Bindings.divide(label.layoutWidthProperty(), 2))); + label.yProperty().bind(startYProperty().add(endYProperty()).divide(2).add(Bindings.divide(label.layoutHeightProperty(), 1.5))); } @Override @@ -130,7 +139,9 @@ public void attachArrow(SmartArrow arrow) { arrow.getTransforms().add(rotation); /* add translation transform to put the arrow touching the circle's bounds */ - Translate t = new Translate(-inbound.getRadius(), 0); + Translate t = new Translate(0, 0); + t.xProperty().bind( inbound.radiusProperty().negate() ); + arrow.getTransforms().add(t); } diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphPanel.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphPanel.java index eb3bd98..c4962c5 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphPanel.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphPanel.java @@ -100,9 +100,22 @@ public class SmartGraphPanel extends Pane { private Consumer> vertexClickConsumer = null; private Consumer> edgeClickConsumer = null; + /* + OPTIONAL PROVIDERS FOR LABELS, RADII AND SHAPE TYPES OF NODES. + THESE HAVE PRIORITY OVER ANY MODEL ANNOTATIONS (E.G., SmartLabelSource) + */ + private SmartLabelProvider vertexLabelProvider; + private SmartLabelProvider edgeLabelProvider; + private SmartRadiusProvider vertexRadiusProvider; + private SmartShapeTypeProvider vertexShapeTypeProvider; + /* AUTOMATIC LAYOUT RELATED ATTRIBUTES */ + + /** + * Property to toggle the automatic layout of nodes. + */ public final BooleanProperty automaticLayoutProperty; private final AnimationTimer timer; private ForceDirectedLayoutStrategy automaticLayoutStrategy; @@ -398,7 +411,7 @@ public void update() { } //this will be called from a non-javafx thread, so this must be guaranteed to run of the graphics thread - Platform.runLater(() -> updateNodes()); + Platform.runLater(() -> updateViewModel()); } /** @@ -422,7 +435,7 @@ public void updateAndWait() { } final FutureTask update = new FutureTask<>(() -> { - updateNodes(); + updateViewModel(); return true; }); @@ -438,15 +451,15 @@ public void updateAndWait() { Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); } } else { - updateNodes(); + updateViewModel(); } } - private synchronized void updateNodes() { + private synchronized void updateViewModel() { removeNodes(); insertNodes(); - updateLabels(); + updateNodes(); } /* @@ -470,15 +483,69 @@ public void setEdgeDoubleClickAction(Consumer> action) { this.edgeClickConsumer = action; } - /* - NODES CREATION/UPDATES + /* PROVIDERS */ + + /** + * Sets the vertex label provider for this SmartGraphPanel. + *
+ * The label provider has priority over any other method of obtaining the same values, such as annotations. + *
+ * To remove the provider, call this method with a null argument. + * + * @param labelProvider the label provider to set + */ + public void setVertexLabelProvider(SmartLabelProvider labelProvider) { + this.vertexLabelProvider = labelProvider; + } + + /** + * Sets the edge label provider for this SmartGraphPanel. + *
+ * The label provider has priority over any other method of obtaining the same values, such as annotations. + *
+ * To remove the provider, call this method with a null argument. + * + * @param labelProvider the label provider to set */ + public void setEdgeLabelProvider(SmartLabelProvider labelProvider) { + this.edgeLabelProvider = labelProvider; + } + + /** + * Sets the radius provider for this SmartGraphPanel. + *
+ * The radius provider has priority over any other method of obtaining the same values, such as annotations. + *
+ * To remove the provider, call this method with a null argument. + * + * @param vertexRadiusProvider the radius provider to set + */ + public void setVertexRadiusProvider(SmartRadiusProvider vertexRadiusProvider) { + this.vertexRadiusProvider = vertexRadiusProvider; + } + + /** + * Sets the shape type provider for this SmartGraphPanel. + *
+ * The shape type provider has priority over any other method of obtaining the same values, such as annotations. + *
+ * To remove the provider, call this method with a null argument. + * + * @param vertexShapeTypeProvider the shape type provider to set + */ + public void setVertexShapeTypeProvider(SmartShapeTypeProvider vertexShapeTypeProvider) { + this.vertexShapeTypeProvider = vertexShapeTypeProvider; + } + + /* + NODES CREATION/UPDATES + */ private void initNodes() { /* create vertex graphical representations */ for (Vertex vertex : listOfVertices()) { - SmartGraphVertexNode vertexAnchor = new SmartGraphVertexNode<>(vertex, 0, 0, - graphProperties.getVertexRadius(), graphProperties.getVertexAllowUserMove()); + + SmartGraphVertexNode vertexAnchor = createVertex(vertex, 0, 0); vertexNodes.put(vertex, vertexAnchor); } @@ -531,6 +598,16 @@ private void initNodes() { } } + private SmartGraphVertexNode createVertex(Vertex v, double x, double y) { + // Read shape type from annotation or use default (circle) + String shapeType = inferVertexShapeType(v.element()); + + // Read shape radius from annotation or use default + double shapeRadius = inferVertexShapeRadius(v.element()); + + return new SmartGraphVertexNode<>(v, x, y, shapeRadius, shapeType, graphProperties.getVertexAllowUserMove()); + } + private SmartGraphEdgeBase createEdge(Edge edge, SmartGraphVertexNode graphVertexInbound, SmartGraphVertexNode graphVertexOutbound) { /* Even if edges are later removed, the corresponding index remains the same. Otherwise, we would have to @@ -559,7 +636,7 @@ private SmartGraphEdgeBase createEdge(Edge edge, SmartGraphVertexNode private void addVertex(SmartGraphVertexNode v) { this.getChildren().add(v); - String labelText = generateVertexLabel(v.getUnderlyingVertex().element()); + String labelText = inferVertexLabel(v.getUnderlyingVertex().element()); if (graphProperties.getUseVertexTooltip()) { Tooltip t = new Tooltip(labelText); @@ -580,7 +657,7 @@ private void addEdge(SmartGraphEdgeBase e, Edge edge) { this.getChildren().add(0, (Node) e); edgeNodes.put(edge, e); - String labelText = generateEdgeLabel(edge.element()); + String labelText = inferEdgeLabel(edge.element()); if (graphProperties.getUseEdgeTooltip()) { Tooltip t = new Tooltip(labelText); @@ -642,8 +719,7 @@ The opposite vertex exists in the (di)graph, but we have not yet } } - SmartGraphVertexNode newVertex = new SmartGraphVertexNode<>(vertex, - x, y, graphProperties.getVertexRadius(), graphProperties.getVertexAllowUserMove()); + SmartGraphVertexNode newVertex = createVertex(vertex, x, y); //track new nodes newVertices.add(newVertex); @@ -757,17 +833,24 @@ private void removeVertex(SmartGraphVertexNode v) { /** * Updates node's labels */ - private void updateLabels() { + private void updateNodes() { theGraph.vertices().forEach((v) -> { SmartGraphVertexNode vertexNode = vertexNodes.get(v); if (vertexNode != null) { SmartLabel label = vertexNode.getAttachedLabel(); if(label != null) { - String text = generateVertexLabel(v.element()); - label.setText( text ); + String text = inferVertexLabel(v.element()); + label.setText_( text ); } } + + double radius = inferVertexShapeRadius(v.element()); + vertexNode.setRadius(radius); + + String shapeType = inferVertexShapeType(v.element()); + vertexNode.setShapeType(shapeType); + }); theGraph.edges().forEach((e) -> { @@ -775,21 +858,27 @@ private void updateLabels() { if (edgeNode != null) { SmartLabel label = edgeNode.getAttachedLabel(); if (label != null) { - String text = generateEdgeLabel(e.element()); - label.setText( text ); + String text = inferEdgeLabel(e.element()); + label.setText_( text ); } } }); } - private String generateVertexLabel(V vertex) { + private String inferVertexLabel(V vertexElement) { + + if(vertexElement == null) return ""; + + if(vertexLabelProvider != null) { + return vertexLabelProvider.valueFor(vertexElement); + } try { - Class clazz = vertex.getClass(); + Class clazz = vertexElement.getClass(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(SmartLabelSource.class)) { method.setAccessible(true); - Object value = method.invoke(vertex); + Object value = method.invoke(vertexElement); return value.toString(); } } @@ -797,17 +886,23 @@ private String generateVertexLabel(V vertex) { Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); } - return vertex != null ? vertex.toString() : ""; + return vertexElement.toString(); } - private String generateEdgeLabel(E edge) { + private String inferEdgeLabel(E edgeElement) { + + if(edgeElement == null) return ""; + + if(edgeLabelProvider != null) { + return edgeLabelProvider.valueFor(edgeElement); + } try { - Class clazz = edge.getClass(); + Class clazz = edgeElement.getClass(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(SmartLabelSource.class)) { method.setAccessible(true); - Object value = method.invoke(edge); + Object value = method.invoke(edgeElement); return value.toString(); } } @@ -815,7 +910,55 @@ private String generateEdgeLabel(E edge) { Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); } - return edge != null ? edge.toString() : ""; + return edgeElement.toString(); + } + + private String inferVertexShapeType(V vertexElement) { + + if(vertexElement == null) return graphProperties.getVertexShape(); + + if(vertexShapeTypeProvider != null) { + return vertexShapeTypeProvider.valueFor(vertexElement); + } + + try { + Class clazz = vertexElement.getClass(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(SmartShapeTypeSource.class)) { + method.setAccessible(true); + Object value = method.invoke(vertexElement); + return value.toString(); + } + } + } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); + } + + return graphProperties.getVertexShape(); + } + + private double inferVertexShapeRadius(V vertexElement) { + + if(vertexElement == null) return graphProperties.getVertexRadius(); + + if(vertexRadiusProvider != null) { + return vertexRadiusProvider.valueFor(vertexElement); + } + + try { + Class clazz = vertexElement.getClass(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(SmartRadiusSource.class)) { + method.setAccessible(true); + Object value = method.invoke(vertexElement); + return Double.valueOf(value.toString()); + } + } + } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + Logger.getLogger(SmartGraphPanel.class.getName()).log(Level.SEVERE, null, ex); + } + + return graphProperties.getVertexRadius(); } /** diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphProperties.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphProperties.java index 7c7a4ee..d46b258 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphProperties.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphProperties.java @@ -49,6 +49,9 @@ public class SmartGraphProperties { private static final double DEFAULT_VERTEX_RADIUS = 15; private static final String PROPERTY_VERTEX_RADIUS = "vertex.radius"; + private static final String DEFAULT_VERTEX_SHAPE = "circle"; + private static final String PROPERTY_VERTEX_SHAPE = "vertex.shape"; + private static final boolean DEFAULT_VERTEX_USE_TOOLTIP = true; private static final String PROPERTY_VERTEX_USE_TOOLTIP = "vertex.tooltip"; @@ -107,7 +110,11 @@ public SmartGraphProperties(InputStream inputStream) { Logger.getLogger(SmartGraphProperties.class.getName()).log(Level.WARNING, msg); } } - + + /** + * Reads properties from the desired string. + * @param content string from where to read the properties + */ public SmartGraphProperties(String content) { properties = new Properties(); try { @@ -137,11 +144,22 @@ public boolean getVertexAllowUserMove() { public double getVertexRadius() { return getDoubleProperty(PROPERTY_VERTEX_RADIUS, DEFAULT_VERTEX_RADIUS); } - + + /** + * Returns a property that indicates the shape of each vertex. + * + * @return corresponding property value + */ + public String getVertexShape() { + return getStringProperty(PROPERTY_VERTEX_SHAPE, DEFAULT_VERTEX_SHAPE); + } + /** * Returns a property that indicates the repulsion force to use in the * automatic force-based layout. - * + * + * @deprecated since version 1.1 + * * @return corresponding property value */ public double getRepulsionForce() { @@ -151,7 +169,9 @@ public double getRepulsionForce() { /** * Returns a property that indicates the attraction force to use in the * automatic force-based layout. - * + * + * @deprecated since version 1.1 + * * @return corresponding property value */ public double getAttractionForce() { @@ -161,7 +181,9 @@ public double getAttractionForce() { /** * Returns a property that indicates the attraction scale to use in the * automatic force-based layout. - * + * + * @deprecated since version 1.1 + * * @return corresponding property value */ public double getAttractionScale() { @@ -247,10 +269,18 @@ private boolean getBooleanProperty(String propertyName, boolean defaultValue) { } } - + private String getStringProperty(String propertyName, String defaultValue) { + return properties.getProperty(propertyName, defaultValue); + } + + /** + * Test program. + * @param args not used + */ public static void main(String[] args) { SmartGraphProperties props = new SmartGraphProperties(); + System.out.println("Prop vertex radius: " + props.getVertexRadius()); - System.out.println("Prop vertex use label: " + props.getUseVertexLabel()); + System.out.println("Prop vertex shape: " + props.getVertexShape()); } } diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphVertexNode.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphVertexNode.java index 39a0890..9a9fc1a 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphVertexNode.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartGraphVertexNode.java @@ -24,8 +24,13 @@ package com.brunomnsilva.smartgraph.graphview; import com.brunomnsilva.smartgraph.graph.Vertex; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Point2D; import javafx.scene.Cursor; +import javafx.scene.Group; import javafx.scene.input.MouseEvent; import javafx.scene.shape.Circle; @@ -48,24 +53,36 @@ * * @author brunomnsilva */ -public class SmartGraphVertexNode extends Circle implements SmartGraphVertex, SmartLabelledNode { +public class SmartGraphVertexNode extends Group implements SmartGraphVertex, SmartLabelledNode { private final Vertex underlyingVertex; + private boolean isDragging; + /* Critical for performance, so we don't rely on the efficiency of the Graph.areAdjacent method */ private final Set> adjacentVertices; - private SmartLabel attachedLabel = null; - private boolean isDragging = false; - /* Automatic layout functionality members */ private final PointVector forceVector = new PointVector(0, 0); private final PointVector updatedPosition = new PointVector(0, 0); + private final DoubleProperty centerX; + private final DoubleProperty centerY; + private final DoubleProperty radius; /* Styling proxy */ private final SmartStyleProxy styleProxy; + private SmartLabel attachedLabel; + + /* Shape proxy and related properties used to represent the underlying vertex. + * We will allow to change the shape at runtime, but other elements (e.g., lines/arrows) + * when created will bind to the vertex's location and radius values. + * Hence, we need separate properties here to be bound; later we'll bind and unbind these + * to the concrete shape being used. + */ + private ShapeWithRadius shapeProxy; + private String shapeProxyName; /** * Constructor which sets the instance attributes. @@ -73,25 +90,151 @@ public class SmartGraphVertexNode extends Circle implements SmartGraphVertex< * @param v the underlying vertex * @param x initial x position on the parent pane * @param y initial y position on the parent pane - * @param radius radius of this vertex representation, i.e., a circle + * @param radius radius of this vertex representation + * @param shapeType type of the shape to represent this vertex, see {@link ShapeFactory} * @param allowMove should the vertex be draggable with the mouse + * @throws IllegalArgumentException if shapeType is invalid or if x or y or + * radius are negative. */ - public SmartGraphVertexNode(Vertex v, double x, double y, double radius, boolean allowMove) { - super(x, y, radius); - + public SmartGraphVertexNode(Vertex v, double x, double y, double radius, String shapeType, boolean allowMove) { this.underlyingVertex = v; + this.adjacentVertices = new HashSet<>(); + this.attachedLabel = null; this.isDragging = false; - this.adjacentVertices = new HashSet<>(); + /* Shape proxy */ + this.centerX = new SimpleDoubleProperty(); + this.centerY = new SimpleDoubleProperty(); + this.radius = new SimpleDoubleProperty(); - styleProxy = new SmartStyleProxy(this); + this.shapeProxy = ShapeFactory.create(shapeType, x, y, radius); + this.shapeProxyName = shapeType; + + bindShapeProperties(this.shapeProxy); + + this.getChildren().add(this.shapeProxy.getShape()); + + /* Styling proxy */ + styleProxy = new SmartStyleProxy(this.shapeProxy.getShape()); styleProxy.addStyleClass("vertex"); + /* Enable dragging */ if (allowMove) { enableDrag(); } } + + /** + * Returns the x-coordinate of the center of this node. + * + * @return the x-coordinate of the center of this node + */ + public double getCenterX() { + return centerX.doubleValue(); + } + + /** + * Sets the x-coordinate of the center of this node. + * + * @param x the x-coordinate of the center of this node + */ + public void setCenterX(double x) { + centerX.set(x); + } + + /** + * Returns the y-coordinate of the center of this node. + * + * @return the y-coordinate of the center of this node + */ + public double getCenterY() { + return centerY.doubleValue(); + } + + /** + * Sets the y-coordinate of the center of this node. + * + * @param y the y-coordinate of the center of this node + */ + public void setCenterY(double y) { + centerY.set(y); + } + + /** + * Returns the property representing the x-coordinate of the center of this node. + * + * @return the property representing the x-coordinate of the center of this node + */ + public DoubleProperty centerXProperty() { + return centerX; + } + + /** + * Returns the property representing the y-coordinate of the center of this node. + * + * @return the property representing the y-coordinate of the center of this node + */ + public DoubleProperty centerYProperty() { + return centerY; + } + + /** + * Returns the property representing the radius of this node. + * + * @return the property representing the radius of this node + */ + public ReadOnlyDoubleProperty radiusProperty() { + return radius; + } + + /** + * Returns the radius of this node. + * + * @return the radius of this node + */ + public double getRadius() { + return radius.doubleValue(); + } + + /** + * Sets the radius of this node. + * Since it is a bound value, the value will only be updated if it has changed. + * + * @param radius the new radius of this node + */ + public void setRadius(double radius) { + if (Double.compare(getRadius(), radius) != 0) { + this.radius.set(radius); + } + } + + /** + * Changes the shape used to represent this node. + *
+ * When "swapping" shapes, the new shape will retain the positioning, radius and styling of the previous shape. + * + * @param shapeType the shape type name. See {@link ShapeFactory}. + */ + public void setShapeType(String shapeType) { + // If the shape is the same, no need to change it + if(shapeProxyName.compareToIgnoreCase(shapeType) == 0) return; + + ShapeWithRadius newShapeProxy = ShapeFactory.create(shapeType, getCenterX(), getCenterY(), getRadius()); + // Shape correctly instantiated, i.e., 'shapeType' is valid, proceed... + + // Style copying and proxy set + SmartStyleProxy.copyStyling(this.shapeProxy.getShape(), newShapeProxy.getShape()); + styleProxy.setClient(newShapeProxy.getShape()); + + this.shapeProxy = newShapeProxy; + this.shapeProxyName = shapeType; + + bindShapeProperties(newShapeProxy); + + this.getChildren().clear(); + this.getChildren().add(newShapeProxy.getShape()); + } /** * Adds a vertex to the internal list of adjacent vertices. @@ -149,6 +292,15 @@ public Point2D getPosition() { return new Point2D(getCenterX(), getCenterY()); } + /** + * Sets the position of the instance in pixels. + * + * @param p coordinates + */ + public void setPosition(Point2D p) { + setPosition(p.getX(), p.getY()); + } + /** * Sets the position of the instance in pixels. * @@ -164,7 +316,7 @@ public void setPosition(double x, double y) { setCenterX(x); setCenterY(y); } - + @Override public double getPositionCenterX() { return getCenterX(); @@ -175,16 +327,6 @@ public double getPositionCenterY() { return getCenterY(); } - - /** - * Sets the position of the instance in pixels. - * - * @param p coordinates - */ - public void setPosition(Point2D p) { - setPosition(p.getX(), p.getY()); - } - /** * Resets the current computed external force vector. * @@ -254,6 +396,49 @@ public void moveFromForces() { setPosition(updatedPosition.x, updatedPosition.y); } + @Override + public void attachLabel(SmartLabel label) { + this.attachedLabel = label; + + label.xProperty().bind(centerXProperty().subtract(Bindings.divide( label.layoutWidthProperty(), 2.0))); + label.yProperty().bind(centerYProperty().add(Bindings.add( shapeProxy.radiusProperty(), label.layoutHeightProperty()))); + } + + @Override + public SmartLabel getAttachedLabel() { + return attachedLabel; + } + + @Override + public Vertex getUnderlyingVertex() { + return underlyingVertex; + } + + @Override + public void setStyleInline(String css) { + styleProxy.setStyleInline(css); + } + + @Override + public void setStyleClass(String cssClass) { + styleProxy.setStyleClass(cssClass); + } + + @Override + public void addStyleClass(String cssClass) { + styleProxy.addStyleClass(cssClass); + } + + @Override + public boolean removeStyleClass(String cssClass) { + return styleProxy.removeStyleClass(cssClass); + } + + @Override + public SmartStylableNode getStylableLabel() { + return this.attachedLabel; + } + /** * Make a node movable by dragging it around with the mouse primary button. */ @@ -309,6 +494,9 @@ private void enableDrag() { }); } + /* + * Limit a value within an interval. + */ private double boundCenterCoordinate(double value, double min, double max) { double radius = getRadius(); @@ -321,45 +509,28 @@ private double boundCenterCoordinate(double value, double min, double max) { } } - @Override - public void attachLabel(SmartLabel label) { - this.attachedLabel = label; - label.xProperty().bind(centerXProperty().subtract(label.getLayoutBounds().getWidth() / 2.0)); - label.yProperty().bind(centerYProperty().add(getRadius() + label.getLayoutBounds().getHeight())); - } - - @Override - public SmartLabel getAttachedLabel() { - return attachedLabel; - } - - @Override - public Vertex getUnderlyingVertex() { - return underlyingVertex; - } - - - @Override - public void setStyleClass(String cssClass) { - styleProxy.setStyleClass(cssClass); - } + /* + * (re)Bind properties of the exposed properties and the underlying shape. + */ + private void bindShapeProperties(ShapeWithRadius shape) { + if( this.shapeProxy != null && this.centerX.isBound() ) { + this.centerX.unbindBidirectional(this.shapeProxy.centerXProperty()); + } - @Override - public void addStyleClass(String cssClass) { - styleProxy.addStyleClass(cssClass); - } + if( this.shapeProxy != null && this.centerY.isBound() ) { + this.centerY.unbindBidirectional(this.shapeProxy.centerYProperty()); + } - @Override - public boolean removeStyleClass(String cssClass) { - return styleProxy.removeStyleClass(cssClass); - } + if( this.shapeProxy != null && this.radius.isBound() ) { + this.radius.unbindBidirectional(this.shapeProxy.radiusProperty()); + } - @Override - public SmartStylableNode getStylableLabel() { - return this.attachedLabel; + this.centerX.bindBidirectional(shape.centerXProperty()); + this.centerY.bindBidirectional(shape.centerYProperty()); + this.radius.bindBidirectional(shape.radiusProperty()); } - /** + /* * Internal representation of a 2D point or vector for quick access to its * attributes. */ diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabel.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabel.java index 0e31cb9..24848e2 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabel.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabel.java @@ -23,6 +23,9 @@ */ package com.brunomnsilva.smartgraph.graphview; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.text.Text; /** @@ -37,6 +40,10 @@ public class SmartLabel extends Text implements SmartStylableNode { private final SmartStyleProxy styleProxy; + private final DoubleProperty layoutWidth; + private final DoubleProperty layoutHeight; + + /** * Default constructor. * @param text the text of the SmartLabel. @@ -54,8 +61,56 @@ public SmartLabel(String text) { public SmartLabel(double x, double y, String text) { super(x, y, text); styleProxy = new SmartStyleProxy(this); + + this.layoutWidth = new SimpleDoubleProperty( ); + this.layoutHeight = new SimpleDoubleProperty( ); + + layoutBoundsProperty().addListener((observableValue, oldValue, newValue) -> { + if(newValue != null) { + if(Double.compare(layoutWidth.doubleValue(), newValue.getWidth()) != 0) { + layoutWidth.set(newValue.getWidth()); + } + if(Double.compare(layoutHeight.doubleValue(), newValue.getHeight()) != 0) { + layoutHeight.set(newValue.getHeight()); + } + } + }); } - + + + /** + * Returns the read-only property representing the layout width of this label. + * + * @return the read-only property representing the layout width + */ + public ReadOnlyDoubleProperty layoutWidthProperty() { + return layoutWidth; + } + + /** + * Returns the read-only property representing the layout height of this label. + * + * @return the read-only property representing the layout height + */ + public ReadOnlyDoubleProperty layoutHeightProperty() { + return layoutHeight; + } + + /** + * Use instead of {@link #setText(String)} to allow for correct layout adjustments and label placement. + * @param text the text to display on the label + */ + public void setText_(String text) { + if(getText().compareTo(text) != 0) { + setText(text); + } + } + + @Override + public void setStyleInline(String css) { + styleProxy.setStyleInline(css); + } + @Override public void setStyleClass(String cssClass) { styleProxy.setStyleClass(cssClass); @@ -70,5 +125,5 @@ public void addStyleClass(String cssClass) { public boolean removeStyleClass(String cssClass) { return styleProxy.removeStyleClass(cssClass); } - + } diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelProvider.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelProvider.java new file mode 100755 index 0000000..b293020 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelProvider.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +/** + * A provider interface for generating labels. + * + * @param the type of the elements for which labels are generated + */ +public interface SmartLabelProvider { + + /** + * Returns the label for the specified element. + *
+ * The returned value is expected to be non-null. + * + * @param element the element for which the label is generated + * @return the label for the specified element + */ + String valueFor(T element); +} \ No newline at end of file diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelSource.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelSource.java index 0199168..f373314 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelSource.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartLabelSource.java @@ -42,7 +42,6 @@ * * @author brunomnsilva */ - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface SmartLabelSource { diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusProvider.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusProvider.java new file mode 100755 index 0000000..eb31ce4 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusProvider.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +/** + * A provider interface for generating radii values. + * + * @param the type of the elements for which radii are generated + */ +public interface SmartRadiusProvider { + + /** + * Returns the radius for the specified element. + *
+ * The returned value is expected to be positive. + * + * @param vertexElement the element for which the radius is generated + * @return the radius for the specified element + */ + double valueFor(T vertexElement); +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusSource.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusSource.java new file mode 100755 index 0000000..e5ab133 --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartRadiusSource.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2023-2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Method annotation to override an element's vertex shape radius. + *
+ * The annotated method must return a value (Double) with a non-negative value, otherwise an exception will be thrown. + *
+ * By default, the shape radius is defined in "smartgraph.properties" or in a custom properties file. + *
+ * If multiple annotations exist, the behavior is undefined. + * + * @author brunomnsilva + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SmartRadiusSource { + +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeProvider.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeProvider.java new file mode 100755 index 0000000..b93ccfb --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeProvider.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +/** + * A provider interface for generating shape types. + * + * @param the type of the elements for which shape types are generated + */ +public interface SmartShapeTypeProvider { + /** + * Returns the shape type for the specified element. + *
+ * The returned value is expected to be non-null and a valid type, see {@link ShapeFactory}. + * + * @param vertexElement the element for which the shape type is generated + * @return the shape type for the specified element + */ + String valueFor(T vertexElement); +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeSource.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeSource.java new file mode 100755 index 0000000..f60b97f --- /dev/null +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartShapeTypeSource.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * JavaFXSmartGraph | Copyright 2023-2024 brunomnsilva@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.brunomnsilva.smartgraph.graphview; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Method annotation to override an element's vertex shape type representation. + *
+ * The annotated method must return a value (String) with a valid type (see {@link ShapeFactory}), otherwise an exception will be thrown. + *
+ * By default, a circle is used for the vertex shape representation. + *
+ * If multiple annotations exist, the behavior is undefined. + * + * @author brunomnsilva + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SmartShapeTypeSource { + +} diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStylableNode.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStylableNode.java index 9f89f77..3d2e0c2 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStylableNode.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStylableNode.java @@ -43,11 +43,11 @@ public interface SmartStylableNode { * But will be discarded if you use {@link SmartStylableNode#setStyleClass(java.lang.String) } *
* If you need to clear any previously set inline styles, use - * .setStyle(null) + * .setStyleInline(null) * * @param css styles */ - void setStyle(String css); + void setStyleInline(String css); /** * Applies the CSS styling defined in class selector cssClass. diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStyleProxy.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStyleProxy.java index df2cf15..915922e 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStyleProxy.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/SmartStyleProxy.java @@ -38,14 +38,26 @@ */ public class SmartStyleProxy implements SmartStylableNode { - private final Shape client; - + private Shape client; + + /** + * Creates a new style proxy for a shape client. + * @param client the shape client + */ public SmartStyleProxy(Shape client) { this.client = client; } + + /** + * Changes the shape client of this proxy. + * @param client the new shape client + */ + public void setClient(Shape client) { + this.client = client; + } @Override - public void setStyle(String css) { + public void setStyleInline(String css) { client.setStyle(css); } @@ -65,5 +77,14 @@ public void addStyleClass(String cssClass) { public boolean removeStyleClass(String cssClass) { return client.getStyleClass().remove(cssClass); } - + + /** + * Copies all the styles and classes (currently applied) of source to destination. + * @param source the shape whose styles are to be copied + * @param destination the shape that receives the copied styles + */ + public static void copyStyling(Shape source, Shape destination) { + destination.setStyle(source.getStyle()); + destination.getStyleClass().addAll(source.getStyleClass()); + } } diff --git a/src/main/java/com/brunomnsilva/smartgraph/graphview/UtilitiesJavaFX.java b/src/main/java/com/brunomnsilva/smartgraph/graphview/UtilitiesJavaFX.java index 6cb73ba..06ec017 100644 --- a/src/main/java/com/brunomnsilva/smartgraph/graphview/UtilitiesJavaFX.java +++ b/src/main/java/com/brunomnsilva/smartgraph/graphview/UtilitiesJavaFX.java @@ -58,7 +58,7 @@ public static Node pick(Node node, double sceneX, double sceneY) { // at this point we know that _at least_ the given node is a valid // answer to the given point, so we will return that if we don't find // a better child option - if (node instanceof Parent) { + if (node instanceof Parent && !(node instanceof SmartGraphVertexNode)) { // we iterate through all children in reverse order, and stop when we find a match. // We do this as we know the elements at the end of the list have a higher // z-order, and are therefore the better match, compared to children that diff --git a/version.properties b/version.properties index e6dff0d..6940089 100644 --- a/version.properties +++ b/version.properties @@ -22,4 +22,4 @@ # THE SOFTWARE. # -project.version=1.1.1 +project.version=2.0.0-rc1