2424package com .brunomnsilva .smartgraph .graphview ;
2525
2626import com .brunomnsilva .smartgraph .graph .Edge ;
27- import javafx .beans .value .ObservableValue ;
2827import javafx .geometry .Point2D ;
2928import javafx .scene .shape .CubicCurve ;
3029import javafx .scene .transform .Rotate ;
5150 */
5251public class SmartGraphEdgeCurve <E , V > extends CubicCurve implements SmartGraphEdgeBase <E , V > {
5352
54- private static final double MAX_EDGE_CURVE_ANGLE = 20 ;
53+ private static final double MAX_EDGE_CURVE_ANGLE = 45 ;
54+ private static final double MIN_EDGE_CURVE_ANGLE = 3 ;
55+ public static final int DISTANCE_THRESHOLD = 400 ;
56+ public static final int LOOP_RADIUS_FACTOR = 4 ;
5557
5658 private final Edge <E , V > underlyingEdge ;
5759
@@ -61,7 +63,7 @@ public class SmartGraphEdgeCurve<E, V> extends CubicCurve implements SmartGraphE
6163 private SmartLabel attachedLabel = null ;
6264 private SmartArrow attachedArrow = null ;
6365
64- private double randomAngleFactor = 0 ;
66+ private double randomAngleFactor ;
6567
6668 /* Styling proxy */
6769 private final SmartStyleProxy styleProxy ;
@@ -112,63 +114,70 @@ private void update() {
112114 /* Make a loop using the control points proportional to the vertex radius */
113115
114116 //TODO: take into account several "self-loops" with randomAngleFactor
115- double midpointX1 = outbound .getCenterX () - inbound .getRadius () * 5 ;
116- double midpointY1 = outbound .getCenterY () - inbound .getRadius () * 2 ;
117+ double midpointX1 = outbound .getCenterX () - inbound .getRadius () * LOOP_RADIUS_FACTOR ;
118+ double midpointY1 = outbound .getCenterY () - inbound .getRadius () * LOOP_RADIUS_FACTOR ;
117119
118- double midpointX2 = outbound .getCenterX () + inbound .getRadius () * 5 ;
119- double midpointY2 = outbound .getCenterY () - inbound .getRadius () * 2 ;
120+ double midpointX2 = outbound .getCenterX () + inbound .getRadius () * LOOP_RADIUS_FACTOR ;
121+ double midpointY2 = outbound .getCenterY () - inbound .getRadius () * LOOP_RADIUS_FACTOR ;
120122
121123 setControlX1 (midpointX1 );
122124 setControlY1 (midpointY1 );
123125 setControlX2 (midpointX2 );
124126 setControlY2 (midpointY2 );
125127
126128 } else {
127- /* Make a curved edge. The curve is proportional to the distance */
128- double midpointX = (outbound .getCenterX () + inbound .getCenterX ()) / 2 ;
129- double midpointY = (outbound .getCenterY () + inbound .getCenterY ()) / 2 ;
130-
131- Point2D midpoint = new Point2D (midpointX , midpointY );
129+ /* Make a curved edge. The curvature is bounded and proportional to the distance;
130+ higher curvature for closer vertices */
132131
133132 Point2D startpoint = new Point2D (inbound .getCenterX (), inbound .getCenterY ());
134133 Point2D endpoint = new Point2D (outbound .getCenterX (), outbound .getCenterY ());
135134
136- //TODO: improvement lower max_angle_placement according to distance between vertices
137- double angle = MAX_EDGE_CURVE_ANGLE ;
138-
139135 double distance = startpoint .distance (endpoint );
140136
141- //TODO: remove "magic number" 1500 and provide a distance function for the
142- //decreasing angle with distance
143- angle = angle - (distance / 1500 * angle );
137+ double angle = linearDecay (MAX_EDGE_CURVE_ANGLE , MIN_EDGE_CURVE_ANGLE , distance , DISTANCE_THRESHOLD );
144138
145- midpoint = UtilitiesPoint2D .rotate (midpoint ,
146- startpoint ,
147- (-angle ) + randomAngleFactor * (angle - (-angle )));
139+ Point2D midpoint = UtilitiesPoint2D .calculateTriangleBetween (startpoint , endpoint ,
140+ (-angle ) + randomAngleFactor * 2 * angle );
148141
149142 setControlX1 (midpoint .getX ());
150143 setControlY1 (midpoint .getY ());
151144 setControlX2 (midpoint .getX ());
152145 setControlY2 (midpoint .getY ());
153146 }
147+ }
154148
149+ /**
150+ * Provides the decreasing linear function decay.
151+ * @param initialValue initial value
152+ * @param finalValue maximum value
153+ * @param distance current distance
154+ * @param distanceThreshold distance threshold (maximum distance -> maximum value)
155+ * @return the decay function value for <code>distance</code>
156+ */
157+ private static double linearDecay (double initialValue , double finalValue , double distance , double distanceThreshold ) {
158+ //Args.requireNonNegative(distance, "distance");
159+ //Args.requireNonNegative(distanceThreshold, "distanceThreshold");
160+ // Parameters are internally guaranteed to be positive. We avoid two method calls.
161+
162+ if (distance >= distanceThreshold ) return finalValue ;
163+
164+ return initialValue + (finalValue - initialValue ) * distance / distanceThreshold ;
155165 }
156166
157- /*
158- With a curved edge we need to continuously update the control points.
159- TODO: Maybe we can achieve this solely with bindings.
160- */
161167 private void enableListeners () {
162- this .startXProperty ().addListener ((ObservableValue <? extends Number > ov , Number t , Number t1 ) -> {
168+ // With a curved edge we need to continuously update the control points.
169+ // TODO: Maybe we can achieve this solely with bindings? Maybe there's no performance gain in doing so.
170+
171+ this .startXProperty ().addListener ((ov , oldValue , newValue ) -> {
163172 update ();
164173 });
165- this .startYProperty ().addListener ((ObservableValue <? extends Number > ov , Number t , Number t1 ) -> {
174+ this .startYProperty ().addListener ((ov , oldValue , newValue ) -> {
166175 update ();
167176 });
168- this .endXProperty ().addListener ((ObservableValue <? extends Number > ov , Number t , Number t1 ) -> {
177+ this .endXProperty ().addListener ((ov , oldValue , newValue ) -> {
169178 update ();
170179 });
171- this .endYProperty ().addListener ((ObservableValue <? extends Number > ov , Number t , Number t1 ) -> {
180+ this .endYProperty ().addListener ((ov , oldValue , newValue ) -> {
172181 update ();
173182 });
174183 }
0 commit comments