0

I want to draw a line with arrow head on it (depicting an arrow shape). I referred to a solution to this from : JavaFX - draw line with arrow (Canvas)

The above solution does not allow to make the arrow drag able, so i wrote a small code to make this drag able

void drawArrow(int x1, int y1, int x2, int y2) {
        gc.clearRect(0, 0,drawingCanvas.getWidth(), drawingCanvas.getHeight());
        gc.setFill(Color.BLACK);
        double dx = x2 - x1, dy = y2 - y1;
        double angle = Math.atan2(dy, dx);
        int len = (int) Math.sqrt(dx * dx + dy * dy);
        Transform transform = Transform.translate(x1, y1);
        transform = transform.createConcatenation(Transform.rotate(Math.toDegrees(angle), 0, 0));
        gc.setTransform(new Affine(transform));
        gc.strokeLine(0, 0, len, 0);

        gc.fillPolygon(new double[]{len, len - ARR_SIZE, len - ARR_SIZE, len}, new double[]{0, -ARR_SIZE, ARR_SIZE, 0},
                4);
    }


        gc=drawingCanvas.getGraphicsContext2D();
        drawingCanvas.setOnMousePressed( e -> {
            startX = prevX = currentX = (int)e.getX();
            startY = prevY = currentY = (int)e.getY();
            dragging = true;
            drawArrow(startX, startY, currentX, currentY);
        });
        drawingCanvas.setOnMouseDragged( e -> { 

              if (!dragging)
                    return;
                currentX = (int)e.getX();
                currentY = (int)e.getY();
             gc.clearRect(0,0,drawingCanvas.getWidth(),drawingCanvas.getHeight());
                if (startX == currentX || startY == currentY)
                    return;
                drawArrow(startX, startY, currentX, currentY);

            prevX = currentX;
            prevY = currentY;
        });
        drawingCanvas.setOnMouseReleased( e -> {
            dragging = false;
             if (startX == currentX || startY == currentY)
                    return;

                drawArrow(startX, startY, currentX, currentY);
                prevX = currentX;
                prevY = currentY;
        });

The problem here is the previous arrows are not getting cleared even after using gc.clearRect(0,0,drawingCanvas.getWidth(),drawingCanvas.getHeight());

This is what is happening : enter image description here

Expected Result : enter image description here

