Repaint performace question

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Alfred63
Posts: 22
Joined: Mon Jun 21, 2004 9:38 pm

Repaint performace question

Post by Alfred63 » Mon Jan 10, 2005 6:49 pm

Hello.

I have to say that JFreeChart is a great toolkit. I'm very impressed with all of the work that has been done and the feature set available.

I have been using the .20 library with great success and now I find myself trying to use JFreeChart in a way that I know you never intended. :)


You see... I'm using JFreeChart as an interactive tool to drill down into data via a mouse click. As feedback to the user, as they move the mouse over the 3d Bars, the bar changes color.

I had this working weeks ago, but the powers that be decided they wanted something else. Now the time has come to revisit this issue and for some reason I cannot get the same performance that I had before.

I'm hoping someone can give me a few pointers on how to squeeze out the most performance when repainting. I realize that JFreeChart by design is not meant for this, as it repaints everything whenever there is the slightest change. Given that... is there anything that I can do to eek out a bit more performance? I'm noticing a 1.5 second delay repainting the bar graph on a linux 700mhz pII box. (which is the targeted low end web browser client) :)

Any ideas?

-Dennis

Code: Select all

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Paint;
import javax.swing.JPanel;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.entity.CategoryItemEntity;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.BarRenderer3D;
import org.jfree.data.CategoryDataset;
import org.jfree.data.DefaultCategoryDataset;
import org.jfree.ui.TextAnchor;


/**
 *
 * @author  dklotz
 */
public class CallQDistributionChart extends JPanel implements TableModelListener, ChartMouseListener, ChartChangeListener
{
    
    private static boolean DEBUG = false;
    
    protected DefaultCategoryDataset dataset = new DefaultCategoryDataset();
    
    private CallQGraphTableModel dbModel;
    private JFreeChart chart;
    private int selectedItem;
    public Color barColor = Color.GREEN;
    
    private ChartPanel chartPanel;
    
    private String sourceColumnName;
    
    private CallQDistributionChartToolTipGenerator tooltips;
    
    private CallQStackedBarRenderer3D renderer; 

    private int oldSelectedItem = -1;

    private Paint oldpaint = null;

    
    static class LabelGenerator extends StandardCategoryLabelGenerator
    {
        public String generateItemLabel (final CategoryDataset dataset,
                                         final int series,
                                         final int category)
        {
            return dataset.getRowKey (series).toString ();
        }
    }
    
    
    /** Creates a new instance of CallQDistributionChart */
    public CallQDistributionChart(CallQGraphTableModel dbModel, String sourceColumnName)
    {
        super();
        
        this.dbModel          = dbModel;
        this.sourceColumnName = sourceColumnName;
        
        setLayout(new BorderLayout());
        
        tooltips = new CallQDistributionChartToolTipGenerator();

        createDataset();
        
        chart = createChart();
        
        chartPanel = new ChartPanel(chart);

        chartPanel.addChartMouseListener (this);
        
        add(chartPanel, BorderLayout.CENTER);
        
    }
    
    public JFreeChart getChart()
    {
        return chart;
    }
    
    protected void removeDataset()
    {
        dataset.clear();
    }
    
