001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * XYAreaRenderer.java 029 * ------------------- 030 * (C) Copyright 2002-2005, 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: XYAreaRenderer.java,v 1.12.2.4 2005/12/02 11:59:43 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 HTML 049 * 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 overriding 062 * easier. Also moved state class into this class (DG); 063 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 064 * XYToolTipGenerator --> XYItemLabelGenerator (DG); 065 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 066 * getYValue() (DG); 067 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 068 * 19-Jan-2005 : Now accesses primitives only from dataset (DG); 069 * 21-Mar-2005 : Override getLegendItem() and equals() methods (DG); 070 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 071 * 072 */ 073 074 package org.jfree.chart.renderer.xy; 075 076 import java.awt.Graphics2D; 077 import java.awt.Paint; 078 import java.awt.Polygon; 079 import java.awt.Shape; 080 import java.awt.Stroke; 081 import java.awt.geom.GeneralPath; 082 import java.awt.geom.Line2D; 083 import java.awt.geom.Rectangle2D; 084 import java.io.IOException; 085 import java.io.ObjectInputStream; 086 import java.io.ObjectOutputStream; 087 import java.io.Serializable; 088 089 import org.jfree.chart.LegendItem; 090 import org.jfree.chart.axis.ValueAxis; 091 import org.jfree.chart.entity.EntityCollection; 092 import org.jfree.chart.entity.XYItemEntity; 093 import org.jfree.chart.event.RendererChangeEvent; 094 import org.jfree.chart.labels.XYSeriesLabelGenerator; 095 import org.jfree.chart.labels.XYToolTipGenerator; 096 import org.jfree.chart.plot.CrosshairState; 097 import org.jfree.chart.plot.PlotOrientation; 098 import org.jfree.chart.plot.PlotRenderingInfo; 099 import org.jfree.chart.plot.XYPlot; 100 import org.jfree.chart.urls.XYURLGenerator; 101 import org.jfree.data.xy.XYDataset; 102 import org.jfree.io.SerialUtilities; 103 import org.jfree.util.PublicCloneable; 104 import org.jfree.util.ShapeUtilities; 105 106 /** 107 * Area item renderer for an {@link XYPlot}. This class can draw (a) shapes at 108 * each point, or (b) lines between points, or (c) both shapes and lines, 109 * or (d) filled areas, or (e) filled areas and shapes. 110 */ 111 public class XYAreaRenderer extends AbstractXYItemRenderer 112 implements XYItemRenderer, 113 Cloneable, 114 PublicCloneable, 115 Serializable { 116 117 /** For serialization. */ 118 private static final long serialVersionUID = -4481971353973876747L; 119 120 /** 121 * A state object used by this renderer. 122 */ 123 static class XYAreaRendererState extends XYItemRendererState { 124 125 /** Working storage for the area under one series. */ 126 public Polygon area; 127 128 /** Working line that can be recycled. */ 129 public Line2D line; 130 131 /** 132 * Creates a new state. 133 * 134 * @param info the plot rendering info. 135 */ 136 public XYAreaRendererState(PlotRenderingInfo info) { 137 super(info); 138 this.area = new Polygon(); 139 this.line = new Line2D.Double(); 140 } 141 142 } 143 144 /** Useful constant for specifying the type of rendering (shapes only). */ 145 public static final int SHAPES = 1; 146 147 /** Useful constant for specifying the type of rendering (lines only). */ 148 public static final int LINES = 2; 149 150 /** 151 * Useful constant for specifying the type of rendering (shapes and lines). 152 */ 153 public static final int SHAPES_AND_LINES = 3; 154 155 /** Useful constant for specifying the type of rendering (area only). */ 156 public static final int AREA = 4; 157 158 /** 159 * Useful constant for specifying the type of rendering (area and shapes). 160 */ 161 public static final int AREA_AND_SHAPES = 5; 162 163 /** A flag indicating whether or not shapes are drawn at each XY point. */ 164 private boolean plotShapes; 165 166 /** A flag indicating whether or not lines are drawn between XY points. */ 167 private boolean plotLines; 168 169 /** A flag indicating whether or not Area are drawn at each XY point. */ 170 private boolean plotArea; 171 172 /** A flag that controls whether or not the outline is shown. */ 173 private boolean showOutline; 174 175 /** 176 * The shape used to represent an area in each legend item (this should 177 * never be <code>null</code>). 178 */ 179 private transient Shape legendArea; 180 181 /** 182 * Constructs a new renderer. 183 */ 184 public XYAreaRenderer() { 185 this(AREA); 186 } 187 188 /** 189 * Constructs a new renderer. 190 * 191 * @param type the type of the renderer. 192 */ 193 public XYAreaRenderer(int type) { 194 this(type, null, null); 195 } 196 197 /** 198 * Constructs a new renderer. 199 * <p> 200 * To specify the type of renderer, use one of the constants: SHAPES, LINES, 201 * SHAPES_AND_LINES, AREA or AREA_AND_SHAPES. 202 * 203 * @param type the type of renderer. 204 * @param toolTipGenerator the tool tip generator to use 205 * (<code>null</code> permitted). 206 * @param urlGenerator the URL generator (<code>null</code> permitted). 207 */ 208 public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator, 209 XYURLGenerator urlGenerator) { 210 211 super(); 212 setBaseToolTipGenerator(toolTipGenerator); 213 setURLGenerator(urlGenerator); 214 215 if (type == SHAPES) { 216 this.plotShapes = true; 217 } 218 if (type == LINES) { 219 this.plotLines = true; 220 } 221 if (type == SHAPES_AND_LINES) { 222 this.plotShapes = true; 223 this.plotLines = true; 224 } 225 if (type == AREA) { 226 this.plotArea = true; 227 } 228 if (type == AREA_AND_SHAPES) { 229 this.plotArea = true; 230 this.plotShapes = true; 231 } 232 this.showOutline = false; 233 GeneralPath area = new GeneralPath(); 234 area.moveTo(0.0f, -4.0f); 235 area.lineTo(3.0f, -2.0f); 236 area.lineTo(4.0f, 4.0f); 237 area.lineTo(-4.0f, 4.0f); 238 area.lineTo(-3.0f, -2.0f); 239 area.closePath(); 240 this.legendArea = area; 241 242 } 243 244 /** 245 * Returns a flag that controls whether or not outlines of the areas are 246 * drawn. 247 * 248 * @return The flag. 249 */ 250 public boolean isOutline() { 251 return this.showOutline; 252 } 253 254 /** 255 * Sets a flag that controls whether or not outlines of the areas are drawn. 256 * 257 * @param show the flag. 258 */ 259 public void setOutline(boolean show) { 260 this.showOutline = show; 261 } 262 263 /** 264 * Returns true if shapes are being plotted by the renderer. 265 * 266 * @return <code>true</code> if shapes are being plotted by the renderer. 267 */ 268 public boolean getPlotShapes() { 269 return this.plotShapes; 270 } 271 272 /** 273 * Returns true if lines are being plotted by the renderer. 274 * 275 * @return <code>true</code> if lines are being plotted by the renderer. 276 */ 277 public boolean getPlotLines() { 278 return this.plotLines; 279 } 280 281 /** 282 * Returns true if Area is being plotted by the renderer. 283 * 284 * @return <code>true</code> if Area is being plotted by the renderer. 285 */ 286 public boolean getPlotArea() { 287 return this.plotArea; 288 } 289 290 /** 291 * Returns the shape used to represent an area in the legend. 292 * 293 * @return The legend area (never <code>null</code>). 294 */ 295 public Shape getLegendArea() { 296 return this.legendArea; 297 } 298 299 /** 300 * Sets the shape used as an area in each legend item and sends a 301 * {@link RendererChangeEvent} to all registered listeners. 302 * 303 * @param area the area (<code>null</code> not permitted). 304 */ 305 public void setLegendArea(Shape area) { 306 if (area == null) { 307 throw new IllegalArgumentException("Null 'area' argument."); 308 } 309 this.legendArea = area; 310 notifyListeners(new RendererChangeEvent(this)); 311 } 312 313 /** 314 * Initialises the renderer and returns a state object that should be 315 * passed to all subsequent calls to the drawItem() method. 316 * 317 * @param g2 the graphics device. 318 * @param dataArea the area inside the axes. 319 * @param plot the plot. 320 * @param data the data. 321 * @param info an optional info collection object to return data back to 322 * the caller. 323 * 324 * @return A state object for use by the renderer. 325 */ 326 public XYItemRendererState initialise(Graphics2D g2, 327 Rectangle2D dataArea, 328 XYPlot plot, 329 XYDataset data, 330 PlotRenderingInfo info) { 331 XYAreaRendererState state = new XYAreaRendererState(info); 332 return state; 333 } 334 335 /** 336 * Returns a default legend item for the specified series. Subclasses 337 * should override this method to generate customised items. 338 * 339 * @param datasetIndex the dataset index (zero-based). 340 * @param series the series index (zero-based). 341 * 342 * @return A legend item for the series. 343 */ 344 public LegendItem getLegendItem(int datasetIndex, int series) { 345 LegendItem result = null; 346 XYPlot xyplot = getPlot(); 347 if (xyplot != null) { 348 XYDataset dataset = xyplot.getDataset(datasetIndex); 349 if (dataset != null) { 350 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 351 String label = lg.generateLabel(dataset, series); 352 String description = label; 353 String toolTipText = null; 354 if (getLegendItemToolTipGenerator() != null) { 355 toolTipText = getLegendItemToolTipGenerator().generateLabel( 356 dataset, series 357 ); 358 } 359 String urlText = null; 360 if (getLegendItemURLGenerator() != null) { 361 urlText = getLegendItemURLGenerator().generateLabel( 362 dataset, series 363 ); 364 } 365 Paint paint = getSeriesPaint(series); 366 result = new LegendItem(label, description, toolTipText, 367 urlText, this.legendArea, paint); 368 } 369 } 370 return result; 371 } 372 373 /** 374 * Draws the visual representation of a single data item. 375 * 376 * @param g2 the graphics device. 377 * @param state the renderer state. 378 * @param dataArea the area within which the data is being drawn. 379 * @param info collects information about the drawing. 380 * @param plot the plot (can be used to obtain standard color information 381 * etc). 382 * @param domainAxis the domain axis. 383 * @param rangeAxis the range axis. 384 * @param dataset the dataset. 385 * @param series the series index (zero-based). 386 * @param item the item index (zero-based). 387 * @param crosshairState crosshair information for the plot 388 * (<code>null</code> permitted). 389 * @param pass the pass index. 390 */ 391 public void drawItem(Graphics2D g2, 392 XYItemRendererState state, 393 Rectangle2D dataArea, 394 PlotRenderingInfo info, 395 XYPlot plot, 396 ValueAxis domainAxis, 397 ValueAxis rangeAxis, 398 XYDataset dataset, 399 int series, 400 int item, 401 CrosshairState crosshairState, 402 int pass) { 403 404 if (!getItemVisible(series, item)) { 405 return; 406 } 407 XYAreaRendererState areaState = (XYAreaRendererState) state; 408 409 // get the data point... 410 double x1 = dataset.getXValue(series, item); 411 double y1 = dataset.getYValue(series, item); 412 if (Double.isNaN(y1)) { 413 y1 = 0.0; 414 } 415 double transX1 = domainAxis.valueToJava2D( 416 x1, dataArea, plot.getDomainAxisEdge() 417 ); 418 double transY1 = rangeAxis.valueToJava2D( 419 y1, dataArea, plot.getRangeAxisEdge() 420 ); 421 422 // get the previous point and the next point so we can calculate a 423 // "hot spot" for the area (used by the chart entity)... 424 int itemCount = dataset.getItemCount(series); 425 double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); 426 double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); 427 if (Double.isNaN(y0)) { 428 y0 = 0.0; 429 } 430 double transX0 = domainAxis.valueToJava2D( 431 x0, dataArea, plot.getDomainAxisEdge() 432 ); 433 double transY0 = rangeAxis.valueToJava2D( 434 y0, dataArea, plot.getRangeAxisEdge() 435 ); 436 437 double x2 = dataset.getXValue( 438 series, Math.min(item + 1, itemCount - 1) 439 ); 440 double y2 = dataset.getYValue( 441 series, Math.min(item + 1, itemCount - 1) 442 ); 443 if (Double.isNaN(y2)) { 444 y2 = 0.0; 445 } 446 double transX2 = domainAxis.valueToJava2D( 447 x2, dataArea, plot.getDomainAxisEdge() 448 ); 449 double transY2 = rangeAxis.valueToJava2D( 450 y2, dataArea, plot.getRangeAxisEdge() 451 ); 452 453 double transZero = rangeAxis.valueToJava2D( 454 0.0, dataArea, plot.getRangeAxisEdge() 455 ); 456 Polygon hotspot = null; 457 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 458 hotspot = new Polygon(); 459 hotspot.addPoint( 460 (int) transZero, (int) ((transX0 + transX1) / 2.0) 461 ); 462 hotspot.addPoint( 463 (int) ((transY0 + transY1) / 2.0), 464 (int) ((transX0 + transX1) / 2.0) 465 ); 466 hotspot.addPoint((int) transY1, (int) transX1); 467 hotspot.addPoint( 468 (int) ((transY1 + transY2) / 2.0), 469 (int) ((transX1 + transX2) / 2.0) 470 ); 471 hotspot.addPoint( 472 (int) transZero, (int) ((transX1 + transX2) / 2.0) 473 ); 474 } 475 else { // vertical orientation 476 hotspot = new Polygon(); 477 hotspot.addPoint( 478 (int) ((transX0 + transX1) / 2.0), (int) transZero 479 ); 480 hotspot.addPoint( 481 (int) ((transX0 + transX1) / 2.0), 482 (int) ((transY0 + transY1) / 2.0) 483 ); 484 hotspot.addPoint((int) transX1, (int) transY1); 485 hotspot.addPoint( 486 (int) ((transX1 + transX2) / 2.0), 487 (int) ((transY1 + transY2) / 2.0) 488 ); 489 hotspot.addPoint( 490 (int) ((transX1 + transX2) / 2.0), (int) transZero 491 ); 492 } 493 494 if (item == 0) { // create a new area polygon for the series 495 areaState.area = new Polygon(); 496 // the first point is (x, 0) 497 double zero = rangeAxis.valueToJava2D( 498 0.0, dataArea, plot.getRangeAxisEdge() 499 ); 500 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 501 areaState.area.addPoint((int) transX1, (int) zero); 502 } 503 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 504 areaState.area.addPoint((int) zero, (int) transX1); 505 } 506 } 507 508 // Add each point to Area (x, y) 509 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 510 areaState.area.addPoint((int) transX1, (int) transY1); 511 } 512 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 513 areaState.area.addPoint((int) transY1, (int) transX1); 514 } 515 516 PlotOrientation orientation = plot.getOrientation(); 517 Paint paint = getItemPaint(series, item); 518 Stroke stroke = getItemStroke(series, item); 519 g2.setPaint(paint); 520 g2.setStroke(stroke); 521 522 Shape shape = null; 523 if (getPlotShapes()) { 524 shape = getItemShape(series, item); 525 if (orientation == PlotOrientation.VERTICAL) { 526 shape = ShapeUtilities.createTranslatedShape( 527 shape, transX1, transY1 528 ); 529 } 530 else if (orientation == PlotOrientation.HORIZONTAL) { 531 shape = ShapeUtilities.createTranslatedShape( 532 shape, transY1, transX1 533 ); 534 } 535 g2.draw(shape); 536 } 537 538 if (getPlotLines()) { 539 if (item > 0) { 540 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 541 areaState.line.setLine(transX0, transY0, transX1, transY1); 542 } 543 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 544 areaState.line.setLine(transY0, transX0, transY1, transX1); 545 } 546 g2.draw(areaState.line); 547 } 548 } 549 550 // Check if the item is the last item for the series. 551 // and number of items > 0. We can't draw an area for a single point. 552 if (getPlotArea() && item > 0 && item == (itemCount - 1)) { 553 554 if (orientation == PlotOrientation.VERTICAL) { 555 // Add the last point (x,0) 556 areaState.area.addPoint((int) transX1, (int) transZero); 557 } 558 else if (orientation == PlotOrientation.HORIZONTAL) { 559 // Add the last point (x,0) 560 areaState.area.addPoint((int) transZero, (int) transX1); 561 } 562 563 g2.fill(areaState.area); 564 565 // draw an outline around the Area. 566 if (isOutline()) { 567 g2.setStroke(getItemOutlineStroke(series, item)); 568 g2.setPaint(getItemOutlinePaint(series, item)); 569 g2.draw(areaState.area); 570 } 571 } 572 573 updateCrosshairValues( 574 crosshairState, x1, y1, transX1, transY1, orientation 575 ); 576 577 // collect entity and tool tip information... 578 if (state.getInfo() != null) { 579 EntityCollection entities = state.getEntityCollection(); 580 if (entities != null && hotspot != null) { 581 String tip = null; 582 XYToolTipGenerator generator 583 = getToolTipGenerator(series, item); 584 if (generator != null) { 585 tip = generator.generateToolTip(dataset, series, item); 586 } 587 String url = null; 588 if (getURLGenerator() != null) { 589 url = getURLGenerator().generateURL(dataset, series, item); 590 } 591 XYItemEntity entity = new XYItemEntity( 592 hotspot, dataset, series, item, tip, url 593 ); 594 entities.add(entity); 595 } 596 } 597 598 } 599 600 /** 601 * Returns a clone of the renderer. 602 * 603 * @return A clone. 604 * 605 * @throws CloneNotSupportedException if the renderer cannot be cloned. 606 */ 607 public Object clone() throws CloneNotSupportedException { 608 return super.clone(); 609 } 610 611 /** 612 * Tests this renderer for equality with an arbitrary object. 613 * 614 * @param obj the object (<code>null</code> permitted). 615 * 616 * @return A boolean. 617 */ 618 public boolean equals(Object obj) { 619 if (obj == this) { 620 return true; 621 } 622 if (!(obj instanceof XYAreaRenderer)) { 623 return false; 624 } 625 XYAreaRenderer that = (XYAreaRenderer) obj; 626 if (this.plotArea != that.plotArea) { 627 return false; 628 } 629 if (this.plotLines != that.plotLines) { 630 return false; 631 } 632 if (this.plotShapes != that.plotShapes) { 633 return false; 634 } 635 if (this.showOutline != that.showOutline) { 636 return false; 637 } 638 if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) { 639 return false; 640 } 641 return true; 642 } 643 644 /** 645 * Provides serialization support. 646 * 647 * @param stream the input stream. 648 * 649 * @throws IOException if there is an I/O error. 650 * @throws ClassNotFoundException if there is a classpath problem. 651 */ 652 private void readObject(ObjectInputStream stream) 653 throws IOException, ClassNotFoundException { 654 stream.defaultReadObject(); 655 this.legendArea = SerialUtilities.readShape(stream); 656 } 657 658 /** 659 * Provides serialization support. 660 * 661 * @param stream the output stream. 662 * 663 * @throws IOException if there is an I/O error. 664 */ 665 private void writeObject(ObjectOutputStream stream) throws IOException { 666 stream.defaultWriteObject(); 667 SerialUtilities.writeShape(this.legendArea, stream); 668 } 669 } 670