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