【问题标题】:How to mix ScatterChart with horizontal and vertical lines如何将散点图与水平线和垂直线混合
【发布时间】:2018-01-15 21:30:06
【问题描述】:

我正在尝试在 ScatterChart 上创建垂直和水平线以及点。我找不到混合它们的方法。

这是我在图表上生成点的代码。

vbox {
    add(ScatterChart(NumberAxis(), NumberAxis()).apply {
        val seriesMap: HashMap<String, XYChart.Series<Number, Number>> = HashMap()

        pointsList
                .map { it.decisionClass }
                .distinct()
                .forEach {
                    seriesMap.put(it, XYChart.Series())
                }

        for (point in pointsList) {
            seriesMap.get(point.decisionClass)?.data(point.axisesValues[0], point.axisesValues[1])
        }

        seriesMap
                .toSortedMap()
                .forEach { key, value ->
                    value.name = key
                    data.add(value)
                }
        (xAxis as NumberAxis).setForceZeroInRange(false)
        (yAxis as NumberAxis).setForceZeroInRange(false)
    })
}

【问题讨论】:

  • 看看你是否可以使用stackoverflow.com/questions/38871202/… 中描述的技术(如果没有,我可以在Java中发布一个方法来做到这一点,但我不知道Kotlin......)
  • 恐怕我需要向您寻求帮助。一个简单的 Java 示例会很棒。

标签: javafx kotlin tornadofx


【解决方案1】:

我不知道 Kotlin,所以这个答案是用 Java 编写的。我认为您可能可以将其翻译为 Kotlin(如果可以,请随时发布另一个答案)。

要向图表添加其他节点,您需要做三件事:

  1. 调用getPlotChildren() 并将新节点添加到图表的“绘图子节点”
  2. 覆盖layoutPlotChildren() 方法以在图表布局时更新节点的位置
  3. 使用getDisplayPosition(...)(在Axis 中定义)从轴上的值获取图表绘图区域坐标系中的位置。

下面的 SSCCE 创建了一个与您在屏幕截图中发布的有点相似的散点图,并添加了门控指定系列的线条(即左侧的线条延伸了图表的高度并通过了最小 x 值系列的;顶部的线延伸图表的宽度,并通过系列的最大 y 值等)。我添加了单选按钮,以便您可以选择哪个系列由线条“界定”。

import java.util.Random;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
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.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class ScatterChartWithLines extends Application {

    private final class ScatterChartWithBoundary extends ScatterChart<Number, Number> {

        private Series<Number, Number> boundedSeries ;

        private final NumberAxis yAxis;
        private final NumberAxis xAxis;
        private final Line leftLine = new Line();
        private final Line rightLine = new Line();
        private final Line topLine = new Line();
        private final Line bottomLine = new Line();
        {
            getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
        }

        private ScatterChartWithBoundary(NumberAxis xAxis, NumberAxis yAxis) {
            super(xAxis, yAxis);
            this.yAxis = yAxis;
            this.xAxis = xAxis;
        }

        @Override
        protected void layoutPlotChildren() {
            super.layoutPlotChildren();
            getPlotChildren().removeAll(leftLine, rightLine, topLine, bottomLine);
            if (boundedSeries != null) {
                getPlotChildren().addAll(leftLine, rightLine, topLine, bottomLine);
                double minX = Double.MAX_VALUE ;
                double minY = Double.MAX_VALUE ;
                double maxX = Double.MIN_VALUE ;
                double maxY = Double.MIN_VALUE ;
                for (Data<Number, Number> d : boundedSeries.getData()) {
                    if (d.getXValue().doubleValue() < minX) minX = d.getXValue().doubleValue() ;
                    if (d.getXValue().doubleValue() > maxX) maxX = d.getXValue().doubleValue() ;
                    if (d.getYValue().doubleValue() < minY) minY = d.getYValue().doubleValue() ;
                    if (d.getYValue().doubleValue() > maxY) maxY = d.getYValue().doubleValue() ;
                }
                positionLineInAxisCoordinates(leftLine, minX, yAxis.getLowerBound(), minX, yAxis.getUpperBound());
                positionLineInAxisCoordinates(rightLine, maxX, yAxis.getLowerBound(), maxX, yAxis.getUpperBound());
                positionLineInAxisCoordinates(bottomLine, xAxis.getLowerBound(), minY, xAxis.getUpperBound(), minY);
                positionLineInAxisCoordinates(topLine, xAxis.getLowerBound(), maxY, xAxis.getUpperBound(), maxY);
            }
        }

        private void positionLineInAxisCoordinates(Line line, double startX, double startY, double endX, double endY) {
            double x0 = xAxis.getDisplayPosition(startX);
            double x1 = xAxis.getDisplayPosition(endX);
            double y0 = yAxis.getDisplayPosition(startY);
            double y1 = yAxis.getDisplayPosition(endY);


            line.setStartX(x0);
            line.setStartY(y0);
            line.setEndX(x1);
            line.setEndY(y1);
        }

        public void setBoundedSeries(Series<Number, Number> boundedSeries) {
            if (! getData().contains(boundedSeries)) {
                throw new IllegalArgumentException("Specified series is not displayed in this chart");
            }
            this.boundedSeries = boundedSeries ;
            requestChartLayout();
        }
    }

    private final Random rng = new Random();

    @Override
    public void start(Stage primaryStage) {
        Series<Number, Number> series1 = new Series<>("Series 1", FXCollections.observableArrayList());
        Series<Number, Number> series2 = new Series<>("Series 2", FXCollections.observableArrayList());
        Series<Number, Number> series3 = new Series<>("Series 3", FXCollections.observableArrayList());

        for (int i = 0 ; i < 40 ; i++) {
            series1.getData().add(new Data<>(rng.nextDouble()*2 + 4, rng.nextDouble()*3 + 2));
            series2.getData().add(new Data<>(rng.nextDouble()*2.5 + 4.75, rng.nextDouble()*1.5 + 2));
            series3.getData().add(new Data<>(rng.nextDouble()*3 + 5, rng.nextDouble()*1.5 + 2.75));         
        }

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        xAxis.setForceZeroInRange(false);
        yAxis.setForceZeroInRange(false);

        ScatterChartWithBoundary chart = new ScatterChartWithBoundary(xAxis, yAxis);

        chart.getData().addAll(series1, series2, series3);

        VBox buttons = new VBox(2);
        ToggleGroup toggleGroup = new ToggleGroup();
        for (Series<Number, Number> series : chart.getData()) { 
            RadioButton rb = new RadioButton(series.getName());
            rb.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
                if (isNowSelected) {
                    chart.setBoundedSeries(series);
                }
            });
            rb.setToggleGroup(toggleGroup);
            buttons.getChildren().add(rb);
        }


        BorderPane root = new BorderPane(chart);
        root.setTop(buttons);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

这是一个典型的结果:

【讨论】:

  • 谢谢,成功了。但是你知道刷新线条的方法吗?假设我异步添加了更多行,每次添加后我希望它们在图表上更新,但不幸的是 layoutPlotChildren 受到保护。
  • @Humberd 您需要为图表子类创建一个命名类,并提供具有更改相应参数功能的方法。调用requestChartLayout() 以在下一个渲染脉冲上布局图表:layoutPlotChildren() 将在布局期间调用。我修改了答案,所以它的结构是这样的。
猜你喜欢
  • 2020-08-23
  • 1970-01-01
  • 2020-07-27
  • 2017-07-16
  • 1970-01-01
  • 1970-01-01
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多