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 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 094 import org.jfree.chart.LegendItem; 095 import org.jfree.chart.axis.ValueAxis; 096 import org.jfree.chart.entity.EntityCollection; 097 import org.jfree.chart.entity.XYItemEntity; 098 import org.jfree.chart.event.RendererChangeEvent; 099 import org.jfree.chart.labels.XYSeriesLabelGenerator; 100 import org.jfree.chart.labels.XYToolTipGenerator; 101 import org.jfree.chart.plot.CrosshairState; 102 import org.jfree.chart.plot.PlotOrientation; 103 import org.jfree.chart.plot.PlotRenderingInfo; 104 import org.jfree.chart.plot.XYPlot; 105 import org.jfree.chart.urls.XYURLGenerator; 106 import org.jfree.data.xy.XYDataset; 107 import org.jfree.io.SerialUtilities; 108 import org.jfree.util.PublicCloneable; 109 import org.jfree.util.ShapeUtilities; 110 111 /** 112 * Area item renderer for an {@link XYPlot}. The example shown here is 113 * generated by the <code>XYAreaRenderer2Demo1.java</code> program included in 114 * the JFreeChart demo collection: 115 * <br><br> 116 * <img src="../../../../../images/XYAreaRenderer2Sample.png" 117 * alt="XYAreaRenderer2Sample.png" /> 118 */ 119 public class XYAreaRenderer2 extends AbstractXYItemRenderer 120 implements XYItemRenderer, PublicCloneable { 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 // Check if the item is the last item for the series. 381 // and number of items > 0. We can't draw an area for a single point. 382 g2.fill(hotspot); 383 384 // draw an outline around the Area. 385 if (isOutline()) { 386 g2.setStroke(lookupSeriesOutlineStroke(series)); 387 g2.setPaint(lookupSeriesOutlinePaint(series)); 388 g2.draw(hotspot); 389 } 390 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 391 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 392 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 393 rangeAxisIndex, transX1, transY1, orientation); 394 395 // collect entity and tool tip information... 396 if (state.getInfo() != null) { 397 EntityCollection entities = state.getEntityCollection(); 398 if (entities != null && hotspot != null) { 399 String tip = null; 400 XYToolTipGenerator generator = getToolTipGenerator(series, 401 item); 402 if (generator != null) { 403 tip = generator.generateToolTip(dataset, series, item); 404 } 405 String url = null; 406 if (getURLGenerator() != null) { 407 url = getURLGenerator().generateURL(dataset, series, item); 408 } 409 XYItemEntity entity = new XYItemEntity(hotspot, dataset, 410 series, item, tip, url); 411 entities.add(entity); 412 } 413 } 414 415 } 416 417 /** 418 * Tests this renderer for equality with an arbitrary object. 419 * 420 * @param obj the object (<code>null</code> not permitted). 421 * 422 * @return A boolean. 423 */ 424 public boolean equals(Object obj) { 425 if (obj == this) { 426 return true; 427 } 428 if (!(obj instanceof XYAreaRenderer2)) { 429 return false; 430 } 431 XYAreaRenderer2 that = (XYAreaRenderer2) obj; 432 if (this.showOutline != that.showOutline) { 433 return false; 434 } 435 if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) { 436 return false; 437 } 438 return super.equals(obj); 439 } 440 441 /** 442 * Returns a clone of the renderer. 443 * 444 * @return A clone. 445 * 446 * @throws CloneNotSupportedException if the renderer cannot be cloned. 447 */ 448 public Object clone() throws CloneNotSupportedException { 449 XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone(); 450 clone.legendArea = ShapeUtilities.clone(this.legendArea); 451 return clone; 452 } 453 454 /** 455 * Provides serialization support. 456 * 457 * @param stream the input stream. 458 * 459 * @throws IOException if there is an I/O error. 460 * @throws ClassNotFoundException if there is a classpath problem. 461 */ 462 private void readObject(ObjectInputStream stream) 463 throws IOException, ClassNotFoundException { 464 stream.defaultReadObject(); 465 this.legendArea = SerialUtilities.readShape(stream); 466 } 467 468 /** 469 * Provides serialization support. 470 * 471 * @param stream the output stream. 472 * 473 * @throws IOException if there is an I/O error. 474 */ 475 private void writeObject(ObjectOutputStream stream) throws IOException { 476 stream.defaultWriteObject(); 477 SerialUtilities.writeShape(this.legendArea, stream); 478 } 479 480 } 481