Search code examples
javaresizejfreechartaspect-ratio

Recompute JFreeChart display on ChartPanel resize


I have a chart embedded in a ChartPanel. When the panel is first rendered, everything looks good:

Initial ratio

But when the size changes, the panel content is stretched, making the text harder to read:

Bigger size Smaller size

It's like JFreeChart is pre-rendering the display so it can quickly draw it on screen, which is nice for e.g. PNG export (and I know it's a feature David Gilbert, JFreeChart author, wants to keep), but when the cart size changes too much, the display doesn't look good.

Is there a way to tell JFreeChart to somehow re-render the chart on container resize?

Here is the SSCCE:

public static void main(String[] args) {
    JFrame frm = new JFrame("JFreeChart resizing");
    frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    CategoryDataset ds = new DefaultCategoryDataset();
    JFreeChart chart = ChartFactory.createLineChart("Title", "X axis", "Y axis", ds);
    ChartPanel cp = new ChartPanel(chart);
    frm.getContentPane().add(cp, BorderLayout.CENTER);
    
    frm.pack();
    frm.setVisible(true);
}

Solution

  • Solving Common Layout Problems suggests, "be sure that your component's container uses a layout manager that respects the requested size of the component." Your fragment uses BorderLayout.CENTER, which is a good choice: it allows the the ChartPanel to resize smoothly as the frame is resized, but it ignores the panel's minimum size. Eventually, resampling artifact and distortion appear. As you want to retain the resize behavior, one approach is to set the chart panel's minimum draw width and heigh to zero and (optionally) limit the frame's minimum size accordingly:

    cp.setMinimumDrawWidth(0);
    cp.setMinimumDrawHeight(0);
    // optionally
    f.setMinimumSize(new Dimension(
        cp.getMinimumDrawWidth(),
        cp.getMinimumDrawHeight()));
    

    The draw width and heigh may also be specified in the constructor, as shown here. In the example below, note:

    Preferred size: initial

    Smaller size: smaller

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.data.category.CategoryDataset;
    import org.jfree.data.category.DefaultCategoryDataset;
    
    /**
     * @see https://stackoverflow.com/q/69720552/230513
     */
    public class ChartTest {
    
        private static final int W = 320;
        private static final int H = 240;
    
        private void display() {
            JFrame f = new JFrame("JFreeChart Resizing");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            CategoryDataset ds = new DefaultCategoryDataset();
            JFreeChart chart = ChartFactory.createLineChart(
                "Chart Title", "X axis", "Y axis", ds);
            ChartPanel cp = new ChartPanel(chart) {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(W, H);
                }
            };
            f.add(cp, BorderLayout.CENTER);
            f.add(new JLabel("Java v" + System.getProperty("java.version")
                + "; JFreeChart 1.5.3", JLabel.CENTER), BorderLayout.PAGE_END);
            cp.setMinimumDrawWidth(0);
            cp.setMinimumDrawHeight(0);
            f.setMinimumSize(new Dimension(cp.getMinimumDrawWidth(), cp.getMinimumDrawHeight()));
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new ChartTest()::display);
        }
    }