/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Plotter.java * Created: Sep 15, 2002 * By: LEvans */ package org.openquark.cal.samples.plotter; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Toolkit; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.UIManager; /** * A Plotter is a component which is capable of displaying a plotted diagram. * A PlotDiagram represents the plotted diagram and this is displayed by a * PlotPanel. * PlotDiagrams can be updated on-the-fly and the PlotPanel associated with * a diagram will update to show the changes. * * TODO: * -- Initialise charting extents (and optionally lock) * - Axis control * - Grid control * * <p> * Creation: Sep 17, 2002 at 12:05:49 AM */ public class Plotter extends JFrame { private static final long serialVersionUID = 7617414987956545587L; private static Font areaFont = new Font("Dialog", Font.PLAIN, 10); private JPanel ivjJFrameContentPane = null; private JScrollPane ivjScrollPane = null; private PlotPanel ivjPlotPanel = null; private Rectangle range; // Initial/limited private boolean rangeLocked; // Whether limited /** * Construct a Plotter from a title string * @param title java.lang.String */ public Plotter(String title) { super(); initialize(); setTitle(title); } /** * Construct a Plotter from a title string,a range and whether locked * @param title java.lang.String * @param range the initial/fixed range * @param rangeLocked whether the range is locked (true) or allowed to expand (false) */ public Plotter(String title, Rectangle range, boolean rangeLocked) { this.range = range; this.rangeLocked = rangeLocked; initialize(); setTitle(title); } /** * Returns the scroll pane. * @return JScrollPane */ private JScrollPane getScrollPane() { if (ivjScrollPane == null) { ivjScrollPane = new JScrollPane(); ivjScrollPane.setBackground(Color.white); ivjScrollPane.setViewportView(getPanel()); } return ivjScrollPane; } /** * Returns the plot panel. * @return JPanel */ private JPanel getPanel() { if (ivjPlotPanel == null) { if (range != null) { ivjPlotPanel = new PlotPanel(range, rangeLocked); } else { ivjPlotPanel = new PlotPanel(); } ivjPlotPanel.setSize(0, 0); ivjPlotPanel.setPreferredSize(new Dimension(0, 0)); ivjPlotPanel.setBackground(Color.white); } return ivjPlotPanel; } /** * Return the JFrameContentPane property value. * @return javax.swing.JPanel */ /* WARNING: THIS METHOD WILL BE REGENERATED. */ private javax.swing.JPanel getJFrameContentPane() { if (ivjJFrameContentPane == null) { try { // user code begin {1} ivjJFrameContentPane = new JPanel(); ivjJFrameContentPane.setName("JFrameContentPane"); ivjJFrameContentPane.setLayout(new java.awt.BorderLayout()); ivjJFrameContentPane.add(getScrollPane(), "Center"); // user code end } catch (java.lang.Throwable ivjExc) { // user code begin {2} // user code end handleException(ivjExc); } } return ivjJFrameContentPane; } /** * Called whenever the part throws an exception. * @param exception java.lang.Throwable */ private void handleException(java.lang.Throwable exception) { /* Uncomment the following lines to print uncaught exceptions to stdout */ System.out.println("--------- UNCAUGHT EXCEPTION ---------"); exception.printStackTrace(System.out); } /** * Initializes connections * @exception java.lang.Exception The exception description. */ /* WARNING: THIS METHOD WILL BE REGENERATED. */ private void initConnections() throws java.lang.Exception { } /** * Initialize the class. */ private void initialize() { try { /* Calculate the screen size */ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // Center frame on the screen // Shrink if needbe //pack(); // Get size, enforce maximum Dimension frameSize = new Dimension((int) (screenSize.width * 0.5), (int) (screenSize.height * 0.5)); setSize(frameSize); // Set the location setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); setVisible(true); // Set the icon //setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/Resources/gemscope.gif"))); setName("CALPlotter"); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setTitle("Plotter"); setContentPane(getJFrameContentPane()); initConnections(); validate(); repaint(); } catch (java.lang.Throwable ivjExc) { handleException(ivjExc); } } /** * Starts the application. * @param args an array of command-line arguments */ public static void main(java.lang.String[] args) { try { /* Set native look and feel */ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // Make stuff even more like Windows if (UIManager.getSystemLookAndFeelClassName().endsWith("WindowsLookAndFeel")) { Font systemPlain11font = new Font("System", Font.PLAIN, 11); //set menu fonts UIManager.put("Menu.font", systemPlain11font); UIManager.put("MenuItem.font", systemPlain11font); UIManager.put("CheckBoxMenuItem.font", systemPlain11font); UIManager.put("RadioButtonMenuItem.font", systemPlain11font); //set fonts for buttons & check boxes UIManager.put("Button.font", systemPlain11font); UIManager.put("RadioButton.font", systemPlain11font); UIManager.put("ToggleButton.font", systemPlain11font); UIManager.put("CheckBox.font", systemPlain11font); //set fonts for text UIManager.put("Label.font", systemPlain11font); UIManager.put("TabbedPane.font", systemPlain11font); UIManager.put("List.font", systemPlain11font); UIManager.put("Tree.font", systemPlain11font); UIManager.put("TextField.font", systemPlain11font); UIManager.put("TextArea.font", systemPlain11font); UIManager.put("PasswordField.font", systemPlain11font); UIManager.put("ComboBox.font", systemPlain11font); UIManager.put("Slider.font", systemPlain11font); } // Create the frame // Plotter plotter = new Plotter("Test Plotter"); /// Add a windowListener for the windowClosedEvent plotter.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosed(java.awt.event.WindowEvent e) { System.exit(0); } }); plotter.repaint(); // Run some nice test plots plotter.drawTest(3); } catch (Throwable exception) { System.err.println("Exception occured in main."); exception.printStackTrace(System.out); } } /** * Get the PlotDocument in use */ public PlotDocument getPlotDocument() { // Get the plot document PlotPanel plotPanel = (PlotPanel) ivjScrollPane.getViewport().getView(); return plotPanel.getPlotDocument(); } /** * Perform a draw test * @param testNumber the test number */ private void drawTest(int testNumber) { // Get the plot document PlotDocument plotDoc = getPlotDocument(); switch (testNumber) { case 0 : default : // Draw a diagonal line from -200 to 200 with point plots only // To optimise plotter page sizing, we plot the end points first plotDoc.plotPoint(-200, -200, Color.black); plotDoc.plotPoint(200, 200, Color.black); plotDoc.plotPoint(-199, -199, Color.red); for (int i = 0; i < 199; i++) { plotDoc.plotPoint(i, i, Color.red); } break; case 1: // Draw some rectangles at -100, plotDoc.plotRect(-200, -200, 200, 200, Color.red); plotDoc.plotRect(100, 100, 100, 100, Color.blue); break; case 2: // Draw some rectangles at -100, plotDoc.plotSector(50, 50, 100, 0, 45, Color.red); plotDoc.plotSector(50, 50, 100, 180, 45, Color.blue); break; case 3: // Draw some box labels plotDoc.plotRect(-400,-400,10,10); plotDoc.plotRect(400,400,10,10); plotDoc.plotBoxLabel(50, 50, 100, 80, 80, "Hello", Color.red, Color.yellow, Color.black); plotDoc.plotBoxLabel(-50, -50, 100, 80, 10, "Goodbye", Color.blue, Color.white, Color.black); } } /** * A PlotterException is thrown on errors within the Plotter * * <p> * Creation: Sep 15, 2002 at 6:24:31 PM */ public class PlotterException extends Exception { private static final long serialVersionUID = -6385354219917954488L; /** * Constructor for PlotterException. */ public PlotterException() { super(); } /** * Constructor for PlotterException. * @param message */ public PlotterException(String message) { super(message); } /** * Constructor for PlotterException. * @param message * @param cause */ public PlotterException(String message, Throwable cause) { super(message, cause); } /** * Constructor for PlotterException. * @param cause */ public PlotterException(Throwable cause) { super(cause); } } /** * Drawable defines the interface for all things which can draw themselves * as part of a plot. * * <p> * Creation: Sep 15, 2002 at 7:50:23 PM */ public interface Drawable { /** * Draw the representation of this 'plot' into the given * graphics context * @param gc the graphics context */ public void draw(Graphics2D gc); /** * Get the extents of this Drawable (i.e. enclosing rectangle) * @return Rectangle the enclosing extents rectangle */ public Rectangle getExtents(); /** * Get the origin of this Drawable. This is the x,y from the * extents (without size information) * @return Point the origin point */ public Point getOrigin(); } /** * This is the listener interface for classes wishing to listen * to PlotDocument changes. * * <p> * Creation: Sep 15, 2002 at 8:12:11 PM */ public interface PlotDocumentListener { /** * Informs the listener that the full extents of the PlotDocument * have changed when a document addition occured * @param plotObject object causing the update */ public void update(Drawable plotObject); /** * Informs the listener that the full extents of the PlotDocument * have changed when a document addition occured * @param plotObject object causing the update * @param newExtents the new document extents (minX, minY, width, height) */ public void updateWithExtentsChange(Drawable plotObject, Rectangle newExtents); } /** * A PlotDocument represents a plot diagram. These can be built up * by a seried of plot operations. * * <p> * Creation: Sep 15, 2002 at 6:50:49 PM */ public class PlotDocument implements Drawable { /** * The representation of a single point of given colour. * * <p> * Creation: Sep 15, 2002 at 7:11:46 PM */ public class PlotPoint implements Drawable { private int x, y; // location private Color colour; /** * Create a point of given colour * @param x the x coord (document coords) * @param y the y coord (documnet coords) * @param colour the colour of the point */ public PlotPoint(int x, int y, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the point gc.setColor(colour); // Points are lines with no length gc.drawLine(x, y, x, y); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // A point has extents at x,y and no width/height return new Rectangle(x, y, 0, 0); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // A point has origin at x,y return new Point(x, y); } } /** * The representation of a circle of given colour. * * <p> * Creation: Sep 15, 2002 at 7:11:46 PM */ public class PlotCircle implements Drawable { private int x, y, r; // location, radius private Color colour; /** * Create a point of given colour * @param x the x coord of the centre (document coords) * @param y the y coord of the centre (document coords) * @param r the radius (documnet coords) * @param colour the colour of the point */ public PlotCircle(int x, int y, int r, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.r = r; this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the circle gc.setColor(colour); // Draw circle gc.drawOval(x - r, y - r, r * 2, r * 2); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // The circle lives at x-r,y-r and it's size is the diameter return new Rectangle(x - r, y - r, 2 * r, 2 * r); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // A circle has origin at x-r, y-r return new Point(x - r, y - r); } } /** * The representation of a line of given colour. * * <p> * Creation: Sep 15, 2002 at 7:11:46 PM */ public class PlotLine implements Drawable { private int x1, y1, x2, y2; // location private Color colour; /** * Create a line of given colour * @param x1 the x coord of the start (document coords) * @param y1 the y coord of the start (document coords) * @param x2 the x coord of the end (document coords) * @param y2 the y coord of the end (document coords) * @param colour the colour of the point */ public PlotLine(int x1, int y1, int x2, int y2, Color colour) { this.x1 = x1; this.y1 = -y1; // Use canonical +y 'up' this.x2 = x2; this.y2 = -y2; // Use canonical +y 'up' this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the line gc.setColor(colour); // Draw line gc.drawLine(x1, y1, x2, y2); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // The line lives in its bounding rectangle return new Rectangle(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1)); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // A line has origin at least x,y return new Point(Math.min(x1, x2), Math.min(y1, y2)); } } /** * The representation of a glyph of given type and colour. * * <p> * Creation: Sep 15, 2002 at 7:11:46 PM */ public class PlotGlyph implements Drawable { public final int GLYPH_TYPE_X = 0; public final int GLYPH_TYPE_PLUS = 1; public final int GLYPH_TYPE_O = 2; public final int GLYPH_TYPE_SQUARE = 3; public final int GLYPH_TYPE_TRIANGLE = 4; private int type; private int x, y, size; // location, size private Color colour; /** * Create a point of given colour * @param x the x coord of the centre (document coords) * @param y the y coord of the centre (document coords) * @param size the size: half width/height (document coords) * @param type the type of the glyph: one of GLYPH_TYPE_* * @param colour the colour of the point */ public PlotGlyph(int x, int y, int size, int type, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.size = size; this.type = type; this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the glyph gc.setColor(colour); // Depends on glyph type switch (type) { case GLYPH_TYPE_X : default : // Draw an 'X' gc.drawLine(x - size, y - size, x + size, y + size); gc.drawLine(x - size, y + size, x + size, y - size); break; case GLYPH_TYPE_PLUS : // Draw a '+' gc.drawLine(x - size, y, x + size, y); gc.drawLine(x, y - size, x, y + size); break; case GLYPH_TYPE_O : // Draw an 'O' gc.drawOval(x - size, y - size, size * 2, size * 2); break; case GLYPH_TYPE_SQUARE : // Draw a square gc.drawLine(x - size, y - size, x + size, y - size); gc.drawLine(x + size, y - size, x + size, y + size); gc.drawLine(x + size, y + size, x - size, y + size); gc.drawLine(x - size, y + size, x - size, y - size); break; case GLYPH_TYPE_TRIANGLE : // Draw a triangle gc.drawLine(x, y - size, x + size, y + size); gc.drawLine(x + size, y + size, x - size, y - size); gc.drawLine(x - size, y - size, x, y - size); break; } } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // The glyph lives at x-size, y-size and it's size is 'size' return new Rectangle(x - size, y - size, 2 * size, 2 * size); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // A glyph has origin at x-size, y-size return new Point(x - size, y - size); } } /** * The representation of text of given size, rotation and colour. * * <p> * Creation: Sep 15, 2002 at 7:11:46 PM */ public class PlotText implements Drawable { private int x, y, size; // location, rotation, size private String text; private Color colour; private Rectangle cachedExtents; /** * Create a point of given colour * @param x the x coord of the centre (document coords) * @param y the y coord of the centre (document coords) * @param text the text the text * @param size the size of the text (points) * @param colour the colour of the point */ public PlotText(int x, int y, String text, int size, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.text = text; this.size = size; this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just draw the text gc.setColor(colour); // Set the size Font textFont = areaFont; textFont = new Font(textFont.getName(), textFont.getStyle(), size); // Draw text gc.setFont(textFont); gc.drawString(text, x, y); } /** * Determine the extents of the text and cache these * @return Rectangle the extents */ private Rectangle getCachedExtents() { if (cachedExtents == null) { // Determine the extents BufferedImage bi = new BufferedImage(1, 1, 1); Graphics2D g2c = (Graphics2D) bi.getGraphics(); Font textFont = areaFont; FontMetrics fm = g2c.getFontMetrics(new Font(textFont.getName(), textFont.getStyle(), size)); Rectangle stringRect = (fm.getStringBounds(text, g2c)).getBounds(); stringRect.translate(x, y); cachedExtents = stringRect; g2c.dispose(); } return cachedExtents; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { return getCachedExtents(); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // Get the origin of the extents return getCachedExtents().getLocation(); } } /** * The representation of a rectangle of given colour. * * <p> * Creation: Dec 14, 2005 at 12:11:46 PM */ public class PlotRect implements Drawable { private int x, y, w, h; // location, size private Color colour; /** * Create a rectangle of given colour * @param x the x coord (document coords) * @param y the y coord (document coords) * @param w the width of the rectangle * @param h the height of the rectangle * @param colour the colour of the point */ public PlotRect(int x, int y, int w, int h, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.w = w; this.h = h; this.colour = colour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the rectangle gc.setColor(colour); // Draw line gc.fillRect(x, y, w, h); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // Get the bounding rectangle return new Rectangle(x, y, w, h); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // Get the rectangle origin return new Point(x,y); } } /** * The representation of a circle sector of given colour. * * <p> * Creation: Dec 14, 2005 at 12:11:46 PM */ public class PlotSector implements Drawable { private int x, y, r; // location, radius length, private double sa, ia; // start angle, interval angle private Arc2D arc2D; private Color colour; /** * Create a sector of given colour * @param x the x coord of circle centre (document coords) * @param y the y coord of circle centre (document coords) * @param r the radius length * @param sa the angle of the starting radius * @param ia the angle of the interval (to the ending radius) * @param colour the colour of the point */ public PlotSector(int x, int y, int r, double sa, double ia, Color colour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.r = r; this.sa = sa; this.ia = ia; this.colour = colour; this.arc2D = new Arc2D.Double(); arc2D.setArcByCenter((double)this.x,(double)this.y,(double)this.r,this.sa,this.ia, Arc2D.PIE); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the sector gc.setColor(colour); // Draw line gc.fill(arc2D); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // Get the bounding rectangle of this sector return arc2D.getBounds2D().getBounds(); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // Get the origin of the bounding rectangle return getExtents().getLocation(); } } /** * The representation of a text label in a box (of given colours). * * <p> * Creation: Dec 19, 2005 at 12:11:46 PM */ public class PlotBoxLabel implements Drawable { private int x, y, w, h; // location, size private int size; // font size private String text; private Color textColour; private Color backColour; private Color borderColour; /** * Create a rectangle of given colour * @param x the x coord (document coords) * @param y the y coord (document coords) * @param w the width of the rectangle * @param h the height of the rectangle * @param size the size of the font * @param text the text of the label * @param textColour the colour of the point * @param backColour the colour of the background (box) * @param borderColour the colour of the box border */ public PlotBoxLabel(int x, int y, int w, int h, int size, String text, Color textColour, Color backColour, Color borderColour) { this.x = x; this.y = -y; // Use canonical +y 'up' this.w = w; this.h = h; this.size = size; this.text = text; this.textColour = textColour; this.backColour = backColour; this.borderColour = borderColour; } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Just plot the rectangle of the right size gc.setColor(backColour); gc.fillRect(x, y, w, h); // Set the clip rectangle for text drawing Shape oldClipRegion = gc.getClip(); gc.setClip(x, y, w, h); // Set the font size Font textFont = areaFont; textFont = new Font(textFont.getName(), textFont.getStyle(), size); gc.setFont(textFont); // Determine the text size FontMetrics fm = gc.getFontMetrics(); Rectangle stringRect = (fm.getStringBounds(text, gc)).getBounds(); // Justify the stringRect within the rectangle x,y,w,h, centred or left-justified if oversize int txtX, txtY; int stringHeight = (int)stringRect.getHeight(); int stringWidth = (int)stringRect.getWidth(); if (stringHeight > h) { // Align bottom of rectangles txtY = y + (stringHeight - h); } else { // Centred vertically txtY = y + ((h - stringHeight) / 2); // - } if (stringWidth > w) { // Align left of rectangles txtX = x; } else { // Centred horizontally txtX = x + ((w - stringWidth) / 2); } // TEMP //gc.setColor(Color.black); //int txtX2 = txtX + stringWidth; //int txtY2 = txtY + stringHeight; //gc.drawLine(txtX, txtY, txtX2, txtY); //gc.drawLine(txtX2, txtY, txtX2, txtY2); //gc.drawLine(txtX2, txtY2, txtX, txtY2); //gc.drawLine(txtX, txtY2, txtX, txtY); // Draw text gc.setColor(textColour); gc.drawString(text, txtX, txtY + fm.getAscent()); // Reset the clip gc.setClip(oldClipRegion); // Plot the border lines of the right colour gc.setColor(borderColour); int x2 = x + w; int y2 = y + h; gc.drawLine(x, y, x2, y); gc.drawLine(x2, y, x2, y2); gc.drawLine(x2, y2, x, y2); gc.drawLine(x, y2, x, y); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getExtents() */ public Rectangle getExtents() { // Get the bounding rectangle return new Rectangle(x, y, w, h); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { // Get the rectangle origin return new Point(x,y); } } private int minX, minY = minX = Integer.MAX_VALUE; // Minimal extents in each dimension private int maxX, maxY = maxX = -Integer.MAX_VALUE; // Maximal extents in each dimension private List<Drawable> ops; // Plot operations (Drawables) private HashSet<PlotDocumentListener> listeners; private boolean rangeLocked = false; /** * Create an empty plot document */ public PlotDocument() { // Create the plot operations list - initialise to 100 operations ops = new ArrayList<Drawable>(100); // Create the listeners set (we usually have at least one) listeners = new HashSet<PlotDocumentListener>(); } /** * Create an empty plot document with initial size and lock status * @param initialRange the initial range/limits * @param lockedRange if initialRange is given, fixes this if true * otherwise the range is allowed to grow */ public PlotDocument(Rectangle initialRange, boolean lockedRange) { this(); // Set initial limits if (initialRange != null) { minX = initialRange.x; minY = initialRange.y; maxX = minX + initialRange.width; maxY = minY + initialRange.height; // Set whether this is locked rangeLocked = lockedRange; } } /** * Get the extents of the current document. * This is the rectange which encloses the diagram. * @return Rectangle the extents rectangle (document coords) */ public Rectangle getExtents() { return new Rectangle(minX, minY, 1 + maxX - minX, 1 + maxY - minY); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#getOrigin() */ public Point getOrigin() { return new Point(minX, minY); } /** * Add a listener * @param listener the new listener */ public void addListener(PlotDocumentListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Remove a listener * @param listener the listener to remove */ public void removeListener(PlotDocumentListener listener) { synchronized (listeners) { listeners.remove(listener); } } /** * Plot a point in the given colour * @param x the x coord of the point (document coords) * @param y the y coord of the point (document coords) * @param colour the colour of the point * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotPoint(int x, int y, Color colour) { addOp(new PlotPoint(x, y, colour)); return this; } /** * Plot a point in black * @param x the x coord of the point (document coords) * @param y the y coord of the point (document coords) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotPoint(int x, int y) { // Defer to more general routine return plotPoint(x, y, Color.black); } /** * Plot a circle in the given colour * @param x the x coord of the circle centre (document coords) * @param y the y coord of the circle centre (document coords) * @param r the radius of the circle (document coords) * @param colour the colour of the circle * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotCircle(int x, int y, int r, Color colour) { addOp(new PlotCircle(x, y, r, colour)); return this; } /** * Plot a circle in black * @param x the x coord of the circle centre (document coords) * @param y the y coord of the circle centre (document coords) * @param r the radius of the circle (document coords) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotCircle(int x, int y, int r) { // Defer to the more general routine return plotCircle(x, y, r, Color.black); } /** * Plot a line in the given colour * @param x1 the x coord of the start (document coords) * @param y1 the y coord of the start (document coords) * @param x2 the x coord of the end (document coords) * @param y2 the y coord of the end (document coords) * @param colour the colour of the line * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotLine(int x1, int y1, int x2, int y2, Color colour) { addOp(new PlotLine(x1, y1, x2, y2, colour)); return this; } /** * Plot a line in black * @param x1 the x coord of the start (document coords) * @param y1 the y coord of the start (document coords) * @param x2 the x coord of the end (document coords) * @param y2 the y coord of the end (document coords) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotLine(int x1, int y1, int x2, int y2) { // Defer to the more general routine return plotLine(x1, y1, x2, y2, Color.black); } /** * Plot a glyph in the given colour * @param x the x coord of the glyph (document coords) * @param y the y coord of the glyph (document coords) * @param size the size of the glyph (document coords) * @param type the type of the glyph (PlotGlyph.GLYPH_TYPE.*) * @param colour the colour of the glyph * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotGlyph(int x, int y, int size, int type, Color colour) { addOp(new PlotGlyph(x, y, size, type, colour)); return this; } /** * Plot a glyph in black * @param x the x coord of the glyph (document coords) * @param y the y coord of the glyph (document coords) * @param size the size of the glyph (document coords) * @param type the type of the glyph (PlotGlyph.GLYPH_TYPE.*) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotGlyph(int x, int y, int size, int type) { // Defer to the more general routine return plotGlyph(x, y, size, type, Color.black); } /** * Plot some text in the given colour * @param x the x coord of the text (document coords) * @param y the y coord of the text (document coords) * @param text the text * @param size the size of the text (points) * @param colour the colour of the glyph * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotText(int x, int y, String text, int size, Color colour) { addOp(new PlotText(x, y, text, size, colour)); return this; } /** * Plot some text in black * @param x the x coord of the text (document coords) * @param y the y coord of the text (document coords) * @param text the text * @param size the size of the text (points) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotText(int x, int y, String text, int size) { // Defer to the more general routine return plotText(x, y, text, size, Color.black); } /** * Plot a rectangle in the given colour * @param x the x coord of the rectangle (document coords) * @param y the y coord of the rectangle (document coords) * @param w the width of the rectangle * @param h the height of the rectangle * @param colour the colour of the rectangle * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotRect(int x, int y, int w, int h, Color colour) { addOp(new PlotRect(x, y, w, h, colour)); return this; } /** * Plot a rectangle in black * @param x the x coord of the rectangle (document coords) * @param y the y coord of the rectangle (document coords) * @param w the width of the rectangle * @param h the height of the rectangle * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotRect(int x, int y, int w, int h) { // Defer to the more general routine return plotRect(x, y, w, h, Color.black); } /** * Plot a sector in the given colour * @param x the x coord of the sector's circle centre (document coords) * @param y the y coord of the sector's circle centre (document coords) * @param r the radius length of the sector * @param sa the angle of the starting bounding radius * @param ia the angle of the interval (to the ending radius) * @param colour the colour of the rectangle * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotSector(int x, int y, int r, double sa, double ia, Color colour) { addOp(new PlotSector(x, y, r, sa, ia, colour)); return this; } /** * Plot a sector in black * @param x the x coord of the sector's circle centre (document coords) * @param y the y coord of the sector's circle centre (document coords) * @param r the radius length of the sector * @param sa the angle of the starting bounding radius * @param ia the angle of the interval (to the ending radius) * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotSector(int x, int y, int r, double sa, double ia) { // Defer to the more general routine return plotSector(x, y, r, sa, ia, Color.black); } /** * Plot a box label in given colours * @param x the x coord of the box label (document coords) * @param y the y coord of the box label (document coords) * @param w the width of the box label * @param h the height of the box label * @param size the size of the font in the label * @param text the label text * @param textColour the colour of the text * @param backColour the colour of the background * @param borderColour the colour of the border * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotBoxLabel(int x, int y, int w, int h, int size, String text, Color textColour, Color backColour, Color borderColour) { addOp(new PlotBoxLabel(x, y, w, h, size, text, textColour, backColour, borderColour)); return this; } /** * Plot a box label in black and white * @param x the x coord of the box label (document coords) * @param y the y coord of the box label (document coords) * @param w the width of the box label * @param h the height of the box label * @param size the size of the font in the label * @param text the label text * @return PlotDocument the document (for chaining convenience) */ public PlotDocument plotBoxLabel(int x, int y, int w, int h, int size, String text) { // Defer to the more general routine return plotBoxLabel(x, y, w, h, size, text, Color.black, Color.white, Color.black); } /** * @see org.openquark.cal.samples.plotter.Plotter.Drawable#draw(Graphics2D) */ public void draw(Graphics2D gc) { // Draw all the drawables in this PlotDocument synchronized (ops) { for (Iterator<Drawable> it = ops.iterator(); it.hasNext();) { it.next().draw(gc); } } } /** * Get the number of plot objects currently being plotted * @return int the number of plot objects */ public int getNumPlotObjects() { return ops.size(); } /** * Normalise the origin of the rectangle rec as if the origin of the * document was at (0,0). This is typically used to determine where in * a regular zero origin coord space a given document rectangle might be * located * @param rec the rectangle which is OVERWRITTEN * @return Rectangle the same rectangle (for convenience) */ public Rectangle translateRect(Rectangle rec) { rec.translate(-minX, -minY); return rec; } /** * Normalise the origin of a point as if the origin of the * document was at (0,0). This is typically used to determine where in * a regular zero origin coord space a given document point might be * located * @param point the point which is OVERWRITTEN * @return Point the same point (for convenience) */ public Point translatePoint(Point point) { point.x -= minX; point.y -= minY; return point; } /** * Add a plot operation to this document. * The given plot operation is added to the end of he plot operations list. */ private void addOp(Drawable plotOp) { // Update the document extents Rectangle extents = plotOp.getExtents(); boolean extentsChanged = false; if (!rangeLocked) { if (extents.x < minX) { minX = extents.x; extentsChanged = true; } if (extents.y < minY) { minY = extents.y; extentsChanged = true; } int eMaxX = extents.x + extents.width; if (eMaxX > maxX) { maxX = eMaxX; extentsChanged = true; } int eMaxY = extents.y + extents.height; if (eMaxY > maxY) { maxY = eMaxY; extentsChanged = true; } } // If the range is locked and the plot object lies outside of the // plot range, then we ignore it - it can never be seen if (rangeLocked) { if (extents.width == 0 || extents.height == 0) { // Special handing for zero size if (!(extents.x >= minX) && (extents.x <= maxX)) { return; } if (!(extents.y >= minY) && (extents.y <= maxY)) { return; } } else if (!extents.intersects(minX, minY, maxX - minX, maxY - minY)) { // Reject this return; } } // Add the plot object synchronized (ops) { ops.add(plotOp); } // If the extents changed, then tell whoever needs to know if (extentsChanged) { extentsChanged(plotOp); } else { // Otherwise we tell whoever that some but updated changed(plotOp); } } /** * Inform all interested parties that the content of the PlotDocument * changed * @param plotOp the Drawable (plot operation) which caused the change */ private void changed(Drawable plotOp) { // Tell subscribed listeners for (Iterator<PlotDocumentListener> it = listeners.iterator(); it.hasNext();) { it.next().update(plotOp); } } /** * Inform all interested parties that the extents of the PlotDocument * changed * @param plotOp the Drawable (plot operation) which caused the change */ private void extentsChanged(Drawable plotOp) { // Tell subscribed listeners Rectangle extents = getExtents(); for (Iterator<PlotDocumentListener> it = listeners.iterator(); it.hasNext();) { it.next().updateWithExtentsChange(plotOp, extents); } } } /** * The PlotPanel is a panel which can draw a PlotDocument * * <p> * Creation: Sep 15, 2002 at 6:39:34 PM */ private class PlotPanel extends JPanel implements PlotDocumentListener { private static final long serialVersionUID = -1733420467952757267L; private static final int AXIS_TYPE_NONE = 0; private static final int AXIS_TYPE_AUTOXY = 1; private static final int GRID_TYPE_NONE = 0; private static final int GRID_TYPE_AUTOXY = 1; private PlotDocument plotDocument; // The document we are viewing private BufferedImage bi; // The image of the plot private Color backgroundColour = Color.white; private int axisType = AXIS_TYPE_AUTOXY; private int gridType = GRID_TYPE_AUTOXY; private int gridSize = 10; /** * Create a PlotPanel with an empty PlotDocument */ public PlotPanel() { setPlotDocument(new PlotDocument()); sizePanel(); } /** * Create a PlotPanel with an empty PlotDocument which is range limited */ public PlotPanel(Rectangle range, boolean rangeLocked) { setPlotDocument(new PlotDocument(range, rangeLocked)); setSize(range.getSize()); sizePanel(); } /** * Create a PlotPanel with a given background colour */ public PlotPanel(Color background) { this(); setBackgroundColour(background); } /** * Create a PlotPanel from a given PlotDocument */ public PlotPanel(PlotDocument document) { setPlotDocument(document); } /** * Create a PlotPanel from a given PlotDocument and a background colour */ public PlotPanel(PlotDocument document, Color backgroundColour) { setBackgroundColour(backgroundColour); setPlotDocument(document); } /** * Configure this panel so that it is the correct size etc. * for the containing document * @param docExtents the document extents (when known) */ private void sizePanel(Rectangle docExtents) { // Size the panel and revalidate (to get scroll bar updated) setPreferredSize(docExtents.getSize()); revalidate(); //seems to do nothing } /** * Configure this panel so that it is the correct size etc. * for the containing document */ private void sizePanel() { // Defer to more general routine sizePanel(plotDocument.getExtents()); } /** * Set a Graphics2D coord space to map the document onto the context * origin (document paints at (0,0)). * This is achieved by setting an AffineTransform into the GC * Creation date: (11/6/00 3:44:40 PM) * @param g2c java.awt.Graphics2D the graphics context to map */ private void setDocCoordTransform(Graphics2D g2c) { Point docOrigin = plotDocument.getOrigin(); AffineTransform transform = g2c.getTransform(); transform.setToTranslation(- (docOrigin.x), - (docOrigin.y)); g2c.setTransform(transform); } /** * Draw a grid of the appropriate (set) type into this BufferedImage * @param bi the BufferedImage * @param extents the extents of the grid */ private void drawGrid(BufferedImage bi, Rectangle extents) { // Get the GC Graphics2D g2c = (Graphics2D) bi.getGraphics(); int height = bi.getHeight(); int width = bi.getWidth(); // Always draw a nice border around the edge g2c.setColor(Color.lightGray); g2c.drawRect(1, 1, width - 2, height - 2); // Depends on grid type switch (gridType) { case GRID_TYPE_AUTOXY : // Get start point registered to document origin Point startPoint = new Point((extents.x / gridSize) * gridSize, (extents.y / gridSize) * gridSize); plotDocument.translatePoint(startPoint); Point tensRegister = new Point(0, 0); plotDocument.translatePoint(tensRegister); // For verticals for (int i = startPoint.x; i < width; i += gridSize) { if ((i - tensRegister.x) % (gridSize * 10) == 0) { g2c.setColor(Color.gray); } else { g2c.setColor(Color.lightGray); } g2c.drawLine(i, 0, i, height); } // For horizontals for (int i = startPoint.y; i < height; i += gridSize) { if ((i - tensRegister.y) % (gridSize * 10) == 0) { g2c.setColor(Color.gray); } else { g2c.setColor(Color.lightGray); } g2c.drawLine(0, i, width, i); } break; case GRID_TYPE_NONE : default : break; } // Dispose of the GC g2c.dispose(); } /** * Draw axes of the appropriate (set) type into this BufferedImage * @param bi the BufferedImage */ private void drawAxes(BufferedImage bi) { // Nothing to do if no axes if (axisType == AXIS_TYPE_NONE) { return; } // Determine where the origin is Point origin = new Point(0, 0); plotDocument.translatePoint(origin); // Get the GC Graphics2D g2c = (Graphics2D) bi.getGraphics(); int height = bi.getHeight(); int width = bi.getWidth(); switch (axisType) { case AXIS_TYPE_AUTOXY : // Draw axes g2c.setColor(Color.black); g2c.drawLine(0, origin.y, width, origin.y); g2c.drawLine(origin.x, 0, origin.x, height); break; case AXIS_TYPE_NONE : default : break; } // Dispose of the GC g2c.dispose(); } /** * Paint the given part of this MapPanel. * Creation date: (11/6/00 3:44:40 PM) * @param g java.awt.Graphics the paint info */ public void paintComponent(java.awt.Graphics g) { super.paintComponent(g); Graphics2D g2c = (Graphics2D) g; // Dimension dim = getSize(); // Insets insets = getInsets(); // Paint in the image if we have one if (bi != null) { synchronized (bi) { g2c.drawImage(bi, null, 0, 0); } } // Dispose of GC g2c.dispose(); } /** * Returns the plotDocument. * @return PlotDocument the document this panel is viewing */ public PlotDocument getPlotDocument() { return plotDocument; } /** * Sets the plotDocument. * @param plotDocument the new PlotDocument to view */ public void setPlotDocument(PlotDocument plotDocument) { // Unregister as listener from any previous document if (this.plotDocument != null) { this.plotDocument.removeListener(this); } // Clear image info synchronized (this) { bi = null; } // Set and register as listener this.plotDocument = plotDocument; this.plotDocument.addListener(this); // Update updateSizeAndImage(this.plotDocument, this.plotDocument.getExtents()); } /** * Update the buffered image with this plot object (paint into image) * @param plotObject the object to draw into the image */ private void updateImage(Drawable plotObject) { // Get the extents of the object Rectangle bounds = plotObject.getExtents(); // Minimum extents of 1x1 if (bounds.width <= 0) { bounds.width = 1; } if (bounds.height <= 0) { bounds.height = 1; } // If there's no current image, create one if (bi == null) { // Create image synchronized (this) { bi = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_RGB); // Get a graphics context and paint it the background colour Graphics2D g2c = (Graphics2D) bi.getGraphics(); g2c.setColor(backgroundColour); g2c.fillRect(0, 0, bounds.width, bounds.height); // Draw its grid and axes drawGrid(bi, bounds); drawAxes(bi); // Dispose of gc g2c.dispose(); } // Get the panel to resize to these extents sizePanel(bounds); } // Update the image synchronized (bi) { // Get a graphics context Graphics2D g2c = (Graphics2D) bi.getGraphics(); // Set up the coord space correctly setDocCoordTransform(g2c); plotObject.draw(g2c); // Dispose of GC g2c.dispose(); } // Schedule a repaint for the effected area plotDocument.translateRect(bounds); repaint(bounds.x, bounds.y, bounds.width, bounds.height); } /** * @see org.openquark.cal.samples.plotter.Plotter.PlotDocumentListener#update(Plotter.Drawable) */ public void update(Drawable plotObject) { // The document has been updated without changing the document's // overall extents // The bounding rectangle defines where the change occured within // the document updateImage(plotObject); } /** * Update the buffered image to take account of the new size * and this new plot object (paint into image) * @param plotObject the object to draw into the image * @param newExtents the new bounds of the document */ private synchronized void updateSizeAndImage(Drawable plotObject, Rectangle newExtents) { // Get the extents of the object //Rectangle bounds = plotObject.getExtents(); // Minimum extents of 1x1 if (newExtents.width <= 0) { newExtents.width = 1; } if (newExtents.height <= 0) { newExtents.height = 1; } // Remember the old image BufferedImage oldImage = bi; // Create new image bi = new BufferedImage(newExtents.width, newExtents.height, BufferedImage.TYPE_INT_RGB); // Get a graphics context and paint it the background colour Graphics2D g2c = (Graphics2D) bi.getGraphics(); g2c.setColor(backgroundColour); g2c.fillRect(0, 0, newExtents.width, newExtents.height); // Draw its grid and axes drawGrid(bi, newExtents); drawAxes(bi); // If we had an old image, copy this in if (false && oldImage != null) { // TODO - copy old image // Use imageExtents (of the old image) to do this // Update with this drawable updateImage(plotObject); } else { // Draw everything back in synchronized (bi) { // Set up the coord space correctly setDocCoordTransform(g2c); plotDocument.draw(g2c); } // Schedule repaint of everything repaint(); } // Dispose of the image GC g2c.dispose(); // Size the panel sizePanel(newExtents); } /** * @see org.openquark.cal.samples.plotter.Plotter.PlotDocumentListener#updateWithExtentsChange(Plotter.Drawable, Rectangle) */ public void updateWithExtentsChange(Drawable plotObject, Rectangle newExtents) { // The document is indicating that it's overall size has changed // The size of this panel needs to changed accordingly and the // draw buffer should be cleared updateSizeAndImage(plotObject, newExtents); } /** * Sets the backgroundColour. * @param backgroundColour The backgroundColor to set */ public void setBackgroundColour(Color backgroundColour) { this.backgroundColour = backgroundColour; } } }