javaswingjfreechartjlistlistcellrenderer

OutOfMemoryError while rendering JList content in a ListCellRenderer


I'm trying to represent some data in charts of JList. Before I created JList of ChartPanels here. For now I'm trying JList of XYSeriesCollection and then render its content in a custom ListCellRenderer.

But I encountered OutOfMemoryError when I'm trying scroll the JScrollpane by dragging a cursor.

Java Virtual Machine Options: -d64 -Xmx400m

Why this happen? What should I change in rendering?

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.Random;

import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;

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 ThumbnailChartsJList{

public static JScrollPane scrollPane;
public static JList<XYSeriesCollection> chartList;

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

    EventQueue.invokeLater(new Runnable() {

        @Override
        public void run() {
            JFrame f = new JFrame("Test");
            JPanel panel = new JPanel(new BorderLayout());

            chartList = new ChartList();

            scrollPane = new JScrollPane(chartList,
                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);


            panel.add(scrollPane, BorderLayout.CENTER);
            f.setPreferredSize(new Dimension(1000,700));
            f.setContentPane(panel);
            f.pack();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setVisible(true);
        }
    });

}

}

class ChartList extends JList<XYSeriesCollection>{

    private static final Random random = new Random();
    private static final int NumberCharts = 50;
    private static final int Samples = 200;

    private static final int W = 300;
    private static final int H = W;

    ChartList(){
        DefaultListModel<XYSeriesCollection> listModel = new DefaultListModel<XYSeriesCollection>();


        for (int i=0; i<NumberCharts; i++){
            XYSeriesCollection serie = createRandomSerie();
            listModel.addElement(serie);
        }
        this.setModel(listModel);
        this.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        this.setCellRenderer(new ChartListRenderer());
        this.setVisibleRowCount(0); //0 - dynamic rows
        this.setLayoutOrientation(JList.HORIZONTAL_WRAP);
        this.setSelectionForeground(Color.RED);
    }

    private static XYSeriesCollection createRandomSerie() {

        final XYSeries series = new XYSeries("Data");
        for (int i = 0; i < random.nextInt(Samples) + Samples; i++) {
            series.add(i, random.nextGaussian());
        }
        XYSeriesCollection dataset = new XYSeriesCollection(series);
        return dataset;    
    }

    static class ChartListRenderer implements ListCellRenderer<XYSeriesCollection> {

        @Override
        public Component getListCellRendererComponent(JList<? extends XYSeriesCollection> chartList, XYSeriesCollection serie, int index,
            boolean isSelected, boolean cellHasFocus) {

            JFreeChart chart = ChartFactory.createXYLineChart("Random", "counts", "samples", serie);

            ChartPanel chartPanel = new ChartPanel(chart, true, true, true, false, true){
                @Override
                public Dimension getPreferredSize() {
                return new Dimension(W, H);}
            };
            return chartPanel;
        }

    }
}

Solution

  • As @PaL suggests here, your ListCellRenderer can create a single instance of JFreeChart and its enclosing ChartPanel. When the renderer's getListCellRendererComponent() implementation is called, it can simply update the dataset used by the chart's plot. The result runs easily in the samll heap shown.

    @Override
    public Component getListCellRendererComponent(
        JList modelList, XYSeriesCollection series,
            int index, boolean isSelected, boolean hasFocus) {
        XYPlot plot = chart.getXYPlot();
        plot.setDataset(series);
        return chartPanel;
    }
    

    image

    As tested: $ java -d64 -Xmx64m -cp .:$JFREE_LIB/* ModelListTest

    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.util.Random;
    
    import javax.swing.DefaultListModel;
    import javax.swing.JFrame;
    import javax.swing.JList;
    import javax.swing.JScrollPane;
    import javax.swing.ListCellRenderer;
    import javax.swing.ListSelectionModel;
    
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.data.xy.XYSeries;
    import org.jfree.data.xy.XYSeriesCollection;
    
    /**
     * @see https://stackoverflow.com/q/46527131/230513
     * @see https://stackoverflow.com/a/40445144/230513
     */
    public class ModelListTest {
    
        public static void main(final String[] args) {
    
            EventQueue.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    JFrame f = new JFrame("Test");
                    f.add(new JScrollPane(new ModelList()));
                    f.pack();
                    f.setSize(640, 480);
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    
        private static class ModelList extends JList<XYSeriesCollection> {
    
            private static final Random random = new Random();
            private static final int charts = 50;
            private static final int samples = 200;
            private static final int W = 300;
            private static final int H = W;
    
            ModelList() {
                DefaultListModel<XYSeriesCollection> listModel = new DefaultListModel<>();
                for (int i = 0; i < charts; i++) {
                    listModel.addElement(createRandomSeries());
                }
                this.setModel(listModel);
                this.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
                this.setCellRenderer(new ModelListRenderer());
                this.setVisibleRowCount(0);
                this.setLayoutOrientation(JList.HORIZONTAL_WRAP);
                this.setSelectionForeground(Color.RED);
            }
    
            private static XYSeriesCollection createRandomSeries() {
                final XYSeries series = new XYSeries("Data");
                for (int i = 0; i < random.nextInt(samples) + samples; i++) {
                    series.add(i, random.nextGaussian());
                }
                XYSeriesCollection dataset = new XYSeriesCollection(series);
                return dataset;
            }
    
            private static class ModelListRenderer implements ListCellRenderer<XYSeriesCollection> {
    
                private JFreeChart chart;
                private ChartPanel chartPanel;
    
                public ModelListRenderer() {
                    chart = ChartFactory.createXYLineChart("Random", "counts", "samples", null);
                    chartPanel = new ChartPanel(chart, true, true, true, false, true) {
                        @Override
                        public Dimension getPreferredSize() {
                            return new Dimension(W, H);
                        }
                    };
                }
    
                @Override
                public Component getListCellRendererComponent(
                    JList modelList, XYSeriesCollection series, int index, boolean isSelected, boolean hasFocus) {
                    XYPlot plot = chart.getXYPlot();
                    plot.setDataset(series);
                    return chartPanel;
                }
            }
        }
    }