Javafx AreaChart效率低——如何让滚动更流畅?

标签 java javafx

在我的项目中,我创建了多个 AreaCharts,如下所示:

enter image description here

问题是显示图表的最大数量为 10,但 X 轴的长度可能会有所不同。在每个图表中,我使用 4 个系列:

  • 一个用于黑色刻度
  • 一个用于红色勾号
  • 一个用于绿色刻度
  • 发生数据的最后一个

问题是当图表的 xAxis 长度达到大约 1200 时,滚动 scrollPane 一点也不流畅,几乎无法滚动。

我的问题是:有没有办法让我的图表显示更高效?

编辑:布局层次结构如下所示:

滚动 Pane (垂直框(图表))

预计到达时间: 这是独立版本,显示平均效率,图表中显示的值数量更多。 注意:运行此程序可能需要几秒钟才能显示图表。我怎样才能使这些 View 的滚动更加顺畅?

程序类:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Program2 extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        try {
            ScrollPane scrollPane = new ScrollPane();
            VBox vBox = new VBox();
            scrollPane.setContent(vBox);
            Scene scene = new Scene(scrollPane, 600, 600);
            primaryStage.setMaximized(true);
            primaryStage.setScene(scene);
            primaryStage.show();
            for (int i = 0; i < 10; i++) {
                CustomChart2 customChart2 = new CustomChart2();
                AreaChart.Series<Number, Number> series = new AreaChart.Series<>();
                for (int j = 0; j < 2500; j += i + 2) {
                    series.getData().add(new XYChart.Data<>(j, -0.2));
                    series.getData().add(new XYChart.Data<>(j, 1));
                    series.getData().add(new XYChart.Data<>(j + 1, 1));
                    series.getData().add(new XYChart.Data<>(j + 1, -0.2));
                }
                customChart2.getData().add(series);
                AreaChart.Series<Number, Number> series2 = new AreaChart.Series<>();
                for (int j = 0; j < 2500; j += i + 1) {
                    series2.getData().add(new XYChart.Data<>(j, -0.2));
                    series2.getData().add(new XYChart.Data<>(j, 1.5));
                    series2.getData().add(new XYChart.Data<>(j + 0.01, 1.5));
                    series2.getData().add(new XYChart.Data<>(j + 0.01, -0.2));
                }
                customChart2.getData().add(series2);
                AreaChart.Series<Number, Number> series3 = new AreaChart.Series<>();
                for (int j = 2; j < 2500; j += i + 1) {
                    series3.getData().add(new XYChart.Data<>(j, -0.2));
                    series3.getData().add(new XYChart.Data<>(j, 1.2));
                    series3.getData().add(new XYChart.Data<>(j + 0.01, 1.2));
                    series3.getData().add(new XYChart.Data<>(j + 0.01, -0.2));
                }
                customChart2.getData().add(series3);
                vBox.getChildren().add(customChart2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

修改后的图表类:

import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;

public class CustomChart2 extends AreaChart<Number, Number> {

    private NumberAxis xAxis;
    private NumberAxis yAxis;

    public CustomChart2() {
        super(new NumberAxis(0, 2500, 5), new NumberAxis(0, 1.5, 1.5));
        this.xAxis = (NumberAxis) getXAxis();
        this.yAxis = (NumberAxis) getYAxis();
        configure();
        configureXAxis();
        configureYAxis();
    }

    private void configure() {
        setAnimated(false);
        setCreateSymbols(false);
        setMinWidth(2500 * 20);
        computeMaxHeight(60);
        setMaxHeight(60);
        maxHeight(60);
        setMinHeight(60);
        setHorizontalGridLinesVisible(false);
        setVerticalGridLinesVisible(false);
        setLegendVisible(false);
        setVerticalZeroLineVisible(false);
        setHorizontalZeroLineVisible(false);
    }

    private void configureXAxis() {
        xAxis.setAutoRanging(false);
    }

    private void configureYAxis() {
        yAxis.setPrefSize(0, 0);
        yAxis.setAutoRanging(false);
        yAxis.setTickMarkVisible(false);
        yAxis.setTickLabelsVisible(false);
        yAxis.setMinorTickVisible(false);
    }
}

最佳答案

您提到的性能问题有点奇怪:图像看起来不太复杂。没有看到 mcve 就很难提供任何建议。 .

如果您生成太多节点(数万个)并且无法减少该数量,您有一些选择:

  1. 使用 canvas基于图表库。一个示例库是 JFreeChart ,它可以绘制到 JavaFX Canvas 上。
  2. 预先生成图表并拍摄图像 snapshot放置在 ScrollPane 内。如果使用此方法,请确保 animation在图表上关闭。
  3. 使用 virtualized grid ,它只呈现当前可见的图表(网格单元格)。

其他general performance tips (例如打开节点缓存),可能有助于提高性能。

Virtualized grid seems to be a solution for my problem, BUT I don't see any options for horizontal scrolling so in this case it's useless

我从未使用过 ControlsFX GridView,但我猜它的工作方式与其他虚拟化控件(如 ListView 或 TableView)相同,它们会在必要时隐式显示适当的滚动条(例如,如果可用高度 >= 首选高度并且可用宽度 < 首选宽度,则不会显示垂直滚动,但会显示水平滚动)。因为滚动行为和显示是隐式的,所以在 GridView 的控件 API 上不需要任何选项。只是一个猜测。

更新

我尝试了 GridView,但不幸的是,它并不像我想的那么简单让水平滚动起作用。隐含地它只是进行垂直滚动,如果有一个 API 可以让水平滚动与当前实现一起工作(我怀疑),它没有记录并且使用起来不直观。添加或记录功能将是一个不错的 feature request请求 ControlsFX,但这现在对您没有帮助...所以也许坚持我的其他性能优化建议,或者想出更多您自己的或来自其他回答者的建议。

使用示例代码更新

所以我修改了 ControlsFX GridView 控件,使其可以使用水平滚动条。可能我做的方式不是它应该做的方式,但它似乎有效。

无论如何,这是一个在虚拟 GridView 中显示超过 9000 个图表的示例。 ChartGridCell 和 unpad-chart.css 源自对 How to make the chart content area take up the maximum area available to it? 的回答。除了一些测试代码和数据(它们并不是解决方案真正不可或缺的)之外,不要真正向这个答案添加任何有趣的东西。

示例代码对每个图表使用相同的系列数据,但您可以将系列数据放在 GridView 项目列表中,并在项目更新时更新图表(类似于图表标题和颜色以及在提供的样本)。

sample

ManyChartsSample.java

import impl.org.controlsfx.skin.FixedWidthGridViewSkin;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.controlsfx.control.GridView;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Random;

public class ManyChartsSample extends Application {
    private static final double FIXED_GRID_WIDTH = 3_000;
    private static final int OVER_9000 = 9001;

    private static final Random r = new Random(42);

    @Override
    public void start(Stage stage) throws URISyntaxException, MalformedURLException {
        GridView<ChartGridCell.CellData> grid = new GridView<>();
        grid.setSkin(new FixedWidthGridViewSkin<>(grid, FIXED_GRID_WIDTH));

        grid.setCellFactory(gridView -> new ChartGridCell());

        grid.setHorizontalCellSpacing(5);
        grid.setVerticalCellSpacing(5);

        grid.setCellHeight(200);
        grid.setCellWidth(200);

        for(int i = 0; i < OVER_9000; i++) {
            Color c1 = new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), 1.0);
            Color c2 = new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), 1.0);
            ChartGridCell.CellData data = new ChartGridCell.CellData(i, c1, c2);

            grid.getItems().add(data);
        }

        grid.setPrefSize(300, 200);

        Scene scene = new Scene(grid);
        scene.getStylesheets().add(
                getClass().getResource("unpad-chart.css").toURI().toURL().toExternalForm()
        );

        stage.setScene(scene);
        stage.show();
    }

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

