JavaFX LineChart или ScatterChart: подключение чередующихся точек - PullRequest
0 голосов
/ 09 октября 2019

Я пытаюсь использовать 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. Объекты данных. Но по стилю это было бы невозможно, так как я хочу, чтобы здесь было несколько серий с разными цветами.

Есть ли здесь простое решение? Я знаю, что в худшем случае я могу сделать это с помощью Paths, хотя я не уверен, каким образом мне придется исследовать это, но я не уверен, является ли это лучшим решением.

РЕДАКТИРОВАТЬ:Ниже приведены примеры того, что я пытаюсь сделать. Я хочу, чтобы что-то выглядело так, но без вертикальных линий;Я хочу, чтобы они были отключены.

РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ: Я изменил точки данных, чтобы они могли перекрывать друг друга, для другого аспекта требований. Я добавил политику, чтобы не сортировать точки ввода для линейного графика. Это не требуется или не подходит для случая, когда это точечная диаграмма, поэтому, если вы идете туда-сюда, вам нужно закомментировать строку chart.setAxisSortingPolicy(LineChart.SortingPolicy.NONE);

enter image description here enter image description here Вот исходный код. Переход от одного к другому так же прост, как замена LineChart на ScatterChart (и комментирование упомянутой выше строки):

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();

    }
}

Это отдельный класс, который фактически запускает это (обходной путь длякакая-то странность с моей настройкой. Вы должны иметь возможность использовать метод main внутри DummyGanttLineChartApp, но я получаю ошибку). Не беспокойся об этом.

import javafx.application.Application;

public class DummyGanttLineChart {

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

}

1 Ответ

0 голосов
/ 11 октября 2019

Спасибо за предоставление скриншота и данных. Учитывая ваше требование, я думаю, что настройка ScatterChart может помочь вам получить желаемое поведение.

Идея состоит в том, что мы вычисляем минимальные и максимальные точки данных для каждого уникального значения Y и рисуем линию между ними (яиспользуя путь). Я выбираю цвет серии из самих точек данных и применяю его на Пути. Это хорошо работает и при изменении размера диаграммы.

Примечание: я не тестировал часть производительности, как вы упомянули (десять тысяч событий), возможно, вы можете попробовать и сообщить мне опроизводительность:)

Ниже приведен пример демонстрации того, что я пробовал:

enter image description here

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;
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...