[英]How to add shapes on JavaFX LineChart
I am going to add some shapes on LineChart
.我将在
LineChart
上添加一些形状。 I put LineChart
and AnchorPane
into the StackPane
.我将
LineChart
和AnchorPane
放入StackPane
。 I added shapes to AnchorPane
by getting x and y coordinates from the chart series.我通过从图表系列中获取 x 和 y 坐标向
AnchorPane
添加了形状。 Here is example.这是示例。
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图表内容.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);
}
}
I am refreshing chart series every five seconds and redrawing its shapes as well.我每五秒刷新一次图表系列并重新绘制其形状。 But after the shapes added to the
AnchorPane
, they are not there where I expect them to be.但是在将形状添加到
AnchorPane
,它们不在我期望的位置。
Expected Result预期结果
Actual Result实际结果
First, note that for the exact functionality you're trying to achieve, this can be done simply by setting a node on the data.首先,请注意,对于您要实现的确切功能,只需在数据上设置节点即可完成。
(Aside: it could be argued, and I would argue, that making a node a property of the data displayed in the chart violates pretty much every good practice on the separation of view from data in UI development. The Chart API has a number of bad design flaws, imho, and this is one of them. There probably should be something like a Function<Data<X,Y>, Node> nodeFactory
property of the Chart itself for this. However, it is what it is.) (旁白:可以争论,我会争论,使节点成为图表中显示的数据的属性几乎违反了 UI 开发中将视图与数据分离的所有良好实践。图表 API 有许多糟糕的设计缺陷,恕我直言,这就是其中之一。可能应该有类似 Chart 本身的
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);
}
This works if your node is simple enough that centering it on the point is what you need.如果您的节点足够简单以至于您需要将其集中在该点上,则此方法有效。
If you want something more complex, for which this doesn't work, the supported mechanism is to subclass the chart class and override the layoutPlotChildren()
method.如果您想要更复杂的东西,而这对其不起作用,则支持的机制是子类化图表类并覆盖
layoutPlotChildren()
方法。 Here's the complete class using this approach:这是使用这种方法的完整类:
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");
}
}
This results in这导致
You can use this technique to, for example, add best fit lines to scatter plots or trend lines to line charts, etc.例如,您可以使用此技术将最佳拟合线添加到散点图或将趋势线添加到折线图等。
I can't tell exactly why the code you used doesn't work, but it makes several assumptions about how the layout is managed (ie the location of chart-plot-background
in relation to the overall chart itself) and also about when measurements are taken in order to do things like compute the scale in the axes for the mapping from "chart coordinates" to "pixel coordinates".我无法确切说明为什么您使用的代码不起作用,但它对布局的管理方式(即
chart-plot-background
相对于整体图表本身的位置)以及何时进行测量做出了一些假设是为了做一些事情,比如计算从“图表坐标”到“像素坐标”的映射的轴中的比例。 It's not too hard to imagine these becoming invalid when the data changes and only being recalculated at the beginning of the layout process, for example.例如,不难想象当数据更改时这些数据会变得无效并且仅在布局过程开始时重新计算。 Logging the "data values" (
data.getXValue()
and data.getYValue()
) alongside the values you get from Axis.getDisplayValue(...)
for those values suggests that something akin to the latter explanation may be the case, as those definitely do not seem to produce the correct transformations.将“数据值”(
data.getXValue()
和data.getYValue()
)与您从Axis.getDisplayValue(...)
获得的值一起Axis.getDisplayValue(...)
这些值表明可能类似于后一种解释的情况,如那些肯定不会产生正确的转换。
Hooking into the layoutPlotChildren()
method is more reliable.连接到
layoutPlotChildren()
方法更可靠。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.