如何在JavaFX LineChart上添加形状 [英] How to add shapes on JavaFX LineChart

查看:153
本文介绍了如何在JavaFX LineChart上添加形状的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将在 LineChart 上添加一些形状。我将 LineChart AnchorPane 放入 StackPane 。我通过从图表系列中获取x和y坐标,将形状添加到 AnchorPane 。以下是示例。

I am going to add some shapes on LineChart. I put LineChart and AnchorPane into the StackPane. I added shapes to AnchorPane by getting x and y coordinates from the chart series. Here is example.

LineChartApp.java

package shapes;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class LineChartApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(new ChartContent()));
        primaryStage.setMaximized(true);
        primaryStage.show();
    }

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

}

ChartContent.java

package shapes;

import java.util.ArrayList;
import java.util.List;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.Node;
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.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;

public class ChartContent extends StackPane {

    private AnchorPane objectsLayer;
    private LineChart<Number, Number> chart;
    private NumberAxis xAxis;
    private NumberAxis yAxis;
    private Series<Number, Number> series = new Series<Number, Number>();
    private int level = 0;
    private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
            { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
            { 1000, 1341, 1211, 1562, 1400, 1600, 1550 }

    };
    private List<Shape> shapes = new ArrayList<Shape>();

    public ChartContent() {

        xAxis = new NumberAxis();
        yAxis = new NumberAxis();

        yAxis.setSide(Side.RIGHT);
        yAxis.setForceZeroInRange(false);

        xAxis.setForceZeroInRange(false);

        chart = new LineChart<Number, Number>(xAxis, yAxis);
        chart.setCreateSymbols(false);
        chart.setLegendVisible(false);
        chart.setAnimated(false);
        chart.setVerticalZeroLineVisible(false);

        Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
                new EventHandler<ActionEvent>() {

                    @Override
                    public void handle(ActionEvent event) {

                        chartRefresh();
                    }
                }));
        timer.setCycleCount(datas.length - 1);
        timer.play();

        objectsLayer = new AnchorPane();
        objectsLayer.prefHeightProperty().bind(heightProperty());
        objectsLayer.prefWidthProperty().bind(widthProperty());

        getChildren().addAll(chart, objectsLayer);
        chartRefresh();
    }

    private void chartRefresh() {

        series.getData().clear();
        if (level < datas.length) {

            for (int i = 0; i < datas[level].length; i++) {
                series.getData().add(
                        new Data<Number, Number>(i, datas[level][i]));
            }
        }
        level++;

        chart.getData().clear();
        chart.getData().add(series);
        series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");

        reDrawShapes(series);
    }

    private void reDrawShapes(Series<Number, Number> series) {

        Node chartPlotBackground = chart.lookup(".chart-plot-background");
        chartPlotBackground.setStyle("-fx-background-color:white");

        Circle circle;
        objectsLayer.getChildren().removeAll(shapes);

        shapes.clear();
        double top = chart.getPadding().getTop(), left = chart.getPadding()
                .getLeft();
        double minX = chartPlotBackground.getBoundsInParent().getMinX();
        double minY = chartPlotBackground.getBoundsInParent().getMinY();

        for (Data<Number, Number> data : series.getData()) {

            circle = new Circle(minX
                    + chart.getXAxis().getDisplayPosition(data.getXValue())
                    + left, minY
                    + chart.getYAxis().getDisplayPosition(data.getYValue())
                    + top, 3, Color.RED);

            shapes.add(circle);
        }

        objectsLayer.getChildren().addAll(shapes);
    }
}

我每五秒刷新一次图表系列并重新绘制形状也是如此。但是在将形状添加到 AnchorPane 后,它们不在我预期的位置。

I am refreshing chart series every five seconds and redrawing its shapes as well. But after the shapes added to the AnchorPane, they are not there where I expect them to be.

预期结果

实际结果

推荐答案

首先,请注意,对于您尝试实现的确切功能,只需在数据上设置一个节点即可完成。

First, note that for the exact functionality you're trying to achieve, this can be done simply by setting a node on the data.

(旁白:可以争辩,并且我认为,使节点成为图表中显示的数据的属性违反了相当多的ev在UI开发中将视图与数据分离的良好实践。 Chart API有许多不好的设计缺陷,imho,这就是其中之一。可能应该有像功能< Data< X,Y>,Node> nodeFactory 图表本身的属性。然而,它就是它。)

(Aside: it could be argued, and I would argue, that making a node a property of the data displayed in the chart violates pretty much every good practice on the separation of view from data in UI development. The Chart API has a number of bad design flaws, imho, and this is one of them. There probably should be something like a Function<Data<X,Y>, Node> nodeFactory property of the Chart itself for this. However, it is what it is.)

