当前位置: 首页 > 知识库问答 >
问题:

堆叠两个图表时保持轴同步

须志新
2023-03-14

我想像这里一样堆叠两个不同的XYChart。

然而,在本例中,轴的边界是相同的,数据是静态的。

在我的例子中,我有动态数据要绘制:新值在数据序列可用时添加到数据序列中。因此,当新数据到达时,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());
    }
}

结果看起来像这样:

Y轴看起来不太好:有两个轴,由于它们不再具有相同的边界,因此无法正确覆盖。

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

>

  • 创建一个类变量:私有NumberAxis yAxis;
  • createYax方法修改如下(它是一个无效方法并设置变量):

    私有void createYaxis(){yAxis=newnumberaxis();yAxis.setAutoRanging(true);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());
        }
    });
    

    }

    initSeries方法的末尾调用createYaxis方法。

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

    BarChart图表=新BarChart

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

    因此,我的下一步是创建一个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
        }
    }
    

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

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

    编辑:这个问题与那个问题有关。

  • 共有2个答案

    羊舌墨一
    2023-03-14

    需要注意的是,不需要编写源代码来相互堆叠图表:您还可以在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;
    }
    
    夏嘉德
    2023-03-14

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

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

    以下是结果:

     类似资料:
    • 关于我的图表,我有以下问题/疑问: > 如何防止正确的 y 轴刻度值被部分删除或进入图表内部? 当我使用yValuesTripId作为左和右y轴域的域时,图表绘制得很好。如何使用yAxisFirstStopTimes作为左y轴域值和yAxisLastStopTimes作为右y轴值使其绘制精细? 您可以单击此处查看或编辑图表: 代码如下:

    • 我正在创建一个JFreeChart堆叠区域图表。 我希望我的y轴标签的宽度是固定的,我不想移动图表随着宽度的增加。见说明问题的图像。 我也面临着JFreeChart论坛上发布的类似问题。根据论坛,它是固定的,但它还没有发布。有没有人知道它的解决办法。我们迫不及待地等待下一个版本,有谁知道我们可以应用的黑客吗? 希望能找到解决办法。

    • 柱形图和面积图可以设置成堆叠的形式,堆叠后同一个分类下的数据不再是水平依次排列而是依次从上到下堆叠在一起。 堆叠有两种形式,普通的堆叠和按百分比堆叠;普通堆叠是按照数值大小依次堆叠,百分比堆叠是按照数值所占百分比进行堆叠。 下面是堆叠图和百分比堆叠图例子: 图 5-1 堆叠图 图 5-2 百分比堆叠图 通过指定柱形图或面积图的 stacking 属性即可是图形堆叠,示例代码如下: plotOpit

    • 我试图在3.2.0中创建一个混合堆叠的水平条形图和折线图Charts.js。 预期行为:一个轴在底部的堆叠条,包括轴标题和一个轴在顶部的折线图。 实际行为:底部的所有轴都显示了一个额外的轴,该轴似乎与任何数据集都不对应。不显示轴标题。 注意:代码中有几个条形的值为零,根据生成的数据,这些值可以是非零的。 我的代码: jsfiddle 当前行为的屏幕截图

    • 问题内容: 在我的作业中,第三步是调用方法merge来合并list1中的两个列表,以便list1 保持排序。 我编写了代码,但效果不佳,输出显示错误,因为排序很重要 问题答案: 假设两个列表都已排序,则只需要一个循环: 如果未排序,则需要两个循环:

    • 在这篇 Matplotlib 数据可视化教程中,我们要介绍如何创建堆叠图。 堆叠图用于显示『部分对整体』随时间的关系。 堆叠图基本上类似于饼图,只是随时间而变化。 让我们考虑一个情况,我们一天有 24 小时,我们想看看我们如何花费时间。 我们将我们的活动分为:睡觉,吃饭,工作和玩耍。 我们假设我们要在 5 天的时间内跟踪它,因此我们的初始数据将如下所示: import matplotlib.pyp