如何在JavaFX LineChart上添加形状

我将在LineChart上添加一些形状。 我将LineChartAnchorPane放入StackPane 。 我通过从图表系列中获取x和y坐标将形状添加到AnchorPane 。 这是一个例子。

LineChartApp.java

 package shapes; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; public class LineChartApp extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(new ChartContent())); primaryStage.setMaximized(true); primaryStage.show(); } public static void main(String[] args) { launch(args); } } 

ChartContent.java

 package shapes; import java.util.ArrayList; import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Side; import javafx.scene.Node; 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.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Shape; import javafx.util.Duration; public class ChartContent extends StackPane { private AnchorPane objectsLayer; private LineChart chart; private NumberAxis xAxis; private NumberAxis yAxis; private Series series = new Series(); private int level = 0; private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 }, { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 }, { 1000, 1341, 1211, 1562, 1400, 1600, 1550 } }; private List shapes = new ArrayList(); public ChartContent() { xAxis = new NumberAxis(); yAxis = new NumberAxis(); yAxis.setSide(Side.RIGHT); yAxis.setForceZeroInRange(false); xAxis.setForceZeroInRange(false); chart = new LineChart(xAxis, yAxis); chart.setCreateSymbols(false); chart.setLegendVisible(false); chart.setAnimated(false); chart.setVerticalZeroLineVisible(false); Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5), new EventHandler() { @Override public void handle(ActionEvent event) { chartRefresh(); } })); timer.setCycleCount(datas.length - 1); timer.play(); objectsLayer = new AnchorPane(); objectsLayer.prefHeightProperty().bind(heightProperty()); objectsLayer.prefWidthProperty().bind(widthProperty()); getChildren().addAll(chart, objectsLayer); chartRefresh(); } private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { series.getData().add( new Data(i, datas[level][i])); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); reDrawShapes(series); } private void reDrawShapes(Series series) { Node chartPlotBackground = chart.lookup(".chart-plot-background"); chartPlotBackground.setStyle("-fx-background-color:white"); Circle circle; objectsLayer.getChildren().removeAll(shapes); shapes.clear(); double top = chart.getPadding().getTop(), left = chart.getPadding() .getLeft(); double minX = chartPlotBackground.getBoundsInParent().getMinX(); double minY = chartPlotBackground.getBoundsInParent().getMinY(); for (Data data : series.getData()) { circle = new Circle(minX + chart.getXAxis().getDisplayPosition(data.getXValue()) + left, minY + chart.getYAxis().getDisplayPosition(data.getYValue()) + top, 3, Color.RED); shapes.add(circle); } objectsLayer.getChildren().addAll(shapes); } } 

我每五秒刷一次图表系列并重新绘制它的形状。 但是在将形状添加到AnchorPane ,它们并不存在于我期望它们的位置。


预期结果

预期结果:数据点处带有红色圆圈的折线图


实际结果

实际结果:在看似随机的地方有红色圆圈的折线图

首先,请注意,对于您尝试实现的确切function,只需在数据上设置节点即可完成此操作。

(旁白:可以说,我认为,将节点作为图表中显示的数据的属性,违反了在UI开发中将视图与数据分离的所有良好实践.Chart API具有多个糟糕的设计缺陷,imho,这就是其中之一。可能应该有类似于Chart本身的Function, Node> nodeFactory属性。但是,它就是它。)

 private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { Data data = new Data(i, datas[level][i]); data.setNode(new Circle(3, Color.RED)); series.getData().add(data); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); // reDrawShapes(series); } 

如果您的节点足够简单,可以将其集中在点上,那么这就可以了。

如果你想要一些更复杂的东西,但这不起作用,支持的机制是子类化图表类并覆盖layoutPlotChildren()方法。 以下是使用此方法的完整类:

 import java.util.ArrayList; import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Side; 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.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Shape; import javafx.util.Duration; public class ChartContent extends StackPane { private LineChart chart; private NumberAxis xAxis; private NumberAxis yAxis; private Series series = new Series(); private int level = 0; private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 }, { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 }, { 1000, 1341, 1211, 1562, 1400, 1600, 1550 } }; public ChartContent() { xAxis = new NumberAxis(); yAxis = new NumberAxis(); yAxis.setSide(Side.RIGHT); yAxis.setForceZeroInRange(false); xAxis.setForceZeroInRange(false); chart = new LineChart(xAxis, yAxis) { private List shapes = new ArrayList<>(); @Override public void layoutPlotChildren() { super.layoutPlotChildren(); getPlotChildren().removeAll(shapes); shapes.clear(); for (Data d : series.getData()) { double x = xAxis.getDisplayPosition(d.getXValue()); double y = yAxis.getDisplayPosition(d.getYValue()); shapes.add(new Circle(x, y, 3, Color.RED)); } getPlotChildren().addAll(shapes); } }; chart.setCreateSymbols(false); chart.setLegendVisible(false); chart.setAnimated(false); chart.setVerticalZeroLineVisible(false); Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5), new EventHandler() { @Override public void handle(ActionEvent event) { chartRefresh(); } })); timer.setCycleCount(datas.length - 1); timer.play(); getChildren().addAll(chart); chartRefresh(); } private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { Data data = new Data(i, datas[level][i]); data.setNode(new Circle(3, Color.RED)); series.getData().add(data); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); } } 

这导致了

在数据点的红色圆圈的折线图


例如,您可以使用此技术添加最佳拟合线以将曲线图或趋势线添加到折线图等。

我无法确切地说出为什么你使用的代码不起作用,但它对如何管理布局(即chart-plot-background相对于整个图表本身的位置)以及测量时间做了几个假设这是为了做一些事情,比如计算从“图表坐标”到“像素坐标”的映射轴的比例。 例如,当数据发生变化并且仅在布局过程开始时重新计算时,不难想象它们会变得无效。 记录“数据值”( data.getXValue()data.getYValue() )以及从Axis.getDisplayValue(...)获取的值以表示这些值表明类似于后者的解释可能就是这种情况,如那些肯定似乎没有产生正确的转换。

挂钩layoutPlotChildren()方法更可靠。