简体   繁体   中英

Why is the parent node of an axis in an XYChart not the chart itself? (JavaFX)

I have been trying to do some work with XYCharts, specifically placing things on or around them. I have been needing to use the coordinates of the bounding boxes of the axes in relation to the coordinate space defined by the whole chart. However, I have realised that the .getBoundsInParent() method is not returning what I expect when applied to the axes. It seems to be because the axis parent isn't the chart and I don't know what the parent is. For example the following boolean is false which to me seems like terrible design (assume chart is an instance of LineChart):

chart.getXAxis().getParent() == chart

what is going on here? Thanks.

There's no direct API to get the bounds of the chart plot area. One approach would be to use lookups, but that is not very robust.

If you want additional content in the chart plot area , the recommended approach is to override the chart's layoutPlotChildren method, as in this question .

Probably the best approach for adding content outside the chart area, is to subclass Region and override the layoutChildren method to layout the chart and the extra content you want. You can determine coordinates for a specific "chart value" by using ValueAxis.getDisplayPosition(value) to get the coordinates relative to the axis, and then use a couple of transformations to get those coordinates relative to the region.

Here is a quick example:

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class LineChartWithRightPanel extends Application {

    private final Color[] RECT_COLORS = 
            new Color[] {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE};
    private final double RECT_WIDTH = 20 ;

    @Override
    public void start(Stage primaryStage) {

        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis(0, 1, 0.2);
        LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);

        Region chartWithPanel = new Region() {

            private List<Rectangle> rectangles = 
                    Stream.of(RECT_COLORS)
                    .map(c -> new Rectangle(RECT_WIDTH, 0, c))
                    .collect(Collectors.toList());

            {
                getChildren().add(chart);
                getChildren().addAll(rectangles);
            }

            @Override
            protected void layoutChildren() {
                double w = getWidth();
                double h = getHeight();
                chart.resizeRelocate(0, 0, w-RECT_WIDTH, h);
                chart.layout();

                double chartMinY = yAxis.getLowerBound();
                double chartMaxY = yAxis.getUpperBound();
                double chartYRange = chartMaxY - chartMinY ;

                for (int i = 0 ; i < rectangles.size(); i++) {

                    // easier ways to do this in this example, 
                    // but this technique shows how to map "chart coords"
                    // to coords in this pane:

                    double rectMinYInChart = chartMinY + i * chartYRange / rectangles.size();
                    double rectMaxYInChart = chartMinY + (i+1) * chartYRange / rectangles.size();
                    double rectMinYInAxis = yAxis.getDisplayPosition(rectMinYInChart);
                    double rectMaxYInAxis = yAxis.getDisplayPosition(rectMaxYInChart);
                    double rectMinY = sceneToLocal(yAxis.localToScene(new Point2D(0, rectMinYInAxis))).getY();
                    double rectMaxY = sceneToLocal(yAxis.localToScene(new Point2D(0, rectMaxYInAxis))).getY();

                    rectangles.get(i).setHeight(Math.abs(rectMaxY - rectMinY));
                    rectangles.get(i).setX(w - RECT_WIDTH);
                    rectangles.get(i).setY(Math.min(rectMinY, rectMaxY));

                    Tooltip.install(rectangles.get(i), 
                            new Tooltip(String.format("%.2f - %.2f", rectMinYInChart, rectMaxYInChart)));
                }
            }
        };

        Series<Number, Number> series = new Series<>();
        series.setName("Data");
        Random rng = new Random();
        for (int i = 0; i < 20; i++) {
            series.getData().add(new Data<>(i, rng.nextDouble()));
        }
        chart.getData().add(series);

        BorderPane root = new BorderPane(chartWithPanel);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }


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

带有彩色带区的图表的屏幕截图

Note that you can get the bounds of the entire plot area using the same idea:

double minXInAxis = xAxis.getDisplayPosition(xAxis.getLowerBound());
double maxXInAxis = xAxis.getDisplayPosition(xAxis.getUpperBound());
double minYInAxis = yAxis.getDisplayPosition(yAxis.getLowerBound());
double maxYInAxis = yAxis.getDisplayPosition(yAxis.getUpperBound());

double minX = sceneToLocal(xAxis.localToScene(new Point2D(minXInAxis,0))).getX();
double maxX = sceneToLocal(xAxis.localToScene(new Point2D(maxXInAxis,0))).getX();
double minY = sceneToLocal(yAxis.localToScene(new Point2D(0,minYInAxis))).getY();
double maxY = sceneToLocal(yAxis.localToScene(new Point2D(0,maxYInAxis))).getY();

Then minX , maxX , minY , maxY define the bounds of the plot area.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM