在 JavaFX 图表之上高效地更新 lines/markers

Efficiently updating lines/markers on top of a JavaFX chart

我使用 JavaFX LineChart,它需要将近一秒钟的时间来渲染,我想在这个更新非常频繁的图表上移动一个垂直标记(光标位置)。

我测试了以下解决方案:

但是,这些解决方案不适用于大图表上的频繁标记更新。每当标记更改时,整个图表都会重新绘制。

如何防止 Java 在标记更新时重新绘制整个图表? 我想到了一个可以放在图表周围并缓存图表图像的包装器组件,只要图表内容和图表边界不变即可。是否存在类似的东西或者如何实现它?

事实证明,解决方案比我想象的要容易得多。 每个 JavaFX 组件 (Node) 都有一个 cache 属性 可以将其设置为 true 以便将渲染的节点缓存为位图。这正是我要找的。

因此,一个有效的解决方案是将图表和所有标记线放入 StackPane 并为图表启用缓存。 Adding a line in a JavaFX chart and How to add a value marker to JavaFX chart? 中已经解释了其余部分(如何创建 StackPane 以及如何计算和更新标记线)。

正如 Stefan 所提到的,最好的方法是将您不想更新的节点的 cache 属性 设置为 true。我想补充的是 StackPane 不是绝对必要的,它可以是 Pane 或另一个区域(实际上 StackPane 有自己的方式来添加新的 children 这让你很困难操纵)。 这是我用来实现沿 LineChart 移动的 LineMarker 的代码片段(在我的示例中,我有一个视频,LineMarker 跟随图表中的时间)

主要示例

Pane pane; 
LineChart chart;
MediaPlayer mediaPlayer;
// Create pane, chart (and mediaplayer for this example)
pane.getChildren.add(chart);
List<XYChart.Data<Number, Number> dataList;
XYChart.Series series = new XYChart.Series();
// Fill the data 
ObservableList<XYChart.Data<Number, Number>> list;
list = FXCollections.observableList(dataList);
series.setData(list);
chart.getData().add(series);
chart.setCache(true);
LineMarker lineMarker = new LineMarker(pane, (ValueAxis) chart.getXAxis(), 0, (ValueAxis) chart.getYAxis());
mediaPlayer.currentTimeProperty().addListener((v, oldValue, newValue) -> lineMarker.updateMarker(newValue.doubleValue()));

LineMarker.java

public class LineMarker extends Line{
private final DoubleProperty value = new SimpleDoubleProperty();
private Pane pane;
private ValueAxis xAxis;
private ValueAxis yAxis;

public LineMarker(Pane pane, ValueAxis xAxis, double value, ValueAxis yAxis){
    this.pane = pane;
    this.xAxis = xAxis;
    this.yAxis = yAxis;
    Number lowerY = yAxis.toRealValue(yAxis.getLowerBound());
    double minY = yAxis.getDisplayPosition(lowerY);
    setStartY(getYPositionParent(minY, pane));
    Number upperY = yAxis.toRealValue(yAxis.getUpperBound());
    double maxY = yAxis.getDisplayPosition(upperY);
    setEndY(getYPositionParent(maxY, pane));
    double xPosInAxis = xAxis.getDisplayPosition(value);
    setStartX(getXPositionParent(xPosInAxis, pane));
    setEndX(getStartX());
    pane.getChildren().add(this);
}

private double getXPositionParent(double xPosInAxis, Node parent){
    double xPosInParent = xPosInAxis - xAxis.getBoundsInLocal().getMinX();
    Node node = xAxis;
    while(!node.equals(parent)){
        xPosInParent = xPosInParent + node.getBoundsInParent().getMinX();
        node = node.getParent();
    }
    return xPosInParent;
}

private double getYPositionParent(double yPosInAxis, Node parent){
    double yPosInParent = yPosInAxis - yAxis.getBoundsInLocal().getMinY();
    Node node = yAxis;
    while(!node.equals(parent)){
        yPosInParent = yPosInParent + node.getBoundsInParent().getMinY();
        node = node.getParent();
    }
    return yPosInParent;
}

public void updateMarker(double value){
    pane.getChildren().remove(this);
    double xPosInAxis = xAxis.getDisplayPosition(value);
    setStartX(getXPositionParent(xPosInAxis, pane));
    setEndX(getStartX());
    pane.getChildren().add(this);
    pane.requestLayout();
}