From: <ma...@us...> - 2011-10-07 08:57:14
|
Revision: 2283 http://jfreechart.svn.sourceforge.net/jfreechart/?rev=2283&view=rev Author: matinh Date: 2011-10-07 08:57:06 +0000 (Fri, 07 Oct 2011) Log Message: ----------- new features for the PolarCharts: * source/org/jfree/chart/renderer/PolarItemRenderer.java (useFillPaint): New field, (legendLine): Likewise; (connectFirstAndLastPoint): New method, (toolTipGeneratorList): Likewise, (baseToolTipGenerator): Likewise, (urlGenerator): Likewise, (legendItemToolTipGenerator): Likewise, (legendItemURLGenerator): Likewise, (getConnectFirstAndLastPoint): Likewise, (setConnectFirstAndLastPoint): Likewise, (getUseFillPaint): Likewise, (setUseFillPaint): Likewise, (getLegendLine): Likewise, (setLegendLine): Likewise, (getToolTipGenerator): Likewise, (getSeriesToolTipGenerator): Likewise, (setSeriesToolTipGenerator): Likewise, (getBaseToolTipGenerator): Likewise, (setBaseToolTipGenerator): Likewise, (getURLGenerator): Likewise, (setURLGenerator): Likewise, (getLegendItemToolTipGenerator): Likewise, (setLegendItemToolTipGenerator): Likewise, (getLegendItemURLGenerator): Likewise, (setLegendItemURLGenerator): Likewise. * source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java (getToolTipGenerator): New method, (getSeriesToolTipGenerator): Likewise, (setSeriesToolTipGenerator): Likewise, (getBaseToolTipGenerator): Likewise, (setBaseToolTipGenerator): Likewise, (getURLGenerator): Likewise, (setURLGenerator): Likewise. * source/org/jfree/chart/plot/PolarPlot.java (angleOffset): New field, (counterClockwise): Likewise; (getAxisCount): New method, (getDatasetCount): Likewise, (getAngleOffset): Likewise, (setAngleOffset): Likewise, (isCounterClockwise): Likewise, (setCounterClockwise): Likewise, (getAxisIndex): Likewise, Modified Paths: -------------- branches/jfreechart-1.0.x-branch/ChangeLog branches/jfreechart-1.0.x-branch/README.txt branches/jfreechart-1.0.x-branch/source/org/jfree/chart/plot/PolarPlot.java branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/PolarItemRenderer.java Modified: branches/jfreechart-1.0.x-branch/ChangeLog =================================================================== --- branches/jfreechart-1.0.x-branch/ChangeLog 2011-10-06 21:52:45 UTC (rev 2282) +++ branches/jfreechart-1.0.x-branch/ChangeLog 2011-10-07 08:57:06 UTC (rev 2283) @@ -1,3 +1,50 @@ +2011-10-07 Martin Hoeller <mar...@xs...> + + * source/org/jfree/chart/renderer/PolarItemRenderer.java + (useFillPaint): New field, + (legendLine): Likewise; + (connectFirstAndLastPoint): New method, + (toolTipGeneratorList): Likewise, + (baseToolTipGenerator): Likewise, + (urlGenerator): Likewise, + (legendItemToolTipGenerator): Likewise, + (legendItemURLGenerator): Likewise, + (getConnectFirstAndLastPoint): Likewise, + (setConnectFirstAndLastPoint): Likewise, + (getUseFillPaint): Likewise, + (setUseFillPaint): Likewise, + (getLegendLine): Likewise, + (setLegendLine): Likewise, + (getToolTipGenerator): Likewise, + (getSeriesToolTipGenerator): Likewise, + (setSeriesToolTipGenerator): Likewise, + (getBaseToolTipGenerator): Likewise, + (setBaseToolTipGenerator): Likewise, + (getURLGenerator): Likewise, + (setURLGenerator): Likewise, + (getLegendItemToolTipGenerator): Likewise, + (setLegendItemToolTipGenerator): Likewise, + (getLegendItemURLGenerator): Likewise, + (setLegendItemURLGenerator): Likewise. + * source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java + (getToolTipGenerator): New method, + (getSeriesToolTipGenerator): Likewise, + (setSeriesToolTipGenerator): Likewise, + (getBaseToolTipGenerator): Likewise, + (setBaseToolTipGenerator): Likewise, + (getURLGenerator): Likewise, + (setURLGenerator): Likewise. + * source/org/jfree/chart/plot/PolarPlot.java + (angleOffset): New field, + (counterClockwise): Likewise; + (getAxisCount): New method, + (getDatasetCount): Likewise, + (getAngleOffset): Likewise, + (setAngleOffset): Likewise, + (isCounterClockwise): Likewise, + (setCounterClockwise): Likewise, + (getAxisIndex): Likewise, + 2011-10-06 David Gilbert <dav...@ob...> * source/org/jfree/chart/renderer/xy/AbstractXYItemRenderer.java Modified: branches/jfreechart-1.0.x-branch/README.txt =================================================================== --- branches/jfreechart-1.0.x-branch/README.txt 2011-10-06 21:52:45 UTC (rev 2282) +++ branches/jfreechart-1.0.x-branch/README.txt 2011-10-07 08:57:06 UTC (rev 2283) @@ -744,6 +744,7 @@ - Nick Guenther - Aiman Han - Cameron Hayne + - Martin Hoeller (xS+S) - Jon Iles - Wolfgang Irler - Sergei Ivanov Modified: branches/jfreechart-1.0.x-branch/source/org/jfree/chart/plot/PolarPlot.java =================================================================== --- branches/jfreechart-1.0.x-branch/source/org/jfree/chart/plot/PolarPlot.java 2011-10-06 21:52:45 UTC (rev 2282) +++ branches/jfreechart-1.0.x-branch/source/org/jfree/chart/plot/PolarPlot.java 2011-10-07 08:57:06 UTC (rev 2283) @@ -51,6 +51,8 @@ * 03-Sep-2009 : Applied patch 2850344 by Martin Hoeller (DG); * 27-Nov-2009 : Added support for multiple datasets, renderers and axes (DG); * 09-Dec-2009 : Extended getLegendItems() to handle multiple datasets (DG); + * 25-Jun-2010 : Better support for multiple axes (MH); + * 03-Oct-2011 : Added support for angleOffset and direction (MH); * */ @@ -79,19 +81,22 @@ import java.util.List; import java.util.Map; import java.util.ResourceBundle; +import java.util.TreeMap; -import java.util.TreeMap; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisLocation; 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.renderer.xy.XYItemRenderer; import org.jfree.chart.util.ResourceBundleWrapper; import org.jfree.data.Range; import org.jfree.data.general.Dataset; @@ -131,6 +136,13 @@ */ public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0; + /** + * The default angle offset. + * + * @since 1.0.14 + */ + public static final double DEFAULT_ANGLE_OFFSET = -90.0; + /** The default grid line stroke. */ public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke( 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, @@ -166,6 +178,21 @@ */ private TickUnit angleTickUnit; + /** + * An offset for the angles, to start with 0 degrees at north, east, south + * or west. + * + * @since 1.0.14 + */ + private double angleOffset; + + /** + * A flag indicating if the angles increase counterclockwise or clockwise. + * + * @since 1.0.14 + */ + private boolean counterClockwise; + /** A flag that controls whether or not the angle labels are visible. */ private boolean angleLabelsVisible = true; @@ -246,14 +273,13 @@ this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE); this.axes = new ObjectList(); + this.datasetToAxesMap = new TreeMap(); this.axes.set(0, radiusAxis); if (radiusAxis != null) { radiusAxis.setPlot(this); radiusAxis.addChangeListener(this); } - this.datasetToAxesMap = new TreeMap(); - // define the default locations for up to 8 axes... this.axisLocations = new ObjectList(); this.axisLocations.set(0, PolarAxisLocation.EAST_ABOVE); @@ -272,6 +298,8 @@ renderer.addChangeListener(this); } + this.angleOffset = DEFAULT_ANGLE_OFFSET; + this.counterClockwise = false; this.angleGridlinesVisible = true; this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; @@ -480,6 +508,17 @@ } /** + * Returns the number of domain axes. + * + * @return The axis count. + * + * @since 1.0.14 + **/ + public int getAxisCount() { + return this.axes.size(); + } + + /** * Returns the primary dataset for the plot. * * @return The primary dataset (possibly <code>null</code>). @@ -550,6 +589,17 @@ } /** + * Returns the number of datasets. + * + * @return The number of datasets. + * + * @since 1.0.14 + */ + public int getDatasetCount() { + return this.datasets.size(); + } + + /** * Returns the index of the specified dataset, or <code>-1</code> if the * dataset does not belong to the plot. * @@ -685,6 +735,59 @@ } /** + * Returns the offset that is used for all angles. + * + * @return The offset for the angles. + * @since 1.0.14 + */ + public double getAngleOffset() + { + return angleOffset; + } + + /** + * Sets the offset that is used for all angles and sends a + * {@link PlotChangeEvent} to all registered listeners. + * + * This is useful to let 0 degrees be at the north, east, south or west + * side of the chart. + * + * @param offset The offset + * @since 1.0.14 + */ + public void setAngleOffset(double offset) + { + this.angleOffset = offset; + fireChangeEvent(); + } + + /** + * Get the direction for growing angle degrees. + * + * @return <code>true</code> if angle increases counterclockwise, + * <code>false</code> otherwise. + * @since 1.0.14 + */ + public boolean isCounterClockwise() + { + return counterClockwise; + } + + /** + * Sets the flag for increasing angle degrees direction. + * + * <code>true</code> for counterclockwise, <code>false</code> for + * clockwise. + * + * @param counterClockwise The flag. + * @since 1.0.14 + */ + public void setCounterClockwise(boolean counterClockwise) + { + this.counterClockwise = counterClockwise; + } + + /** * Returns a flag that controls whether or not the angle labels are visible. * * @return A boolean. @@ -847,7 +950,7 @@ /** * Returns <code>true</code> if the radius axis grid is visible, and - * <code>false<code> otherwise. + * <code>false</code> otherwise. * * @return <code>true</code> or <code>false</code>. * @@ -1038,32 +1141,8 @@ List ticks = new ArrayList(); for (double currentTickVal = 0.0; currentTickVal < 360.0; currentTickVal += this.angleTickUnit.getSize()) { - TextAnchor ta = TextAnchor.CENTER; - if (currentTickVal == 0.0 || currentTickVal == 360.0) { - ta = TextAnchor.BOTTOM_CENTER; - } - else if (currentTickVal > 0.0 && currentTickVal < 90.0) { - ta = TextAnchor.BOTTOM_LEFT; - } - else if (currentTickVal == 90.0) { - ta = TextAnchor.CENTER_LEFT; - } - else if (currentTickVal > 90.0 && currentTickVal < 180.0) { - ta = TextAnchor.TOP_LEFT; - } - else if (currentTickVal == 180) { - ta = TextAnchor.TOP_CENTER; - } - else if (currentTickVal > 180.0 && currentTickVal < 270.0) { - ta = TextAnchor.TOP_RIGHT; - } - else if (currentTickVal == 270) { - ta = TextAnchor.CENTER_RIGHT; - } - else if (currentTickVal > 270.0 && currentTickVal < 360.0) { - ta = TextAnchor.BOTTOM_RIGHT; - } - + + TextAnchor ta = calculateTextAnchor(currentTickVal); NumberTick tick = new NumberTick(new Double(currentTickVal), this.angleTickUnit.valueToString(currentTickVal), ta, TextAnchor.CENTER, 0.0); @@ -1073,6 +1152,52 @@ } /** + * Calculate the text position for the given degrees. + * + * @return The optimal text anchor. + * @since 1.0.14 + */ + protected TextAnchor calculateTextAnchor(double angleDegrees) + { + TextAnchor ta = TextAnchor.CENTER; + + // normalize angle + double offset = angleOffset; + while (offset < 0.0) + offset += 360.0; + double normalizedAngle = (((counterClockwise ? -1 : 1) * angleDegrees) + + offset) % 360; + while (counterClockwise && (normalizedAngle < 0.0)) + normalizedAngle += 360.0; + + if (normalizedAngle == 0.0) { + ta = TextAnchor.CENTER_LEFT; + } + else if (normalizedAngle > 0.0 && normalizedAngle < 90.0) { + ta = TextAnchor.TOP_LEFT; + } + else if (normalizedAngle == 90.0) { + ta = TextAnchor.TOP_CENTER; + } + else if (normalizedAngle > 90.0 && normalizedAngle < 180.0) { + ta = TextAnchor.TOP_RIGHT; + } + else if (normalizedAngle == 180) { + ta = TextAnchor.CENTER_RIGHT; + } + else if (normalizedAngle > 180.0 && normalizedAngle < 270.0) { + ta = TextAnchor.BOTTOM_RIGHT; + } + else if (normalizedAngle == 270) { + ta = TextAnchor.BOTTOM_CENTER; + } + else if (normalizedAngle > 270.0 && normalizedAngle < 360.0) { + ta = TextAnchor.BOTTOM_LEFT; + } + return ta; + } + + /** * Maps a dataset to a particular axis. All data will be plotted * against axis zero by default, no mapping is required for this case. * @@ -1164,6 +1289,28 @@ } /** + * Returns the index of the given axis. + * + * @param axis the axis. + * + * @return The axis index or -1 if axis is not used in this plot. + * + * @since 1.0.14 + */ + public int getAxisIndex(ValueAxis axis) { + int result = this.axes.indexOf(axis); + if (result < 0) { + // try the parent plot + Plot parent = getParent(); + if (parent instanceof PolarPlot) { + PolarPlot p = (PolarPlot) parent; + result = p.getAxisIndex(axis); + } + } + return result; + } + + /** * Returns the index of the specified renderer, or <code>-1</code> if the * renderer is not assigned to this plot. * @@ -1444,16 +1591,51 @@ * @param percent the amount of the zoom. */ public void zoom(double percent) { - // FIXME : handle multiple axes - if (percent > 0.0) { - double radius = getAxis().getUpperBound(); - double scaledRadius = radius * percent; - getAxis().setUpperBound(scaledRadius); - getAxis().setAutoRange(false); + for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { + final ValueAxis axis = getAxis(axisIdx); + if (axis != null) { + if (percent > 0.0) { + double radius = axis.getUpperBound(); + double scaledRadius = radius * percent; + axis.setUpperBound(scaledRadius); + axis.setAutoRange(false); + } + else { + axis.setAutoRange(true); + } + } } - else { - getAxis().setAutoRange(true); + } + + /** + * A utility method that returns a list of datasets that are mapped to a + * particular axis. + * + * @param axisIndex the axis index (<code>null</code> not permitted). + * + * @return A list of datasets. + * + * @since 1.0.14 + */ + private List getDatasetsMappedToAxis(Integer axisIndex) { + if (axisIndex == null) { + throw new IllegalArgumentException("Null 'axisIndex' argument."); } + List result = new ArrayList(); + for (int i = 0; i < this.datasets.size(); i++) { + List mappedAxes = (List) this.datasetToAxesMap.get(new Integer(i)); + if (mappedAxes == null) { + if (axisIndex.equals(ZERO)) { + result.add(this.datasets.get(i)); + } + } + else { + if (mappedAxes.contains(axisIndex)) { + result.add(this.datasets.get(i)); + } + } + } + return result; } /** @@ -1464,12 +1646,28 @@ * @return The range. */ public Range getDataRange(ValueAxis axis) { - // FIXME: handle multiple datasets Range result = null; - if (getDataset() != null) { - result = Range.combine(result, - DatasetUtilities.findRangeBounds(getDataset())); + int axisIdx = getAxisIndex(axis); + List mappedDatasets = new ArrayList(); + + if (axisIdx >= 0) { + mappedDatasets = getDatasetsMappedToAxis(new Integer(axisIdx)); } + + // iterate through the datasets that map to the axis and get the union + // of the ranges. + Iterator iterator = mappedDatasets.iterator(); + int datasetIdx = -1; + while (iterator.hasNext()) { + datasetIdx++; + XYDataset d = (XYDataset) iterator.next(); + if (d != null) { + // FIXME better ask the renderer instead of DatasetUtilities + result = Range.combine(result, + DatasetUtilities.findRangeBounds(d)); + } + } + return result; } @@ -1481,9 +1679,11 @@ * @param event information about the event (not used here). */ public void datasetChanged(DatasetChangeEvent event) { - // FIXME : configure all axes - if (getAxis() != null) { - getAxis().configure(); + for (int i = 0; i < this.axes.size(); i++) { + final ValueAxis axis = (ValueAxis) this.axes.get(i); + if (axis != null) { + axis.configure(); + } } if (getParent() != null) { getParent().datasetChanged(event); @@ -1561,6 +1761,14 @@ if (this.angleGridlinesVisible != that.angleGridlinesVisible) { return false; } + if (this.angleOffset != that.angleOffset) + { + return false; + } + if (this.counterClockwise != that.counterClockwise) + { + return false; + } if (this.angleLabelsVisible != that.angleLabelsVisible) { return false; } @@ -1778,19 +1986,23 @@ */ public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) { - // FIXME : handle multiple axes - if (useAnchor) { - // get the source coordinate - this plot has always a VERTICAL - // orientation - double sourceX = source.getX(); - double anchorX = getAxis().java2DToValue(sourceX, - info.getDataArea(), RectangleEdge.BOTTOM); - getAxis().resizeRange(factor, anchorX); + // get the source coordinate - this plot has always a VERTICAL + // orientation + final double sourceX = source.getX(); + + for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { + final ValueAxis axis = getAxis(axisIdx); + if (axis != null) { + if (useAnchor) { + double anchorX = axis.java2DToValue(sourceX, + info.getDataArea(), RectangleEdge.BOTTOM); + axis.resizeRange(factor, anchorX); + } + else { + axis.resizeRange(factor); + } + } } - else { - getAxis().resizeRange(factor); - } - } /** @@ -1849,7 +2061,9 @@ public Point translateToJava2D(double angleDegrees, double radius, ValueAxis axis, Rectangle2D dataArea) { - double radians = Math.toRadians(angleDegrees - 90.0); + if (counterClockwise) + angleDegrees = -angleDegrees; + double radians = Math.toRadians(angleDegrees + this.angleOffset); double minx = dataArea.getMinX() + this.margin; double maxx = dataArea.getMaxX() - this.margin; @@ -1900,7 +2114,9 @@ public Point translateValueThetaRadiusToJava2D(double angleDegrees, double radius, Rectangle2D dataArea) { - double radians = Math.toRadians(angleDegrees - 90.0); + if (counterClockwise) + angleDegrees = -angleDegrees; + double radians = Math.toRadians(angleDegrees + this.angleOffset); double minx = dataArea.getMinX() + this.margin; double maxx = dataArea.getMaxX() - this.margin; Modified: branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java =================================================================== --- branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java 2011-10-06 21:52:45 UTC (rev 2282) +++ branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/DefaultPolarItemRenderer.java 2011-10-07 08:57:06 UTC (rev 2283) @@ -49,7 +49,12 @@ * 18-May-2007 : Set dataset for LegendItem (DG); * 03-Sep-2009 : Applied patch 2850344 by Martin Hoeller (DG); * 27-Nov-2009 : Updated for modification to PolarItemRenderer interface (DG); - * + * 03-Oct-2011 : Fixed potential NPE in equals() (MH); + * 03-Oct-2011 : Added flag to connectFirstAndLastPoint (MH); + * 03-Oct-2011 : Added tooltip and URL generator support (MH); + * 03-Oct-2011 : Added some configuration options for the legend (MH); + * 03-Oct-2011 : Added support for PolarPlot's angleOffset and direction (MH); + * */ package org.jfree.chart.renderer; @@ -59,24 +64,41 @@ import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; -import java.awt.Polygon; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.Iterator; import java.util.List; import org.jfree.chart.LegendItem; import org.jfree.chart.axis.NumberTick; import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.entity.EntityCollection; +import org.jfree.chart.entity.XYItemEntity; +import org.jfree.chart.event.RendererChangeEvent; +import org.jfree.chart.labels.XYSeriesLabelGenerator; +import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.DrawingSupplier; +import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.PolarPlot; +import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; +import org.jfree.chart.urls.XYURLGenerator; import org.jfree.data.xy.XYDataset; +import org.jfree.io.SerialUtilities; import org.jfree.text.TextUtilities; import org.jfree.util.BooleanList; import org.jfree.util.BooleanUtilities; +import org.jfree.util.ObjectList; +import org.jfree.util.ObjectUtilities; +import org.jfree.util.PublicCloneable; import org.jfree.util.ShapeUtilities; /** @@ -104,17 +126,75 @@ * * @since 1.0.14 */ - private Composite fillComposite; + private transient Composite fillComposite; /** + * A flag that controls whether the fill paint is used for filling + * shapes. + * + * @since 1.0.14 + */ + private boolean useFillPaint; + + /** + * The shape that is used to represent a line in the legend. + * + * @since 1.0.14 + */ + private transient Shape legendLine; + + /** * Flag that controls whether item shapes are visible or not. * * @since 1.0.14 */ private boolean shapesVisible; + + /** + * Flag that controls if the first and last point of the dataset should be + * connected or not. + * + * @since 1.0.14 + */ + private boolean connectFirstAndLastPoint; - /** + * A list of tool tip generators (one per series). + * + * @since 1.0.14 + */ + private ObjectList toolTipGeneratorList; + + /** + * The base tool tip generator. + * + * @since 1.0.14 + */ + private XYToolTipGenerator baseToolTipGenerator; + + /** + * The URL text generator. + * + * @since 1.0.14 + */ + private XYURLGenerator urlGenerator; + + /** + * The legend item tool tip generator. + * + * @since 1.0.14 + */ + private XYSeriesLabelGenerator legendItemToolTipGenerator; + + /** + * The legend item URL generator. + * + * @since 1.0.14 + */ + private XYSeriesLabelGenerator legendItemURLGenerator; + + + /** * Creates a new instance of DefaultPolarItemRenderer */ public DefaultPolarItemRenderer() { @@ -122,7 +202,15 @@ this.drawOutlineWhenFilled = true; this.fillComposite = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.3f); + this.useFillPaint = false; // use item paint for fills by default + this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); this.shapesVisible = true; + this.connectFirstAndLastPoint = true; + + this.toolTipGeneratorList = new ObjectList(); + this.urlGenerator = null; + this.legendItemToolTipGenerator = null; + this.legendItemURLGenerator = null; } /** @@ -149,7 +237,7 @@ /** * Returns <code>true</code> if the renderer will draw an outline around - * a filled polygon, <code>false/<code> otherwise. + * a filled polygon, <code>false</code> otherwise. * * @return A boolean. * @@ -202,7 +290,7 @@ } /** - * Returns <code>true</tt> if a shape will be drawn for every item, or + * Returns <code>true</code> if a shape will be drawn for every item, or * <code>false</code> if not. * * @return A boolean. @@ -224,9 +312,38 @@ */ public void setShapesVisible(boolean shapesVisible) { this.shapesVisible = shapesVisible; + fireChangeEvent(); } /** + * Returns <code>true</code> if first and last point of a series will be + * connected, <code>false</code> otherwise. + * + * @return The current status of the flag. + * + * @since 1.0.14 + */ + public boolean getConnectFirstAndLastPoint() + { + return connectFirstAndLastPoint; + } + + /** + * Set the flag that controls whether the first and last point of a series + * will be connected or not and sends a {@link RendererChangeEvent} to all + * registered listeners. + * + * @param connect the flag. + * + * @since 1.0.14 + */ + public void setConnectFirstAndLastPoint(boolean connect) + { + this.connectFirstAndLastPoint = connect; + fireChangeEvent(); + } + + /** * Returns the drawing supplier from the plot. * * @return The drawing supplier. @@ -268,6 +385,108 @@ } /** + * Returns <code>true</code> if the renderer should use the fill paint + * setting to fill shapes, and <code>false</code> if it should just + * use the regular paint. + * + * @return A boolean. + * + * @see #setUseFillPaint(boolean) + * @since 1.0.14 + */ + public boolean getUseFillPaint() { + return this.useFillPaint; + } + + /** + * Sets the flag that controls whether the fill paint is used to fill + * shapes, and sends a {@link RendererChangeEvent} to all + * registered listeners. + * + * @param flag the flag. + * + * @see #getUseFillPaint() + * @since 1.0.14 + */ + public void setUseFillPaint(boolean flag) { + this.useFillPaint = flag; + fireChangeEvent(); + } + + /** + * Returns the shape used to represent a line in the legend. + * + * @return The legend line (never <code>null</code>). + * + * @see #setLegendLine(Shape) + */ + public Shape getLegendLine() { + return this.legendLine; + } + + /** + * Sets the shape used as a line in each legend item and sends a + * {@link RendererChangeEvent} to all registered listeners. + * + * @param line the line (<code>null</code> not permitted). + * + * @see #getLegendLine() + */ + public void setLegendLine(Shape line) { + if (line == null) { + throw new IllegalArgumentException("Null 'line' argument."); + } + this.legendLine = line; + fireChangeEvent(); + } + + /** + * Adds an entity to the collection. + * + * @param entities the entity collection being populated. + * @param area the entity area (if <code>null</code> a default will be + * used). + * @param dataset the dataset. + * @param series the series. + * @param item the item. + * @param entityX the entity's center x-coordinate in user space (only + * used if <code>area</code> is <code>null</code>). + * @param entityY the entity's center y-coordinate in user space (only + * used if <code>area</code> is <code>null</code>). + */ + // this method was copied from AbstractXYItemRenderer on 03-Oct-2011 + protected void addEntity(EntityCollection entities, Shape area, + XYDataset dataset, int series, int item, + double entityX, double entityY) { + if (!getItemCreateEntity(series, item)) { + return; + } + Shape hotspot = area; + if (hotspot == null) { + double r = getDefaultEntityRadius(); + double w = r * 2; + if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { + hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); + } + else { + hotspot = new Ellipse2D.Double(entityY - r, entityX - r, w, w); + } + } + String tip = null; + XYToolTipGenerator generator = getToolTipGenerator(series, item); + if (generator != null) { + tip = generator.generateToolTip(dataset, series, item); + } + String url = null; + if (getURLGenerator() != null) { + url = getURLGenerator().generateURL(dataset, series, item); + } + XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, + tip, url); + entities.add(entity); + } + + /** * Plots the data for a given series. * * @param g2 the drawing surface. @@ -281,15 +500,25 @@ PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, int seriesIndex) { - Polygon poly = new Polygon(); + + GeneralPath poly = null; ValueAxis axis = plot.getAxisForDataset(plot.indexOf(dataset)); final int numPoints = dataset.getItemCount(seriesIndex); for (int i = 0; i < numPoints; i++) { double theta = dataset.getXValue(seriesIndex, i); double radius = dataset.getYValue(seriesIndex, i); Point p = plot.translateToJava2D(theta, radius, axis, dataArea); - poly.addPoint(p.x, p.y); + if (poly == null) { + poly = new GeneralPath(); + poly.moveTo(p.x, p.y); + } + else + poly.lineTo(p.x, p.y); } + + if (getConnectFirstAndLastPoint()) + poly.closePath(); + g2.setPaint(lookupSeriesPaint(seriesIndex)); g2.setStroke(lookupSeriesStroke(seriesIndex)); if (isSeriesFilled(seriesIndex)) { @@ -310,17 +539,45 @@ // draw the item shapes if (this.shapesVisible) { - for (int i = 0; i < numPoints; i++) { - final int x = poly.xpoints[i]; - final int y = poly.ypoints[i]; + // setup for collecting optional entity info... + EntityCollection entities = null; + if (info != null) { + entities = info.getOwner().getEntityCollection(); + } + + PathIterator pi = poly.getPathIterator(null); + int i = 0; + while (!pi.isDone()) { + final float[] coords = new float[6]; + final int segType = pi.currentSegment(coords); + pi.next(); + if (segType != PathIterator.SEG_LINETO && + segType != PathIterator.SEG_MOVETO) + continue; + final int x = Math.round(coords[0]); + final int y = Math.round(coords[1]); final Shape shape = ShapeUtilities.createTranslatedShape( - getItemShape(seriesIndex, i), x, y); + getItemShape(seriesIndex, i++), x, y); - g2.setPaint(lookupSeriesFillPaint(seriesIndex)); + Paint paint; + if (useFillPaint) + paint = lookupSeriesFillPaint(seriesIndex); + else + paint = lookupSeriesPaint(seriesIndex); + g2.setPaint(paint); g2.fill(shape); - g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); - g2.setStroke(lookupSeriesOutlineStroke(seriesIndex)); - g2.draw(shape); + if (isSeriesFilled(seriesIndex) && drawOutlineWhenFilled) { + g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); + g2.setStroke(lookupSeriesOutlineStroke(seriesIndex)); + g2.draw(shape); + } + + // add an entity for the item, but only if it falls within the + // data area... + if (entities != null && + AbstractXYItemRenderer.isPointInRect(dataArea, x, y)) { + addEntity(entities, shape, dataset, seriesIndex, i-1, x, y); + } } } } @@ -388,7 +645,8 @@ Iterator iterator = ticks.iterator(); while (iterator.hasNext()) { NumberTick tick = (NumberTick) iterator.next(); - Point p = plot.translateValueThetaRadiusToJava2D(90.0, + double angleDegrees = plot.isCounterClockwise() ? plot.getAngleOffset() : -plot.getAngleOffset(); + Point p = plot.translateValueThetaRadiusToJava2D(angleDegrees, tick.getNumber().doubleValue(), dataArea); int r = p.x - center.x; int upperLeftX = center.x - r; @@ -414,21 +672,164 @@ return null; } XYDataset dataset = plot.getDataset(plot.getIndexOf(this)); - if (dataset != null) { - String label = dataset.getSeriesKey(series).toString(); - String description = label; - Shape shape = lookupSeriesShape(series); - Paint paint = lookupSeriesPaint(series); - Paint outlinePaint = lookupSeriesOutlinePaint(series); - Stroke outlineStroke = lookupSeriesOutlineStroke(series); - result = new LegendItem(label, description, null, null, - shape, paint, outlineStroke, outlinePaint); - result.setDataset(dataset); + if (dataset == null) { + return null; } + + String toolTipText = null; + if (getLegendItemToolTipGenerator() != null) { + toolTipText = getLegendItemToolTipGenerator().generateLabel( + dataset, series); + } + String urlText = null; + if (getLegendItemURLGenerator() != null) { + urlText = getLegendItemURLGenerator().generateLabel(dataset, + series); + } + + String label = dataset.getSeriesKey(series).toString(); + String description = label; + Shape shape = lookupSeriesShape(series); + Paint paint; + if (useFillPaint) + paint = lookupSeriesFillPaint(series); + else + paint = lookupSeriesPaint(series); + Stroke stroke = lookupSeriesStroke(series); + Paint outlinePaint = lookupSeriesOutlinePaint(series); + Stroke outlineStroke = lookupSeriesOutlineStroke(series); + boolean shapeOutlined = isSeriesFilled(series) + && this.drawOutlineWhenFilled; + result = new LegendItem(label, description, toolTipText, urlText, + getShapesVisible(), shape, /* shapeFilled=*/ true, paint, + shapeOutlined, outlinePaint, outlineStroke, + /* lineVisible= */ true, this.legendLine, stroke, paint); + result.setToolTipText(toolTipText); + result.setURLText(urlText); + result.setDataset(dataset); + return result; } /** + * @since 1.0.14 + */ + public XYToolTipGenerator getToolTipGenerator(int series, int item) + { + XYToolTipGenerator generator + = (XYToolTipGenerator) this.toolTipGeneratorList.get(series); + if (generator == null) { + generator = this.baseToolTipGenerator; + } + return generator; + } + + /** + * @since 1.0.14 + */ + public XYToolTipGenerator getSeriesToolTipGenerator(int series) + { + return (XYToolTipGenerator) this.toolTipGeneratorList.get(series); + } + + /** + * @since 1.0.14 + */ + public void setSeriesToolTipGenerator(int series, + XYToolTipGenerator generator) + { + this.toolTipGeneratorList.set(series, generator); + fireChangeEvent(); + } + + /** + * @since 1.0.14 + */ + public XYToolTipGenerator getBaseToolTipGenerator() + { + return this.baseToolTipGenerator; + } + + /** + * @since 1.0.14 + */ + public void setBaseToolTipGenerator(XYToolTipGenerator generator) + { + this.baseToolTipGenerator = generator; + fireChangeEvent(); + } + + /** + * @since 1.0.14 + */ + public XYURLGenerator getURLGenerator() + { + return this.urlGenerator; + } + + /** + * @since 1.0.14 + */ + public void setURLGenerator(XYURLGenerator urlGenerator) + { + this.urlGenerator = urlGenerator; + fireChangeEvent(); + } + + /** + * Returns the legend item tool tip generator. + * + * @return The tool tip generator (possibly <code>null</code>). + * + * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) + * @since 1.0.14 + */ + public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { + return this.legendItemToolTipGenerator; + } + + /** + * Sets the legend item tool tip generator and sends a + * {@link RendererChangeEvent} to all registered listeners. + * + * @param generator the generator (<code>null</code> permitted). + * + * @see #getLegendItemToolTipGenerator() + * @since 1.0.14 + */ + public void setLegendItemToolTipGenerator( + XYSeriesLabelGenerator generator) { + this.legendItemToolTipGenerator = generator; + fireChangeEvent(); + } + + /** + * Returns the legend item URL generator. + * + * @return The URL generator (possibly <code>null</code>). + * + * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) + * @since 1.0.14 + */ + public XYSeriesLabelGenerator getLegendItemURLGenerator() { + return this.legendItemURLGenerator; + } + + /** + * Sets the legend item URL generator and sends a + * {@link RendererChangeEvent} to all registered listeners. + * + * @param generator the generator (<code>null</code> permitted). + * + * @see #getLegendItemURLGenerator() + * @since 1.0.14 + */ + public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { + this.legendItemURLGenerator = generator; + fireChangeEvent(); + } + + /** * Tests this renderer for equality with an arbitrary object. * * @param obj the object (<code>null</code> not permitted). @@ -450,12 +851,39 @@ if (this.drawOutlineWhenFilled != that.drawOutlineWhenFilled) { return false; } - if (!this.fillComposite.equals(that.fillComposite)) { + if (!ObjectUtilities.equal(this.fillComposite, that.fillComposite)) { return false; } + if (this.useFillPaint != that.useFillPaint) { + return false; + } + if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { + return false; + } if (this.shapesVisible != that.shapesVisible) { return false; } + if (this.connectFirstAndLastPoint != that.connectFirstAndLastPoint) { + return false; + } + if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) { + return false; + } + if (!ObjectUtilities.equal(this.baseToolTipGenerator, + that.baseToolTipGenerator)) { + return false; + } + if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) { + return false; + } + if (!ObjectUtilities.equal(this.legendItemToolTipGenerator, + that.legendItemToolTipGenerator)) { + return false; + } + if (!ObjectUtilities.equal(this.legendItemURLGenerator, + that.legendItemURLGenerator)) { + return false; + } return super.equals(obj); } @@ -469,8 +897,54 @@ public Object clone() throws CloneNotSupportedException { DefaultPolarItemRenderer clone = (DefaultPolarItemRenderer) super.clone(); + if (this.legendLine != null) { + clone.legendLine = ShapeUtilities.clone(this.legendLine); + } clone.seriesFilled = (BooleanList) this.seriesFilled.clone(); + clone.toolTipGeneratorList + = (ObjectList) this.toolTipGeneratorList.clone(); + if (clone.baseToolTipGenerator instanceof PublicCloneable) { + clone.baseToolTipGenerator = (XYToolTipGenerator) + ObjectUtilities.clone(this.baseToolTipGenerator); + } + if (clone.urlGenerator instanceof PublicCloneable) { + clone.urlGenerator = (XYURLGenerator) + ObjectUtilities.clone(this.urlGenerator); + } + if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { + clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator) + ObjectUtilities.clone(this.legendItemToolTipGenerator); + } + if (clone.legendItemURLGenerator instanceof PublicCloneable) { + clone.legendItemURLGenerator = (XYSeriesLabelGenerator) + ObjectUtilities.clone(this.legendItemURLGenerator); + } return clone; } + /** + * 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.legendLine = SerialUtilities.readShape(stream); + } + + /** + * 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.writeShape(this.legendLine, stream); + } } Modified: branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/PolarItemRenderer.java =================================================================== --- branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/PolarItemRenderer.java 2011-10-06 21:52:45 UTC (rev 2282) +++ branches/jfreechart-1.0.x-branch/source/org/jfree/chart/renderer/PolarItemRenderer.java 2011-10-07 08:57:06 UTC (rev 2283) @@ -35,6 +35,7 @@ * Changes * ------- * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG); + * 03-Oct-2011 : Added tooltip and URL generator support (MH); * */ @@ -46,9 +47,12 @@ import org.jfree.chart.LegendItem; import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.event.RendererChangeEvent; import org.jfree.chart.event.RendererChangeListener; +import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.PolarPlot; +import org.jfree.chart.urls.XYURLGenerator; import org.jfree.data.xy.XYDataset; /** @@ -132,4 +136,90 @@ */ public void removeChangeListener(RendererChangeListener listener); + + //// TOOL TIP GENERATOR /////////////////////////////////////////////////// + + /** + * Returns the tool tip generator for a data item. + * + * @param row the row index (zero based). + * @param column the column index (zero based). + * + * @return The generator (possibly <code>null</code>). + * + * @since 1.0.14 + */ + public XYToolTipGenerator getToolTipGenerator(int row, int column); + + /** + * Returns the tool tip generator for a series. + * + * @param series the series index (zero based). + * + * @return The generator (possibly <code>null</code>). + * + * @see #setSeriesToolTipGenerator(int, XYToolTipGenerator) + * + * @since 1.0.14 + */ + public XYToolTipGenerator getSeriesToolTipGenerator(int series); + + /** + * Sets the tool tip generator for a series and sends a + * {@link RendererChangeEvent} to all registered listeners. + * + * @param series the series index (zero based). + * @param generator the generator (<code>null</code> permitted). + * + * @see #getSeriesToolTipGenerator(int) + * + * @since 1.0.14 + */ + public void setSeriesToolTipGenerator(int series, + XYToolTipGenerator generator); + + /** + * Returns the base tool tip generator. + * + * @return The generator (possibly <code>null</code>). + * + * @see #setBaseToolTipGenerator(XYToolTipGenerator) + * + * @since 1.0.14 + */ + public XYToolTipGenerator getBaseToolTipGenerator(); + + /** + * Sets the base tool tip generator and sends a {@link RendererChangeEvent} + * to all registered listeners. + * + * @param generator the generator (<code>null</code> permitted). + * + * @see #getBaseToolTipGenerator() + * + * @since 1.0.14 + */ + public void setBaseToolTipGenerator(XYToolTipGenerator generator); + + + //// URL GENERATOR //////////////////////////////////////////////////////// + + /** + * Returns the URL generator for HTML image maps. + * + * @return The URL generator (possibly null). + * + * @since 1.0.14 + */ + public XYURLGenerator getURLGenerator(); + + /** + * Sets the URL generator for HTML image maps. + * + * @param urlGenerator the URL generator (null permitted). + * + * @since 1.0.14 + */ + public void setURLGenerator(XYURLGenerator urlGenerator); + } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |