我要在LineChart
上添加一些形状。我将LineChart
和AnchorPane
放入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<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
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<Shape> shapes = new ArrayList<Shape>();
public ChartContent() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
yAxis.setForceZeroInRange(false);
xAxis.setForceZeroInRange(false);
chart = new LineChart<Number, Number>(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<ActionEvent>() {
@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<Number, Number>(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<Number, Number> 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<Number, Number> 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
之后,它们并不存在于我期望它们的位置。
预期结果
实际结果
答案 0 :(得分:6)
首先,请注意,对于您尝试实现的确切功能,只需在数据上设置节点即可完成此操作。
(旁白:可以说,我认为,将节点作为图表中显示的数据的属性,违反了在UI开发中将视图与数据分离的所有良好实践.Chart API具有一些糟糕的设计缺陷,imho,这就是其中之一。可能应该有类似于图表本身的Function<Data<X,Y>, Node> nodeFactory
属性。但是,它就是它。)
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
Data<Number, Number> data = new Data<Number, Number>(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<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
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<Number, Number>(xAxis, yAxis) {
private List<Shape> shapes = new ArrayList<>();
@Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
getPlotChildren().removeAll(shapes);
shapes.clear();
for (Data<Number, Number> 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<ActionEvent>() {
@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<Number, Number> data = new Data<Number, Number>(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()
方法更可靠。