2014-11-02 12 views
9

Ich erstelle ein Diagramm in JavaFX, das durch gerichtete Kanten verbunden sein soll. Am besten wäre eine bikubische Kurve. Kann jemand die Pfeilspitzen hinzufügen?JavaFX-Linie/Kurve mit Pfeilspitze

Die Pfeilspitzen sollten natürlich abhängig vom Ende der Kurve gedreht werden.

Hier ist ein einfaches Beispiel ohne die Pfeile:

import javafx.application.Application; 
import javafx.scene.Group; 
import javafx.scene.Scene; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.CubicCurve; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 

public class BasicConnection extends Application { 

    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) { 

     Group root = new Group(); 

     // bending curve 
     Rectangle srcRect1 = new Rectangle(100,100,50,50); 
     Rectangle dstRect1 = new Rectangle(300,300,50,50); 

     CubicCurve curve1 = new CubicCurve(125, 150, 125, 200, 325, 200, 325, 300); 
     curve1.setStroke(Color.BLACK); 
     curve1.setStrokeWidth(1); 
     curve1.setFill(null); 

     root.getChildren().addAll(srcRect1, dstRect1, curve1); 

     // steep curve 
     Rectangle srcRect2 = new Rectangle(100,400,50,50); 
     Rectangle dstRect2 = new Rectangle(200,500,50,50); 

     CubicCurve curve2 = new CubicCurve(125, 450, 125, 450, 225, 500, 225, 500); 
     curve2.setStroke(Color.BLACK); 
     curve2.setStrokeWidth(1); 
     curve2.setFill(null); 

     root.getChildren().addAll(srcRect2, dstRect2, curve2); 

     primaryStage.setScene(new Scene(root, 800, 600)); 
     primaryStage.show(); 
    } 
} 

Was ist die beste Praxis? Soll ich ein benutzerdefiniertes Steuerelement erstellen oder zwei Pfeilsteuerelemente pro Kurve hinzufügen und drehen (scheint mir zuviel)? Oder gibt es eine bessere Lösung?

Oder weiß jemand, wie man den Winkel berechnet, mit dem die kubische Kurve endet? Ich habe versucht, einen einfachen kleinen Pfeil zu erstellen und ihn am Ende der Kurve zu platzieren, aber es sieht nicht gut aus, wenn man ihn nicht leicht dreht.

Vielen Dank!

edit: Hier ist eine Lösung, in der ich Josés Mechanismus jewelsea der kubische Kurve Manipulator (CubicCurve JavaFX), falls jemand Nees angewendet werden:

import java.util.ArrayList; 
import java.util.List; 

import javafx.application.Application; 
import javafx.beans.property.DoubleProperty; 
import javafx.event.EventHandler; 
import javafx.geometry.Point2D; 
import javafx.scene.Cursor; 
import javafx.scene.Group; 
import javafx.scene.Scene; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.scene.shape.CubicCurve; 
import javafx.scene.shape.Line; 
import javafx.scene.shape.Polygon; 
import javafx.scene.shape.StrokeLineCap; 
import javafx.scene.shape.StrokeType; 
import javafx.scene.transform.Rotate; 
import javafx.stage.Stage; 

/** 
* Example of how a cubic curve works, drag the anchors around to change the curve. 
* Extended with arrows with the help of José Pereda: https://stackoverflow.com/questions/26702519/javafx-line-curve-with-arrow-head 
* Original code by jewelsea: https://stackoverflow.com/questions/13056795/cubiccurve-javafx 
*/ 
public class CubicCurveManipulatorWithArrows extends Application { 

    List<Arrow> arrows = new ArrayList<Arrow>(); 

    public static class Arrow extends Polygon { 

     public double rotate; 
     public float t; 
     CubicCurve curve; 
     Rotate rz; 

     public Arrow(CubicCurve curve, float t) { 
      super(); 
      this.curve = curve; 
      this.t = t; 
      init(); 
     } 

     public Arrow(CubicCurve curve, float t, double... arg0) { 
      super(arg0); 
      this.curve = curve; 
      this.t = t; 
      init(); 
     } 

     private void init() { 

      setFill(Color.web("#ff0900")); 

      rz = new Rotate(); 
      { 
       rz.setAxis(Rotate.Z_AXIS); 
      } 
      getTransforms().addAll(rz); 

      update(); 
     } 

