001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * --------------------
028 * XYAreaRenderer2.java
029 * --------------------
030 * (C) Copyright 2004-2008, by Hari and Contributors.
031 *
032 * Original Author: Hari (ourhari@hotmail.com);
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * Richard Atkinson;
035 * Christian W. Zuckschwerdt;
036 *
037 * Changes:
038 * --------
039 * 03-Apr-2002 : Version 1, contributed by Hari. This class is based on the
040 * StandardXYItemRenderer class (DG);
041 * 09-Apr-2002 : Removed the translated zero from the drawItem method -
042 * overridden the initialise() method to calculate it (DG);
043 * 30-May-2002 : Added tool tip generator to constructor to match super
044 * class (DG);
045 * 25-Jun-2002 : Removed unnecessary local variable (DG);
046 * 05-Aug-2002 : Small modification to drawItem method to support URLs for
047 * HTML image maps (RA);
048 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
049 * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
050 * 25-Mar-2003 : Implemented Serializable (DG);
051 * 01-May-2003 : Modified drawItem() method signature (DG);
052 * 27-Jul-2003 : Made line and polygon properties protected rather than
053 * private (RA);
054 * 30-Jul-2003 : Modified entity constructor (CZ);
055 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
056 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057 * 07-Oct-2003 : Added renderer state (DG);
058 * 08-Dec-2003 : Modified hotspot for chart entity (DG);
059 * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste
060 * overriding easier. Also moved state class into this
061 * class (DG);
062 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
063 * XYToolTipGenerator --> XYItemLabelGenerator (DG);
064 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
065 * getYValue() (DG);
066 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
067 * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
068 * 21-Mar-2005 : Override getLegendItem() (DG);
069 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
070 * ------------- JFREECHART 1.0.x ---------------------------------------------
071 * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
072 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
073 * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
074 * change (DG);
075 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
076 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
077 * 17-Jun-2008 : Apply legend font and paint attributes (DG);
078 *
079 */
080
081 package org.jfree.chart.renderer.xy;
082
083
084 import java.awt.Graphics2D;
085 import java.awt.Paint;
086 import java.awt.Polygon;
087 import java.awt.Shape;
088 import java.awt.Stroke;
089 import java.awt.geom.GeneralPath;
090 import java.awt.geom.Rectangle2D;
091 import java.io.IOException;
092 import java.io.ObjectInputStream;
093 import java.io.ObjectOutputStream;
094 import java.io.Serializable;
095
096 import org.jfree.chart.LegendItem;
097 import org.jfree.chart.axis.ValueAxis;
098 import org.jfree.chart.entity.EntityCollection;
099 import org.jfree.chart.entity.XYItemEntity;
100 import org.jfree.chart.event.RendererChangeEvent;
101 import org.jfree.chart.labels.XYSeriesLabelGenerator;
102 import org.jfree.chart.labels.XYToolTipGenerator;
103 import org.jfree.chart.plot.CrosshairState;
104 import org.jfree.chart.plot.PlotOrientation;
105 import org.jfree.chart.plot.PlotRenderingInfo;
106 import org.jfree.chart.plot.XYPlot;
107 import org.jfree.chart.urls.XYURLGenerator;
108 import org.jfree.data.xy.XYDataset;
109 import org.jfree.io.SerialUtilities;
110 import org.jfree.util.PublicCloneable;
111 import org.jfree.util.ShapeUtilities;
112
113 /**
114 * Area item renderer for an {@link XYPlot}.
115 */
116 public class XYAreaRenderer2 extends AbstractXYItemRenderer
117 implements XYItemRenderer,
118 Cloneable,
119 PublicCloneable,
120 Serializable {
121
122 /** For serialization. */
123 private static final long serialVersionUID = -7378069681579984133L;
124
125 /** A flag that controls whether or not the outline is shown. */
126 private boolean showOutline;
127
128 /**
129 * The shape used to represent an area in each legend item (this should
130 * never be <code>null</code>).
131 */
132 private transient Shape legendArea;
133
134 /**
135 * Constructs a new renderer.
136 */
137 public XYAreaRenderer2() {
138 this(null, null);
139 }
140
141 /**
142 * Constructs a new renderer.
143 *
144 * @param labelGenerator the tool tip generator to use. <code>null</code>
145 * is none.
146 * @param urlGenerator the URL generator (null permitted).
147 */
148 public XYAreaRenderer2(XYToolTipGenerator labelGenerator,
149 XYURLGenerator urlGenerator) {
150 super();
151 this.showOutline = false;
152 setBaseToolTipGenerator(labelGenerator);
153 setURLGenerator(urlGenerator);
154 GeneralPath area = new GeneralPath();
155 area.moveTo(0.0f, -4.0f);
156 area.lineTo(3.0f, -2.0f);
157 area.lineTo(4.0f, 4.0f);
158 area.lineTo(-4.0f, 4.0f);
159 area.lineTo(-3.0f, -2.0f);
160 area.closePath();
161 this.legendArea = area;
162 }
163
164 /**
165 * Returns a flag that controls whether or not outlines of the areas are
166 * drawn.
167 *
168 * @return The flag.
169 *
170 * @see #setOutline(boolean)
171 */
172 public boolean isOutline() {
173 return this.showOutline;
174 }
175
176 /**
177 * Sets a flag that controls whether or not outlines of the areas are
178 * drawn, and sends a {@link RendererChangeEvent} to all registered
179 * listeners.
180 *
181 * @param show the flag.
182 *
183 * @see #isOutline()
184 */
185 public void setOutline(boolean show) {
186 this.showOutline = show;
187 fireChangeEvent();
188 }
189
190 /**
191 * This method should not be used.
192 *
193 * @return <code>false</code> always.
194 *
195 * @deprecated This method was included in the API by mistake and serves
196 * no useful purpose. It has always returned <code>false</code>.
197 *
198 */
199 public boolean getPlotLines() {
200 return false;
201 }
202
203 /**
204 * Returns the shape used to represent an area in the legend.
205 *
206 * @return The legend area (never <code>null</code>).
207 *
208 * @see #setLegendArea(Shape)
209 */
210 public Shape getLegendArea() {
211 return this.legendArea;
212 }
213
214 /**
215 * Sets the shape used as an area in each legend item and sends a
216 * {@link RendererChangeEvent} to all registered listeners.
217 *
218 * @param area the area (<code>null</code> not permitted).
219 *
220 * @see #getLegendArea()
221 */
222 public void setLegendArea(Shape area) {
223 if (area == null) {
224 throw new IllegalArgumentException("Null 'area' argument.");
225 }
226 this.legendArea = area;
227 fireChangeEvent();
228 }
229
230 /**
231 * Returns a default legend item for the specified series. Subclasses
232 * should override this method to generate customised items.
233 *
234 * @param datasetIndex the dataset index (zero-based).
235 * @param series the series index (zero-based).
236 *
237 * @return A legend item for the series.
238 */
239 public LegendItem getLegendItem(int datasetIndex, int series) {
240 LegendItem result = null;
241 XYPlot xyplot = getPlot();
242 if (xyplot != null) {
243 XYDataset dataset = xyplot.getDataset(datasetIndex);
244 if (dataset != null) {
245 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
246 String label = lg.generateLabel(dataset, series);
247 String description = label;
248 String toolTipText = null;
249 if (getLegendItemToolTipGenerator() != null) {
250 toolTipText = getLegendItemToolTipGenerator().generateLabel(
251 dataset, series);
252 }
253 String urlText = null;
254 if (getLegendItemURLGenerator() != null) {
255 urlText = getLegendItemURLGenerator().generateLabel(
256 dataset, series);
257 }
258 Paint paint = lookupSeriesPaint(series);
259 result = new LegendItem(label, description, toolTipText,
260 urlText, this.legendArea, paint);
261 result.setLabelFont(lookupLegendTextFont(series));
262 Paint labelPaint = lookupLegendTextPaint(series);
263 if (labelPaint != null) {
264 result.setLabelPaint(labelPaint);
265 }
266 result.setDataset(dataset);
267 result.setDatasetIndex(datasetIndex);
268 result.setSeriesKey(dataset.getSeriesKey(series));
269 result.setSeriesIndex(series);
270 }
271 }
272 return result;
273 }
274
275 /**
276 * Draws the visual representation of a single data item.
277 *
278 * @param g2 the graphics device.
279 * @param state the renderer state.
280 * @param dataArea the area within which the data is being drawn.
281 * @param info collects information about the drawing.
282 * @param plot the plot (can be used to obtain standard color
283 * information etc).
284 * @param domainAxis the domain axis.
285 * @param rangeAxis the range axis.
286 * @param dataset the dataset.
287 * @param series the series index (zero-based).
288 * @param item the item index (zero-based).
289 * @param crosshairState crosshair information for the plot
290 * (<code>null</code> permitted).
291 * @param pass the pass index.
292 */
293 public void drawItem(Graphics2D g2,
294 XYItemRendererState state,
295 Rectangle2D dataArea,
296 PlotRenderingInfo info,
297 XYPlot plot,
298 ValueAxis domainAxis,
299 ValueAxis rangeAxis,
300 XYDataset dataset,
301 int series,
302 int item,
303 CrosshairState crosshairState,
304 int pass) {
305
306 if (!getItemVisible(series, item)) {
307 return;
308 }
309 // get the data point...
310 double x1 = dataset.getXValue(series, item);
311 double y1 = dataset.getYValue(series, item);
312 if (Double.isNaN(y1)) {
313 y1 = 0.0;
314 }
315
316 double transX1 = domainAxis.valueToJava2D(x1, dataArea,
317 plot.getDomainAxisEdge());
318 double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
319 plot.getRangeAxisEdge());
320
321 // get the previous point and the next point so we can calculate a
322 // "hot spot" for the area (used by the chart entity)...
323 double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
324 double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
325 if (Double.isNaN(y0)) {
326 y0 = 0.0;
327 }
328 double transX0 = domainAxis.valueToJava2D(x0, dataArea,
329 plot.getDomainAxisEdge());
330 double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
331 plot.getRangeAxisEdge());
332
333 int itemCount = dataset.getItemCount(series);
334 double x2 = dataset.getXValue(series, Math.min(item + 1,
335 itemCount - 1));
336 double y2 = dataset.getYValue(series, Math.min(item + 1,
337 itemCount - 1));
338 if (Double.isNaN(y2)) {
339 y2 = 0.0;
340 }
341 double transX2 = domainAxis.valueToJava2D(x2, dataArea,
342 plot.getDomainAxisEdge());
343 double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
344 plot.getRangeAxisEdge());
345
346 double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
347 plot.getRangeAxisEdge());
348 Polygon hotspot = null;
349 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
350 hotspot = new Polygon();
351 hotspot.addPoint((int) transZero,
352 (int) ((transX0 + transX1) / 2.0));
353 hotspot.addPoint((int) ((transY0 + transY1) / 2.0),
354 (int) ((transX0 + transX1) / 2.0));
355 hotspot.addPoint((int) transY1, (int) transX1);
356 hotspot.addPoint((int) ((transY1 + transY2) / 2.0),
357 (int) ((transX1 + transX2) / 2.0));
358 hotspot.addPoint((int) transZero,
359 (int) ((transX1 + transX2) / 2.0));
360 }
361 else { // vertical orientation
362 hotspot = new Polygon();
363 hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
364 (int) transZero);
365 hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
366 (int) ((transY0 + transY1) / 2.0));
367 hotspot.addPoint((int) transX1, (int) transY1);
368 hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
369 (int) ((transY1 + transY2) / 2.0));
370 hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
371 (int) transZero);
372 }
373
374 PlotOrientation orientation = plot.getOrientation();
375 Paint paint = getItemPaint(series, item);
376 Stroke stroke = getItemStroke(series, item);
377 g2.setPaint(paint);
378 g2.setStroke(stroke);
379
380 if (getPlotLines()) {
381 if (item > 0) {
382 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
383 state.workingLine.setLine(transX0, transY0, transX1,
384 transY1);
385 }
386 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
387 state.workingLine.setLine(transY0, transX0, transY1,
388 transX1);
389 }
390 g2.draw(state.workingLine);
391 }
392 }
393
394 // Check if the item is the last item for the series.
395 // and number of items > 0. We can't draw an area for a single point.
396 g2.fill(hotspot);
397
398 // draw an outline around the Area.
399 if (isOutline()) {
400 g2.setStroke(lookupSeriesOutlineStroke(series));
401 g2.setPaint(lookupSeriesOutlinePaint(series));
402 g2.draw(hotspot);
403 }
404 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
405 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
406 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
407 rangeAxisIndex, transX1, transY1, orientation);
408
409 // collect entity and tool tip information...
410 if (state.getInfo() != null) {
411 EntityCollection entities = state.getEntityCollection();
412 if (entities != null && hotspot != null) {
413 String tip = null;
414 XYToolTipGenerator generator = getToolTipGenerator(
415 series, item
416 );
417 if (generator != null) {
418 tip = generator.generateToolTip(dataset, series, item);
419 }
420 String url = null;
421 if (getURLGenerator() != null) {
422 url = getURLGenerator().generateURL(dataset, series, item);
423 }
424 XYItemEntity entity = new XYItemEntity(hotspot, dataset,
425 series, item, tip, url);
426 entities.add(entity);
427 }
428 }
429
430 }
431
432 /**
433 * Tests this renderer for equality with an arbitrary object.
434 *
435 * @param obj the object (<code>null</code> not permitted).
436 *
437 * @return A boolean.
438 */
439 public boolean equals(Object obj) {
440 if (obj == this) {
441 return true;
442 }
443 if (!(obj instanceof XYAreaRenderer2)) {
444 return false;
445 }
446 XYAreaRenderer2 that = (XYAreaRenderer2) obj;
447 if (this.showOutline != that.showOutline) {
448 return false;
449 }
450 if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
451 return false;
452 }
453 return super.equals(obj);
454 }
455
456 /**
457 * Returns a clone of the renderer.
458 *
459 * @return A clone.
460 *
461 * @throws CloneNotSupportedException if the renderer cannot be cloned.
462 */
463 public Object clone() throws CloneNotSupportedException {
464 XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
465 clone.legendArea = ShapeUtilities.clone(this.legendArea);
466 return clone;
467 }
468
469 /**
470 * Provides serialization support.
471 *
472 * @param stream the input stream.
473 *
474 * @throws IOException if there is an I/O error.
475 * @throws ClassNotFoundException if there is a classpath problem.
476 */
477 private void readObject(ObjectInputStream stream)
478 throws IOException, ClassNotFoundException {
479 stream.defaultReadObject();
480 this.legendArea = SerialUtilities.readShape(stream);
481 }
482
483 /**
484 * Provides serialization support.
485 *
486 * @param stream the output stream.
487 *
488 * @throws IOException if there is an I/O error.
489 */
490 private void writeObject(ObjectOutputStream stream) throws IOException {
491 stream.defaultWriteObject();
492 SerialUtilities.writeShape(this.legendArea, stream);
493 }
494
495 }
496