如何将散点图与水平线和垂直线混合使用

How to mix ScatterChart with horizontal and vertical lines

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

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

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)
    })
}

我不了解 Kotlin,所以这个答案在 Java 中。我认为您可能可以将其翻译成 Kotlin(如果可以,请随时 post 另一个答案)。

要向图表添加额外的节点,您需要做三件事:

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

以下 SSCCE 创建的散点图有点类似于您在屏幕截图中 post 编辑的散点图,并添加了控制指定系列的线(即左侧的线扩展了图表的高度,通过系列的最小 x 值;顶部的线延伸图表的宽度,并通过系列的最大 y 值,等等)。我添加了单选按钮,因此您可以按行选择哪个系列是 "bounded"。

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

这是一个典型的结果: