Adding a line in a JavaFX chart

前端 未结 2 1743
忘掉有多难
忘掉有多难 2020-11-28 15:45

I have problems with adding a line at a defined position in JavaFX. The line has to be line a constant line as shown here: How to add a value marker to JavaFX chart?

<
2条回答
  •  情深已故
    2020-11-28 16:13

    Unfortunately, ValueMarkers are not supported in XYCharts (that's probably the place in the hierarchy were it should be done) (Mis-)using data with constant value is a hack which might (or not) be acceptable/possible in some contexts.

    A cleaner way out is a custom Chart that supports such markers. F.i. a custom ScatterChart like:

    public class ScatterXChart extends ScatterChart {
    
        // data defining horizontal markers, xValues are ignored
        private ObservableList> horizontalMarkers;
    
        public ScatterXChart(Axis xAxis, Axis yAxis) {
            super(xAxis, yAxis);
            // a list that notifies on change of the yValue property
            horizontalMarkers = FXCollections.observableArrayList(d -> new Observable[] {d.YValueProperty()});
            // listen to list changes and re-plot
            horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
        }
    
        /**
         * Add horizontal value marker. The marker's Y value is used to plot a
         * horizontal line across the plot area, its X value is ignored.
         * 
         * @param marker must not be null.
         */
        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);
        }
    
        /**
         * Remove horizontal value marker.
         * 
         * @param horizontalMarker must not be null
         */
        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);
        }
    
        /**
         * Overridden to layout the value markers.
         */
        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();
            for (Data horizontalMarker : horizontalMarkers) {
                double lower = ((ValueAxis) getXAxis()).getLowerBound();
                X lowerX = getXAxis().toRealValue(lower);
                double upper = ((ValueAxis) getXAxis()).getUpperBound();
                X upperX = getXAxis().toRealValue(upper);
                Line line = (Line) horizontalMarker.getNode();
                line.setStartX(getXAxis().getDisplayPosition(lowerX));
                line.setEndX(getXAxis().getDisplayPosition(upperX));
                line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()));
                line.setEndY(line.getStartY());
    
            }
        }
    }
    

    A snippet testing the chart (f.i. insert into the online example in oracle's tutorial):

    // instantiate chart
    NumberAxis xAxis = new NumberAxis(0, 10, 1);
    NumberAxis yAxis = new NumberAxis(-100, 500, 100);        
    ScatterXChart sc = new ScatterXChart<>(xAxis,yAxis);
    // .. fill with some data
    ...
    // ui to add/change/remove a value marker
    Data horizontalMarker = new Data<>(0, 110);
    Button add = new Button("Add Marker");  
    add.setOnAction(e -> sc.addHorizontalValueMarker(horizontalMarker));
    Slider move = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
    move.setShowTickLabels(true);
    move.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
    Button remove = new Button("Remove Marker");
    remove.setOnAction(e -> sc.removeHorizontalValueMarker(horizontalMarker));
    

    Addendum:

    While I wouldn't recommend the approach in the related question (adding the marker line to the chart's parent and managing its position/length externally) it is possible to use it in resizable containers. The crucial thingies to make it work:

    • listen to chart's size/location changes and update the line appropriately
    • set the marker's managed property to false

    in code (updateShift is the part in the original that calculates the yShift/lineX):

    Pane pane = new StackPane(chart);
    chart.widthProperty().addListener(o -> updateShift(chart));
    chart.heightProperty().addListener(o -> updateShift(chart));
    valueMarker.setManaged(false);
    pane.getChildren().add(valueMarker);
    

提交回复
热议问题