    protected void createDataset()
    {
        StringBuffer chartData;
        StringBuffer tooltipData;
        long value;
        
        
        //data[row][0] lower limit of attribut value range (inclusive)
        //data[row][1] upper limit of attribute value range (inclusive)
        //data[row][2] actual minimum value of attribute in the range
        //data[row][3] actual maximum value of attribute in the range
        //data[row][4] number of data values (instances) with attribute values falling
        // within the specified range.

        
        for (int i=0; i<dbModel.getRowCount (); i++)
        {
            if (DEBUG)
            {
                System.out.print ("Min=" + dbModel.getDistributionValueAt (i, 0)); 
                System.out.print (" : Max=" + dbModel.getDistributionValueAt (i, 1)); 
                System.out.print (" : actual Min=" + dbModel.getDistributionValueAt (i, 2));
                System.out.print (" : actual Max=" + dbModel.getDistributionValueAt (i, 3));
                System.out.println (" : Num instances=" + dbModel.getDistributionValueAt (i, 4)); 
            }
            
            chartData    = null;
            chartData    = new StringBuffer(20);
            
            tooltipData  = null;
            tooltipData  = new StringBuffer(20);
            
            value = ((Long)dbModel.getDistributionValueAt (i, 0)).longValue ();
            
            chartData.append (value);
            chartData.append (" - ");
            chartData.append (dbModel.getDistributionValueAt (i, 1));
            
            tooltipData.append ("Actual data range: ");
            tooltipData.append ("(");
            tooltipData.append (dbModel.getDistributionValueAt (i, 2));
            tooltipData.append ("-");
            tooltipData.append (dbModel.getDistributionValueAt (i, 3));
            tooltipData.append (")");
            
            tooltips.addToolTip (tooltipData.toString ());
            
            dataset.addValue (((Long) dbModel.getDistributionValueAt (i, 4)).doubleValue (), chartData.toString (), chartData.toString ()); 
        }
        
    }
    
    public JFreeChart createChart()
    {
        
        String timeLabel = (dbModel.getKeyLevel() == ColDescr.KEY_HOUR?"Time in Hours": "Time in Minutes");

        String chartTitle = "Distribution of " + this.sourceColumnName;
        
        JFreeChart chart = ChartFactory.createBarChart3D (chartTitle,
                                                          "Distribution Range",
                                                          "Number of Calls",  
                                                          dataset, 
                                                          PlotOrientation.VERTICAL,
                                                          false,
                                                          true,
                                                          false);
        
        
        chart.setBackgroundPaint (new GradientPaint (0, 0, Color.white, 0, 1000, CallQBaseUI.backGroundColor));
        
        
        CategoryPlot plot = chart.getCategoryPlot();
        
        plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
        plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
        
        plot.setDomainGridlinesVisible(true);
        plot.setRangeGridlinesVisible(true);
        
        
// modified version to allow greater bar widths when data is arranged
// with 1 category item in a series and there are multiple series...        
        renderer = new CallQStackedBarRenderer3D();
        
        renderer.setLabelGenerator (new CallQDistributionChart.LabelGenerator());
        renderer.setItemLabelsVisible (true);
        renderer.setToolTipGenerator (tooltips);
        
        final ItemLabelPosition p = new ItemLabelPosition (ItemLabelAnchor.OUTSIDE12,
                                                           TextAnchor.CENTER_LEFT,
                                                           TextAnchor.CENTER_LEFT,
                                                           0.0);
               
        renderer.setPositiveItemLabelPosition (p);
        renderer.setItemLabelAnchorOffset (18.0);

        
        final ItemLabelPosition p2 = new ItemLabelPosition (ItemLabelAnchor.OUTSIDE2,
                                                   TextAnchor.CENTER_LEFT,
                                                   TextAnchor.CENTER_LEFT,
                                                   0.0);

        renderer.setPositiveItemLabelPositionFallback (p2);
        renderer.setItemMargin(0.01); // one percent
        
        int numSeries = dataset.getRowCount ();
        for (int i=0; i < numSeries; i++)
        {
            renderer.setSeriesPaint(i, Color.BLUE);
        }
// dk        renderer.setDrawBarOutline (true);
// dk        renderer.setOutlinePaint (Color.BLACK);
        
        plot.setRenderer (renderer);

        final LogarithmicAxis rangeAxis = new LogarithmicAxis("Number of Calls");
        rangeAxis.setAllowNegativesFlag (true);
        rangeAxis.setStrictValuesFlag (false);
        rangeAxis.setLog10TickLabelsFlag (false);
        
        plot.setRangeAxis (rangeAxis);
        
        CategoryAxis axis = plot.getDomainAxis ();
        axis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
        axis.setCategoryMargin(0.30); // Creates a normal sized bar
        
        return chart;
    }
    
    
    
