sim.util.media.chart.ChartGenerator.java Source code

Java tutorial

Introduction

Here is the source code for sim.util.media.chart.ChartGenerator.java

Source

/*
  Copyright 2006 by Sean Luke
  Licensed under the Academic Free License version 3.0
  See the file "LICENSE" for more information
*/

package sim.util.media.chart;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.io.*;

// From MASON (cs.gmu.edu/~eclab/projects/mason/)
import sim.util.gui.LabelledList;
import sim.util.gui.NumberTextField;
import sim.util.gui.PropertyField;

// From JFreeChart (jfreechart.org)
import org.jfree.data.xy.*;
import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.event.*;
import org.jfree.chart.plot.*;
import org.jfree.data.general.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.chart.title.*;

// from iText (www.lowagie.com/iText/)
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

/**
   ChartGenerator is a JPanel which displays a chart using the JFreeChart library.  The class is abstract:
   you'll need to use a concrete subclass to build a specific kind of chart.
   The facility allows multiple time series to be displayed at one time, to be exported to PDF,
   and to be dynamically added and removed.
       
   <p>Subclasses need to override at least four methods: getSeriesDataset(), update(), removeSeries(index), and buildChart().
   Note that ChartGenerator has no standard API for <i>adding</i> a series to the chart, nor any standard way to modify
   this series once it has been added.  This is because JFreeChart has non-standard, non-consistent APIs for different
   kinds of charts.  You will need to implement these on a per-chart basis as you see fit.
       
   <p>ChartGenerator displays three regions:
       
   <p><ul>
   <li>The <tt>chart</tt> proper, stored in a <tt>chartPanel</tt>.  This panel is in turn stored in a JScrollPane.
   <li>The <tt>globalAttributes</tt>, a collection of Components on the top-left which control global features
   of the chart (its title, axis labels, etc.)
   <li>The <tt>seriesAttributes</tt>, a scrollable collection of Components on the bottom-left which control features
   of each separate series in the chart.  Each seriesAttribute is associated in turn with a Stoppable (stored in
   the list <tt>stoppables</tt>) which will have its <tt>stop()</tt> method called when the series is deleted from
   the chart.
   </ul>
                
*/

public abstract class ChartGenerator extends JPanel {
    /** A holder for global attributes components */
    protected Box globalAttributes = Box.createVerticalBox();
    /** A holder for series attributes components */
    protected Box seriesAttributes = Box.createVerticalBox();

    public SeriesAttributes getSeriesAttributes(int seriesIndex) {
        Component[] c = seriesAttributes.getComponents();
        return (SeriesAttributes) c[seriesIndex];
    }

    /** The chart */
    protected JFreeChart chart;
    /** The panel which holds and draws the chart */
    protected ChartPanel chartPanel;
    /** The JScrollPane which holdw the ChartPanel */
    protected JScrollPane chartHolder = new JScrollPane();
    /** The JFrame which stores the whole chart.  Set in createFrame(), else null. */
    protected JFrame frame;
    /** The global attributes chart title field. */
    protected PropertyField titleField;
    /** The global attributes domain axis field. */
    protected PropertyField xLabel;
    /** The global attributes range axis field. */
    protected PropertyField yLabel;

    /** The global attributes logarithmic range axis check box. */
    protected JCheckBox yLog;
    /** The global attributes logarithmic domain axis check box. */
    protected JCheckBox xLog;

    public void setXAxisLogScaled(boolean isLogScaled) {
        xLog.setSelected(isLogScaled);
    }

    public boolean isXAxisLogScaled() {
        return xLog.isSelected();
    }

    public void setYAxisLogScaled(boolean isLogScaled) {
        yLog.setSelected(isLogScaled);
    }

    public boolean isYAxisLogScaled() {
        return yLog.isSelected();
    }

    /** Override this to return the JFreeChart data set used by your Chart.  For example, time series charts
    might return the XYSeriesCollection. */
    public abstract AbstractSeriesDataset getSeriesDataset();

    /** Override this to update the chart to reflect new data. */
    public abstract void update();

    /** Override this to remove a series from the chart. */
    public abstract void removeSeries(int index);

    /** Override this to move a series relative to other series. */
    public abstract void moveSeries(int index, boolean up);

