如何使用JavaFX LineChart添加两条垂直线

如何使用JavaFX LineChart添加两条垂直线,java,javafx,Java,Javafx,我的桌面应用程序有一个启动和停止测试的计时器。在图表上,我想创建两条垂直线来指示开始和停止时间。“使用JavaFX向StackPane添加垂直线”不适用于我的情况,因为我不希望这些线保持在相同的位置,这些线应该在绘图中绘制,而不是在布局中绘制。当用户在图表上缩放时,这些垂直线应与用户缩放的位置相对应。谢谢你给我小费 以下是我创建图表的代码: LineChart<Number, Number> chart = new LineChart<Number, Number>(xA

我的桌面应用程序有一个启动和停止测试的计时器。在图表上,我想创建两条垂直线来指示开始和停止时间。“使用JavaFX向StackPane添加垂直线”不适用于我的情况,因为我不希望这些线保持在相同的位置,这些线应该在绘图中绘制,而不是在布局中绘制。当用户在图表上缩放时,这些垂直线应与用户缩放的位置相对应。谢谢你给我小费

以下是我创建图表的代码:

LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis, dataset);

xAxis.setLabel("time(s)");
yAxis.setLabel("deg/s");
LineChart chart=新的线形图(xAxis、yAxis、dataset);
xAxis.setLabel(“时间”);
yAxis.设置标签(“度/秒”);

我不确定您指的是哪个问题。基本上,您可以使用一些绑定魔法来完成这一切:诀窍是使用
xAxis.getDisplayPosition(…)
将线的
x
值映射到相对于
xAxis
的坐标。然后,您需要将该坐标转换为相对于容纳图表和线条的容器的坐标:最简单的方法是首先使用
xAxis.localToScene(…)
转换为
场景
坐标,然后使用
container.sceneToLocal(…)
转换为容器的坐标

然后,您只需要让绑定观察它需要观察的所有更改:这些将是轴的(数字)边界,图表的(图形)边界,如果直线要移动,则是表示其x值的属性

这是一个SSCCE。在本例中,我使用
滑块
移动直线。我还使线仅在范围内可见,并绑定y坐标,使其跨越
yAxis

import java.util.Random;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;


public class LineChartWithVerticalLine extends Application {