impl.org.controlsfx.skin.FixedWidthGridViewSkin

package impl.org.controlsfx.skin;

import org.controlsfx.control.GridView;

public class FixedWidthGridViewSkin<T> extends GridViewSkin<T> {

    private final double fixedWidth;

    public FixedWidthGridViewSkin(GridView<T> control, double fixedWidth) {
        super(control);
        this.fixedWidth = fixedWidth;
    }

    @Override public GridRow<T> createCell() {
        GridRow<T> row = new GridRow<>();
        row.setPrefWidth(fixedWidth);
        row.updateGridView(getSkinnable());
        return row;
    }

    protected double computeRowWidth() {
        return Math.max(getSkinnable().getWidth() - 18, fixedWidth);
    }
}

ChartGridCell.java

import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color;
import org.controlsfx.control.GridCell;

public class ChartGridCell extends GridCell<ChartGridCell.CellData> {
    private final AreaChart<Number,Number> chart;

    public static class CellData {

        private final int idx;
        private final Color c1;
        private final Color c2;
        private final Color c1t;
        private final Color c2t;

        public CellData(int idx, Color c1, Color c2) {
            this.idx = idx;
            this.c1 = c1;
            this.c2 = c2;
            c1t = new Color(c1.getRed(), c1.getGreen(), c1.getBlue(), 0.2);
            c2t = new Color(c2.getRed(), c2.getGreen(), c2.getBlue(), 0.2);
        }

        public int getIdx() {
            return idx;
        }

        public Color getC1() {
            return c1;
        }

        public Color getC2() {
            return c2;
        }

        public Color getC1t() {
            return c1t;
        }

        public Color getC2t() {
            return c2t;
        }
    }

