1

Starting from this question JavaFX LineChart Hover Values I modified the Gist in the first answer https://gist.github.com/jewelsea/4681797 into this

public class TestCharts extends Application
{

    ObservableList<XYChart.Data<Integer, Integer>> _serie;

    @SuppressWarnings("unchecked")
    @Override
    public void start(Stage stage) {

        LineChart lineChart = new LineChart(new NumberAxis(), new NumberAxis());

        XYChart.Series serie = new XYChart.Series(
            "Test",
            getTestValues()
        );

        lineChart.getData().add(serie);
        lineChart.setCursor(Cursor.CROSSHAIR);
        lineChart.setTitle("First Chart");

        stage.setTitle("First Chart");
        stage.setScene(new Scene(lineChart, 500, 400));
        stage.show();

        secondHandChart();
    }

    private void secondHandChart() {
        LineChart lineChart = new LineChart(new NumberAxis(), new NumberAxis());
        XYChart.Series serie = new XYChart.Series(
            "Test",
            getTestValues()
        );

        lineChart.getData().add(serie);

        lineChart.setCursor(Cursor.CROSSHAIR);

        lineChart.setTitle("Second Chart");
        Stage stage = new Stage();
        stage.setTitle("Second Chart");
        stage.setScene(new Scene(lineChart, 500, 400));
        stage.show();
    }

    private ObservableList<XYChart.Data<Integer, Integer>> getTestValues() {
        return plot(23, 14, 15, 24, 34, 36, 22, 45, 43, 17, 29, 25);
    }

    public ObservableList<XYChart.Data<Integer, Integer>> plot(int... y) {
        if (_serie == null) {
            _serie = FXCollections.observableArrayList();
            int i = 0;
            while (i < y.length) {
                XYChart.Data<Integer, Integer> data = new XYChart.Data<>(i + 1, y[i]);
                data.setNode(
                    new HoveredThresholdNode(
                        (i == 0) ? 0 : y[i - 1],
                        y[i]
                    )
                );

                _serie.add(data);
                i++;
            }
            return _serie;
        }
        else {
            return FXCollections.observableArrayList(_serie);
        }
    }

    /**
     * a node which displays a value on hover, but is otherwise empty
     */
    class HoveredThresholdNode extends StackPane
    {
        HoveredThresholdNode(int priorValue, int value) {
            setPrefSize(8, 8);

            final Label label = createDataThresholdLabel(priorValue, value);

            setOnMouseEntered(new EventHandler<MouseEvent>()
            {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    getChildren().setAll(label);
                    setCursor(Cursor.NONE);
                    toFront();
                }
            });
            setOnMouseExited(new EventHandler<MouseEvent>()
            {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    getChildren().clear();
                    setCursor(Cursor.CROSSHAIR);
                }
            });
        }

        private Label createDataThresholdLabel(int priorValue, int value) {
            final Label label = new Label(value + "");
            label.setStyle("-fx-font-size: 20; -fx-font-weight: bold;");

            label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
            return label;
        }
    }

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

This class has the only pourpose of showing my problem. I want to get two charts of the same serie, getTestValues(). The problem is: the second chart is perfect (2° Img) but the first one (1° Img) is missing the HoveredThresholdNode and when I resize the first chart, the HoveredThresholdNodes in the second chart are changing position (3° Image). How can I get a copy of the serie, containing also a copy of those nodes? I know that printing the same serie doesn't make sense, but in the reality I have a two lists of serie, and I have a chart for each and a chart with the two lists together for compairing pourposes. Anyway the problem can be semplified to just one serie in two charts.

enter image description here enter image description hereenter image description here

Mark
  • 418
  • 3
  • 12

1 Answers1

4

XYChart.Data cannot be shared across charts because it has-a Node that is added to the chart.