    @Override
    public void start(Stage primaryStage) {
        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
        chart.getData().add(createSeries());

        Pane chartHolder = new Pane();
        chartHolder.getChildren().add(chart);


        DoubleProperty lineX = new SimpleDoubleProperty();
        Slider slider = new Slider();
        slider.minProperty().bind(xAxis.lowerBoundProperty());
        slider.maxProperty().bind(xAxis.upperBoundProperty());

        slider.setPadding(new Insets(20));

        lineX.bind(slider.valueProperty());

        chartHolder.getChildren().add(createVerticalLine(chart, xAxis, yAxis, chartHolder, lineX));

        BorderPane root = new BorderPane(chartHolder, null, null, slider, null);

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

    private Line createVerticalLine(XYChart<Number, Number> chart, NumberAxis xAxis, NumberAxis yAxis, Pane container, ObservableDoubleValue x) {
        Line line = new Line();
        line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
                double xInAxis = xAxis.getDisplayPosition(x.get());
                Point2D pointInScene = xAxis.localToScene(xInAxis, 0);
                double xInContainer = container.sceneToLocal(pointInScene).getX();
                return xInContainer ;
            }, 
            x, 
            chart.boundsInParentProperty(), 
            xAxis.lowerBoundProperty(),
            xAxis.upperBoundProperty()));
        line.endXProperty().bind(line.startXProperty());
        line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
                double lowerY = yAxis.getDisplayPosition(yAxis.getLowerBound());
                Point2D pointInScene = yAxis.localToScene(0, lowerY);
                double yInContainer = container.sceneToLocal(pointInScene).getY();
                return yInContainer ;
            }, 
            chart.boundsInParentProperty(), 
            yAxis.lowerBoundProperty()));
        line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
                double upperY = yAxis.getDisplayPosition(yAxis.getUpperBound());
                Point2D pointInScene = yAxis.localToScene(0, upperY);
                double yInContainer = container.sceneToLocal(pointInScene).getY();
                return yInContainer ;
            }, 
            chart.boundsInParentProperty(), 
            yAxis.lowerBoundProperty()));

        line.visibleProperty().bind(
                Bindings.lessThan(x, xAxis.lowerBoundProperty())
                .and(Bindings.greaterThan(x, xAxis.upperBoundProperty())).not());

        return line ;
    }

    private Series<Number, Number> createSeries() {
        Series<Number, Number> series = new Series<>();
        series.setName("Data");
        Random rng = new Random();
        for (int i=0; i<=20; i++) {
            series.getData().add(new Data<>(i, rng.nextInt(101)));
        }
        return series ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
import java.util.Random;
导入javafx.application.application;
导入javafx.beans.binding.Bindings;
导入javafx.beans.property.DoubleProperty;
导入javafx.beans.property.SimpleDoubleProperty;
导入javafx.beans.value.observedOutleValue;
导入javafx.geometry.Insets;
导入javafx.geometry.Point2D;
导入javafx.scene.scene;
导入javafx.scene.chart.LineChart;
导入javafx.scene.chart.NumberAxis;
导入javafx.scene.chart.XYChart;
导入javafx.scene.chart.XYChart.Data;
导入javafx.scene.chart.XYChart.Series;
导入javafx.scene.control.Slider;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.Pane;
导入javafx.scene.shape.Line;
导入javafx.stage.stage;
使用Verticalline扩展应用程序的公共类LineChart{
@凌驾
公共无效开始(阶段primaryStage){
NumberAxis xAxis=新NumberAxis();
NumberAxis yAxis=新的NumberAxis();
线形图=新线形图(xAxis,yAxis);
chart.getData().add(createSeries());
窗格图表持有者=新窗格();
chartHolder.getChildren().add(图表);
DoubleProperty lineX=新的SimpleDoubleProperty();
滑块=新滑块();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
滑块。设置填充(新插图(20));
bind(slider.valueProperty());
getChildren().add(createVerticalLine(chart,xAxis,yAxis,chartHolder,lineX));
BorderPane根=新的BorderPane(图表持有者,null,null,滑块,null);
场景=新场景(根,800600);
初级阶段。场景(场景);
primaryStage.show();
}
专用线createVerticalLine(XYChart图表、NumberAxis-xAxis、NumberAxis-yAxis、窗格容器、观察到的超左值x){
行=新行();
line.startXProperty().bind(Bindings.createDoubleBinding(()->{
double xInAxis=xAxis.getDisplayPosition(x.get());
Point2D pointInScene=xAxis.localToScene(xInAxis,0);
double xInContainer=container.sceneToLocal(pointInScene.getX();
返回集装箱;
}, 
x,,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty());
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(()->{
double-lowerY=yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene=yAxis.localToScene(0,lowerY);
double-yincainer=container.sceneToLocal(pointInScene.getY();
返回集装箱;
}, 
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty());
line.endYProperty().bind(Bindings.createDoubleBinding(()->{
double-upperY=yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene=yAxis.localToScene(0,大写);
double-yincainer=container.sceneToLocal(pointInScene.getY();
返回集装箱;
}, 
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty());
line.visibleProperty().bind(
Bindings.lessThan(x,xAxis.lowerBoundProperty())
.和(Bindings.greaterThan(x,xAxis.upperBoundProperty()).not());
回流线;
}
私有系列createSeries(){
系列=新系列();
series.setName(“数据”);
随机rng=新随机();

对于(int i=0;i您需要扩展LineChart类并重写layoutPlotChildren方法以显示标记

Kleopatra做了一个测试。下面的代码是折线图的修改版本,具有垂直和水平标记:

public class LineChartSample extends Application {

    @Override public void start(Stage stage) {

        final NumberAxis xAxis = new NumberAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Number of Month");

        final LineChartWithMarkers<Number,Number> lineChart = new LineChartWithMarkers<Number,Number>(xAxis,yAxis);

        XYChart.Series series = new XYChart.Series();
        series.setName("My portfolio");

        series.getData().add(new XYChart.Data(1, 23));
        series.getData().add(new XYChart.Data(2, 14));
        series.getData().add(new XYChart.Data(3, 15));
        series.getData().add(new XYChart.Data(4, 24));
        series.getData().add(new XYChart.Data(5, 34));
        series.getData().add(new XYChart.Data(6, 36));
        series.getData().add(new XYChart.Data(7, 22));
        series.getData().add(new XYChart.Data(8, 45));
        series.getData().add(new XYChart.Data(9, 43));
        series.getData().add(new XYChart.Data(10, 17));
        series.getData().add(new XYChart.Data(11, 29));
        series.getData().add(new XYChart.Data(12, 25));

        lineChart.getData().add(series);

        Data<Number, Number> horizontalMarker = new Data<>(0, 25);
        lineChart.addHorizontalValueMarker(horizontalMarker);

        Data<Number, Number> verticalMarker = new Data<>(10, 0);
        lineChart.addVerticalValueMarker(verticalMarker);

        Slider horizontalMarkerSlider = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
        horizontalMarkerSlider.setOrientation(Orientation.VERTICAL);
        horizontalMarkerSlider.setShowTickLabels(true);
        horizontalMarkerSlider.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
        horizontalMarkerSlider.minProperty().bind(yAxis.lowerBoundProperty());
        horizontalMarkerSlider.maxProperty().bind(yAxis.upperBoundProperty());

        Slider verticalMarkerSlider = new Slider(xAxis.getLowerBound(), xAxis.getUpperBound(), 0);
        verticalMarkerSlider.setOrientation(Orientation.HORIZONTAL);
        verticalMarkerSlider.setShowTickLabels(true);
        verticalMarkerSlider.valueProperty().bindBidirectional(verticalMarker.XValueProperty());
        verticalMarkerSlider.minProperty().bind(xAxis.lowerBoundProperty());
        verticalMarkerSlider.maxProperty().bind(xAxis.upperBoundProperty());

        BorderPane borderPane = new BorderPane();
        borderPane.setCenter( lineChart);
        borderPane.setTop(verticalMarkerSlider);
        borderPane.setRight(horizontalMarkerSlider);

        Scene scene  = new Scene(borderPane,800,600);

        stage.setScene(scene);
        stage.show();
    }

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

    private class LineChartWithMarkers<X,Y> extends LineChart {

        private ObservableList<Data<X, Y>> horizontalMarkers;
        private ObservableList<Data<X, Y>> verticalMarkers;

        public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
            super(xAxis, yAxis);
            horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
            horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
            verticalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
            verticalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
        }

        public void addHorizontalValueMarker(Data<X, Y> marker) {
            Objects.requireNonNull(marker, "the marker must not be null");
            if (horizontalMarkers.contains(marker)) return;
            Line line = new Line();
            marker.setNode(line );
            getPlotChildren().add(line);
            horizontalMarkers.add(marker);
        }

        public void removeHorizontalValueMarker(Data<X, Y> marker) {
            Objects.requireNonNull(marker, "the marker must not be null");
            if (marker.getNode() != null) {
                getPlotChildren().remove(marker.getNode());
                marker.setNode(null);
            }
            horizontalMarkers.remove(marker);
        }

        public void addVerticalValueMarker(Data<X, Y> marker) {
            Objects.requireNonNull(marker, "the marker must not be null");
            if (verticalMarkers.contains(marker)) return;
            Line line = new Line();
            marker.setNode(line );
            getPlotChildren().add(line);
            verticalMarkers.add(marker);
        }

        public void removeVerticalValueMarker(Data<X, Y> marker) {
            Objects.requireNonNull(marker, "the marker must not be null");
            if (marker.getNode() != null) {
                getPlotChildren().remove(marker.getNode());
                marker.setNode(null);
            }
            verticalMarkers.remove(marker);
        }


        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();
            for (Data<X, Y> horizontalMarker : horizontalMarkers) {
                Line line = (Line) horizontalMarker.getNode();
                line.setStartX(0);
                line.setEndX(getBoundsInLocal().getWidth());
                line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()) + 0.5); // 0.5 for crispness
                line.setEndY(line.getStartY());
                line.toFront();
            }
            for (Data<X, Y> verticalMarker : verticalMarkers) {
                Line line = (Line) verticalMarker.getNode();
                line.setStartX(getXAxis().getDisplayPosition(verticalMarker.getXValue()) + 0.5);  // 0.5 for crispness
                line.setEndX(line.getStartX());
                line.setStartY(0d);
                line.setEndY(getBoundsInLocal().getHeight());
                line.toFront();
            }      
        }

    }
}
公共类LineChartSample扩展应用程序{
@覆盖公共无效开始(阶段){
最终数字axis xAxis=新数字axis();
最终数字axis yAxis=新数字axis();
xAxis.setLabel(“月数”);
带标记的最终线形图
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
private ObservableList<Data<X, X>> verticalRangeMarkers;

public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
    ...            
    verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
    verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()}); // 2nd type of the range is X type as well
    verticalRangeMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}        


public void addVerticalRangeMarker(Data<X, X> marker) {
    Objects.requireNonNull(marker, "the marker must not be null");
    if (verticalRangeMarkers.contains(marker)) return;

    Rectangle rectangle = new Rectangle(0,0,0,0);
    rectangle.setStroke(Color.TRANSPARENT);
    rectangle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.2));

    marker.setNode( rectangle);

    getPlotChildren().add(rectangle);
    verticalRangeMarkers.add(marker);
}

public void removeVerticalRangeMarker(Data<X, X> marker) {
    Objects.requireNonNull(marker, "the marker must not be null");
    if (marker.getNode() != null) {
        getPlotChildren().remove(marker.getNode());
        marker.setNode(null);
    }
    verticalRangeMarkers.remove(marker);
}

protected void layoutPlotChildren() {

    ...

    for (Data<X, X> verticalRangeMarker : verticalRangeMarkers) {

        Rectangle rectangle = (Rectangle) verticalRangeMarker.getNode();
        rectangle.setX( getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()) + 0.5);  // 0.5 for crispness
        rectangle.setWidth( getXAxis().getDisplayPosition(verticalRangeMarker.getYValue()) - getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()));
        rectangle.setY(0d);
        rectangle.setHeight(getBoundsInLocal().getHeight());
        rectangle.toBack();

    }
} 
Data<Number, Number> verticalRangeMarker = new Data<>(4, 10);
lineChart.addVerticalRangeMarker(verticalRangeMarker);
/**
 * The ChartView.
 */
public class ChartController {

  private ChartViewModel chartViewModel;
  private CustomLineChart<Number, Number> lineChart;
  private NumberAxis xAxis;
  private NumberAxis yAxis;
  private XYChart.Series<Number, Number> series;
  private List<Integer> data;
  private boolean mouseDragged;
  private double initialNumberStart;
  private double initialNumberEnd;

  @FXML
  private VBox mainContainer;

  @FXML
  private HBox chartContainer;

  /**
   * The constructor.
   */
  public ChartController() {
    chartViewModel = new ChartViewModel();
    mouseDragged = false;
  }

  /**
   * The initialize method.
   */
  public void initialize() {
    createChart();
    handleEvents();
  }

  /**
   * Handles the events.
   */
  private void handleEvents() {
    lineChart.setOnMousePressed(pressed -> {
      int minSize = 1;
      // Get coordinate from the scene and transform to coordinates from the chart axis
      Point2D firstSceneCoordinate = new Point2D(pressed.getSceneX(), pressed.getSceneY());
      double firstX = xAxis.sceneToLocal(firstSceneCoordinate).getX();

      lineChart.setOnMouseDragged(dragged -> {
        mouseDragged = true;

        Point2D draggedSceneCoordinate = new Point2D(dragged.getSceneX(), dragged.getSceneY());
        double draggedX = xAxis.sceneToLocal(draggedSceneCoordinate).getX();

        List<Double> numbers = filterSeries(firstX, draggedX);
        int size = numbers.size();
        double numberStart = size > minSize ? numbers.get(0) : initialNumberStart;
        double numberEnd = numbers.size() > minSize ? numbers.get(size - 1) : initialNumberEnd;

        if (size > minSize) {
          lineChart.addVerticalRangeLines(new Data<>(numberStart, numberEnd));
        }

        lineChart.setOnMouseReleased(released -> {
          if (mouseDragged) {
            initialNumberStart = numberStart;
            initialNumberEnd = numberEnd;
            mouseDragged = false;

            redrawChart();
          }
        });
      });
    });
  }

  /**
   * Creates the charts.
   */
  private void createChart() {
    xAxis = new NumberAxis();
    yAxis = new NumberAxis();

    lineChart = new CustomLineChart<>(xAxis, yAxis);

    data = chartViewModel.getData();

    createSeries(data);
    lineChart.getData().add(series);

    initialNumberStart = 1;
    initialNumberEnd = data.size() - 1;

    chartContainer.getChildren().add(lineChart);

    HBox.setHgrow(lineChart, Priority.ALWAYS);
  }

  /**
   * Creates the series for the line chart.
   * 
   * @param numbers The list of numbers for the series
   */
  private void createSeries(List<Integer> numbers) {
    int size = numbers.size();
    series = new XYChart.Series<>();
    series.setName("Example");

    for (int i = 0; i < size; i++) {
      series.getData().add(new XYChart.Data<Number, Number>(i, numbers.get(i)));
    }
  }

  /**
   * Filters the nodes and returns the node x positions within the firstX and lastX positions.
   * 
   * @param firstX The first x position
   * @param lastX The last x position
   * @return The x positions for the nodes within the firstX and lastX
   */
  private List<Double> filterSeries(double firstX, double lastX) {
    List<Double> nodeXPositions = new ArrayList<>();

    lineChart.getData().get(0).getData().forEach(node -> {

      double nodeXPosition = lineChart.getXAxis().getDisplayPosition(node.getXValue());

      if (nodeXPosition >= firstX && nodeXPosition <= lastX) {
        nodeXPositions.add(Double.parseDouble(node.getXValue().toString()));
      }
    });

    return nodeXPositions;
  }

  /**
   * Updates the series for the chart.
   */
  private void updateSeries() {
    lineChart.getData().remove(0);
    lineChart.getData().add(series);
  }

  /**
   * Redraws the chart.
   */
  private void redrawChart() {
    List<Integer> filteredSeries = new ArrayList<>();

    data.forEach(number -> {
      if (number >= initialNumberStart && number <= initialNumberEnd) {
        filteredSeries.add(number);
      }
    });

    if (!filteredSeries.isEmpty()) {
      createSeries(filteredSeries);
      updateSeries();
      lineChart.removeVerticalRangeLines();
    }
  }

  /**
   * Resets the series for the chart.
   * 
   * @param event The event
   */
  @FXML
  void resetChart(ActionEvent event) {
    createSeries(data);
    updateSeries();
  }

}