堆叠两个XYCharts时保持轴同步

时间:2018-03-25 12:43:37

标签: javafx

我想将两个不同的XYCharts堆叠为here

但是在这个例子中,轴的边界是相同的,数据是静态的。

就我而言,我有动态数据要绘制:新数据在可用时添加到数据系列中。因此,当新数据到达时,y轴(例如)会更新。

此外,这两个数据集并不完全在同一范围内。

这是第一次尝试:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * Demonstrates how to draw layers of XYCharts.
 * https://forums.oracle.com/forums/thread.jspa?threadID=2435995 "Using StackPane to layer more different type charts"
 */
public class LayeredXyChartsSample extends Application {

    private XYChart.Series<String, Number> barSeries;
    private XYChart.Series<String, Number> lineSeries;


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

    @Override public void start(Stage stage) {
        initSeries();

        // Close the application when the window is closed
        stage.setOnCloseRequest(t -> {
            Platform.exit();
            System.exit(0);
        });

        stage.setScene(
                new Scene(
                        layerCharts(
                                createBarChart(),
                                createLineChart()
                        )
                )
        );
        stage.show();

        updateSeries();
    }

    @SuppressWarnings("unchecked")
    private void initSeries() {
        barSeries = new XYChart.Series(
                FXCollections.observableArrayList(
                        new XYChart.Data("Jan", 2),
                        new XYChart.Data("Feb", 10),
                        new XYChart.Data("Mar", 8),
                        new XYChart.Data("Apr", 4),
                        new XYChart.Data("May", 7),
                        new XYChart.Data("Jun", 5),
                        new XYChart.Data("Jul", 4),
                        new XYChart.Data("Aug", 8),
                        new XYChart.Data("Sep", 16.5),
                        new XYChart.Data("Oct", 13.9),
                        new XYChart.Data("Nov", 17),
                        new XYChart.Data("Dec", 10)
                )
        );

        lineSeries = new XYChart.Series(
                FXCollections.observableArrayList(
                        new XYChart.Data("Jan", 1),
                        new XYChart.Data("Feb", 2),
                        new XYChart.Data("Mar", 1.5),
                        new XYChart.Data("Apr", 3),
                        new XYChart.Data("May", 2.5),
                        new XYChart.Data("Jun", 5),
                        new XYChart.Data("Jul", 4),
                        new XYChart.Data("Aug", 8),
                        new XYChart.Data("Sep", 6.5),
                        new XYChart.Data("Oct", 13),
                        new XYChart.Data("Nov", 10),
                        new XYChart.Data("Dec", 20)
                )
        );
    }

    private void updateSeries() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final int index = i;
                final double value = 20 * Math.random();
                Platform.runLater(() -> {
                    barSeries.getData().remove(0);
                    barSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value));

                    lineSeries.getData().remove(0);
                    lineSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value * 2));
                });
            }

            Platform.exit();
            System.exit(0);
        }).start();
    }

    private NumberAxis createYaxis() {
        final NumberAxis axis = new NumberAxis();
        axis.setAutoRanging(true);
        axis.setPrefWidth(35);
        axis.setMinorTickCount(10);

        axis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(axis) {
            @Override public String toString(Number object) {
                return String.format("%7.2f", object.floatValue());
            }
        });

        return axis;
    }

    @SuppressWarnings("unchecked")
    private BarChart<String, Number> createBarChart() {
        final BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), createYaxis());
        setDefaultChartProperties(chart);
        chart.getData().addAll(barSeries);
        return chart;
    }

    @SuppressWarnings("unchecked")
    private LineChart<String, Number> createLineChart() {
        final LineChart<String, Number> chart = new LineChart<>(new CategoryAxis(), createYaxis());
        setDefaultChartProperties(chart);
        chart.setCreateSymbols(false);
        chart.getData().addAll(lineSeries);
        return chart;
    }

    private void setDefaultChartProperties(final XYChart<String, Number> chart) {
        chart.setLegendVisible(false);
        chart.setAnimated(false);
    }

    @SafeVarargs
    private final StackPane layerCharts(final XYChart<String, Number>... charts) {
        for (int i = 1; i < charts.length; i++) {
            configureOverlayChart(charts[i]);
        }

        StackPane stackpane = new StackPane();
        stackpane.getChildren().addAll(charts);

        return stackpane;
    }

    private void configureOverlayChart(final XYChart<String, Number> chart) {
        chart.setAlternativeRowFillVisible(false);
        chart.setAlternativeColumnFillVisible(false);
        chart.setHorizontalGridLinesVisible(false);
        chart.setVerticalGridLinesVisible(false);
        chart.getXAxis().setVisible(false);
        chart.getYAxis().setVisible(false);

        chart.getStylesheets().addAll(getClass().getResource("/overlay-chart.css").toExternalForm());
    }
}

结果如下:

enter image description here

Y轴看起来不太好:有两个轴,因为它们不再有相同的边界,它们没有正确覆盖。