To share data between charts, you have to

  • implement a custom data class that only contains the values
  • keep a list of the shared data
  • for each chart, create a XYChart.Data backed by the shared data
  • manage the sync of the chart data with the backing data (note that the simple example below isn't complete in not updating the chart data on list modifications)

A quick example:

@SuppressWarnings({ "unchecked", "rawtypes" })
public class TestChart extends Application {

    // the shared x/y data pairs
    private ObservableList<XYData<Integer, Integer>> backingData;

    /**
     * Custom xy data class which exposes its values as properties and nothing
     * else.
     */
    public static class XYData<X, Y> {
        private ObjectProperty<X> xValue;

        private ObjectProperty<Y> yValue;

        public XYData(X xValue, Y yValue) {
            this.xValue = new SimpleObjectProperty<>(this, "xValue", xValue);
            this.yValue = new SimpleObjectProperty<>(this, "yValue", yValue);
        }

        public ObjectProperty<X> xValueProperty() {
            return xValue;
        }

        public void setXValue(X value) {
            xValueProperty().set(value);
        }

        public X getXValue() {
            return xValueProperty().get();
        }

        public ObjectProperty<Y> yValueProperty() {
            return yValue;
        }

        public void setYValue(Y value) {
            yValueProperty().set(value);
        }

        public Y getYValue() {
            return yValueProperty().get();
        }
    }

    @Override
    public void start(Stage stage) {
        if (backingData == null) {
            backingData = createBackingData();
        }

       LineChart lineChart = new LineChart(new NumberAxis(), new NumberAxis());

        XYChart.Series serie = new XYChart.Series("Test", createChartData());

        lineChart.getData().add(serie);
        lineChart.setCursor(Cursor.CROSSHAIR);
        lineChart.setTitle("First Chart");

        stage.setTitle("First Chart");
        stage.setScene(new Scene(lineChart, 500, 400));
        stage.show();

        secondHandChart();
    }

    private void secondHandChart() {
        LineChart lineChart = new LineChart(new NumberAxis(), new NumberAxis());
        XYChart.Series serie = new XYChart.Series("Test", createChartData());

        lineChart.getData().add(serie);

        lineChart.setCursor(Cursor.CROSSHAIR);

        lineChart.setTitle("Second Chart");
        Stage stage = new Stage();
        stage.setTitle("Second Chart");
        stage.setScene(new Scene(lineChart, 500, 400));
        stage.show();
    }

    private ObservableList<XYData<Integer, Integer>> createBackingData() {
        ObservableList<XYData<Integer, Integer>> data = FXCollections
                .observableArrayList();
        Integer[] values = { 23, 14, 15, 24, 34, 36, 22, 45, 43, 17, 29, 25 };
        for (int i = 0; i < values.length; i++) {
            data.add(new XYData(i, values[i]));
        }
        return data;
    }

    private ObservableList<XYChart.Data<Integer, Integer>> createChartData() {
        ObservableList<XYChart.Data<Integer, Integer>> chartData = FXCollections
                .observableArrayList();
        for (int i = 0; i < backingData.size(); i++) {
            XYData<Integer, Integer> b = backingData.get(i);
            XYChart.Data<Integer, Integer> cd = new XYChart.Data<>(
                    b.getXValue(), b.getYValue());
            cd.XValueProperty().bind(b.xValueProperty());
            cd.YValueProperty().bind(b.yValueProperty());
            cd.setNode(new HoveredThresholdNode(
                    (i == 0) ? 0 : backingData.get(i - 1).getYValue(),
                    backingData.get(i).getYValue()));
            chartData.add(cd);
        }
        return chartData;
    }

    /**
     * a node which displays a value on hover, but is otherwise empty
     */
    class HoveredThresholdNode extends StackPane {
        HoveredThresholdNode(int priorValue, int value) {
            setPrefSize(8, 8);

            final Label label = createDataThresholdLabel(priorValue, value);

            setOnMouseEntered(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    getChildren().setAll(label);
                    setCursor(Cursor.NONE);
                    toFront();
                }
            });
            setOnMouseExited(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    getChildren().clear();
                    setCursor(Cursor.CROSSHAIR);
                }
            });
        }

        private Label createDataThresholdLabel(int priorValue, int value) {
            final Label label = new Label(value + "");
            label.setStyle("-fx-font-size: 20; -fx-font-weight: bold;");

            label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
            return label;
        }
    }

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

}
kleopatra
  • 51,061
  • 28
  • 99
  • 211