JavaFx 2 - 绘制线重叠轴

时间:2013-12-11 08:34:54

标签: javafx-2 line linechart

单击鼠标左键然后移动鼠标,在此折线图上绘制一条线,并在轴上绘制。

我想在图表上绘制线条,使其不与x轴或y轴重叠。如何做到这一点?

import javafx.application.Application;  
import javafx.event.EventHandler;  
import javafx.geometry.Side;  
import javafx.scene.Group;  
import javafx.scene.Scene;  
import javafx.scene.chart.CategoryAxis;  
import javafx.scene.chart.LineChart;  
import javafx.scene.chart.NumberAxis;  
import javafx.scene.chart.XYChart;  
import javafx.scene.control.Label;  
import javafx.scene.input.MouseEvent;  
import javafx.scene.layout.BorderPane;  
import javafx.scene.layout.Pane;  
import javafx.scene.paint.Color;  
import javafx.scene.shape.Line;  
import javafx.scene.shape.LineTo;  
import javafx.scene.shape.MoveTo;  
import javafx.scene.shape.Path;  
import javafx.stage.Stage;  
public class LinesEdit extends Application {  
Path path;  

public static void main(String[] args) {  
    launch(args);  
}  
@Override  
public void start(Stage stage) {  
    final CategoryAxis xAxis = new CategoryAxis();  
    final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);  
    yAxis.setTickUnit(1);  
    yAxis.setPrefWidth(35);  
    yAxis.setMinorTickCount(10);  
    yAxis.setSide(Side.RIGHT);  
    yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {  
        @Override  
        public String toString(Number object) {  
            String label;  
            label = String.format("%7.2f", object.floatValue());  
            return label;  
        }  
    });  

final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);  
    lineChart.setCreateSymbols(false);  
    lineChart.setAlternativeRowFillVisible(false);  
    lineChart.setLegendVisible(false);  
    XYChart.Series series1 = new XYChart.Series();  
    series1.getData().add(new XYChart.Data("Jan", 1));  
    series1.getData().add(new XYChart.Data("Feb", 4));  
    series1.getData().add(new XYChart.Data("Mar", 2.5));  
    series1.getData().add(new XYChart.Data("Apr", 5));  
    series1.getData().add(new XYChart.Data("May", 6));  
    series1.getData().add(new XYChart.Data("Jun", 8));  
    series1.getData().add(new XYChart.Data("Jul", 12));  
    series1.getData().add(new XYChart.Data("Aug", 8));  
    series1.getData().add(new XYChart.Data("Sep", 11));  
    series1.getData().add(new XYChart.Data("Oct", 13));  
    series1.getData().add(new XYChart.Data("Nov", 10));  
    series1.getData().add(new XYChart.Data("Dec", 20));  

BorderPane bp = new BorderPane();  
    bp.setCenter(lineChart);  
    Scene scene = new Scene(bp, 800, 600);  
    lineChart.setAnimated(false);  
    lineChart.getData().addAll(series1);  

    LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler( bp );  
    bp.setOnMouseClicked( mh );  
    bp.setOnMouseMoved( mh );  
    stage.setScene(scene);  
    path = new Path();  
    path.setStrokeWidth(1);  
    path.setStroke(Color.BLACK);  
    scene.setOnMouseDragged(mh);  
    scene.setOnMousePressed(mh);  
    bp.getChildren().add(path);  
    stage.setScene(scene);  
    stage.show();  
}      

class MouseHandler implements EventHandler< MouseEvent > {  
private boolean gotFirst    = false;  
private Line    line;  
private Pane    pane;  
private double  x1, y1, x2, y2;  
private LineHandler lineHandler;  

public MouseHandler( Pane pane ) {  
    this.pane = pane;  
    lineHandler = new LineHandler(pane);  
}  

class LineHandler implements EventHandler< MouseEvent > {  
double  x, y;  
Pane pane;  
public LineHandler(Pane pane){  
    this.pane = pane;  
}  
@Override  
public void handle( MouseEvent e ) {  
    Line l = (Line) e.getSource();  
    // remove line on right click  
    if( e.getEventType() == MouseEvent.MOUSE_PRESSED  
            && e.isSecondaryButtonDown() ) {  
        pane.getChildren().remove( l );  
    } else if( e.getEventType() == MouseEvent.MOUSE_DRAGGED  
            && e.isPrimaryButtonDown() ) {  
        double tx = e.getX();  
        double ty = e.getY();  
        double dx = tx - x;  
        double dy = ty - y;  
        l.setStartX( l.getStartX() + dx );  
        l.setStartY( l.getStartY() + dy );  
        l.setEndX( l.getEndX() + dx );  
        l.setEndY( l.getEndY() + dy );  
        x = tx;  
        y = ty;  
    } else if( e.getEventType() == MouseEvent.MOUSE_ENTERED ) {  
        // just to show that the line is selected  
        x = e.getX();  
        y = e.getY();  
        l.setStroke( Color.RED );  
    } else if( e.getEventType() == MouseEvent.MOUSE_EXITED ) {  
        l.setStroke( Color.BLACK );  
    }  
    // should not pass event to the parent  
    e.consume();  
}  
}     
@Override  
public void handle( MouseEvent event ) {  
    if( event.getEventType() == MouseEvent.MOUSE_CLICKED ) {  
        if( !gotFirst ) {  
            x1 = x2 = event.getX();  
            y1 = y2 = event.getY();  
            line = new Line( x1, y1, x2, y2 );  
            pane.getChildren().add( line );  
            gotFirst = true;  
        }   
        else {  
            line.setOnMouseEntered( lineHandler );  
            line.setOnMouseExited( lineHandler );  
            line.setOnMouseDragged( lineHandler );  
            line.setOnMousePressed( lineHandler );  
            // to consume the event  
            line.setOnMouseClicked( lineHandler );  
            line.setOnMouseReleased( lineHandler );  
            line = null;  
            gotFirst = false;  
        }  
    }   
        else {  
            if( line != null ) {  
                x2 = event.getX();  
                y2 = event.getY();  
                // update line  
                line.setEndX( x2 );  
                line.setEndY( y2 );  
        }  
     }  
  }  
  }  
  }  