private void chartRefresh() {

    series.getData().clear();
    if (level < datas.length) {

        for (int i = 0; i < datas[level].length; i++) {
            Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
            data.setNode(new Circle(3, Color.RED));
            series.getData().add(data);
        }
    }
    level++;

    chart.getData().clear();
    chart.getData().add(series);
    series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");

    // reDrawShapes(series);
}

如果您的节点足够简单以使其在点上居中,这是有效的你需要。

This works if your node is simple enough that centering it on the point is what you need.

如果你想要一些更复杂的东西,但这不起作用,支持的机制是子类化图表类并覆盖 layoutPlotChildren()方法。以下是使用此方法的完整类:

If you want something more complex, for which this doesn't work, the supported mechanism is to subclass the chart class and override the layoutPlotChildren() method. Here's the complete class using this approach:

import java.util.ArrayList;
import java.util.List;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
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.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;

public class ChartContent extends StackPane {

    private LineChart<Number, Number> chart;
    private NumberAxis xAxis;
    private NumberAxis yAxis;
    private Series<Number, Number> series = new Series<Number, Number>();
    private int level = 0;
    private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
            { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
            { 1000, 1341, 1211, 1562, 1400, 1600, 1550 }

    };

    public ChartContent() {

        xAxis = new NumberAxis();
        yAxis = new NumberAxis();

        yAxis.setSide(Side.RIGHT);
        yAxis.setForceZeroInRange(false);

        xAxis.setForceZeroInRange(false);

        chart = new LineChart<Number, Number>(xAxis, yAxis) {

            private List<Shape> shapes = new ArrayList<>();

            @Override
            public void layoutPlotChildren() {
                super.layoutPlotChildren();
                getPlotChildren().removeAll(shapes);
                shapes.clear();
                for (Data<Number, Number> d : series.getData()) {
                    double x = xAxis.getDisplayPosition(d.getXValue());
                    double y = yAxis.getDisplayPosition(d.getYValue());
                    shapes.add(new Circle(x, y, 3, Color.RED));
                }
                getPlotChildren().addAll(shapes);
            }
        };
        chart.setCreateSymbols(false);
        chart.setLegendVisible(false);
        chart.setAnimated(false);
        chart.setVerticalZeroLineVisible(false);

        Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
                new EventHandler<ActionEvent>() {

                    @Override
                    public void handle(ActionEvent event) {

                        chartRefresh();
                    }
                }));
        timer.setCycleCount(datas.length - 1);
        timer.play();

        getChildren().addAll(chart);
        chartRefresh();
    }

    private void chartRefresh() {

        series.getData().clear();
        if (level < datas.length) {

            for (int i = 0; i < datas[level].length; i++) {
                Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
                data.setNode(new Circle(3, Color.RED));
                series.getData().add(data);
            }
        }
        level++;

        chart.getData().clear();
        chart.getData().add(series);
        series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");

    }


}






这导致


This results in

您可以使用此技术,例如,添加最佳拟合线以将曲线图或趋势线添加到折线图等。

You can use this technique to, for example, add best fit lines to scatter plots or trend lines to line charts, etc.

我无法准确说明为什么你使用的代码不起作用,但它对如何管理布局做了几个假设(即的位置>图表 - 情节 - 背景与整体图表本身有关)以及何时进行测量以执行诸如计算从图表坐标到像素的映射轴的比例之类的事情坐标。例如,当数据发生变化并且仅在布局过程开始时重新计算时,不难想象它们会变得无效。记录数据值( data.getXValue() data.getYValue())以及您获得的值来自 Axis.getDisplayValue(...)这些值表明类似于后者的解释可能就是这种情况,因为那些肯定似乎没有产生正确的转换。

I can't tell exactly why the code you used doesn't work, but it makes several assumptions about how the layout is managed (i.e. the location of chart-plot-background in relation to the overall chart itself) and also about when measurements are taken in order to do things like compute the scale in the axes for the mapping from "chart coordinates" to "pixel coordinates". It's not too hard to imagine these becoming invalid when the data changes and only being recalculated at the beginning of the layout process, for example. Logging the "data values" (data.getXValue() and data.getYValue()) alongside the values you get from Axis.getDisplayValue(...) for those values suggests that something akin to the latter explanation may be the case, as those definitely do not seem to produce the correct transformations.

挂钩 layoutPlotChildren()方法更可靠。

这篇关于如何在JavaFX LineChart上添加形状的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