     public void update() { 
      double size = Math.max(curve.getBoundsInLocal().getWidth(), curve.getBoundsInLocal().getHeight()); 
      double scale = size/4d; 

      Point2D ori = eval(curve, t); 
      Point2D tan = evalDt(curve, t).normalize().multiply(scale); 

      setTranslateX(ori.getX()); 
      setTranslateY(ori.getY()); 

      double angle = Math.atan2(tan.getY(), tan.getX()); 

      angle = Math.toDegrees(angle); 

      // arrow origin is top => apply offset 
      double offset = -90; 
      if(t > 0.5) 
       offset = +90; 

      rz.setAngle(angle + offset); 

     } 

      /** 
      * Evaluate the cubic curve at a parameter 0<=t<=1, returns a Point2D 
      * @param c the CubicCurve 
      * @param t param between 0 and 1 
      * @return a Point2D 
      */ 
      private Point2D eval(CubicCurve c, float t){ 
       Point2D p=new Point2D(Math.pow(1-t,3)*c.getStartX()+ 
         3*t*Math.pow(1-t,2)*c.getControlX1()+ 
         3*(1-t)*t*t*c.getControlX2()+ 
         Math.pow(t, 3)*c.getEndX(), 
         Math.pow(1-t,3)*c.getStartY()+ 
         3*t*Math.pow(1-t, 2)*c.getControlY1()+ 
         3*(1-t)*t*t*c.getControlY2()+ 
         Math.pow(t, 3)*c.getEndY()); 
       return p; 
      } 

      /** 
      * Evaluate the tangent of the cubic curve at a parameter 0<=t<=1, returns a Point2D 
      * @param c the CubicCurve 
      * @param t param between 0 and 1 
      * @return a Point2D 
      */ 
      private Point2D evalDt(CubicCurve c, float t){ 
       Point2D p=new Point2D(-3*Math.pow(1-t,2)*c.getStartX()+ 
         3*(Math.pow(1-t, 2)-2*t*(1-t))*c.getControlX1()+ 
         3*((1-t)*2*t-t*t)*c.getControlX2()+ 
         3*Math.pow(t, 2)*c.getEndX(), 
         -3*Math.pow(1-t,2)*c.getStartY()+ 
         3*(Math.pow(1-t, 2)-2*t*(1-t))*c.getControlY1()+ 
         3*((1-t)*2*t-t*t)*c.getControlY2()+ 
         3*Math.pow(t, 2)*c.getEndY()); 
       return p; 
      } 
    } 



    public static void main(String[] args) throws Exception { launch(args); } 
    @Override public void start(final Stage stage) throws Exception { 
    CubicCurve curve = createStartingCurve(); 

    Line controlLine1 = new BoundLine(curve.controlX1Property(), curve.controlY1Property(), curve.startXProperty(), curve.startYProperty()); 
    Line controlLine2 = new BoundLine(curve.controlX2Property(), curve.controlY2Property(), curve.endXProperty(), curve.endYProperty()); 

    Anchor start = new Anchor(Color.PALEGREEN, curve.startXProperty(), curve.startYProperty()); 
    Anchor control1 = new Anchor(Color.GOLD,  curve.controlX1Property(), curve.controlY1Property()); 
    Anchor control2 = new Anchor(Color.GOLDENROD, curve.controlX2Property(), curve.controlY2Property()); 
    Anchor end  = new Anchor(Color.TOMATO, curve.endXProperty(),  curve.endYProperty()); 

    Group root = new Group(); 
    root.getChildren().addAll(controlLine1, controlLine2, curve, start, control1, control2, end); 

    double[] arrowShape = new double[] { 0,0,10,20,-10,20 }; 

    arrows.add(new Arrow(curve, 0f, arrowShape)); 
    arrows.add(new Arrow(curve, 0.2f, arrowShape)); 
    arrows.add(new Arrow(curve, 0.4f, arrowShape)); 
    arrows.add(new Arrow(curve, 0.6f, arrowShape)); 
    arrows.add(new Arrow(curve, 0.8f, arrowShape)); 
    arrows.add(new Arrow(curve, 1f, arrowShape)); 
    root.getChildren().addAll(arrows); 

    stage.setTitle("Cubic Curve Manipulation Sample"); 
    stage.setScene(new Scene(root, 400, 400, Color.ALICEBLUE)); 
    stage.show(); 
    } 


private CubicCurve createStartingCurve() { 
    CubicCurve curve = new CubicCurve(); 
    curve.setStartX(100); 
    curve.setStartY(100); 
    curve.setControlX1(150); 
    curve.setControlY1(50); 
    curve.setControlX2(250); 
    curve.setControlY2(150); 
    curve.setEndX(300); 
    curve.setEndY(100); 
    curve.setStroke(Color.FORESTGREEN); 
    curve.setStrokeWidth(4); 
    curve.setStrokeLineCap(StrokeLineCap.ROUND); 
    curve.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6)); 
    return curve; 
    } 

    class BoundLine extends Line { 
    BoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) { 
     startXProperty().bind(startX); 
     startYProperty().bind(startY); 
     endXProperty().bind(endX); 
     endYProperty().bind(endY); 
     setStrokeWidth(2); 
     setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5)); 
     setStrokeLineCap(StrokeLineCap.BUTT); 
     getStrokeDashArray().setAll(10.0, 5.0); 
    } 
    } 

    // a draggable anchor displayed around a point. 
    class Anchor extends Circle { 
    Anchor(Color color, DoubleProperty x, DoubleProperty y) { 
     super(x.get(), y.get(), 10); 
     setFill(color.deriveColor(1, 1, 1, 0.5)); 
     setStroke(color); 
     setStrokeWidth(2); 
     setStrokeType(StrokeType.OUTSIDE); 

     x.bind(centerXProperty()); 
     y.bind(centerYProperty()); 
     enableDrag(); 
    } 

    // make a node movable by dragging it around with the mouse. 
    private void enableDrag() { 
     final Delta dragDelta = new Delta(); 
     setOnMousePressed(new EventHandler<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
      // record a delta distance for the drag and drop operation. 
      dragDelta.x = getCenterX() - mouseEvent.getX(); 
      dragDelta.y = getCenterY() - mouseEvent.getY(); 
      getScene().setCursor(Cursor.MOVE); 
     } 
     }); 
     setOnMouseReleased(new EventHandler<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
      getScene().setCursor(Cursor.HAND); 
     } 
     }); 
     setOnMouseDragged(new EventHandler<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
      double newX = mouseEvent.getX() + dragDelta.x; 
      if (newX > 0 && newX < getScene().getWidth()) { 
      setCenterX(newX); 
      } 
      double newY = mouseEvent.getY() + dragDelta.y; 
      if (newY > 0 && newY < getScene().getHeight()) { 
      setCenterY(newY); 
      } 

      // update arrow positions 
      for(Arrow arrow: arrows) { 
       arrow.update(); 
      } 
     } 
     }); 
     setOnMouseEntered(new EventHandler<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
      if (!mouseEvent.isPrimaryButtonDown()) { 
      getScene().setCursor(Cursor.HAND); 
      } 
     } 
     }); 
     setOnMouseExited(new EventHandler<MouseEvent>() { 
     @Override public void handle(MouseEvent mouseEvent) { 
      if (!mouseEvent.isPrimaryButtonDown()) { 
      getScene().setCursor(Cursor.DEFAULT); 
      } 
     } 
     }); 
    } 

    // records relative x and y co-ordinates. 
    private class Delta { double x, y; } 
    } 
} 

enter image description here

Antwort

8

Da Sie bereits mit Formen zu tun sind (Kurven), der beste Ansatz für die Pfeile ist einfach fügen Sie weitere Formen zu der Gruppe, mit Path.

Basierend auf dieser answer, habe ich zwei Methoden hinzugefügt: eine für einen beliebigen Punkt der Kurve bei einem gegebenen Parameter zwischen 0 (Start) und 1 (Ende), eine für die Tangente an die Kurve an diesem Punkt .

Mit diesen Methoden können Sie jetzt einen Pfeil tangential zur Kurve an jedem beliebigen Punkt zeichnen. Und wir nutzen sie zwei erstellen zu Beginn (0) und am Ende (1):