    /**
     * Creates a default ColorGridCell instance.
     */
    public ChartGridCell() {
        getStyleClass().add("chart-grid-cell"); //$NON-NLS-1$

        final NumberAxis xAxis = new NumberAxis(1, 31, 1);
        final NumberAxis yAxis = new NumberAxis(0, 28, 1);

        xAxis.setAutoRanging(false);
        xAxis.setMinorTickVisible(false);
        xAxis.setTickMarkVisible(false);
        xAxis.setTickLabelsVisible(false);
        xAxis.setPrefSize(0, 0);
        yAxis.setAutoRanging(false);
        yAxis.setMinorTickVisible(false);
        yAxis.setTickMarkVisible(false);
        yAxis.setTickLabelsVisible(false);
        yAxis.setPrefSize(0, 0);

        chart = new AreaChart<>(xAxis,yAxis);
        chart.setHorizontalGridLinesVisible(false);
        chart.setVerticalGridLinesVisible(false);
        chart.setLegendVisible(false);
        chart.setVerticalZeroLineVisible(false);
        chart.setHorizontalZeroLineVisible(false);
        chart.setAnimated(false);

        XYChart.Series seriesApril= new XYChart.Series();
        seriesApril.setName("April");
        seriesApril.getData().add(new XYChart.Data(1, 4));
        seriesApril.getData().add(new XYChart.Data(3, 10));
        seriesApril.getData().add(new XYChart.Data(6, 15));
        seriesApril.getData().add(new XYChart.Data(9, 8));
        seriesApril.getData().add(new XYChart.Data(12, 5));
        seriesApril.getData().add(new XYChart.Data(15, 18));
        seriesApril.getData().add(new XYChart.Data(18, 15));
        seriesApril.getData().add(new XYChart.Data(21, 13));
        seriesApril.getData().add(new XYChart.Data(24, 19));
        seriesApril.getData().add(new XYChart.Data(27, 21));
        seriesApril.getData().add(new XYChart.Data(30, 21));
        seriesApril.getData().add(new XYChart.Data(31, 19));

        XYChart.Series seriesMay = new XYChart.Series();
        seriesMay.setName("May");
        seriesMay.getData().add(new XYChart.Data(1, 20));
        seriesMay.getData().add(new XYChart.Data(3, 15));
        seriesMay.getData().add(new XYChart.Data(6, 13));
        seriesMay.getData().add(new XYChart.Data(9, 12));
        seriesMay.getData().add(new XYChart.Data(12, 14));
        seriesMay.getData().add(new XYChart.Data(15, 18));
        seriesMay.getData().add(new XYChart.Data(18, 25));
        seriesMay.getData().add(new XYChart.Data(21, 25));
        seriesMay.getData().add(new XYChart.Data(24, 23));
        seriesMay.getData().add(new XYChart.Data(27, 26));
        seriesMay.getData().add(new XYChart.Data(31, 26));

        chart.getData().addAll(seriesApril, seriesMay);
    }

    @Override protected void updateItem(ChartGridCell.CellData item, boolean empty) {
        super.updateItem(item, empty);

        if (empty) {
            setGraphic(null);
        } else {
            chart.setTitle(item.getIdx() + ": " + toRGBCode(item.getC1()));
            chart.setStyle(
                    "CHART_COLOR_1: " + toRGBCode(item.getC1()) + "; " +
                            "CHART_COLOR_1_TRANS_20: " + toRGBCode(item.getC1t()) + ";" +
                            "CHART_COLOR_2: " + toRGBCode(item.getC2()) + "; " +
                            "CHART_COLOR_2_TRANS_20: " + toRGBCode(item.getC2t()) + ";"
            );
            setGraphic(chart);
        }
    }

    private String toRGBCode( Color color ) {
        return String.format( "#%02X%02X%02X%02X",
                (int)( color.getRed() * 255 ),
                (int)( color.getGreen() * 255 ),
                (int)( color.getBlue() * 255 ),
                (int)( color.getOpacity() * 255 )
        );
    }
}

unpad-chart.css

.chart {
    -fx-padding: 0px;
}
.chart-content {
    -fx-padding: 0px;
}
.axis {
    AXIS_COLOR: transparent;
}
.axis:top > .axis-label,
.axis:left > .axis-label {
    -fx-padding: 0;
}
.axis:bottom > .axis-label,
.axis:right > .axis-label {
    -fx-padding: 0;
}

关于Javafx AreaChart效率低——如何让滚动更流畅?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41129914/

相关文章:

java - 如何在 Java 中将表的增量从数据库读取到 Kafka Producer?

Javafx Textfield 文本对齐会阻止字体更改

java - 使用 OpenJFX 的 javafx 应用程序缺少标题栏

applet - 是否可以在 JavaFX 应用程序(桌面)中使用现有的 Applet

java - java中main读取字符串表达式的方法

java - 将 JSF 2.1 与 JSP 2.0 一起使用时出错 : Unable to read TLD from JAR file

java - 错误: Code too Large in Generated Java File

java - path 在 java_home 中列出了 java 6,但是为什么 cmd 对于 java 使用 java 7 而 javac 使用 java 6?

java - 文字移动动画

java - 如何修改tableview上单个单元格的属性