    /** Override this to construct the appropriate kind of chart.  This is the first thing called from the constructor; so certain
    of your instance variables may not have been set yet and you may need to set them yourself.  */
    protected abstract void buildChart();

    /** Deletes all series from the chart. */
    public void removeAllSeries() {
        for (int x = getSeriesDataset().getSeriesCount() - 1; x >= 0; x--)
            removeSeries(x);
    }

    /** Prepares the chart to be garbage collected.  If you override this, be sure to call super.quit() */
    public void quit() {
        removeAllSeries();
    }

    /** Returns the ChartPanel holding the chart. */
    public ChartPanel getChartPanel() {
        return chartPanel;
    }

    /** Adds a global attribute panel to the frame */
    public void addGlobalAttribute(Component component) {
        globalAttributes.add(component);
    }

    /** Returns the global attribute panel of the given index. */
    public Component getGlobalAttribute(int index) {
        // at present we have a PDF button and a chart global panel --
        // then the global seriesAttributes start
        return globalAttributes.getComponent(index + 2);
    }

    /** Returns the number of global attribute panels. */
    public int getGlobalAttributeCount() {
        // at present we have a PDF button and a chart global panel --
        // then the global seriesAttributes start
        return globalAttributes.getComponentCount() - 2;
    }

    /** Remooves the global attribute at the given index and returns it. */
    public Component removeGlobalAttribute(int index) {
        Component component = getGlobalAttribute(index);
        globalAttributes.remove(index);
        return component;
    }

    /** Sets the title of the chart (and the window frame). */
    public void setTitle(String title) {
        chart.setTitle(title);
        chart.titleChanged(new TitleChangeEvent(new org.jfree.chart.title.TextTitle(title)));
        if (frame != null)
            frame.setTitle(title);
        titleField.setValue(title);
    }

    /** Returns the title of the chart */
    public String getTitle() {
        return chart.getTitle().getText();
    }

    /** Sets the name of the Range Axis label -- usually this is the Y axis. */
    public void setRangeAxisLabel(String val) {
        XYPlot xyplot = (XYPlot) (chart.getPlot());
        xyplot.getRangeAxis().setLabel(val);
        xyplot.axisChanged(new AxisChangeEvent(xyplot.getRangeAxis()));
        yLabel.setValue(val);
    }

    /** Returns the name of the Range Axis Label -- usually this is the Y axis. */
    public String getRangeAxisLabel() {
        return ((XYPlot) (chart.getPlot())).getRangeAxis().getLabel();
    }

    /** Sets the name of the Domain Axis label  -- usually this is the X axis. */
    public void setDomainAxisLabel(String val) {
        XYPlot xyplot = (XYPlot) (chart.getPlot());
        xyplot.getDomainAxis().setLabel(val);
        xyplot.axisChanged(new AxisChangeEvent(xyplot.getDomainAxis()));
        xLabel.setValue(val);
    }

    /** Returns the name of the Domain Axis label -- usually this is the X axis. */
    public String getDomainAxisLabel() {
        return ((XYPlot) (chart.getPlot())).getDomainAxis().getLabel();
    }