    public void tableChanged (TableModelEvent e)
    {
        removeDataset ();
        createDataset ();
    }

    public ChartPanel getChartPanel()
    {
        return chartPanel;
    }
    
    public void chartMouseClicked (ChartMouseEvent event)
    {
        Object o = event.getEntity ();
        
        if (o instanceof CategoryItemEntity)
        {
            CategoryItemEntity cie = (CategoryItemEntity) o;
            Integer categoryindex;
            int i;
            
            selectedItem = cie.getSeries ();
            
            
            if ((cie.getCategory () != null) &&
                (cie.getCategoryIndex () >= 0))
            {
                i = cie.getCategoryIndex ();
// for future use... do the click drill down event now...
            }
        }
    }
    
    /**
     *  As the mouse is moved over a bar, change its color to red.
     */    
    public void chartMouseMoved (ChartMouseEvent event)
    {
        ChartEntity ce = chartPanel.getEntityForPoint (event.getTrigger ().getX (), event.getTrigger ().getY ());
        
        if ((ce != null) &&
            (ce.getClass () == CategoryItemEntity.class))
        {
            Integer categoryindex;
            int i;

            CategoryItemEntity cie = (CategoryItemEntity) ce;
            selectedItem = cie.getSeries ();
            
            
            if ((cie.getCategory () != null) &&
                (cie.getCategoryIndex () >= 0))
            {
                i = cie.getCategoryIndex ();
                
                categoryindex = new Integer(i);
                
                if (oldSelectedItem != selectedItem)
                {
                    if (oldpaint != null)
                    {
                        event.getChart ().getCategoryPlot ().getRenderer ().setSeriesPaint (oldSelectedItem, oldpaint);
                    }
                    oldSelectedItem = selectedItem;
                    oldpaint = event.getChart ().getCategoryPlot ().getRenderer ().getSeriesPaint (selectedItem);
                    event.getChart ().getCategoryPlot ().getRenderer ().setSeriesPaint (selectedItem, Color.RED);
                }
            }
        }
        else
        {
            if ((oldpaint != null) && 
                (oldSelectedItem != -1))
            {
                event.getChart ().getCategoryPlot ().getRenderer ().setSeriesPaint (oldSelectedItem, oldpaint);
                oldSelectedItem = -1;
                oldpaint = null;
            }
        }
    }
    
    public void chartChanged (ChartChangeEvent ce)
    {
        if (DEBUG)
        {
            System.out.println (this.getClass ().getName () + ": chartChanged: " + ce.toString ());
        }
    }
}

Guest

Post by Guest » Thu May 26, 2005 5:17 pm

You could reduce lots of the redraws by making your own version of the plot (BarChart3D) and the factory to create it. In your new class, remove the the registered listener for the "datasetChanged". Now when you have repopulated the the dataset, you will have to call the plot's listener function since it is not listening any more. As a result, only one datasetchanged event is processed by the plot rather than for each added datapoint. I'm doing this as I have a muti-series dynamic plot that is running as an applet and was getting too much flicker. In my case, I wish that I could do this with the axis listener as well as I have several axis that get updated. Unfortunitly the needed functions are private. At many times java programmers tend to close the doors to others my using "private" methods rather than "protected" as they do not see a reason (in there idea of the propper usage) that it will need to be protected.

skunk

Post by skunk » Fri May 27, 2005 11:43 pm

Did you ever try

Code: Select all

chart.setNotify(false);

// make all changes here
// updates to data etc etc.

chart.setNotify(true);


camps
Posts: 1
Joined: Mon Nov 28, 2005 1:51 pm

Repaint performance with large data > 1000

Post by camps » Mon Nov 28, 2005 2:00 pm

I tried to inscrease the repaint performance by reducing the size of data, it means that the scale of figure is ok only with a zoom, the repaint is better.

A other idea is to create a jpeg image of the drawing, and put it in the Jpanel instead of JGraph panel, so the repaint method only repaint an image.


Fred

Locked