下一次尝试包括创建单个轴并将其分配给两个图表。一些变化:

  • 创建了一个类变量:private NumberAxis yAxis;
  • createYaxis方法修改如下(它是一个void方法并设置变量):

    private void createYaxis(){     yAxis = new NumberAxis();     yAxis.setAutoRanging(真);     yAxis.setPrefWidth(35);     yAxis.setMinorTickCount(10);

    yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
        @Override public String toString(Number object) {
            return String.format("%7.2f", object.floatValue());
        }
    });
    

    }

  • createYaxis方法结束时调用initSeries方法。

  • 使用相同的yAxis创建图表,例如:

    BarChart chart = new BarChart&lt;&gt;(new CategoryAxis(),yAxis);

现在,yAxis看起来不错,但是一旦折线图的新值超过轴边界,图形就不会以相同的比例显示(请注意,新的线系列值是条形图系列的2倍)值;图表不考虑它。)

enter image description here

所以我的下一步是创建一个BoundAxis类,它接受一个参考轴并在修改参考轴边界时更新它的边界。像这样:

public class BoundAxis<T> extends Axis<T> {

    private final Axis<T> originalAxis;

    public BoundAxis(Axis<T> originalAxis) {
        this.originalAxis = originalAxis;
    }

    @Override
    protected Object autoRange(double length) {
        return originalAxis.autoRange(length); // Compilation error
    }

    @Override
    protected void setRange(Object range, boolean animate) {
        originalAxis.setRange(range, animate); // Compilation error
    }

    @Override
    protected Object getRange() {
        return originalAxis.getRange(); // Compilation error
    }

    @Override
    public double getZeroPosition() {
        return originalAxis.getZeroPosition();
    }

    @Override
    public double getDisplayPosition(T value) {
        return originalAxis.getDisplayPosition(value);
    }

    @Override
    public T getValueForDisplay(double displayPosition) {
        return originalAxis.getValueForDisplay(displayPosition);
    }

    @Override
    public boolean isValueOnAxis(T value) {
        return originalAxis.isValueOnAxis(value);
    }

    @Override
    public double toNumericValue(T value) {
        return originalAxis.toNumericValue(value);
    }

    @Override
    public T toRealValue(double value) {
        return originalAxis.toRealValue(value);
    }

    @Override
    protected List<T> calculateTickValues(double length, Object range) {
        return originalAxis.calculateTickValues(length, range); // Compilation error
    }

    @Override
    protected String getTickMarkLabel(T value) {
        return originalAxis.getTickMarkLabel(value); // Compilation error
    }
}

但这不能编译,因为有一些我无法调用的受保护方法。

最后一件事,我想要一些相当通用的东西:BoundAxis必须扩展Axis所以我不仅可以使用NumberAxis。

修改:此问题与that one

有关

2 个答案:

答案 0 :(得分:0)

这是我找到的解决方案。基本上,我手动设置上限和下限。

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * Demonstrates how to draw layers of XYCharts.
 * https://forums.oracle.com/forums/thread.jspa?threadID=2435995 "Using StackPane to layer more different type charts"
 */
public class LayeredXyChartsSample extends Application {

    private XYChart.Series<String, Number> barSeries;
    private XYChart.Series<String, Number> lineSeries;

    private NumberAxis yAxis;
    private double lowerBound = Double.MAX_VALUE;
    private double upperBound = Double.MIN_VALUE;

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

    @Override public void start(Stage stage) {
        initSeries();

        // Close the application when the window is closed
        stage.setOnCloseRequest(t -> {
            Platform.exit();
            System.exit(0);
        });

        stage.setScene(
                new Scene(
                        layerCharts(
                                createBarChart(),
                                createLineChart()
                        )
                )
        );
        stage.show();

        updateSeries();
    }

    @SuppressWarnings("unchecked")
    private void initSeries() {
        barSeries = new XYChart.Series(
                FXCollections.observableArrayList(
                        new XYChart.Data("Jan", 2),
                        new XYChart.Data("Feb", 10),
                        new XYChart.Data("Mar", 8),
                        new XYChart.Data("Apr", 4),
                        new XYChart.Data("May", 7),
                        new XYChart.Data("Jun", 5),
                        new XYChart.Data("Jul", 4),
                        new XYChart.Data("Aug", 8),
                        new XYChart.Data("Sep", 16.5),
                        new XYChart.Data("Oct", 13.9),
                        new XYChart.Data("Nov", 17),
                        new XYChart.Data("Dec", 10)
                )
        );

        lineSeries = new XYChart.Series(
                FXCollections.observableArrayList(
                        new XYChart.Data("Jan", 1),
                        new XYChart.Data("Feb", 2),
                        new XYChart.Data("Mar", 1.5),
                        new XYChart.Data("Apr", 3),
                        new XYChart.Data("May", 2.5),
                        new XYChart.Data("Jun", 5),
                        new XYChart.Data("Jul", 4),
                        new XYChart.Data("Aug", 8),
                        new XYChart.Data("Sep", 6.5),
                        new XYChart.Data("Oct", 13),
                        new XYChart.Data("Nov", 10),
                        new XYChart.Data("Dec", 20)
                )
        );

        createYaxis();
    }

