【问题标题】:JavaFX LineChart or ScatterChart: Connect alternating pointsJavaFX LineChart 或 ScatterChart:连接交替点
【发布时间】:2019-10-09 16:04:36
【问题描述】:

我正在尝试使用 ScatterChart 或 LineChart 来制作各种甘特图。我有一系列事件,我想用一条线连接每个事件的起点和终点。 x 轴是时间,y 轴是递增索引。所以换句话说,如果事件 1 是从 t=1 到 t=5,而事件 2 是从 t=3 到 t=10,我想连接点 (1,1) 和 (5,1),和 (3,2) 和 (10,2),没有其他。

在 StackOverflow 上的其他地方有一个甘特图的实现,我开始使用它,但是在尝试绘制一万个事件时它非常慢。

我知道一种解决方案是为每个事件设置一个 XYChart.Series,这样对于 1000 个事件,图表的数据输入将包含 1000 个 XYChart.Series,每个包含 2 个 XYChart.Data 对象。但就风格而言,这是不可能的,因为我希望这里有多个系列,有不同的颜色。

这里有简单的解决方案吗?我知道在最坏的情况下我可以使用 Paths 来做到这一点,虽然我不确定如何所以我必须对此进行研究,但我不确定这是否是最好的解决方案。

编辑:以下是我正在尝试做的示例。我想让一些东西看起来像这样,但没有垂直线;我希望他们断开连接。

编辑编辑:我修改了数据点,以便它们可以相互重叠,以满足需求的另一个方面。我添加了一个不对折线图的输入点进行排序的策略。在散点图的情况下,这不是必需的或有效的选项,因此如果您来回走动,则必须注释掉chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);

这一行

这是源代码。从一个到另一个就像将LineChartScatterChart 交换一样简单(并注释掉上面提到的行):

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class DummyGanttLineChartApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<Data<Number, Number>> dataList1 = FXCollections.observableArrayList();
        ObservableList<Data<Number, Number>> dataList2 = FXCollections.observableArrayList();

        dataList1.add(new Data<Number, Number>(1, 1));
        dataList1.add(new Data<Number, Number>(2, 1));
        dataList1.add(new Data<Number, Number>(1.5, 2));
        dataList1.add(new Data<Number, Number>(2.5, 2));
        dataList1.add(new Data<Number, Number>(2, 3));
        dataList1.add(new Data<Number, Number>(3, 3));

        dataList2.add(new Data<Number, Number>(1.5, 4));
        dataList2.add(new Data<Number, Number>(2.5, 4));
        dataList2.add(new Data<Number, Number>(2, 5));
        dataList2.add(new Data<Number, Number>(3, 5));
        dataList2.add(new Data<Number, Number>(2.5, 6));
        dataList2.add(new Data<Number, Number>(3.5, 6));

        Series<Number, Number> series1 = new Series<Number, Number>("List 1", dataList1);
        Series<Number, Number> series2 = new Series<Number, Number>("List 2", dataList2);

        ObservableList<Series<Number, Number>> data = FXCollections.observableArrayList();
        data.add(series1);
        data.add(series2);

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();

        xAxis.autoRangingProperty().set(false);
        xAxis.setLowerBound(0);
        xAxis.setUpperBound(5);

        LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis);
        chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);

        chart.getData().addAll(data);

        VBox box = new VBox(10);
        box.getChildren().add(chart);
        primaryStage.setScene(new Scene(box));
        primaryStage.show();

    }
}

这是一个单独的类,它实际上启动了它(我的设置有些奇怪的解决方法。您应该能够在DummyGanttLineChartApp 中使用 main 方法,但我得到一个错误)。不用担心。

import javafx.application.Application;

public class DummyGanttLineChart {

    public static void main(String [] args) {
        Application.launch(DummyGanttLineChartApp.class, args);
    }

}

【问题讨论】:

  • 您能否提供您期望的屏幕截图和一些示例数据。这对于分析所需的行为可能有点帮助。
  • 很公平,完成了。感谢您的反馈:)

标签: javafx


【解决方案1】:

感谢您提供屏幕截图和数据。考虑到您的要求,我认为自定义 ScatterChart 可以帮助您获得所需的行为。

我们的想法是,我们计算出每个唯一 Y 值的最小和最大数据点,并在它们之间画一条线(我使用的是路径)。我从数据点本身中挑选系列颜色并将其应用于路径。这在调整图表大小时也很有效。

注意:我没有测试你提到的性能部分(万次事件),也许你可以试试并更新我的性能:)

以下是我尝试过的快速示例演示:

