2

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

    }
}
Mohit Tyagi
  • 2,788
  • 4
  • 17
  • 29
Andrei Sh
  • 113
  • 1
  • 11

2 Answers2

2

Try using just one JFreeChart and one ChartPanel and reuse it for your renderers. Currently you are creating a new chart for each single paint request in each cell.

So these objects should be member objects of your class and uppon request set the data and return the chartPanel.

Edit:

Here is an example. I don't know the exact method signature so I just name the method 'setData':

static class ChartListRenderer implements ListCellRenderer<XYSeriesCollection> {

  private JFreeChart chart = ChartFactory.createXYLineChart("Random", "counts", "samples");
  private ChartPanel chartPanel = new ChartPanel(chart, true, true, true, false, true){
    @Override
    public Dimension getPreferredSize() {
      return new Dimension(W, H);}
  };

  @Override
  public Component getListCellRendererComponent(JList<? extends XYSeriesCollection> chartList, XYSeriesCollection serie, int index,
                                                boolean isSelected, boolean cellHasFocus) {
    chart.setData(serie);
    return chartPanel;
  }
}
PaL
  • 136
  • 2
  • 10
2

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;
            }
        }
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045