How to add two vertical lines with JavaFX LineChart

后端 未结 3 1055
慢半拍i
慢半拍i 2020-12-16 05:31

My desktop application has a timer for starting and stopping a test. On the graph, I want to create two vertical lines to indicate the start and stop time. \"Adding vertical

3条回答
  •  醉梦人生
    2020-12-16 05:52

    You need to extend the LineChart class and override the layoutPlotChildren method in order to show your markers.

    Kleopatra did a very good example for a Scatter chart. The code below is a modified version for a line chart and has both vertical and horizontal markers:

    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 lineChart = new LineChartWithMarkers(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 horizontalMarker = new Data<>(0, 25);
            lineChart.addHorizontalValueMarker(horizontalMarker);
    
            Data 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 extends LineChart {
    
            private ObservableList> horizontalMarkers;
            private ObservableList> verticalMarkers;
    
            public LineChartWithMarkers(Axis xAxis, Axis 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 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 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 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 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 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 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();
                }      
            }
    
        }
    }
    

    enter image description here

    In order to add more marker lines, just use this:

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

    Of course you could as well use a rectangle instead of a line like this:

    private ObservableList> verticalRangeMarkers;
    
    public LineChartWithMarkers(Axis xAxis, Axis 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 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 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 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();
    
        }
    } 
    

    used like this:

    Data verticalRangeMarker = new Data<>(4, 10);
    lineChart.addVerticalRangeMarker(verticalRangeMarker);
    

    To make it look like a range:

    enter image description here

提交回复
热议问题