1 个答案:

答案 0 :(得分:2)

首先要知道的是,许多JavaFX UI-Element图表都包含许多基础子元素。您想要绘制的部分实际上是Region,它是Pane的一部分LineChart的一部分。我真的建议您使用ScenicView,因为它会准确显示您的场景图(包括内置UI组件)的外观。

回到你的问题:你的听众应该只适用于显示数据实际表示的RegionRegion正好在x和y轴的位置结束。以下代码将为您提供Region并使其成为您的侦听器的目标:

//your previous code in start()...
    Pane p = (Pane) lineChart.getChildrenUnmodifiable().get(1);
    Region r = (Region) p.getChildren().get(0);
    LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler(r);

    r.setOnMouseClicked(mh);
    r.setOnMouseMoved(mh);
    stage.setScene(scene);

    path = new Path();
    path.setStrokeWidth(1);
    path.setStroke(Color.BLACK);
    r.setOnMouseDragged(mh);
    r.setOnMousePressed(mh);
    bp.getChildren().add(path);
    stage.setScene(scene);
 //the following code.....
<德尔> 接下来的步骤是:  1.重写你的处理程序方法,以便它们接受一个`Region`对象作为参数  2.重写一下你的行设置代码。背景:您不能将对象添加到“Region”中,因为您无法访问子级的可写“List”。所以你必须将这些行放入保存`Region`的`Pane`对象中。因为每个JavaFX UI-Element都有自己的坐标系,所以你必须计算`Pane`和`Region`之间的偏移量,因为`Pane`有点大。如果不这样做,您的线将被略微高于鼠标指针。你可以通过调用`xyz.getBoundsInLocal()。getWidth()/ height()`来获得`Pane`和/或`Region`的宽度和高度。

更新:按要求提供完整解决方案

根据评论中的要求,我将展示解决此问题的一种方法。 The Situation in Scenic View在此图片中查看您的GUI。它显示了ScenicView中的所有图形元素。如您所见,Pane大于内部Region。对我们来说重要的是要知道JavaFX中所有坐标系的原点都从元素的左上角开始。在这种情况下,您必须向Pane添加一行,但就Region的边框而言。在我之前展示的代码片段中,我将所有侦听器添加到该区域,这意味着我们在区域的坐标系中获得里面的鼠标坐标。现在我们必须“转换”或更好地“转换”这些坐标(您希望设置线的起点或终点的点)到Pane的坐标系(实际放置线的位置,读取)因为我们希望线条准确地从鼠标所在的位置开始。您可以调用一种方法来获取转换矩阵:r.getLocalToParentTransform()。我们需要这个矩阵,因为我们必须得到应用于Region的x和y平移的精确值(您可以看到Region从{{1}移动了近似10个像素} x和y轴的原点)

我写了一个简单的方法来获取PanesRegion之间的x和y翻译:{{1​​}}。 Pane方法的其余部分保持不变(但是我之前写的更改)。但必须修改getCoordDiff(Region r, Pane p)start()的{​​{1}}方法。

handle()

您可以看到我将转换值添加到行起点和终点的部分。这是必需的,以便该行真正开始和结束鼠标所在的点。我移动了MouseHandler时执行的代码,因为它阻止了用户放置行(因此它不跟随光标)。背景:您的光标现在总是(像素完美)在行的末尾,在您第一次放置它时没有“监听器”。丢失的侦听器会阻止LineHandler - 点击转到public class LinesEdit extends Application { Path path; public double[] getCoordDiff(Region r, Pane p) { //Acquires transformation matrix and returns x and y offset/translation from parent double[] diffs = { r.getLocalToParentTransform().getTx(), r.getLocalToParentTransform().getTy() }; return diffs; } public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { final CategoryAxis xAxis = new CategoryAxis(); final NumberAxis yAxis = new NumberAxis(1, 21, 0.1); yAxis.setTickUnit(1); yAxis.setPrefWidth(35); yAxis.setMinorTickCount(10); yAxis.setSide(Side.RIGHT); yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) { @Override public String toString(Number object) { String label; label = String.format("%7.2f", object.floatValue()); return label; } }); final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis); lineChart.setCreateSymbols(false); lineChart.setAlternativeRowFillVisible(false); lineChart.setLegendVisible(false); XYChart.Series series1 = new XYChart.Series(); series1.getData().add(new XYChart.Data("Jan", 1)); series1.getData().add(new XYChart.Data("Feb", 4)); series1.getData().add(new XYChart.Data("Mar", 2.5)); series1.getData().add(new XYChart.Data("Apr", 5)); series1.getData().add(new XYChart.Data("May", 6)); series1.getData().add(new XYChart.Data("Jun", 8)); series1.getData().add(new XYChart.Data("Jul", 12)); series1.getData().add(new XYChart.Data("Aug", 8)); series1.getData().add(new XYChart.Data("Sep", 11)); series1.getData().add(new XYChart.Data("Oct", 13)); series1.getData().add(new XYChart.Data("Nov", 10)); series1.getData().add(new XYChart.Data("Dec", 20)); BorderPane bp = new BorderPane(); bp.setCenter(lineChart); Scene scene = new Scene(bp, 800, 600); lineChart.setAnimated(false); lineChart.getData().addAll(series1); Pane p = (Pane) lineChart.getChildrenUnmodifiable().get(1); Region r = (Region) p.getChildren().get(0); LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler(r); r.setOnMouseClicked(mh); r.setOnMouseMoved(mh); stage.setScene(scene); path = new Path(); path.setStrokeWidth(1); path.setStroke(Color.BLACK); r.setOnMouseDragged(mh); r.setOnMousePressed(mh); bp.getChildren().add(path); stage.setScene(scene); ScenicView.show(scene); stage.show(); } class MouseHandler implements EventHandler<MouseEvent> { private boolean gotFirst = false; private Line line; private Region reg; private double x1, y1, x2, y2; private LineHandler lineHandler; public MouseHandler(Region reg) { this.reg = reg; lineHandler = new LineHandler(reg); } class LineHandler implements EventHandler<MouseEvent> { double x, y; Region reg; public LineHandler(Region reg) { this.reg = reg; } @Override public void handle(MouseEvent e) { Line l = (Line) e.getSource(); l.setStrokeWidth(3); // remove line on right click if (e.getEventType() == MouseEvent.MOUSE_PRESSED && e.isSecondaryButtonDown()) { ((Pane) reg.getParent()).getChildren().remove(l); } else if (e.getEventType() == MouseEvent.MOUSE_DRAGGED && e.isPrimaryButtonDown()) { double tx = e.getX(); double ty = e.getY(); double dx = tx - x; double dy = ty - y; l.setStartX(l.getStartX() + dx); l.setStartY(l.getStartY() + dy); l.setEndX(l.getEndX() + dx); l.setEndY(l.getEndY() + dy); x = tx; y = ty; } else if (e.getEventType() == MouseEvent.MOUSE_ENTERED) { // just to show that the line is selected x = e.getX(); y = e.getY(); l.setStroke(Color.RED); } else if (e.getEventType() == MouseEvent.MOUSE_EXITED) { l.setStroke(Color.BLACK); } // should not pass event to the parent e.consume(); } } @Override public void handle(MouseEvent event) { if (event.getEventType() == MouseEvent.MOUSE_CLICKED) { double[] diff = getCoordDiff(reg, (Pane) reg.getParent()); if (!gotFirst) { //add translation to start/endcoordinates x1 = x2 = event.getX() + diff[0]; y1 = y2 = event.getY() + diff[1]; line = new Line(x1, y1, x2, y2); line.setStrokeWidth(3); ((Pane) reg.getParent()).getChildren().add(line); gotFirst = true; line.setOnMouseClicked(new EventHandler<Event>() { @Override public void handle(Event arg0) { line.setOnMouseEntered(lineHandler); line.setOnMouseExited(lineHandler); line.setOnMouseDragged(lineHandler); line.setOnMousePressed(lineHandler); // to consume the event line.setOnMouseClicked(lineHandler); line.setOnMouseReleased(lineHandler); line = null; gotFirst = false; } }); } } else { if (line != null) { double[] diff = getCoordDiff(reg, (Pane) reg.getParent()); //add translation to end coordinates x2 = event.getX() + diff[0]; y2 = event.getY() + diff[1]; // update line line.setEndX(x2); line.setEndY(y2); } } } } } 。简而言之:“Click”事件现在总是在线本身完成,这就是为什么我们在最终放置线之前需要一个监听器。

剩余错误:无法在图表的黄线上放置一条线。那是因为点击事件没有在线上触发。我可能会在以后解决这个问题,或者你自己试试。