import javafx.application.Application;
import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class DummyGanttLineChartApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<Data<Number, Number>> dataList1 = FXCollections.observableArrayList();
        ObservableList<Data<Number, Number>> dataList2 = FXCollections.observableArrayList();

        dataList1.add(new Data<Number, Number>(1, 1));
        dataList1.add(new Data<Number, Number>(2, 1));
        dataList1.add(new Data<Number, Number>(1.5, 2));
        dataList1.add(new Data<Number, Number>(2.5, 2));
        dataList1.add(new Data<Number, Number>(3.5, 2)); // Including this to test whether it is picking min and max points ;)
        dataList1.add(new Data<Number, Number>(2, 3));
        dataList1.add(new Data<Number, Number>(3, 3));

        dataList2.add(new Data<Number, Number>(1.5, 4));
        dataList2.add(new Data<Number, Number>(2.5, 4));
        dataList2.add(new Data<Number, Number>(2, 5));
        dataList2.add(new Data<Number, Number>(3, 5));
        dataList2.add(new Data<Number, Number>(2.5, 6));
        dataList2.add(new Data<Number, Number>(3.5, 6));

        Series<Number, Number> series1 = new Series<Number, Number>("List 1", dataList1);
        Series<Number, Number> series2 = new Series<Number, Number>("List 2", dataList2);

        ObservableList<Series<Number, Number>> data = FXCollections.observableArrayList();
        data.add(series1);
        data.add(series2);

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();

        xAxis.autoRangingProperty().set(false);
        xAxis.setLowerBound(0);
        xAxis.setUpperBound(5);

        GanttLineChart chart = new GanttLineChart(xAxis, yAxis);
        chart.getData().addAll(data);

        VBox box = new VBox(10);
        box.getChildren().add(chart);
        primaryStage.setScene(new Scene(box));
        primaryStage.show();

    }

    /**
     * Custom GanttLine chart.
     */
    class GanttLineChart extends ScatterChart<Number, Number> {
        /**
         * References to all paths. Usually one path per series.
         */
        final List<Path> paths = new ArrayList<>();

        public GanttLineChart(@NamedArg("xAxis") Axis<Number> xAxis, @NamedArg("yAxis") Axis<Number> yAxis) {
            super(xAxis, yAxis, FXCollections.observableArrayList());
        }

        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();
            // Ensure to remove previous paths.
            getPlotChildren().removeAll(paths);


            for (XYChart.Series<Number, Number> series : getData()) {
                // Map to keep references of min and max items for each unique Y value.
                final Map<Number, MinMax<XYChart.Data<Number, Number>, XYChart.Data<Number, Number>>> yMap = new HashMap<>();
                for (XYChart.Data<Number, Number> item : series.getData()) {
                    final Number y = item.getYValue();
                    MinMax<XYChart.Data<Number, Number>, XYChart.Data<Number, Number>> bounds = yMap.get(y);
                    if (bounds == null) {
                        bounds = new MinMax<>(item, item);
                        yMap.put(y, bounds);
                    }
                    if (bounds.getMin() != item) {
                        if (compareTo(item.getXValue(), bounds.getMin().getXValue()) < 0) {
                            bounds.setMin(item);
                        }
                    }
                    if (bounds.getMax() != item) {
                        if (compareTo(item.getXValue(), bounds.getMin().getXValue()) > 0) {
                            bounds.setMax(item);
                        }
                    }
                }
                final AtomicBoolean filled = new AtomicBoolean();
                // Construct a path per series.
                final Path path = new Path();
                path.setStrokeWidth(2);
                yMap.forEach((k, v) -> {
                    if (!filled.get()) {
                        Region node = (Region) v.getMin().getNode();
                        node.applyCss();
                        path.setFill(node.getBackground().getFills().get(0).getFill());
                        path.setStroke(path.getFill());
                        filled.set(true);
                    }
                    Bounds minPointBounds = v.getMin().getNode().getBoundsInParent();
                    Bounds maxPointBounds = v.getMax().getNode().getBoundsInParent();
                    double minX = minPointBounds.getMinX() + minPointBounds.getWidth() / 2;
                    double minY = minPointBounds.getMinY() + minPointBounds.getHeight() / 2;
                    double maxX = maxPointBounds.getMinX() + maxPointBounds.getWidth() / 2;
                    double maxY = maxPointBounds.getMinY() + maxPointBounds.getHeight() / 2;
                    // Draw a line in the path from min to max points for each unique Y value.
                    path.getElements().addAll(new MoveTo(minX, minY), new LineTo(maxX, maxY));
                });

                // Add the path to plot children and keep a reference for later use.
                getPlotChildren().add(path);
                paths.add(path);
            }
        }

        private int compareTo(Number n1, Number n2) {
            BigDecimal b1 = new BigDecimal(n1.doubleValue());
            BigDecimal b2 = new BigDecimal(n2.doubleValue());
            return b1.compareTo(b2);
        }

        /**
         * Data object to hold min and max items.
         */
        class MinMax<K, V> {
            private K min;
            private V max;

            public MinMax(@NamedArg("key") K min, @NamedArg("value") V max) {
                this.min = min;
                this.max = max;
            }

            public K getMin() {
                return min;
            }

            public V getMax() {
                return max;
            }

            public void setMin(K min) {
                this.min = min;
            }

            public void setMax(V max) {
                this.max = max;
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-03
    • 1970-01-01
    • 2017-06-16
    • 2013-01-14
    • 2017-08-20
    • 2016-07-08
    • 2012-08-03
    • 2018-05-12
    相关资源
    最近更新 更多