带箭头的JavaFX线/曲线 [英] JavaFX line/curve with arrow head

查看:233
本文介绍了带箭头的JavaFX线/曲线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在JavaFX中创建一个图形,它应该通过有向边连接。最好的是双三次曲线。有谁知道如何添加箭头?



箭头当然应该根据曲线的末端旋转。



这是一个没有箭头的简单示例:

  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();

//弯曲曲线
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);

//陡峭曲线
Rectangle srcRect2 = new Rectangle(100,400,50,50);
Rectangle dstRect2 = new Rectangle(200,500,50,50);

CubicCurve curve2 =新的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();
}
}

什么是最佳做法?我应该创建一个自定义控件还是为每条曲线添加2个箭头控件并旋转它们(对我来说似乎有些过分)?或者有更好的解决方案吗?



或者有人知道如何计算三次曲线结束的角度吗?我尝试创建一个简单的小箭头并将其放在曲线的末尾,但是如果你不稍微旋转它看起来不太好。



谢谢非常多!



编辑:这是一个解决方案,我将José的机制应用于jewelsea的立方曲线操纵器(,我添加了两种方法:一种方法可以获得曲线的任意一点给定0(开始)和1(结束)之间的参数,一个用于获取该点曲线的切线。



现在使用这些方法可以绘制箭头切线到任何一点的曲线。我们使用它们在开始(0)和结束(1)创建两个:

  @Override 
public void start(Stage primaryStage){

Group root = new Group();

//弯曲曲线
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());
双倍尺度=尺寸/ 4d;

Point2D ori = eval(curve1,0);
Point2D tan = evalDt(curve1,0).normalize()。multiply(scale);
路径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);
路径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();
}

/ **
*在参数0< = t< = 1时评估三次曲线,返回Point2D
* @param c CubicCurve
* @param t param介于0和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());
返回p;
}

/ **
*在参数0< = t< = 1时评估三次曲线的正切值,返回Point2D
* @param c CubicCurve
* @param t param介于0和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 * tt * 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 * tt * t)* c.getControlY2()+
3 *数学。 pow(t,2)* c.getEndY());
返回p;
}

这就是它的样子:





如果您移动控制点,您将看到箭头已经很好地定向:

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


I'm creating a graph in JavaFX which is supposed to be connected by directed edges. Best would be a bicubic curve. Does anyone know how to do add the arrow heads?

The arrow heads should of course be rotated depending on the end of the curve.

Here's a simple example without the arrows:

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();
    }
}   

What's the best practice? Should I create a custom control or add 2 arrow controls per curve and rotate them (seems overkill to me)? Or is there a better solution?

Or does anyone know how to calculate the angle at which the cubic curve ends? I tried creating a simple small arrow and put it at the end of the curve, but it doesn't look nice if you don't rotate it slightly.

Thank you very much!

edit: Here's a solution in which I applied José's mechanism to jewelsea's cubic curve manipulator (CubicCurve JavaFX) in case someone nees it:

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; }
  }  
}

解决方案

Since you're already dealing with shapes (curves), the best approach for the arrows is just keep adding more shapes to the group, using Path.

Based on this answer, I've added two methods: one for getting any point of the curve at a given parameter between 0 (start) and 1 (end), one for getting the tangent to the curve at that point.

With these methods now you can draw an arrow tangent to the curve at any point. And we use them to create two at the start (0) and at the end (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;
}

And this is what it looks like:

If you move the control points, you'll see that the arrows are already well oriented:

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

这篇关于带箭头的JavaFX线/曲线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