Nirman
  • 103
  • 2
  • 11
  • 1
    Look at [this](https://stackoverflow.com/questions/21396055/moving-shapes-in-javafx-canvas). Jewelsea resets the background color each time the Timeline loops. You should do something similar. – SedJ601 Oct 23 '19 at 14:21
  • 1
    And every time when you need something dragable, you should ask yourself if the Canvas is the right choice anyway. – mipa Oct 23 '19 at 14:30
  • Thankyou @Sedrick, but this solution doesnt work for me. – Nirman Oct 24 '19 at 05:58
  • The only thing that's relevant to you is how the background is cleared by repainting the fill before the redraw of each new circle. – SedJ601 Oct 24 '19 at 12:15
  • @mipa is there any other way/method to do it? – Nirman Oct 24 '19 at 12:36
  • 3
    " Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See: [mre]." – c0der Oct 24 '19 at 14:46
  • 1
    @Nirman Yes, of course. You could have used the scene graph which uses Shapes to represent graphical objects and because they are also Nodes you can attach mouse listeners to them and move them arround without having to clear and repaint things manually. – mipa Oct 24 '19 at 22:54
  • @mipa Thankyou, i will try doing it this way – Nirman Oct 30 '19 at 05:25

1 Answers1

1

Here's my Arrow class. I bind its coordinates to vertices (Buttons) which are draggable.

Dragged Arrows Example GIF

package mycontrols;

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.Group;
import javafx.scene.shape.Polyline;

public class Arrow extends Group {

    private Polyline mainLine = new Polyline();
    private Polyline headA = new Polyline();
    private Polyline headB = new Polyline();
    private SimpleDoubleProperty x1 = new SimpleDoubleProperty();
    private SimpleDoubleProperty y1 = new SimpleDoubleProperty();
    private SimpleDoubleProperty x2 = new SimpleDoubleProperty();
    private SimpleDoubleProperty y2 = new SimpleDoubleProperty();
    private SimpleBooleanProperty headAVisible = new SimpleBooleanProperty(true);
    private SimpleBooleanProperty headBVisible = new SimpleBooleanProperty(true);
    private final double ARROW_SCALER = 20;
    private final double ARROWHEAD_ANGLE = Math.toRadians(20);
    private final double ARROWHEAD_LENGTH = 10;

    public Arrow(double x1, double y1, double x2, double y2){
        this.x1.set(x1);
        this.y1.set(y1);
        this.x2.set(x2);
        this.y2.set(y2);

        getChildren().addAll(mainLine, headA, headB);

        for(SimpleDoubleProperty s : new SimpleDoubleProperty[]{this.x1,this.y1,this.x2,this.y2}){
            s.addListener( (l,o,n) -> update() );
        }

        setUpStyleClassStructure();


        headA.visibleProperty().bind(headAVisible);
        headB.visibleProperty().bind(headBVisible);
        update();
    }

    private void setUpStyleClassStructure() {
        mainLine.getStyleClass().setAll("arrow");
        headA.getStyleClass().setAll("arrow");
        headB.getStyleClass().setAll("arrow");

        headA.getStyleClass().add("arrowhead");
        headB.getStyleClass().add("arrowhead");

        getStyleClass().addListener( (ListChangeListener<? super String>) c -> {
            c.next();
            for(Polyline p : new Polyline[]{mainLine, headA, headB}){
                p.getStyleClass().addAll(c.getAddedSubList());
                p.getStyleClass().removeAll(c.getRemoved());
            }
        });
    }

    private void update() {
        double[] start = scale(x1.get(), y1.get(), x2.get(), y2.get());
        double[] end = scale(x2.get(), y2.get(), x1.get(), y1.get());

        double x1 = start[0];
        double y1 = start[1];
        double x2 = end[0];
        double y2 = end[1];

        mainLine.getPoints().setAll(x1,y1,x2,y2);

        double theta = Math.atan2(y2-y1, x2-x1);

        //arrowhead 1
        double x = x1 + Math.cos(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        double y = y1 + Math.sin(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        headA.getPoints().setAll(x,y,x1,y1);
        x = x1 + Math.cos(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        y = y1 + Math.sin(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        headA.getPoints().addAll(x,y);
        //arrowhead 2
        x = x2 - Math.cos(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        y = y2 - Math.sin(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        headB.getPoints().setAll(x,y,x2,y2);
        x = x2 - Math.cos(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        y = y2 - Math.sin(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
        headB.getPoints().addAll(x,y);
    }

    private double[] scale(double x1, double y1, double x2, double y2){
        double theta = Math.atan2(y2-y1, x2-x1);
        return new double[]{
                x1 + Math.cos(theta) * ARROW_SCALER,
                y1 + Math.sin(theta) * ARROW_SCALER
        };
    }

    //getters and setters
    public double getX1() {
        return x1.get();
    }
    public SimpleDoubleProperty x1Property() {
        return x1;
    }
    public void setX1(double x1) {
        this.x1.set(x1);
    }
    public double getY1() {
        return y1.get();
    }
    public SimpleDoubleProperty y1Property() {
        return y1;
    }
    public void setY1(double y1) {
        this.y1.set(y1);
    }
    public double getX2() {
        return x2.get();
    }
    public SimpleDoubleProperty x2Property() {
        return x2;
    }
    public void setX2(double x2) {
        this.x2.set(x2);
    }
    public double getY2() {
        return y2.get();
    }
    public SimpleDoubleProperty y2Property() {
        return y2;
    }
    public void setY2(double y2) {
        this.y2.set(y2);
    }
    public boolean isHeadAVisible() {
        return headAVisible.get();
    }
    public SimpleBooleanProperty headAVisibleProperty() {
        return headAVisible;
    }
    public void setHeadAVisible(boolean headAVisible) {
        this.headAVisible.set(headAVisible);
    }
    public boolean isHeadBVisible() {
        return headBVisible.get();
    }
    public SimpleBooleanProperty headBVisibleProperty() {
        return headBVisible;
    }
    public void setHeadBVisible(boolean headBVisible) {
        this.headBVisible.set(headBVisible);
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343