    /** Generates a new ChartGenerator with a blank chart.  Before anything else, buildChart() is called.  */
    public ChartGenerator() {
        // create the chart
        buildChart();

        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
        split.setBorder(new EmptyBorder(0, 0, 0, 0));
        JScrollPane scroll = new JScrollPane();
        JPanel b = new JPanel();
        b.setLayout(new BorderLayout());
        b.add(seriesAttributes, BorderLayout.NORTH);
        b.add(new JPanel(), BorderLayout.CENTER);
        scroll.getViewport().setView(b);
        scroll.setBackground(getBackground());
        scroll.getViewport().setBackground(getBackground());
        JPanel p = new JPanel();
        p.setLayout(new BorderLayout());

        LabelledList list = new LabelledList("Chart");
        globalAttributes.add(list);

        JLabel j = new JLabel("Right-Click or Control-Click");
        j.setFont(j.getFont().deriveFont(10.0f).deriveFont(java.awt.Font.ITALIC));
        list.add(j);
        j = new JLabel("on Chart for More Options");
        j.setFont(j.getFont().deriveFont(10.0f).deriveFont(java.awt.Font.ITALIC));
        list.add(j);

        /*
          titleField = new JTextField();
          titleField.setText(chart.getTitle().getText());
          titleField.addKeyListener(new KeyListener()
          {
          public void keyReleased(KeyEvent keyEvent) {}
          public void keyTyped(KeyEvent keyEvent) {}
          public void keyPressed(KeyEvent keyEvent)
          {
          if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
          {
          setTitle(titleField.getText());
          }
          else if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
          titleField.setText(getTitle());
          }
          });
          titleField.addFocusListener(new FocusAdapter()
          {
          public void focusLost ( FocusEvent e )
          {
          setTitle(titleField.getText());
          }
          });
        */

        titleField = new PropertyField() {
            public String newValue(String newValue) {
                setTitle(newValue);
                getChartPanel().repaint();
                return newValue;
            }
        };
        titleField.setValue(chart.getTitle().getText());

        list.add(new JLabel("Title"), titleField);

        /*
          xLabel = new JTextField();
          xLabel.setText(getDomainAxisLabel());
          xLabel.addKeyListener(new KeyListener()
          {
          public void keyReleased(KeyEvent keyEvent) {}
          public void keyTyped(KeyEvent keyEvent) {}
          public void keyPressed(KeyEvent keyEvent)
          {
          if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
          {
          setDomainAxisLabel(xLabel.getText());
          }
          else if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
          xLabel.setText(getDomainAxisLabel());
          }
          });
          xLabel.addFocusListener(new FocusAdapter()
          {
          public void focusLost ( FocusEvent e )
          {
          setDomainAxisLabel(xLabel.getText());
          }
          });
        */
        xLabel = new PropertyField() {
            public String newValue(String newValue) {
                setDomainAxisLabel(newValue);
                getChartPanel().repaint();
                return newValue;
            }
        };
        xLabel.setValue(getDomainAxisLabel());

        list.add(new JLabel("X Label"), xLabel);

        /*
          yLabel = new JTextField();
          yLabel.setText(getRangeAxisLabel());
          yLabel.addKeyListener(new KeyListener()
          {
          public void keyReleased(KeyEvent keyEvent) {}
          public void keyTyped(KeyEvent keyEvent) {}
          public void keyPressed(KeyEvent keyEvent)
          {
          if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
          {
          setRangeAxisLabel(yLabel.getText());
          }
          else if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
          yLabel.setText(getRangeAxisLabel());
          }
          });
          yLabel.addFocusListener(new FocusAdapter()
          {
          public void focusLost ( FocusEvent e )
          {
          setRangeAxisLabel(yLabel.getText());
          }
          });
        */
        yLabel = new PropertyField() {
            public String newValue(String newValue) {
                setRangeAxisLabel(newValue);
                getChartPanel().repaint();
                return newValue;
            }
        };
        yLabel.setValue(getRangeAxisLabel());

        list.add(new JLabel("Y Label"), yLabel);

        xLog = new JCheckBox();
        xLog.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (xLog.isSelected()) {
                    LogarithmicAxis logAxis = new LogarithmicAxis(xLabel.getValue());
                    logAxis.setStrictValuesFlag(false);
                    chart.getXYPlot().setDomainAxis(logAxis);
                } else
                    chart.getXYPlot().setDomainAxis(new NumberAxis(xLabel.getValue()));
            }
        });
        list.add(new JLabel("Log X axis"), xLog);

        yLog = new JCheckBox();
        yLog.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (yLog.isSelected()) {
                    LogarithmicAxis logAxis = new LogarithmicAxis(yLabel.getValue());
                    logAxis.setStrictValuesFlag(false);
                    chart.getXYPlot().setRangeAxis(logAxis);
                } else
                    chart.getXYPlot().setRangeAxis(new NumberAxis(yLabel.getValue()));
            }
        });
        list.add(new JLabel("Log Y axis"), yLog);

        final JCheckBox legendCheck = new JCheckBox();
        legendCheck.setSelected(false);
        ItemListener il = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    LegendTitle title = new LegendTitle(chart.getXYPlot());
                    title.setLegendItemGraphicPadding(new org.jfree.ui.RectangleInsets(0, 8, 0, 4));
                    chart.addLegend(title);
                } else {
                    chart.removeLegend();
                }
            }
        };
        legendCheck.addItemListener(il);
        list.add(new JLabel("Legend"), legendCheck);

        final JCheckBox aliasCheck = new JCheckBox();
        aliasCheck.setSelected(chart.getAntiAlias());
        il = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                chart.setAntiAlias(e.getStateChange() == ItemEvent.SELECTED);
            }
        };
        aliasCheck.addItemListener(il);
        list.add(new JLabel("Antialias"), aliasCheck);

        JPanel pdfButtonPanel = new JPanel();
        pdfButtonPanel.setLayout(new BorderLayout());
        JButton pdfButton = new JButton("Save as PDF");
        pdfButtonPanel.add(pdfButton, BorderLayout.WEST);
        pdfButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                FileDialog fd = new FileDialog(frame, "Choose PDF file...", FileDialog.SAVE);
                fd.setFile(chart.getTitle().getText() + ".pdf");
                fd.setVisible(true);
                ;
                String fileName = fd.getFile();
                if (fileName != null) {
                    Dimension dim = chartPanel.getPreferredSize();
                    generatePDF(chart, dim.width, dim.height, fd.getDirectory() + fileName);
                }
            }
        });
        globalAttributes.add(pdfButtonPanel);

        // we add into an outer box so we can later on add more global seriesAttributes
        // as the user instructs and still have glue be last
        Box outerAttributes = Box.createVerticalBox();
        outerAttributes.add(globalAttributes);
        outerAttributes.add(Box.createGlue());

        p.add(outerAttributes, BorderLayout.NORTH);
        p.add(scroll, BorderLayout.CENTER);
        p.setMinimumSize(new Dimension(0, 0));
        p.setPreferredSize(new Dimension(200, 0));
        split.setLeftComponent(p);

        chartHolder.setMinimumSize(new Dimension(0, 0));
        split.setRightComponent(chartHolder);
        setLayout(new BorderLayout());
        add(split, BorderLayout.CENTER);

        // set the default to be white, which looks good when printed
        chart.setBackgroundPaint(Color.WHITE);
    }

    /** Returns a JFrame suitable or housing the ChartGenerator.  This frame largely calls chart.quit() when
    the JFrame is being closed. */
    public JFrame createFrame(final sim.display.GUIState state) {
        frame = new JFrame() {
            public void dispose() {
                quit();
                super.dispose();
            }
        };

        // these bugs are tickled by our constant redraw requests.
        frame.addComponentListener(new ComponentAdapter() {
            // Bug in MacOS X Java 1.3.1 requires that we force a repaint.
            public void componentResized(ComponentEvent e) {
                // Utilities.doEnsuredRepaint(ChartGenerator.this);
            }
        });

        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(this, BorderLayout.CENTER);
        frame.setResizable(true);
        frame.pack();
        frame.setTitle(chart.getTitle().getText());
        return frame;
    }

    /* Generates PDF from the chart, saving ou to the given file.  width and height are the
       desired width and height of the chart in points. */
    void generatePDF(JFreeChart chart, int width, int height, String fileName) {
        try {
            Document document = new Document(new com.lowagie.text.Rectangle(width, height));
            PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(fileName));
            document.addAuthor("MASON");
            document.open();
            PdfContentByte cb = writer.getDirectContent();
            PdfTemplate tp = cb.createTemplate(width, height);
            Graphics2D g2 = tp.createGraphics(width, height, new DefaultFontMapper());
            Rectangle2D rectangle2D = new Rectangle2D.Double(0, 0, width, height);
            chart.draw(g2, rectangle2D);
            g2.dispose();
            cb.addTemplate(tp, 0, 0);
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static {
        // quaquaify
        try {
            String version = System.getProperty("java.version");
            // both of the following will generate exceptions if the class doesn't exist, so we're okay
            // trying to put them in the UIManager here
            if (version.startsWith("1.3")) {
                // broken
                //UIManager.put("ColorChooserUI", 
                //      Class.forName("ch.randelshofer.quaqua.Quaqua13ColorChooserUI").getName());
            } else // hope there's no one using 1.2! 
                UIManager.put("ColorChooserUI",
                        Class.forName("ch.randelshofer.quaqua.Quaqua14ColorChooserUI").getName());
        } catch (Exception e) {
        }
    }

}