2

I'm trying to create a chart of thumbnails of some data with using JScrollPane, but I encounter performance difficulties. This example has about 100 thumbnails charts with 5000 samples in each one. When I'm trying to scroll down and back to up multiple times, scrolling occurs with delay, CPU load increasing, application memory usage reaches over 500 Mb.

Is there a way to avoid this performance problem without reducing my data?

enter image description here

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

}
}

Edit: I can't understand one thing: Why in this case memory usage still very huge! Please look at this illustration.

image

Addition: I think there is some misunderstanding.

Heap size by monitor visualVM enter image description here After starting applet heap size is only 125 Mb, it's cool. But then I'm starting testing: scrolling and resizing multiple times, more and more -- up and down, up and down, smaller frame and bigger frame. Heap size growing up over 500 Mb! I suppose this situation isn't normal.

Addition #2

Real-world example:

My data has size about only 2 Mb and represented in 90 charts(2 series in each one), one series contain 3000 elements. I've implemented changing number columns by slider. enter image description here

But for this small data heap size growing up over 1.5 GB!

enter image description here

This happens after some actions, changing number columns e.g. For my CPU(core 2 duo 2.2GHz) every drawing table takes time about 4 sec! With this big delay it's hard to control slider.

update:

I've implemented downsampling my data to 100 samples per thumbnail chart. Now It's definitely faster, but problem with HUGE heap size still there. On the picture the one is above 700Mb, and it's not a record. I'm frustrated. enter image description here

Andrei Sh
  • 113
  • 1
  • 11

1 Answers1

5

Use the flyweight pattern to render only visible charts. The approach, used by JTable renderers, is outlined here and shown in the ChartRenderer seen below. For illustration, the dataset is recreated each time a cell is revealed; scroll, resize and switch applications to see the effect. While such rendering scales well into the tens of thousands of cells, each chart still renders N data points. You can limit the number of visible cells in the implementation of the Scrollable method, getPreferredScrollableViewportSize(), as shown below.

How to reduce memory usage to small values?

There is no general answer, but several strategies may prove helpful:

  • Compose charts as early in program initialization as possible, rather than at the time they are rendered; the updated example below constructs a TableModel of ChartPanel instances; the ChartRenderer is correspondingly simpler.

  • Charts having more than a few thousand points are effectively unreadable; consider truncating large datasets and displaying the full data only in response to a ListSelectionEvent, illustrated here.

  • Platform activity monitors may be misleading; profile to verify actual results.

image

After starting applet it's not big, less than 200 Mb, but after scrolling, resizing etc. memory usage reaches values more than 600 Mb. Why?

A typical profiler view of the code below shows only moderate usage and the expected free/used ratio after scrolling and garbage collection; your results may vary.

profile

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);
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I can't understand one thing: why in this case memory usage still very huge! Please look at this illustration -- [link](https://s14.postimg.org/kvg4q7bup/memory_Usage.png) How to reduce memory usage to small values? – Andrei Sh Nov 06 '16 at 08:17
  • I've elaborated above. – trashgod Nov 06 '16 at 13:38
  • But why memory usage growing up? After starting applet it's not big, less than 200 Mb, but after scrolling, resizing etc. memory usage reaches values more than 600 Mb. Why? – Andrei Sh Nov 06 '16 at 14:51
  • I've elaborated above. – trashgod Nov 07 '16 at 12:21
  • I've illustrated my problem again. – Andrei Sh Nov 08 '16 at 12:24
  • What happens when you profile [this version](http://stackoverflow.com/revisions/40445144/3)? When did you evoke gc? – trashgod Nov 08 '16 at 18:50
  • When I'm using your updated version problem still the same, but I didn't tried yet to call system.gc... – Andrei Sh Nov 08 '16 at 19:01
  • I don't know should I try to call system.gc and where I need to do this. – Andrei Sh Nov 08 '16 at 19:06
  • You can't _call_ it; click the profiler's gc button to see what would happen in your app when gc runs. – trashgod Nov 08 '16 at 19:08
  • After clicking GC button, heap size still the same -- over 600 Mb, but used heap decreased to 100 Mb. [illustration](https://s22.postimg.org/r2gwiohnl/heap_GC.png) – Andrei Sh Nov 08 '16 at 19:16
  • Sounds like you'll want to focus on the second bullet above: truncating large datasets until selected for detail view. Please update your question with new code/findings. – trashgod Nov 09 '16 at 03:04