Javafx AreaChart效率低 - 如何使滚动更顺畅?

时间:2016-12-13 20:21:50

标签: java javafx

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

enter image description here

事物是显示的图表的最大数量是10,但X轴的长度可能不同。在每张图表中,我使用的是4个系列:

  • 一个黑色蜱
  • 一个红色蜱
  • 一个用于绿色蜱
  • 最后一个发生数据

问题是当图表的xAxis长度达到大约1200时,滚动scrollPane根本不平滑而且几乎不滚动。

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

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

滚动窗格(垂直框(图表))

ETA: 这是独立版本,可以显示平均效率,图表中显示的值更高。 注意:运行此程序可能需要几秒钟才能显示图表。我怎样才能使这些视图滚动得更顺畅呢?

计划类:

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

2 个答案:

答案 0 :(得分:4)

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

如果您生成的节点太多(数万个)并且无法减少该数量,那么您可以使用以下选项:

  1. 使用基于canvas的图表库。示例库是JFreeChart,可以绘制到JavaFX画布。
  2. 预先生成图表并将图像snapshot放在ScrollPane中。如果使用此方法,请确保在图表上关闭animation
  3. 使用virtualized grid,它只呈现当前可见的图表(网格单元格)。
  4. 其他general performance tips(例如启用节点缓存)可能有助于提高性能。

      

    虚拟网格似乎是我的问题的解决方案,但我没有看到水平滚动的任何选项所以在这种情况下,它是无用的

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

    更新

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

    使用示例代码进行更新

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

    无论如何,这里有一个示例,它在虚拟网格视图中显示超过9000个图表。 ChartGridCell和unpad-chart.css是从How to make the chart content area take up the maximum area available to it?的答案派生而来的,并没有真正添加任何感兴趣的东西,除了一些测试代码和数据(它们不是解决方案的真正组成部分)。

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

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

答案 1 :(得分:2)

执行此操作的最佳方法是使用JFreeChart。这与您创建图形的代码一样快速,我只使用了您所使用的相同值,然后在图像中缓冲图形。这意味着您正在渲染图形的图像而不是实际的图形本身。因此,您不要一次性定位数千个数据点,而只是定位图像。

根据你的例子,这是我做的一个例子。

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
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 {
            StackPane p = new StackPane();
            primaryStage.setTitle("Chart Application");
            Label loader = new Label("Loading...");
            loader.setGraphic(new ImageView(new Image("https://media.giphy.com/media/FmcNeI0PnsAKs/giphy.gif")));
            loader.setFont(new javafx.scene.text.Font(35));
            p.setStyle("-fx-background: #FFFFFF;");
            p.getChildren().add(loader);
            StackPane.setAlignment(loader, Pos.CENTER);

            Scene scene = new Scene(p, 600, 600);
            primaryStage.setScene(scene);
            primaryStage.setMaximized(true);

            Task<ArrayList<ImageView>> loadInitial = new Task<ArrayList<ImageView>>() {
                @Override
                public ArrayList<ImageView> call() {
                    ArrayList<ImageView> images = new ArrayList<ImageView>();

                    for (int i = 0; i < 10; i++) {
                        XYSeries data = new XYSeries(1);
                        XYSeries data2 = new XYSeries(2);
                        XYSeries data3 = new XYSeries(3);

                        System.out.println("Calcuating values for graph" + (i + 1));
                        for (int j = 0; j < 2500; j += i + 2) {
                            data.add(j, -0.2);
                            data.add(j, 1);
                            data.add(j + 1, 1);
                            data.add(j + 1, -0.2);
                        }

                        for (int j = 0; j < 2500; j += i + 1) {
                            data2.add(j, -0.2);
                            data2.add(j, 1.5);
                            data2.add(j + 0.01, 1.5);
                            data2.add(j + 0.01, -0.2);
                        }

                        for (int j = 2; j < 2500; j += i + 1) {
                            data3.add(j, -0.2);
                            data3.add(j, 1.2);
                            data3.add(j + 0.01, 1.2);
                            data3.add(j + 0.01, -0.2);
                        }
                        System.out.println("Finished values for graph" + (i + 1));

                        XYSeriesCollection dataset = new XYSeriesCollection();
                        dataset.addSeries(data);
                        dataset.addSeries(data2);
                        dataset.addSeries(data3);

                        JFreeChart chart = ChartFactory.createXYAreaChart("", "", "", dataset, PlotOrientation.VERTICAL, false, false, false);
                        chart.setBackgroundPaint(Color.WHITE);
                        chart.setBorderVisible(false);
                        chart.setAntiAlias(true);

                        XYPlot plot = (XYPlot) chart.getPlot();

                        ValueAxis range = plot.getRangeAxis();
                        range.setLowerMargin(0);
                        range.setUpperMargin(0);
                        range.setVisible(false);

                        ValueAxis domainAxis = plot.getDomainAxis();
                        domainAxis.setLowerMargin(0);
                        domainAxis.setUpperMargin(0);

                        double maxX = 0;

                        for(Object temp : dataset.getSeries()) {
                            double max = ((XYSeries) temp).getMaxX();
                            if(maxX < max)
                                maxX = max;
                        }

                        Long L = Math.round(maxX);
                        int maxVal = Integer.valueOf(L.intValue());
                        int width = maxVal * 16; //Works out length to nice scale.
                                                 //If you want all values to be the same use 40,000 (commented out)

                        if(width > 250000)
                            width = 250000; //Lags out after this

                        System.out.println("Buffering graph" + (i + 1));
                        BufferedImage capture = chart.createBufferedImage(width, 50);
                        //BufferedImage capture = chart.createBufferedImage(40000, 50);
                        System.out.println("Finished buffering graph" + (i + 1));
                        ImageView imageView = new ImageView();
                        Image chartImg = SwingFXUtils.toFXImage(capture, null);
                        imageView.setImage(chartImg);
                        imageView.setCache(true);
                        imageView.setCacheHint(CacheHint.SPEED);

                        images.add(imageView);
                    }

                    System.out.println("Finished all processes. Loading graphs");
                    return images;
                }
            };

            loadInitial.setOnSucceeded(e -> {
                VBox images = new VBox();
                ArrayList<ImageView> result = loadInitial.getValue();
                for(ImageView image : result) {
                    images.getChildren().add(image);
                }

                ScrollPane scrollPane = new ScrollPane(images);
                scrollPane.setStyle("-fx-background: #FFFFFF;");

                scene.setRoot(scrollPane);
            });

            new Thread(loadInitial).start();

            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我还添加了一个加载屏幕,以便应用程序在加载数据时没有抓住。

依赖项为jcommon-1.0.23.jarjfreechart-1.0.19.jarDownload here if you wishmain download place

编辑
值得注意的是,因为您使用的是JavaFX,所以有一个类允许您查看具有更多实用程序的图表,它只是赢了&#39 ; t看起来像你发布的内容。这称为ChartViewer。这将允许您复制图形或放大和缩小等...

唯一的问题是您需要将jfreechart-1.0.19.jar替换为jfreechart-1.0.19-fx.jar并执行此操作,您需要构建jfreechart-1.0.19-fx.jar。如果你之前从未使用过蚂蚁,那就不那么容易了。因此,如果您希望使用此功能,您可以在官方build-fx.xml下载的ant文件夹中构建JFreeChart,或者您可以使用我构建并放置在此google drive文件夹中的文件。