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