I don't know Kotlin, so this answer is in Java. I think you can probably translate it to Kotlin (and feel free to post another answer if so).
To add additional nodes to a chart, there are three things you need:
- Call
getPlotChildren()
and add the new nodes to the chart's "plot children"
- Override the
layoutPlotChildren()
method to update the positions of your nodes when the chart is laid out
- Use
getDisplayPosition(...)
, defined in Axis
, to get the location in the coordinate system of the plot area of the chart from a value on the axis.
The following SSCCE creates a scatter chart somewhat similar to the one you posted in the screen shot, and adds lines gating a specified series (i.e. the line on the left extends the height of the chart and passes through the minimum x-value of the series; the line at the top extends the width of the chart, and passes through the maximum y-value of the series, etc). I added radio buttons so you can choose which series is "bounded" by the lines.
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);
}
}
Here is a typical result:
