Adding a line in a JavaFX chart

前端 未结 2 1731
忘掉有多难
忘掉有多难 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 15:57

    Why don't you add that line into your chart? I have charts where I display the 100, 99, 95 and 50 percentiles, and the way I solve this is to add a line at the correct y-value for each percentile.

    To do this, just add a line with two points, one at y=60 x=70 (the left-most x-axis value) and the other one at y=60 and x=120 (the right-most x-axis value).

    The upside of this is that you do not have to align the horizontal line yourself manually, the downside being that this horizontal line will also be part of the legend. However, seeing as you do not have a legend that should be OK.

    If you decide to add a legend, be sure to name the horizontal line appropriately, l

    0 讨论(0)
  • 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<X, Y> extends ScatterChart<X, Y> {
    
        // data defining horizontal markers, xValues are ignored
        private ObservableList<Data<X, Y>> horizontalMarkers;
    
        public ScatterXChart(Axis<X> xAxis, Axis<Y> 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<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);
        }
    
        /**
         * Remove horizontal value marker.
         * 
         * @param horizontalMarker must not be null
         */
        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);
        }
    
        /**
         * Overridden to layout the value markers.
         */
        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();
            for (Data<X, Y> 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<Number,Number> sc = new ScatterXChart<>(xAxis,yAxis);
    // .. fill with some data
    ...
    // ui to add/change/remove a value marker
    Data<Number, Number> 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);
    
    0 讨论(0)
提交回复
热议问题