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