    private void updateSeries() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final int index = i;
                final double value = 20 * Math.random();

                Platform.runLater(() -> {
                    barSeries.getData().remove(0);
                    barSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value));

                    lineSeries.getData().remove(0);
                    lineSeries.getData().add(new XYChart.Data<>(String.valueOf(index), value * 2));

                    lowerBound = Double.MAX_VALUE;
                    upperBound = Double.MIN_VALUE;

                    for (int j = 0; j < barSeries.getData().size(); j++) {
                        lowerBound = Math.min(lowerBound, barSeries.getData().get(j).getYValue().doubleValue());
                        lowerBound = Math.min(lowerBound, lineSeries.getData().get(j).getYValue().doubleValue());

                        upperBound = Math.max(upperBound, barSeries.getData().get(j).getYValue().doubleValue());
                        upperBound = Math.max(upperBound, lineSeries.getData().get(j).getYValue().doubleValue());
                    }

                    yAxis.setLowerBound(lowerBound);
                    yAxis.setUpperBound(upperBound);
                });
            }

            Platform.exit();
            System.exit(0);
        }).start();
    }

    private void createYaxis() {
        yAxis = new NumberAxis();
        yAxis.setAutoRanging(false);
        yAxis.setPrefWidth(35);
        yAxis.setMinorTickCount(10);
        yAxis.setLowerBound(0);
        yAxis.setUpperBound(20);

        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
            @Override public String toString(Number object) {
                return String.format("%7.2f", object.floatValue());
            }
        });
    }

    @SuppressWarnings("unchecked")
    private BarChart<String, Number> createBarChart() {
        final BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), yAxis);
        setDefaultChartProperties(chart);
        chart.getData().addAll(barSeries);
        return chart;
    }

    @SuppressWarnings("unchecked")
    private LineChart<String, Number> createLineChart() {
        final LineChart<String, Number> chart = new LineChart<>(new CategoryAxis(), yAxis);
        setDefaultChartProperties(chart);
        chart.setCreateSymbols(false);
        chart.getData().addAll(lineSeries);
        return chart;
    }

    private void setDefaultChartProperties(final XYChart<String, Number> chart) {
        chart.setLegendVisible(false);
        chart.setAnimated(false);
    }

    @SafeVarargs
    private final StackPane layerCharts(final XYChart<String, Number>... charts) {
        for (int i = 1; i < charts.length; i++) {
            configureOverlayChart(charts[i]);
        }

        StackPane stackpane = new StackPane();
        stackpane.getChildren().addAll(charts);

        return stackpane;
    }

    private void configureOverlayChart(final XYChart<String, Number> chart) {
        chart.setAlternativeRowFillVisible(false);
        chart.setAlternativeColumnFillVisible(false);
        chart.setHorizontalGridLinesVisible(false);
        chart.setVerticalGridLinesVisible(false);
        chart.getXAxis().setVisible(false);
        chart.getYAxis().setVisible(false);

        chart.getStylesheets().addAll(getClass().getResource("/overlay-chart.css").toExternalForm());
    }
}

结果如下:

enter image description here

答案 1 :(得分:0)

仅需注意一点,不需要编写源代码就可以彼此堆叠图表:您还可以在FXML文件中设置没有事件和线程的属性,如下所示:

<BarChart fx:id="bc_stock_c" alternativeRowFillVisible="false" animated="false" horizontalZeroLineVisible="false" legendVisible="false" prefHeight="200.0" prefWidth="200.0" title="Produkt C" verticalGridLinesVisible="false" verticalZeroLineVisible="false" GridPane.rowIndex="1">
    <xAxis>
        <CategoryAxis side="BOTTOM" />
    </xAxis>
    <yAxis>
        <NumberAxis animated="false" autoRanging="false" lowerBound="0.0" minorTickCount="100" prefHeight="135.0" prefWidth="35.0" side="LEFT" tickUnit="100.0" upperBound="1000.0" />
    </yAxis>
</BarChart>
<LineChart fx:id="lc_demand_c" alternativeRowFillVisible="false" animated="false" createSymbols="false" horizontalGridLinesVisible="false" horizontalZeroLineVisible="false" legendVisible="false" prefHeight="200.0" prefWidth="200.0" stylesheets="@style.css" title=" " verticalGridLinesVisible="false" verticalZeroLineVisible="false" GridPane.rowIndex="1">
    <xAxis>
        <CategoryAxis side="BOTTOM" />
    </xAxis>
    <yAxis>
        <NumberAxis animated="false" autoRanging="false" lowerBound="0.0" minorTickCount="100" prefHeight="135.0" prefWidth="35.0" side="LEFT" tickUnit="100.0" upperBound="1000.0" />
    </yAxis>
</LineChart>

只需为每个Y轴设置pref_width,pref_height,autoRanging,minorTickCount,tickUnit,upperBound和lowerBound,它应该可以工作。您还应该添加以下css样式,以使每个折线图的图表区域透明:

.chart-plot-background {
    -fx-background-color: transparent;
}