我创建了自定义图表,在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");
}
});
}
但它似乎没有起作用。代码是否正确?难道我设置的高度和宽度都很小?创建透明(不可见)节点但仍能点击它的最佳方法是什么?
答案 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设置。