在 JavaFX 图表上显示缺口
Displaying gaps on charts in JavaFX
我在 JavaFX 8 中使用 XYChart
,我想为指定系列的空单元格显示间隙。当我传递 null 值时,我得到 NullPointerException
:
series.get(index).getData().add(new XYChart.Data<>(Key, null));
我也发现了描述这个问题的错误 https://bugs.openjdk.java.net/browse/JDK-8092134,但我不知道它是否仍然存在。
有谁知道如何解决这个问题?
此致,
迈克尔
很明显,这个功能不包括在内。如果你非常想得到这个行为,你可以试试下面的逻辑。
话虽如此,可以有很多更好的方法,但这个答案是为了让您初步了解如何使用图表的受保护方法调整当前实现。
想法是.. 一旦绘图子布局完成,我们重新计算渲染线路径的逻辑.. 并删除不需要的数据点。如前所述,这仅供参考,如果您有更多数据系列,那么您可能需要相应地工作。
[更新] : 如果您想要每个系列的 paths/data 点,请将“.series”附加到“.chart-series-line”和“.data”样式 类.
请查看以下演示:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.stage.Stage;
import java.util.*;
public class XYChartDemo extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("days");
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("USD");
// AS OF NOW THIS IS THE CORE POINT I AM RELYING ON !! YOU CAN THINK OF A BETTER APPROACH TO IDENTIFY THE GAP POINTS.
List<Integer> s0GapIndexes = Arrays.asList(3, 7);
List<Integer> s1GapIndexes = Arrays.asList(4, 8);
Map<Integer, List<Integer>> seriesGap = new HashMap<>();
seriesGap.put(0, s0GapIndexes);
seriesGap.put(1, s1GapIndexes);
XYChart.Series<String, Double> series0 = new XYChart.Series<>();
series0.getData().add(new Data<>("2000-01-01", 13.2));
series0.getData().add(new Data<>("2000-01-02", 10.1));
series0.getData().add(new Data<>("2000-01-03", 14.1));
series0.getData().add(new Data<>("2000-01-04", 0.0)); // gap (INDEX 3)
series0.getData().add(new Data<>("2000-01-05", 6.3));
series0.getData().add(new Data<>("2000-01-06", 9.82));
series0.getData().add(new Data<>("2000-01-07", 12.82));
series0.getData().add(new Data<>("2000-01-08", 0.0)); // gap (INDEX 7)
series0.getData().add(new Data<>("2000-01-09", 4.82));
series0.getData().add(new Data<>("2000-01-10", 8.82));
series0.getData().add(new Data<>("2000-01-11", 8.82));
XYChart.Series<String, Double> series1 = new XYChart.Series<>();
series1.getData().add(new Data<>("2000-01-01", 20.2));
series1.getData().add(new Data<>("2000-01-02", 14.1));
series1.getData().add(new Data<>("2000-01-03", 7.1));
series1.getData().add(new Data<>("2000-01-04", 9.0));
series1.getData().add(new Data<>("2000-01-05", 0.0)); // gap (INDEX 4)
series1.getData().add(new Data<>("2000-01-06", 5.32));
series1.getData().add(new Data<>("2000-01-07", 11.0));
series1.getData().add(new Data<>("2000-01-08", 15.3));
series1.getData().add(new Data<>("2000-01-09", 0.0)); // gap (INDEX 8)
series1.getData().add(new Data<>("2000-01-10", 4.82));
series1.getData().add(new Data<>("2000-01-11", 6.82));
CustomLineChart lineChart = new CustomLineChart(xAxis, yAxis, seriesGap);
lineChart.getData().addAll(series0, series1);
root.getChildren().addAll(lineChart);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
class CustomLineChart<X, Y> extends LineChart<X, Y> {
Map<Integer, List<Integer>> seriesGap;
public CustomLineChart(Axis<X> xAxis, Axis<Y> yAxis, Map<Integer, List<Integer>> seriesGap) {
super(xAxis, yAxis);
this.seriesGap = seriesGap;
}
@Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
updatePath();
updateDataPoints();
}
private void updatePath() {
seriesGap.forEach((seriesNo, gapIndexes) -> {
Path path = (Path) lookup(".chart-series-line.series" + seriesNo);
System.out.println(path);
if (!path.getElements().isEmpty()) {
int dataSize = getData().get(seriesNo).getData().size();
int pathEleSize = path.getElements().size();
// Just ensuring we are dealing with right path
if (pathEleSize == dataSize + 1) {
// Build a new path, by jumping the gap points
List<PathElement> newPath = new ArrayList<>();
newPath.add(path.getElements().get(0));
for (int i = 1; i < path.getElements().size(); i++) {
if (gapIndexes.contains(i - 1)) {
LineTo lt = (LineTo) path.getElements().get(i + 1);
newPath.add(new MoveTo(lt.getX(), lt.getY()));
} else {
newPath.add(path.getElements().get(i));
}
}
// Update the new path to the current path.
path.getElements().clear();
path.getElements().addAll(newPath);
}
}
});
}
private void updateDataPoints() {
Group plotContent = (Group) lookup(".plot-content");
seriesGap.forEach((seriesNo, gapIndexes) -> {
// Remove all data points at the gap indexes
gapIndexes.forEach(i -> {
Node n = lookup(".series" + seriesNo + ".data" + i);
if (n != null) {
plotContent.getChildren().remove(n);
}
});
});
}
}
}
我在 JavaFX 8 中使用 XYChart
,我想为指定系列的空单元格显示间隙。当我传递 null 值时,我得到 NullPointerException
:
series.get(index).getData().add(new XYChart.Data<>(Key, null));
我也发现了描述这个问题的错误 https://bugs.openjdk.java.net/browse/JDK-8092134,但我不知道它是否仍然存在。
有谁知道如何解决这个问题?
此致,
迈克尔
很明显,这个功能不包括在内。如果你非常想得到这个行为,你可以试试下面的逻辑。
话虽如此,可以有很多更好的方法,但这个答案是为了让您初步了解如何使用图表的受保护方法调整当前实现。
想法是.. 一旦绘图子布局完成,我们重新计算渲染线路径的逻辑.. 并删除不需要的数据点。如前所述,这仅供参考,如果您有更多数据系列,那么您可能需要相应地工作。
[更新] : 如果您想要每个系列的 paths/data 点,请将“.series”附加到“.chart-series-line”和“.data”样式 类.
请查看以下演示:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.stage.Stage;
import java.util.*;
public class XYChartDemo extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("days");
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("USD");
// AS OF NOW THIS IS THE CORE POINT I AM RELYING ON !! YOU CAN THINK OF A BETTER APPROACH TO IDENTIFY THE GAP POINTS.
List<Integer> s0GapIndexes = Arrays.asList(3, 7);
List<Integer> s1GapIndexes = Arrays.asList(4, 8);
Map<Integer, List<Integer>> seriesGap = new HashMap<>();
seriesGap.put(0, s0GapIndexes);
seriesGap.put(1, s1GapIndexes);
XYChart.Series<String, Double> series0 = new XYChart.Series<>();
series0.getData().add(new Data<>("2000-01-01", 13.2));
series0.getData().add(new Data<>("2000-01-02", 10.1));
series0.getData().add(new Data<>("2000-01-03", 14.1));
series0.getData().add(new Data<>("2000-01-04", 0.0)); // gap (INDEX 3)
series0.getData().add(new Data<>("2000-01-05", 6.3));
series0.getData().add(new Data<>("2000-01-06", 9.82));
series0.getData().add(new Data<>("2000-01-07", 12.82));
series0.getData().add(new Data<>("2000-01-08", 0.0)); // gap (INDEX 7)
series0.getData().add(new Data<>("2000-01-09", 4.82));
series0.getData().add(new Data<>("2000-01-10", 8.82));
series0.getData().add(new Data<>("2000-01-11", 8.82));
XYChart.Series<String, Double> series1 = new XYChart.Series<>();
series1.getData().add(new Data<>("2000-01-01", 20.2));
series1.getData().add(new Data<>("2000-01-02", 14.1));
series1.getData().add(new Data<>("2000-01-03", 7.1));
series1.getData().add(new Data<>("2000-01-04", 9.0));
series1.getData().add(new Data<>("2000-01-05", 0.0)); // gap (INDEX 4)
series1.getData().add(new Data<>("2000-01-06", 5.32));
series1.getData().add(new Data<>("2000-01-07", 11.0));
series1.getData().add(new Data<>("2000-01-08", 15.3));
series1.getData().add(new Data<>("2000-01-09", 0.0)); // gap (INDEX 8)
series1.getData().add(new Data<>("2000-01-10", 4.82));
series1.getData().add(new Data<>("2000-01-11", 6.82));
CustomLineChart lineChart = new CustomLineChart(xAxis, yAxis, seriesGap);
lineChart.getData().addAll(series0, series1);
root.getChildren().addAll(lineChart);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
class CustomLineChart<X, Y> extends LineChart<X, Y> {
Map<Integer, List<Integer>> seriesGap;
public CustomLineChart(Axis<X> xAxis, Axis<Y> yAxis, Map<Integer, List<Integer>> seriesGap) {
super(xAxis, yAxis);
this.seriesGap = seriesGap;
}
@Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
updatePath();
updateDataPoints();
}
private void updatePath() {
seriesGap.forEach((seriesNo, gapIndexes) -> {
Path path = (Path) lookup(".chart-series-line.series" + seriesNo);
System.out.println(path);
if (!path.getElements().isEmpty()) {
int dataSize = getData().get(seriesNo).getData().size();
int pathEleSize = path.getElements().size();
// Just ensuring we are dealing with right path
if (pathEleSize == dataSize + 1) {
// Build a new path, by jumping the gap points
List<PathElement> newPath = new ArrayList<>();
newPath.add(path.getElements().get(0));
for (int i = 1; i < path.getElements().size(); i++) {
if (gapIndexes.contains(i - 1)) {
LineTo lt = (LineTo) path.getElements().get(i + 1);
newPath.add(new MoveTo(lt.getX(), lt.getY()));
} else {
newPath.add(path.getElements().get(i));
}
}
// Update the new path to the current path.
path.getElements().clear();
path.getElements().addAll(newPath);
}
}
});
}
private void updateDataPoints() {
Group plotContent = (Group) lookup(".plot-content");
seriesGap.forEach((seriesNo, gapIndexes) -> {
// Remove all data points at the gap indexes
gapIndexes.forEach(i -> {
Node n = lookup(".series" + seriesNo + ".data" + i);
if (n != null) {
plotContent.getChildren().remove(n);
}
});
});
}
}
}