JavaFX drag thin line on chart

233 Views Asked by At

I have a horizontal line drawn on a chart. When hovering over it the cursor changes to CursorType.S_RESIZE. That indicates the user can start to drag. As the line is very thin you have to place the cursor very accurate. For a better user experience I would like to add a margin above and below the line to enter the draggable zone easier.

Is there a way to make the line “thicker” so the setOnMouseMoved() event fires already when approaching?

import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class DragLine extends Application {

    public void start(Stage stage) {

        ChartWithLine chartWithLine = new ChartWithLine(new NumberAxis(), new NumberAxis());

        stage.setScene(new Scene(chartWithLine, 500, 400));
        stage.show();
    }

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

class ChartWithLine<X, Y> extends LineChart {

    public ChartWithLine(Axis axis, Axis axis2) {
        super(axis, axis2);

        line = new Line();
        line.setOnMouseMoved(event -> line.setCursor(Cursor.S_RESIZE));
        getPlotChildren().add(line);
    }

    private Line line;

    public void layoutPlotChildren() {
        super.layoutPlotChildren();
        double yPos = getYAxis().getDisplayPosition(55);
        line.setStartX(0);
        line.setEndX(getBoundsInLocal().getWidth());
        line.setStartY(yPos);
        line.setEndY(yPos);
    }
}
1

There are 1 best solutions below

1
JTfx On

Here is my work around. I am plotting a second, thicker line at the same position where I plot the thin line. The thicker line is set to transparent so it is not visible. The dragging functionality is set to the thicker line. When it is dragged, both lines are plotted to the dragged location. This solves the issue that it is hard to grab a thin line with the mouse. But I do not really like it for two reasons. First there is a second line I do not need at all actually. And second if you hover over the middle of the thicker line where the thin visible line is located, the mouse changes back to not draggable cursor. I would have to implement dragability for the thinner line now too to avoid this. But this is pretty much overkill.

Again, is there some kind of way to set the line thicker without making it look thicker?


import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class DragLine extends Application {

    public void start(Stage stage) {

        ChartWithLine chartWithLine = new ChartWithLine(new NumberAxis(), new NumberAxis());
        stage.setScene(new Scene(chartWithLine, 500, 400));
        stage.show();
    }

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

//ChartWithLine
class ChartWithLine<X, Y> extends LineChart {

    public ChartWithLine(Axis axis, Axis axis2) {
        super(axis, axis2);
        draggableLine = new DraggableLine(this);
        getPlotChildren().addAll(draggableLine.lineToShow, draggableLine.lineToDrag);
    }

    private DraggableLine draggableLine;

    public void layoutPlotChildren() {

        super.layoutPlotChildren();
        updateLinePlot();
    }

    //updateLinePlot called when line was dragged
    public void updateLinePlot() {

        //mouse position after drag
        double yPos = draggableLine.mousePosY;

        System.out.println("Line dragged to: " + getYAxis().getValueForDisplay(yPos));

        //plot lines accordingly to new mouse position
        Line line = draggableLine.lineToDrag;
        line.setStartX(0);
        line.setEndX(getBoundsInLocal().getWidth());
        line.setStartY(yPos);
        line.setEndY(yPos);

        line = draggableLine.lineToShow;
        line.setStartX(0);
        line.setEndX(getBoundsInLocal().getWidth());
        line.setStartY(yPos);
        line.setEndY(yPos);

    }
}

//DraggableLine
class DraggableLine {

    public DraggableLine(ChartWithLine chart) {

        this.chart = chart;

        //lineToShow is thin line plotted visible on chart
        lineToShow = new Line();

        //lineToDrag is plotted at same position on chart as thin visible line.
        lineToDrag = new Line();

        //set transparent to make it not visible
        lineToDrag.setStyle("-fx-stroke: transparent;");

        //set line to drag stroke width very broad so it is easy to grab
        lineToDrag.setStrokeWidth(20.0);


        lineToDrag.setOnMouseMoved(this::mouseOver);
        lineToDrag.setOnMouseDragged(event -> onMouseDragged(event.getY()));
        lineToDrag.setOnMousePressed(this::onMousePressed);
        lineToDrag.setOnMouseReleased(event -> onMouseReleased());

    }

    private ChartWithLine chart;
    public Line lineToShow;
    public Line lineToDrag;
    boolean isDragging = false;
    public double mousePosY = 55;


    //change cursor
    protected void mouseOver(MouseEvent event) {

        if (isDragZone(event)) {
            lineToDrag.setCursor(Cursor.S_RESIZE);
        } else {
            lineToDrag.setCursor(Cursor.DEFAULT);
        }
    }

    //mouse pressed over draggable zone
    void onMousePressed(MouseEvent event) {
        if (isDragZone(event))
            isDragging = true;
    }

    //mouse released
    void onMouseReleased() {
        isDragging = false;
    }

    //change values when mouse is dragging
    void onMouseDragged(double y) {

        if (!isDragging) return;
        mousePosY = y;
        chart.updateLinePlot();
    }

    //check if mouse is in draggable zone
    protected boolean isDragZone(MouseEvent event) {
        return event.getY() > (lineToDrag.getStartY()) || event.getY() < (lineToDrag.getStartY());
    }
}