XYData中的自定义节点

时间:2015-10-19 13:45:13

标签: charts javafx-8

我创建了自定义图表,在javafx中扩展了LineChart。我想要做的是操纵用于渲染XYData的节点。所以我压倒了以下内容:

@Override
protected void dataItemAdded(Series<X, Y> series, int itemIndex,
                             Data<X, Y> item) {

    super.dataItemAdded(series, itemIndex, item);
    StackPane itemNode = new StackPane();
    itemNode.prefHeight(0.5);
    itemNode.prefWidth(0.5);             
    item.setNode(itemNode);

    itemNode.setOnMousePressed(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                // TODO Auto-generated method stub
                LOG.info("node clicked");

            }
        });
}

但它似乎没有起作用。代码是否正确?难道我设置的高度和宽度都很小?创建透明(不可见)节点但仍能点击它的最佳方法是什么?

1 个答案:

答案 0 :(得分:1)

子类化LineChart可能是一种不好的选择。问题是您需要确切知道LineChart实现如何在数据上设置节点。 (让我们忽略JavaFX团队在这里做出的糟糕的设计决策:为什么数据会引用它的视觉节点?应该有一个属于图表的Callback<Data<X,Y>, Node> symbolFactory属性相反。)

事实证明,如果您先将系列添加到图表中,然后将数据添加到系列中,则会为每个数据点调用dataItemAdded方法,因此在此用例中您的方法有效: / p>

import java.util.Random;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class LineChartClickableDataPoints extends Application {

    private static final Logger LOG = Logger.getLogger("LineChartClickableDataPoints");

    @Override
    public void start(Stage primaryStage) {

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis) {
            @Override
            protected void dataItemAdded(Series<Number, Number> series, int itemIndex,
                                         Data<Number, Number> item) {

                super.dataItemAdded(series, itemIndex, item);
                StackPane itemNode = new StackPane();
                itemNode.setPrefHeight(0.5);
                itemNode.setPrefWidth(0.5);             
                item.setNode(itemNode);

                itemNode.setOnMousePressed(new EventHandler<MouseEvent>() {

                        @Override
                        public void handle(MouseEvent event) {
                            LOG.info("node clicked");
                        }
                    });
            }
        };

        Random rng = new Random();
        Series<Number, Number> data = new Series<>();
        chart.getData().add(data);
        data.setName("Data");
        data.getData().addAll(IntStream.rangeClosed(1, 10)
                .mapToObj(x -> new Data<Number, Number>(x, rng.nextDouble()))
                .collect(Collectors.toList()));
        BorderPane root = new BorderPane(chart);
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }



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

另一方面,如果您将数据添加到系列,然后将系列添加到图表,则不会调用dataItemAdded。为此,您需要覆盖seriesAdded。所以你可以这样做,但更深层次的问题是你依赖于LineChart的实现,而不是仅仅依赖于它的规范。因此,即使您通读source code并现在涵盖所有可能性,如果在将来的版本中实现更改,您也无法保证您的代码仍然有效。简而言之,"Inheritance violates encapsulation"

相反,您可以在创建数据时在数据上设置节点:

import java.util.Random;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class LineChartClickableDataPoints extends Application {

    private static final Logger LOG = Logger.getLogger("LineChartClickableDataPoints");

    @Override
    public void start(Stage primaryStage) {

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis) ;

        Random rng = new Random();
        Series<Number, Number> data = new Series<>();
        data.setName("Data");
        data.getData().addAll(IntStream.rangeClosed(1, 10)
                .mapToObj(x -> new Data<Number, Number>(x, rng.nextDouble()))
                .peek(this::addNodeToItem)
                .collect(Collectors.toList()));
        chart.getData().add(data);
        BorderPane root = new BorderPane(chart);
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void addNodeToItem(Data<Number, Number> item) {
        StackPane itemNode = new StackPane();
        itemNode.setPrefHeight(0.5);
        itemNode.setPrefWidth(0.5);             
        item.setNode(itemNode);

        itemNode.setOnMousePressed(new EventHandler<MouseEvent>() {

                @Override
                public void handle(MouseEvent event) {
                    LOG.info(String.format("node clicked: [%.2f, %.2f]%n", 
                            item.getXValue().doubleValue(), item.getYValue().doubleValue()));
                }
            });
    }



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

这种方法适用于任何一种情况。

据我所知,对节点的大小没有要求。请注意,默认样式表应用以下样式规则:

.chart-symbol { /* solid circle */
    -fx-background-color: CHART_COLOR_1;
    -fx-background-radius: 5px;
    -fx-padding: 5px;
}

所以,只要节点有css应用于它,它将添加5个像素的填充。您当然可以在外部样式表中定义自己的CSS设置。