【问题标题】:What is the right way to create thumbnail charts?创建缩略图图表的正确方法是什么?
【发布时间】:2017-03-19 09:07:36
【问题描述】:

我正在尝试使用JScrollPane 创建一些数据的缩略图图表,但遇到了性能问题。这个例子有大约 100 个缩略图,每个有 5000 个样本。当我尝试多次向下和向上滚动时,滚动会出现延迟,CPU 负载增加,应用程序内存使用量达到 500 Mb 以上。

有没有办法在不减少数据的情况下避免这个性能问题?

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ThermometerPlot;
import org.jfree.data.general.DefaultValueDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class ThumbnailChartsTest extends JPanel {
private static final int W = 200;
private static final int H = W;
private static final int N = 5000;
private static final Random random = new Random();

private static ChartPanel createPane() {
    final XYSeries series = new XYSeries("Data");
    for (int i = 0; i < random.nextInt(N) + N; i++) {
        series.add(i, random.nextGaussian());
    }
    XYSeriesCollection dataset = new XYSeriesCollection(series);

    JFreeChart chart = ChartFactory.createXYLineChart("Random", "Domain",
        "Range", dataset, PlotOrientation.VERTICAL, false, false, false);
    return new ChartPanel(chart, W, H, W, H, W, H,
            false, true, true, true, true, true);
}

public static void main(final String[] args) {

    EventQueue.invokeLater(new Runnable() {

        @Override
        public void run() {
            JFrame f = new JFrame("Test");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel panel = new JPanel();
            panel.setLayout(new GridLayout(0, 4));
            for (int i=0; i<100; i++){
                panel.add(createPane());
            }

            JScrollPane scrollPane = new JScrollPane(panel,
                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
            f.add(scrollPane);

            f.pack();
            f.setVisible(true);
        }
    });

}
}

编辑:我无法理解一件事:为什么在这种情况下内存使用量仍然很大!请看这个插图。

补充: 我觉得有些误会。

通过监视器 visualVM 的堆大小 启动后的小程序堆大小只有 125 Mb,很酷。 但后来我开始测试:滚动和调整大小多次,越来越多 - 上下,上下,更小的框架和更大的框架。堆大小超过 500 Mb!我想这种情况是不正常的。

补充 #2

现实世界的例子:

我的数据大小只有 2 Mb,用 90 个图表表示(每个图表 2 个系列),一个系列包含 3000 个元素。我已经实现了通过滑块更改数字列。

但是对于这个超过 1.5 GB 的小数据堆大小!

这发生在某些操作之后,例如更改数字列 对于我的 CPU(core 2 duo 2.2GHz),每个绘图表大约需要 4 秒!有了这么大的延迟,很难控制滑块。

更新:

我已将数据下采样到每个缩略图图表 100 个样本。 现在它肯定更快,但仍然存在巨大堆大小的问题。图片上是700Mb以上,不是记录。我很沮丧。

【问题讨论】:

    标签: java swing thumbnails jscrollpane jfreechart


    【解决方案1】:

    使用flyweight pattern 仅呈现可见图表。 JTablerenderers 使用的方法在here 中进行了概述,并在下面的ChartRenderer 中显示。为了说明,每次显示单元格时都会重新创建数据集;滚动、调整大小和切换应用程序以查看效果。虽然这种渲染可以很好地扩展到数万个单元格,但每个图表仍会渲染N 数据点。您可以在Scrollable方法的实现中限制可见单元格的数量getPreferredScrollableViewportSize(),如下所示。

    如何将内存使用量减少到较小的值?

    没有通用的答案,但有几种策略可能会有所帮助:

    • 尽可能早地在程序初始化时编写图表,而不是在渲染时;下面的updated 示例构造了TableModelChartPanel 实例; ChartRenderer 相应更简单。

    • 超过几千点的图表实际上是不可读的;考虑截断大型数据集并仅显示完整数据以响应ListSelectionEvent,如图所示here

    • 平台活动监视器可能会产生误导; profile验证实际结果。

    启动小程序后,它并不大,小于 200 Mb,但在滚动、调整大小等之后,内存使用量达到了超过 600 Mb 的值。为什么?

    下面代码的典型profiler 视图仅显示了适度的使用以及滚动后预期的free/used ratiogarbage collection;您的结果可能会有所不同。

    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.util.Random;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableCellRenderer;
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.data.xy.XYSeries;
    import org.jfree.data.xy.XYSeriesCollection;
    
    /**
     * @see https://stackoverflow.com/a/40445144/230513
     */
    public class ChartTable {
    
        private static final Random R = new Random();
        private static final int N = 5000;
        private static final int W = 200;
        private static final int H = W;
    
        private void display() {
            JFrame f = new JFrame("ChartTable");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            DefaultTableModel model = new DefaultTableModel(
                new String[]{"", "", "", ""}, 0) {
                @Override
                public Class<?> getColumnClass(int columnIndex) {
                    return ChartPanel.class;
                }
            };
            for (int r = 0; r < 25; r++) {
                ChartPanel[] row = new ChartPanel[4];
                for (int c = 0; c < row.length; c++) {
                    final XYSeries series = new XYSeries("Data");
                    int n = R.nextInt(N);
                    for (int i = 0; i < n; i++) {
                        series.add(i, R.nextGaussian());
                    }
                    XYSeriesCollection dataset = new XYSeriesCollection(series);
                    JFreeChart chart = ChartFactory.createXYLineChart(
                        "Random " + series.getItemCount(), "Domain", "Range", dataset);
                    ChartPanel chartPanel = new ChartPanel(chart) {
                        @Override
                        public Dimension getPreferredSize() {
                            return new Dimension(W, H);
                        }
                    };
                    row[c] = chartPanel;
                }
                model.addRow(row);
            }
            JTable table = new JTable(model) {
                @Override
                public Dimension getPreferredScrollableViewportSize() {
                    return new Dimension(4 * W, 2 * H);
                }
            };
            table.setDefaultRenderer(ChartPanel.class, new ChartRenderer());
            table.setRowHeight(W);
            f.add(new JScrollPane(table));
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
        private static class ChartRenderer implements TableCellRenderer {
    
            @Override
            public Component getTableCellRendererComponent(
                JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
                return (ChartPanel) value;
            }
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new ChartTable()::display);
        }
    }
    

    【讨论】:

    • 我无法理解一件事:为什么在这种情况下内存使用量仍然很大!请看这张图——link如何将内存使用量减少到小值?
    • 我已经在上面详细说明了。
    • 但是为什么内存使用量会增长?启动小程序后它并不大,小于 200 Mb,但在滚动、调整大小等之后,内存使用量达到 600 Mb 以上。为什么?
    • 我已经在上面详细说明了。
    • 我再次说明了我的问题。
    猜你喜欢
    • 2010-09-09
    • 2019-11-17
    • 2011-01-18
    • 2010-09-06
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多