Is there a way to disconnect 2 dots in series in JavaFX LineChart?

徘徊边缘 提交于 2019-12-04 16:11:54

Yes you can

As stated in https://docs.oracle.com/javase/8/javafx/api/javafx/scene/chart/XYChart.Series.html#nodeProperty the serie node is use to store the line. the node is a path that you can modify.

look at this small app :

    package application;

import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
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.shape.Circle;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {

            NumberAxis xAxis = new NumberAxis();
            NumberAxis yAxis = new NumberAxis();
            MyLineChart chart = new MyLineChart(xAxis, yAxis, Arrays.asList(5));


            XYChart.Series<Number, Number> serie = new Series<>();

            serie.getData().add(new Data<Number, Number>(0, 5));
            serie.getData().add(new Data<Number, Number>(1, 5));
            serie.getData().add(new Data<Number, Number>(2, 5));
            serie.getData().add(new Data<Number, Number>(3, 5));
            serie.getData().add(new Data<Number, Number>(4, 5));
            serie.getData().add(new Data<Number, Number>(5, -5));
            serie.getData().add(new Data<Number, Number>(6, -5));
            serie.getData().add(new Data<Number, Number>(7, -5));

            chart.getData().add(serie);

            Path p = (Path) serie.getNode();        

            Scene scene = new Scene(chart,400,400);
            primaryStage.setScene(scene);
            primaryStage.show();



    }

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

    private static class MyLineChart extends LineChart<Number,Number>{


        private List<Integer> breakpointIndex;

        public MyLineChart(Axis<Number> xAxis, Axis<Number> yAxis, List<Integer> breakpointIndex) {
            super(xAxis, yAxis);
            this.breakpointIndex = breakpointIndex;
        }

        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();

            Path p = (Path) getData().get(0).getNode();

            breakpointIndex.forEach(i ->{

                Data<Number, Number> discontinuousPoint = getData().get(0).getData().get(i+1);
                p.getElements().add(i+1, new MoveTo(getXAxis().getDisplayPosition( discontinuousPoint.getXValue()), getYAxis().getDisplayPosition(discontinuousPoint.getYValue())));

            });

            System.out.println("\nnew Path :");
            p.getElements().forEach(e -> System.out.println("p : " + e));


            //getPlotChildren().add(new Circle(50));


        }

    }
}

You have to override the method layoutplotchildren that is responsible to draw the chart (it's interesting to see the code of the origin method if you can).

and then you can draw what you want here.

In my small app i'am just modifying the node that are already create but you can omit the super call and draw what you want.

look at the doc you have method to access chartelement like getPlotChildren(), getchartchildren().

and you can access to the serie added to the chart with getdata()

Apparently in JavaFX 11 internal logic for filling underlying Elements list in Path has changed. Now, if I zoom the chart (in other words, change lower/upper bounds on axis), it gets repopulated only with visible elements, so indexes for Data and PathElement don't match anymore (and neither do the sizes of respective lists). I guess it's an optimization of sorts.

I was forced to rewrite the code the following hacky way:

@Override
protected void layoutPlotChildren() {
    super.layoutPlotChildren();
    for(Series<Number, Number> data : getData()) {
        ObservableList<PathElement> elements = ((Path)data.getNode()).getElements();
        for(int i = 0; i < elements.size() - 1; i++) {
            PathElement oldElement = elements.get(i);
            PathElement newElement = elements.get(i + 1);
            if (oldElement instanceof LineTo && newElement instanceof LineTo) {
                double oldDisplayX = ((LineTo) oldElement).getX();
                double newDisplayX = ((LineTo) newElement).getX();
                if(getXAxis().getValueForDisplay(newDisplayX).doubleValue() -
                       getXAxis().getValueForDisplay(oldDisplayX).doubleValue() > 10)
                    elements.set(i, new MoveTo(newDisplayX, ((LineTo) newElement).getY()));
            }
        }
    }
}

Now (for me) there's no need to keep separate list of breakpoint indexes, not is there a need to add them manually, since I insert a breakpoint on one condition only: space between X values is more than 10. I ignore Data list completely and traverse the Elements list, transforming display coordinates to X values where needed. For a large number of elements this will add some overhead, obviously, since this gets called each time window size changes, or when new element gets added, but for my use case chart consists of 4 series of 6000-15000 dots and for this size calculation takes ~1ms which is negligible. I could also probably remove one of the getValueForDisplay calls, calculating the difference in pixels first, not sure.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!