@Override 
public void start(Stage primaryStage) { 

    Group root = new Group(); 

    // bending curve 
    Rectangle srcRect1 = new Rectangle(100,100,50,50); 
    Rectangle dstRect1 = new Rectangle(300,300,50,50); 

    CubicCurve curve1 = new CubicCurve(125, 150, 125, 225, 325, 225, 325, 300); 
    curve1.setStroke(Color.BLACK); 
    curve1.setStrokeWidth(1); 
    curve1.setFill(null); 

    double size=Math.max(curve1.getBoundsInLocal().getWidth(), 
         curve1.getBoundsInLocal().getHeight()); 
    double scale=size/4d; 

    Point2D ori=eval(curve1,0); 
    Point2D tan=evalDt(curve1,0).normalize().multiply(scale); 
    Path arrowIni=new Path(); 
    arrowIni.getElements().add(new MoveTo(ori.getX()+0.2*tan.getX()-0.2*tan.getY(), 
             ori.getY()+0.2*tan.getY()+0.2*tan.getX())); 
    arrowIni.getElements().add(new LineTo(ori.getX(), ori.getY())); 
    arrowIni.getElements().add(new LineTo(ori.getX()+0.2*tan.getX()+0.2*tan.getY(), 
             ori.getY()+0.2*tan.getY()-0.2*tan.getX())); 

    ori=eval(curve1,1); 
    tan=evalDt(curve1,1).normalize().multiply(scale); 
    Path arrowEnd=new Path(); 
    arrowEnd.getElements().add(new MoveTo(ori.getX()-0.2*tan.getX()-0.2*tan.getY(), 
             ori.getY()-0.2*tan.getY()+0.2*tan.getX())); 
    arrowEnd.getElements().add(new LineTo(ori.getX(), ori.getY())); 
    arrowEnd.getElements().add(new LineTo(ori.getX()-0.2*tan.getX()+0.2*tan.getY(), 
             ori.getY()-0.2*tan.getY()-0.2*tan.getX())); 

    root.getChildren().addAll(srcRect1, dstRect1, curve1, arrowIni, arrowEnd); 

    primaryStage.setScene(new Scene(root, 800, 600)); 
    primaryStage.show(); 
} 

/** 
* Evaluate the cubic curve at a parameter 0<=t<=1, returns a Point2D 
* @param c the CubicCurve 
* @param t param between 0 and 1 
* @return a Point2D 
*/ 
private Point2D eval(CubicCurve c, float t){ 
    Point2D p=new Point2D(Math.pow(1-t,3)*c.getStartX()+ 
      3*t*Math.pow(1-t,2)*c.getControlX1()+ 
      3*(1-t)*t*t*c.getControlX2()+ 
      Math.pow(t, 3)*c.getEndX(), 
      Math.pow(1-t,3)*c.getStartY()+ 
      3*t*Math.pow(1-t, 2)*c.getControlY1()+ 
      3*(1-t)*t*t*c.getControlY2()+ 
      Math.pow(t, 3)*c.getEndY()); 
    return p; 
} 

/** 
* Evaluate the tangent of the cubic curve at a parameter 0<=t<=1, returns a Point2D 
* @param c the CubicCurve 
* @param t param between 0 and 1 
* @return a Point2D 
*/ 
private Point2D evalDt(CubicCurve c, float t){ 
    Point2D p=new Point2D(-3*Math.pow(1-t,2)*c.getStartX()+ 
      3*(Math.pow(1-t, 2)-2*t*(1-t))*c.getControlX1()+ 
      3*((1-t)*2*t-t*t)*c.getControlX2()+ 
      3*Math.pow(t, 2)*c.getEndX(), 
      -3*Math.pow(1-t,2)*c.getStartY()+ 
      3*(Math.pow(1-t, 2)-2*t*(1-t))*c.getControlY1()+ 
      3*((1-t)*2*t-t*t)*c.getControlY2()+ 
      3*Math.pow(t, 2)*c.getEndY()); 
    return p; 
} 

Und das ist, wie es aussieht:

CubicCurve with arrows

Wenn Sie den Regler aufdrehen Punkte, werden Sie sehen, dass die Pfeile sind bereits gut orientiert:

CubicCurve curve1 = new CubicCurve(125, 150, 55, 285, 375, 155, 325, 300); 

CubicCurve with arrows

+0

Vielen Dank! Mit Ihrer Lösung habe ich einen schnellen Hack mit einer Form gemacht, siehe Code im ersten Post. Funktioniert super. – Roland

+0

Ich bin froh, Ihnen zu helfen, danke, dass Sie Ihre Lösung freigegeben haben. –

+2

Coole Antwort @ JoséPereda. Beachten Sie, dass Sie dies für diesen speziellen Anwendungsfall etwas vereinfachen können. Sie werten nur die Kurve am Start- und am Endpunkt aus, die sich offensichtlich nur am Anfang und Ende selbst auswertet. Weniger offensichtlich ist die Kurve am Anfang tangential zum Liniensegment zwischen dem Startpunkt und dem ersten Kontrollpunkt, und die Kurve am Ende ist tangential zum Liniensegment zwischen dem Endpunkt und dem letzten Kontrollpunkt. So können Sie 'Point2D tan = new Point2D (startX-control1X, startY-control1Y) .normalize();', usw. Die allgemeinen Formeln werden aber für Menschen nützlich sein. –