Java tutorial
/* 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) { } } }