/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * -------------- * PolarPlot.java * -------------- * (C) Copyright 2004-2008, by Solution Engineering, Inc. and Contributors. * * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; * Contributor(s): David Gilbert (for Object Refinery Limited); * Martin Hoeller (patch 1871902); * * Changes * ------- * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG); * 07-Apr-2004 : Changed text bounds calculation (DG); * 05-May-2005 : Updated draw() method parameters (DG); * 09-Jun-2005 : Fixed getDataRange() and equals() methods (DG); * 25-Oct-2005 : Implemented Zoomable (DG); * ------------- JFREECHART 1.0.x --------------------------------------------- * 07-Feb-2007 : Fixed bug 1599761, data value less than axis minimum (DG); * 21-Mar-2007 : Fixed serialization bug (DG); * 24-Sep-2007 : Implemented new zooming methods (DG); * 17-Feb-2007 : Added angle tick unit attribute (see patch 1871902 by * Martin Hoeller) (DG); * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by * Jess Thrysoee (DG); * */ package org.jfree.chart.plot; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.axis.AxisState; import org.jfree.chart.axis.NumberTick; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.TickUnit; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.event.PlotChangeEvent; import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.event.RendererChangeListener; import org.jfree.chart.renderer.PolarItemRenderer; import org.jfree.chart.util.ResourceBundleWrapper; import org.jfree.data.Range; import org.jfree.data.general.DatasetChangeEvent; import org.jfree.data.general.DatasetUtilities; import org.jfree.data.xy.XYDataset; import org.jfree.io.SerialUtilities; import org.jfree.text.TextUtilities; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; import org.jfree.util.ObjectUtilities; import org.jfree.util.PaintUtilities; /** * Plots data that is in (theta, radius) pairs where * theta equal to zero is due north and increases clockwise. */ public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable, RendererChangeListener, Cloneable, Serializable { /** For serialization. */ private static final long serialVersionUID = 3794383185924179525L; /** The default margin. */ private static final int MARGIN = 20; /** The annotation margin. */ private static final double ANNOTATION_MARGIN = 7.0; /** * The default angle tick unit size. * * @since 1.0.10 */ public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0; /** The default grid line stroke. */ public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke( 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]{2.0f, 2.0f}, 0.0f); /** The default grid line paint. */ public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray; /** The resourceBundle for the localization. */ protected static ResourceBundle localizationResources = ResourceBundleWrapper.getBundle( "org.jfree.chart.plot.LocalizationBundle"); /** The angles that are marked with gridlines. */ private List angleTicks; /** The axis (used for the y-values). */ private ValueAxis axis; /** The dataset. */ private XYDataset dataset; /** * Object responsible for drawing the visual representation of each point * on the plot. */ private PolarItemRenderer renderer; /** * The tick unit that controls the spacing between the angular grid lines. * * @since 1.0.10 */ private TickUnit angleTickUnit; /** A flag that controls whether or not the angle labels are visible. */ private boolean angleLabelsVisible = true; /** The font used to display the angle labels - never null. */ private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12); /** The paint used to display the angle labels. */ private transient Paint angleLabelPaint = Color.black; /** A flag that controls whether the angular grid-lines are visible. */ private boolean angleGridlinesVisible; /** The stroke used to draw the angular grid-lines. */ private transient Stroke angleGridlineStroke; /** The paint used to draw the angular grid-lines. */ private transient Paint angleGridlinePaint; /** A flag that controls whether the radius grid-lines are visible. */ private boolean radiusGridlinesVisible; /** The stroke used to draw the radius grid-lines. */ private transient Stroke radiusGridlineStroke; /** The paint used to draw the radius grid-lines. */ private transient Paint radiusGridlinePaint; /** The annotations for the plot. */ private List cornerTextItems = new ArrayList(); /** * Default constructor. */ public PolarPlot() { this(null, null, null); } /** * Creates a new plot. * * @param dataset the dataset (<code>null</code> permitted). * @param radiusAxis the radius axis (<code>null</code> permitted). * @param renderer the renderer (<code>null</code> permitted). */ public PolarPlot(XYDataset dataset, ValueAxis radiusAxis, PolarItemRenderer renderer) { super(); this.dataset = dataset; if (this.dataset != null) { this.dataset.addChangeListener(this); } this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE); this.axis = radiusAxis; if (this.axis != null) { this.axis.setPlot(this); this.axis.addChangeListener(this); } this.renderer = renderer; if (this.renderer != null) { this.renderer.setPlot(this); this.renderer.addChangeListener(this); } this.angleGridlinesVisible = true; this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; this.radiusGridlinesVisible = true; this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE; this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT; } /** * Add text to be displayed in the lower right hand corner and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param text the text to display (<code>null</code> not permitted). * * @see #removeCornerTextItem(String) */ public void addCornerTextItem(String text) { if (text == null) { throw new IllegalArgumentException("Null 'text' argument."); } this.cornerTextItems.add(text); fireChangeEvent(); } /** * Remove the given text from the list of corner text items and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param text the text to remove (<code>null</code> ignored). * * @see #addCornerTextItem(String) */ public void removeCornerTextItem(String text) { boolean removed = this.cornerTextItems.remove(text); if (removed) { fireChangeEvent(); } } /** * Clear the list of corner text items and sends a {@link PlotChangeEvent} * to all registered listeners. * * @see #addCornerTextItem(String) * @see #removeCornerTextItem(String) */ public void clearCornerTextItems() { if (this.cornerTextItems.size() > 0) { this.cornerTextItems.clear(); fireChangeEvent(); } } /** * Returns the plot type as a string. * * @return A short string describing the type of plot. */ public String getPlotType() { return PolarPlot.localizationResources.getString("Polar_Plot"); } /** * Returns the axis for the plot. * * @return The radius axis (possibly <code>null</code>). * * @see #setAxis(ValueAxis) */ public ValueAxis getAxis() { return this.axis; } /** * Sets the axis for the plot and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param axis the new axis (<code>null</code> permitted). */ public void setAxis(ValueAxis axis) { if (axis != null) { axis.setPlot(this); } // plot is likely registered as a listener with the existing axis... if (this.axis != null) { this.axis.removeChangeListener(this); } this.axis = axis; if (this.axis != null) { this.axis.configure(); this.axis.addChangeListener(this); } fireChangeEvent(); } /** * Returns the primary dataset for the plot. * * @return The primary dataset (possibly <code>null</code>). * * @see #setDataset(XYDataset) */ public XYDataset getDataset() { return this.dataset; } /** * Sets the dataset for the plot, replacing the existing dataset if there * is one. * * @param dataset the dataset (<code>null</code> permitted). * * @see #getDataset() */ public void setDataset(XYDataset dataset) { // if there is an existing dataset, remove the plot from the list of // change listeners... XYDataset existing = this.dataset; if (existing != null) { existing.removeChangeListener(this); } // set the new m_Dataset, and register the chart as a change listener... this.dataset = dataset; if (this.dataset != null) { setDatasetGroup(this.dataset.getGroup()); this.dataset.addChangeListener(this); } // send a m_Dataset change event to self... DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset); datasetChanged(event); } /** * Returns the item renderer. * * @return The renderer (possibly <code>null</code>). * * @see #setRenderer(PolarItemRenderer) */ public PolarItemRenderer getRenderer() { return this.renderer; } /** * Sets the item renderer, and notifies all listeners of a change to the * plot. * <P> * If the renderer is set to <code>null</code>, no chart will be drawn. * * @param renderer the new renderer (<code>null</code> permitted). * * @see #getRenderer() */ public void setRenderer(PolarItemRenderer renderer) { if (this.renderer != null) { this.renderer.removeChangeListener(this); } this.renderer = renderer; if (this.renderer != null) { this.renderer.setPlot(this); } fireChangeEvent(); } /** * Returns the tick unit that controls the spacing of the angular grid * lines. * * @return The tick unit (never <code>null</code>). * * @since 1.0.10 */ public TickUnit getAngleTickUnit() { return this.angleTickUnit; } /** * Sets the tick unit that controls the spacing of the angular grid * lines, and sends a {@link PlotChangeEvent} to all registered listeners. * * @param unit the tick unit (<code>null</code> not permitted). * * @since 1.0.10 */ public void setAngleTickUnit(TickUnit unit) { if (unit == null) { throw new IllegalArgumentException("Null 'unit' argument."); } this.angleTickUnit = unit; fireChangeEvent(); } /** * Returns a flag that controls whether or not the angle labels are visible. * * @return A boolean. * * @see #setAngleLabelsVisible(boolean) */ public boolean isAngleLabelsVisible() { return this.angleLabelsVisible; } /** * Sets the flag that controls whether or not the angle labels are visible, * and sends a {@link PlotChangeEvent} to all registered listeners. * * @param visible the flag. * * @see #isAngleLabelsVisible() */ public void setAngleLabelsVisible(boolean visible) { if (this.angleLabelsVisible != visible) { this.angleLabelsVisible = visible; fireChangeEvent(); } } /** * Returns the font used to display the angle labels. * * @return A font (never <code>null</code>). * * @see #setAngleLabelFont(Font) */ public Font getAngleLabelFont() { return this.angleLabelFont; } /** * Sets the font used to display the angle labels and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param font the font (<code>null</code> not permitted). * * @see #getAngleLabelFont() */ public void setAngleLabelFont(Font font) { if (font == null) { throw new IllegalArgumentException("Null 'font' argument."); } this.angleLabelFont = font; fireChangeEvent(); } /** * Returns the paint used to display the angle labels. * * @return A paint (never <code>null</code>). * * @see #setAngleLabelPaint(Paint) */ public Paint getAngleLabelPaint() { return this.angleLabelPaint; } /** * Sets the paint used to display the angle labels and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (<code>null</code> not permitted). */ public void setAngleLabelPaint(Paint paint) { if (paint == null) { throw new IllegalArgumentException("Null 'paint' argument."); } this.angleLabelPaint = paint; fireChangeEvent(); } /** * Returns <code>true</code> if the angular gridlines are visible, and * <code>false<code> otherwise. * * @return <code>true</code> or <code>false</code>. * * @see #setAngleGridlinesVisible(boolean) */ public boolean isAngleGridlinesVisible() { return this.angleGridlinesVisible; } /** * Sets the flag that controls whether or not the angular grid-lines are * visible. * <p> * If the flag value is changed, a {@link PlotChangeEvent} is sent to all * registered listeners. * * @param visible the new value of the flag. * * @see #isAngleGridlinesVisible() */ public void setAngleGridlinesVisible(boolean visible) { if (this.angleGridlinesVisible != visible) { this.angleGridlinesVisible = visible; fireChangeEvent(); } } /** * Returns the stroke for the grid-lines (if any) plotted against the * angular axis. * * @return The stroke (possibly <code>null</code>). * * @see #setAngleGridlineStroke(Stroke) */ public Stroke getAngleGridlineStroke() { return this.angleGridlineStroke; } /** * Sets the stroke for the grid lines plotted against the angular axis and * sends a {@link PlotChangeEvent} to all registered listeners. * <p> * If you set this to <code>null</code>, no grid lines will be drawn. * * @param stroke the stroke (<code>null</code> permitted). * * @see #getAngleGridlineStroke() */ public void setAngleGridlineStroke(Stroke stroke) { this.angleGridlineStroke = stroke; fireChangeEvent(); } /** * Returns the paint for the grid lines (if any) plotted against the * angular axis. * * @return The paint (possibly <code>null</code>). * * @see #setAngleGridlinePaint(Paint) */ public Paint getAngleGridlinePaint() { return this.angleGridlinePaint; } /** * Sets the paint for the grid lines plotted against the angular axis. * <p> * If you set this to <code>null</code>, no grid lines will be drawn. * * @param paint the paint (<code>null</code> permitted). * * @see #getAngleGridlinePaint() */ public void setAngleGridlinePaint(Paint paint) { this.angleGridlinePaint = paint; fireChangeEvent(); } /** * Returns <code>true</code> if the radius axis grid is visible, and * <code>false<code> otherwise. * * @return <code>true</code> or <code>false</code>. * * @see #setRadiusGridlinesVisible(boolean) */ public boolean isRadiusGridlinesVisible() { return this.radiusGridlinesVisible; } /** * Sets the flag that controls whether or not the radius axis grid lines * are visible. * <p> * If the flag value is changed, a {@link PlotChangeEvent} is sent to all * registered listeners. * * @param visible the new value of the flag. * * @see #isRadiusGridlinesVisible() */ public void setRadiusGridlinesVisible(boolean visible) { if (this.radiusGridlinesVisible != visible) { this.radiusGridlinesVisible = visible; fireChangeEvent(); } } /** * Returns the stroke for the grid lines (if any) plotted against the * radius axis. * * @return The stroke (possibly <code>null</code>). * * @see #setRadiusGridlineStroke(Stroke) */ public Stroke getRadiusGridlineStroke() { return this.radiusGridlineStroke; } /** * Sets the stroke for the grid lines plotted against the radius axis and * sends a {@link PlotChangeEvent} to all registered listeners. * <p> * If you set this to <code>null</code>, no grid lines will be drawn. * * @param stroke the stroke (<code>null</code> permitted). * * @see #getRadiusGridlineStroke() */ public void setRadiusGridlineStroke(Stroke stroke) { this.radiusGridlineStroke = stroke; fireChangeEvent(); } /** * Returns the paint for the grid lines (if any) plotted against the radius * axis. * * @return The paint (possibly <code>null</code>). * * @see #setRadiusGridlinePaint(Paint) */ public Paint getRadiusGridlinePaint() { return this.radiusGridlinePaint; } /** * Sets the paint for the grid lines plotted against the radius axis and * sends a {@link PlotChangeEvent} to all registered listeners. * <p> * If you set this to <code>null</code>, no grid lines will be drawn. * * @param paint the paint (<code>null</code> permitted). * * @see #getRadiusGridlinePaint() */ public void setRadiusGridlinePaint(Paint paint) { this.radiusGridlinePaint = paint; fireChangeEvent(); } /** * Generates a list of tick values for the angular tick marks. * * @return A list of {@link NumberTick} instances. * * @since 1.0.10 */ protected List refreshAngleTicks() { List ticks = new ArrayList(); for (double currentTickVal = 0.0; currentTickVal < 360.0; currentTickVal += this.angleTickUnit.getSize()) { NumberTick tick = new NumberTick(new Double(currentTickVal), this.angleTickUnit.valueToString(currentTickVal), TextAnchor.CENTER, TextAnchor.CENTER, 0.0); ticks.add(tick); } return ticks; } /** * Draws the plot on a Java 2D graphics device (such as the screen or a * printer). * <P> * This plot relies on a {@link PolarItemRenderer} to draw each * item in the plot. This allows the visual representation of the data to * be changed easily. * <P> * The optional info argument collects information about the rendering of * the plot (dimensions, tooltip information etc). Just pass in * <code>null</code> if you do not need this information. * * @param g2 the graphics device. * @param area the area within which the plot (including axes and * labels) should be drawn. * @param anchor the anchor point (<code>null</code> permitted). * @param parentState ignored. * @param info collects chart drawing information (<code>null</code> * permitted). */ public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) { // if the plot area is too small, just return... boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); if (b1 || b2) { return; } // record the plot area... if (info != null) { info.setPlotArea(area); } // adjust the drawing area for the plot insets (if any)... RectangleInsets insets = getInsets(); insets.trim(area); Rectangle2D dataArea = area; if (info != null) { info.setDataArea(dataArea); } // draw the plot background and axes... drawBackground(g2, dataArea); double h = Math.min(dataArea.getWidth() / 2.0, dataArea.getHeight() / 2.0) - MARGIN; Rectangle2D quadrant = new Rectangle2D.Double(dataArea.getCenterX(), dataArea.getCenterY(), h, h); AxisState state = drawAxis(g2, area, quadrant); if (this.renderer != null) { Shape originalClip = g2.getClip(); Composite originalComposite = g2.getComposite(); g2.clip(dataArea); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, getForegroundAlpha())); this.angleTicks = refreshAngleTicks(); drawGridlines(g2, dataArea, this.angleTicks, state.getTicks()); // draw... render(g2, dataArea, info); g2.setClip(originalClip); g2.setComposite(originalComposite); } drawOutline(g2, dataArea); drawCornerTextItems(g2, dataArea); } /** * Draws the corner text items. * * @param g2 the drawing surface. * @param area the area. */ protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) { if (this.cornerTextItems.isEmpty()) { return; } g2.setColor(Color.black); double width = 0.0; double height = 0.0; for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { String msg = (String) it.next(); FontMetrics fm = g2.getFontMetrics(); Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm); width = Math.max(width, bounds.getWidth()); height += bounds.getHeight(); } double xadj = ANNOTATION_MARGIN * 2.0; double yadj = ANNOTATION_MARGIN; width += xadj; height += yadj; double x = area.getMaxX() - width; double y = area.getMaxY() - height; g2.drawRect((int) x, (int) y, (int) width, (int) height); x += ANNOTATION_MARGIN; for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { String msg = (String) it.next(); Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, g2.getFontMetrics()); y += bounds.getHeight(); g2.drawString(msg, (int) x, (int) y); } } /** * A utility method for drawing the axes. * * @param g2 the graphics device. * @param plotArea the plot area. * @param dataArea the data area. * * @return A map containing the axis states. */ protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea) { return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea, RectangleEdge.TOP, null); } /** * Draws a representation of the data within the dataArea region, using the * current m_Renderer. * * @param g2 the graphics device. * @param dataArea the region in which the data is to be drawn. * @param info an optional object for collection dimension * information (<code>null</code> permitted). */ protected void render(Graphics2D g2, Rectangle2D dataArea, PlotRenderingInfo info) { // now get the data and plot it (the visual representation will depend // on the m_Renderer that has been set)... if (!DatasetUtilities.isEmptyOrNull(this.dataset)) { int seriesCount = this.dataset.getSeriesCount(); for (int series = 0; series < seriesCount; series++) { this.renderer.drawSeries(g2, dataArea, info, this, this.dataset, series); } } else { drawNoDataMessage(g2, dataArea); } } /** * Draws the gridlines for the plot, if they are visible. * * @param g2 the graphics device. * @param dataArea the data area. * @param angularTicks the ticks for the angular axis. * @param radialTicks the ticks for the radial axis. */ protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea, List angularTicks, List radialTicks) { // no renderer, no gridlines... if (this.renderer == null) { return; } // draw the domain grid lines, if any... if (isAngleGridlinesVisible()) { Stroke gridStroke = getAngleGridlineStroke(); Paint gridPaint = getAngleGridlinePaint(); if ((gridStroke != null) && (gridPaint != null)) { this.renderer.drawAngularGridLines(g2, this, angularTicks, dataArea); } } // draw the radius grid lines, if any... if (isRadiusGridlinesVisible()) { Stroke gridStroke = getRadiusGridlineStroke(); Paint gridPaint = getRadiusGridlinePaint(); if ((gridStroke != null) && (gridPaint != null)) { this.renderer.drawRadialGridLines(g2, this, this.axis, radialTicks, dataArea); } } } /** * Zooms the axis ranges by the specified percentage about the anchor point. * * @param percent the amount of the zoom. */ public void zoom(double percent) { if (percent > 0.0) { double radius = getMaxRadius(); double scaledRadius = radius * percent; this.axis.setUpperBound(scaledRadius); getAxis().setAutoRange(false); } else { getAxis().setAutoRange(true); } } /** * Returns the range for the specified axis. * * @param axis the axis. * * @return The range. */ public Range getDataRange(ValueAxis axis) { Range result = null; if (this.dataset != null) { result = Range.combine(result, DatasetUtilities.findRangeBounds(this.dataset)); } return result; } /** * Receives notification of a change to the plot's m_Dataset. * <P> * The axis ranges are updated if necessary. * * @param event information about the event (not used here). */ public void datasetChanged(DatasetChangeEvent event) { if (this.axis != null) { this.axis.configure(); } if (getParent() != null) { getParent().datasetChanged(event); } else { super.datasetChanged(event); } } /** * Notifies all registered listeners of a property change. * <P> * One source of property change events is the plot's m_Renderer. * * @param event information about the property change. */ public void rendererChanged(RendererChangeEvent event) { fireChangeEvent(); } /** * Returns the number of series in the dataset for this plot. If the * dataset is <code>null</code>, the method returns 0. * * @return The series count. */ public int getSeriesCount() { int result = 0; if (this.dataset != null) { result = this.dataset.getSeriesCount(); } return result; } /** * Returns the legend items for the plot. Each legend item is generated by * the plot's m_Renderer, since the m_Renderer is responsible for the visual * representation of the data. * * @return The legend items. */ public LegendItemCollection getLegendItems() { LegendItemCollection result = new LegendItemCollection(); // get the legend items for the main m_Dataset... if (this.dataset != null) { if (this.renderer != null) { int seriesCount = this.dataset.getSeriesCount(); for (int i = 0; i < seriesCount; i++) { LegendItem item = this.renderer.getLegendItem(i); result.add(item); } } } return result; } /** * Tests this plot for equality with another object. * * @param obj the object (<code>null</code> permitted). * * @return <code>true</code> or <code>false</code>. */ public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PolarPlot)) { return false; } PolarPlot that = (PolarPlot) obj; if (!ObjectUtilities.equal(this.axis, that.axis)) { return false; } if (!ObjectUtilities.equal(this.renderer, that.renderer)) { return false; } if (!this.angleTickUnit.equals(that.angleTickUnit)) { return false; } if (this.angleGridlinesVisible != that.angleGridlinesVisible) { return false; } if (this.angleLabelsVisible != that.angleLabelsVisible) { return false; } if (!this.angleLabelFont.equals(that.angleLabelFont)) { return false; } if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) { return false; } if (!ObjectUtilities.equal(this.angleGridlineStroke, that.angleGridlineStroke)) { return false; } if (!PaintUtilities.equal( this.angleGridlinePaint, that.angleGridlinePaint )) { return false; } if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) { return false; } if (!ObjectUtilities.equal(this.radiusGridlineStroke, that.radiusGridlineStroke)) { return false; } if (!PaintUtilities.equal(this.radiusGridlinePaint, that.radiusGridlinePaint)) { return false; } if (!this.cornerTextItems.equals(that.cornerTextItems)) { return false; } return super.equals(obj); } /** * Returns a clone of the plot. * * @return A clone. * * @throws CloneNotSupportedException this can occur if some component of * the plot cannot be cloned. */ public Object clone() throws CloneNotSupportedException { PolarPlot clone = (PolarPlot) super.clone(); if (this.axis != null) { clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis); clone.axis.setPlot(clone); clone.axis.addChangeListener(clone); } if (clone.dataset != null) { clone.dataset.addChangeListener(clone); } if (this.renderer != null) { clone.renderer = (PolarItemRenderer) ObjectUtilities.clone(this.renderer); } clone.cornerTextItems = new ArrayList(this.cornerTextItems); return clone; } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtilities.writeStroke(this.angleGridlineStroke, stream); SerialUtilities.writePaint(this.angleGridlinePaint, stream); SerialUtilities.writeStroke(this.radiusGridlineStroke, stream); SerialUtilities.writePaint(this.radiusGridlinePaint, stream); SerialUtilities.writePaint(this.angleLabelPaint, stream); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.angleGridlineStroke = SerialUtilities.readStroke(stream); this.angleGridlinePaint = SerialUtilities.readPaint(stream); this.radiusGridlineStroke = SerialUtilities.readStroke(stream); this.radiusGridlinePaint = SerialUtilities.readPaint(stream); this.angleLabelPaint = SerialUtilities.readPaint(stream); if (this.axis != null) { this.axis.setPlot(this); this.axis.addChangeListener(this); } if (this.dataset != null) { this.dataset.addChangeListener(this); } } /** * This method is required by the {@link Zoomable} interface, but since * the plot does not have any domain axes, it does nothing. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point (in Java2D coordinates). */ public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source) { // do nothing } /** * This method is required by the {@link Zoomable} interface, but since * the plot does not have any domain axes, it does nothing. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point (in Java2D coordinates). * @param useAnchor use source point as zoom anchor? * * @since 1.0.7 */ public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source, boolean useAnchor) { // do nothing } /** * This method is required by the {@link Zoomable} interface, but since * the plot does not have any domain axes, it does nothing. * * @param lowerPercent the new lower bound. * @param upperPercent the new upper bound. * @param state the plot state. * @param source the source point (in Java2D coordinates). */ public void zoomDomainAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { // do nothing } /** * Multiplies the range on the range axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point (in Java2D coordinates). */ public void zoomRangeAxes(double factor, PlotRenderingInfo state, Point2D source) { zoom(factor); } /** * Multiplies the range on the range axis by the specified factor. * * @param factor the zoom factor. * @param info the plot rendering info. * @param source the source point (in Java2D space). * @param useAnchor use source point as zoom anchor? * * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) * * @since 1.0.7 */ public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) { if (useAnchor) { // get the source coordinate - this plot has always a VERTICAL // orientation double sourceX = source.getX(); double anchorX = this.axis.java2DToValue(sourceX, info.getDataArea(), RectangleEdge.BOTTOM); this.axis.resizeRange(factor, anchorX); } else { this.axis.resizeRange(factor); } } /** * Zooms in on the range axes. * * @param lowerPercent the new lower bound. * @param upperPercent the new upper bound. * @param state the plot state. * @param source the source point (in Java2D coordinates). */ public void zoomRangeAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { zoom((upperPercent + lowerPercent) / 2.0); } /** * Returns <code>false</code> always. * * @return <code>false</code> always. */ public boolean isDomainZoomable() { return false; } /** * Returns <code>true</code> to indicate that the range axis is zoomable. * * @return <code>true</code>. */ public boolean isRangeZoomable() { return true; } /** * Returns the orientation of the plot. * * @return The orientation. */ public PlotOrientation getOrientation() { return PlotOrientation.HORIZONTAL; } /** * Returns the upper bound of the radius axis. * * @return The upper bound. */ public double getMaxRadius() { return this.axis.getUpperBound(); } /** * Translates a (theta, radius) pair into Java2D coordinates. If * <code>radius</code> is less than the lower bound of the axis, then * this method returns the centre point. * * @param angleDegrees the angle in degrees. * @param radius the radius. * @param dataArea the data area. * * @return A point in Java2D space. */ public Point translateValueThetaRadiusToJava2D(double angleDegrees, double radius, Rectangle2D dataArea) { double radians = Math.toRadians(angleDegrees - 90.0); double minx = dataArea.getMinX() + MARGIN; double maxx = dataArea.getMaxX() - MARGIN; double miny = dataArea.getMinY() + MARGIN; double maxy = dataArea.getMaxY() - MARGIN; double lengthX = maxx - minx; double lengthY = maxy - miny; double length = Math.min(lengthX, lengthY); double midX = minx + lengthX / 2.0; double midY = miny + lengthY / 2.0; double axisMin = this.axis.getLowerBound(); double axisMax = getMaxRadius(); double adjustedRadius = Math.max(radius, axisMin); double xv = length / 2.0 * Math.cos(radians); double yv = length / 2.0 * Math.sin(radians); float x = (float) (midX + (xv * (adjustedRadius - axisMin) / (axisMax - axisMin))); float y = (float) (midY + (yv * (adjustedRadius - axisMin) / (axisMax - axisMin))); int ix = Math.round(x); int iy = Math.round(y); Point p = new Point(ix, iy); return p; } }