001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * --------------------------- 028 * StandardXYItemRenderer.java 029 * --------------------------- 030 * (C) Copyright 2001-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Mark Watson (www.markwatson.com); 034 * Jonathan Nash; 035 * Andreas Schneider; 036 * Norbert Kiesel (for TBD Networks); 037 * Christian W. Zuckschwerdt; 038 * Bill Kelemen; 039 * Nicolas Brodu (for Astrium and EADS Corporate Research 040 * Center); 041 * 042 */ 043 044package org.jfree.chart.renderer.xy; 045 046import java.awt.Graphics2D; 047import java.awt.Image; 048import java.awt.Paint; 049import java.awt.Point; 050import java.awt.Shape; 051import java.awt.Stroke; 052import java.awt.geom.GeneralPath; 053import java.awt.geom.Line2D; 054import java.awt.geom.Rectangle2D; 055import java.io.IOException; 056import java.io.ObjectInputStream; 057import java.io.ObjectOutputStream; 058import java.io.Serializable; 059 060import org.jfree.chart.LegendItem; 061import org.jfree.chart.axis.ValueAxis; 062import org.jfree.chart.entity.EntityCollection; 063import org.jfree.chart.event.RendererChangeEvent; 064import org.jfree.chart.labels.XYToolTipGenerator; 065import org.jfree.chart.plot.CrosshairState; 066import org.jfree.chart.plot.Plot; 067import org.jfree.chart.plot.PlotOrientation; 068import org.jfree.chart.plot.PlotRenderingInfo; 069import org.jfree.chart.plot.XYPlot; 070import org.jfree.chart.ui.RectangleEdge; 071import org.jfree.chart.urls.XYURLGenerator; 072import org.jfree.chart.util.BooleanList; 073import org.jfree.chart.util.Args; 074import org.jfree.chart.util.PublicCloneable; 075import org.jfree.chart.util.SerialUtils; 076import org.jfree.chart.util.ShapeUtils; 077import org.jfree.chart.util.UnitType; 078import org.jfree.data.xy.XYDataset; 079 080/** 081 * Standard item renderer for an {@link XYPlot}. This class can draw (a) 082 * shapes at each point, or (b) lines between points, or (c) both shapes and 083 * lines. 084 * <P> 085 * This renderer has been retained for historical reasons and, in general, you 086 * should use the {@link XYLineAndShapeRenderer} class instead. 087 */ 088public class StandardXYItemRenderer extends AbstractXYItemRenderer 089 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 090 091 /** For serialization. */ 092 private static final long serialVersionUID = -3271351259436865995L; 093 094 /** Constant for the type of rendering (shapes only). */ 095 public static final int SHAPES = 1; 096 097 /** Constant for the type of rendering (lines only). */ 098 public static final int LINES = 2; 099 100 /** Constant for the type of rendering (shapes and lines). */ 101 public static final int SHAPES_AND_LINES = SHAPES | LINES; 102 103 /** Constant for the type of rendering (images only). */ 104 public static final int IMAGES = 4; 105 106 /** Constant for the type of rendering (discontinuous lines). */ 107 public static final int DISCONTINUOUS = 8; 108 109 /** Constant for the type of rendering (discontinuous lines). */ 110 public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS; 111 112 /** A flag indicating whether or not shapes are drawn at each XY point. */ 113 private boolean baseShapesVisible; 114 115 /** A flag indicating whether or not lines are drawn between XY points. */ 116 private boolean plotLines; 117 118 /** A flag indicating whether or not images are drawn between XY points. */ 119 private boolean plotImages; 120 121 /** A flag controlling whether or not discontinuous lines are used. */ 122 private boolean plotDiscontinuous; 123 124 /** Specifies how the gap threshold value is interpreted. */ 125 private UnitType gapThresholdType = UnitType.RELATIVE; 126 127 /** Threshold for deciding when to discontinue a line. */ 128 private double gapThreshold = 1.0; 129 130 /** 131 * A table of flags that control (per series) whether or not shapes are 132 * filled. 133 */ 134 private BooleanList seriesShapesFilled; 135 136 /** The default value returned by the getShapeFilled() method. */ 137 private boolean baseShapesFilled; 138 139 /** 140 * A flag that controls whether or not each series is drawn as a single 141 * path. 142 */ 143 private boolean drawSeriesLineAsPath; 144 145 /** 146 * The shape that is used to represent a line in the legend. 147 * This should never be set to {@code null}. 148 */ 149 private transient Shape legendLine; 150 151 /** 152 * Constructs a new renderer. 153 */ 154 public StandardXYItemRenderer() { 155 this(LINES, null); 156 } 157 158 /** 159 * Constructs a new renderer. To specify the type of renderer, use one of 160 * the constants: {@link #SHAPES}, {@link #LINES} or 161 * {@link #SHAPES_AND_LINES}. 162 * 163 * @param type the type. 164 */ 165 public StandardXYItemRenderer(int type) { 166 this(type, null); 167 } 168 169 /** 170 * Constructs a new renderer. To specify the type of renderer, use one of 171 * the constants: {@link #SHAPES}, {@link #LINES} or 172 * {@link #SHAPES_AND_LINES}. 173 * 174 * @param type the type of renderer. 175 * @param toolTipGenerator the item label generator ({@code null} 176 * permitted). 177 */ 178 public StandardXYItemRenderer(int type, 179 XYToolTipGenerator toolTipGenerator) { 180 this(type, toolTipGenerator, null); 181 } 182 183 /** 184 * Constructs a new renderer. To specify the type of renderer, use one of 185 * the constants: {@link #SHAPES}, {@link #LINES} or 186 * {@link #SHAPES_AND_LINES}. 187 * 188 * @param type the type of renderer. 189 * @param toolTipGenerator the item label generator ({@code null} 190 * permitted). 191 * @param urlGenerator the URL generator. 192 */ 193 public StandardXYItemRenderer(int type, XYToolTipGenerator toolTipGenerator, 194 XYURLGenerator urlGenerator) { 195 196 super(); 197 setDefaultToolTipGenerator(toolTipGenerator); 198 setURLGenerator(urlGenerator); 199 if ((type & SHAPES) != 0) { 200 this.baseShapesVisible = true; 201 } 202 if ((type & LINES) != 0) { 203 this.plotLines = true; 204 } 205 if ((type & IMAGES) != 0) { 206 this.plotImages = true; 207 } 208 if ((type & DISCONTINUOUS) != 0) { 209 this.plotDiscontinuous = true; 210 } 211 212 this.seriesShapesFilled = new BooleanList(); 213 this.baseShapesFilled = true; 214 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 215 this.drawSeriesLineAsPath = false; 216 } 217 218 /** 219 * Returns true if shapes are being plotted by the renderer. 220 * 221 * @return {@code true} if shapes are being plotted by the renderer. 222 * 223 * @see #setBaseShapesVisible 224 */ 225 public boolean getBaseShapesVisible() { 226 return this.baseShapesVisible; 227 } 228 229 /** 230 * Sets the flag that controls whether or not a shape is plotted at each 231 * data point. 232 * 233 * @param flag the flag. 234 * 235 * @see #getBaseShapesVisible 236 */ 237 public void setBaseShapesVisible(boolean flag) { 238 if (this.baseShapesVisible != flag) { 239 this.baseShapesVisible = flag; 240 fireChangeEvent(); 241 } 242 } 243 244 // SHAPES FILLED 245 246 /** 247 * Returns the flag used to control whether or not the shape for an item is 248 * filled. 249 * <p> 250 * The default implementation passes control to the 251 * {@code getSeriesShapesFilled()} method. You can override this method 252 * if you require different behaviour. 253 * 254 * @param series the series index (zero-based). 255 * @param item the item index (zero-based). 256 * 257 * @return A boolean. 258 * 259 * @see #getSeriesShapesFilled(int) 260 */ 261 public boolean getItemShapeFilled(int series, int item) { 262 263 // otherwise look up the paint table 264 Boolean flag = this.seriesShapesFilled.getBoolean(series); 265 if (flag != null) { 266 return flag; 267 } 268 else { 269 return this.baseShapesFilled; 270 } 271 } 272 273 /** 274 * Returns the flag used to control whether or not the shapes for a series 275 * are filled. 276 * 277 * @param series the series index (zero-based). 278 * 279 * @return A boolean. 280 */ 281 public Boolean getSeriesShapesFilled(int series) { 282 return this.seriesShapesFilled.getBoolean(series); 283 } 284 285 /** 286 * Sets the 'shapes filled' flag for a series and sends a 287 * {@link RendererChangeEvent} to all registered listeners. 288 * 289 * @param series the series index (zero-based). 290 * @param flag the flag. 291 * 292 * @see #getSeriesShapesFilled(int) 293 */ 294 public void setSeriesShapesFilled(int series, Boolean flag) { 295 this.seriesShapesFilled.setBoolean(series, flag); 296 fireChangeEvent(); 297 } 298 299 /** 300 * Returns the base 'shape filled' attribute. 301 * 302 * @return The base flag. 303 * 304 * @see #setBaseShapesFilled(boolean) 305 */ 306 public boolean getBaseShapesFilled() { 307 return this.baseShapesFilled; 308 } 309 310 /** 311 * Sets the base 'shapes filled' flag and sends a 312 * {@link RendererChangeEvent} to all registered listeners. 313 * 314 * @param flag the flag. 315 * 316 * @see #getBaseShapesFilled() 317 */ 318 public void setBaseShapesFilled(boolean flag) { 319 this.baseShapesFilled = flag; 320 } 321 322 /** 323 * Returns true if lines are being plotted by the renderer. 324 * 325 * @return {@code true} if lines are being plotted by the renderer. 326 * 327 * @see #setPlotLines(boolean) 328 */ 329 public boolean getPlotLines() { 330 return this.plotLines; 331 } 332 333 /** 334 * Sets the flag that controls whether or not a line is plotted between 335 * each data point and sends a {@link RendererChangeEvent} to all 336 * registered listeners. 337 * 338 * @param flag the flag. 339 * 340 * @see #getPlotLines() 341 */ 342 public void setPlotLines(boolean flag) { 343 if (this.plotLines != flag) { 344 this.plotLines = flag; 345 fireChangeEvent(); 346 } 347 } 348 349 /** 350 * Returns the gap threshold type (relative or absolute). 351 * 352 * @return The type. 353 * 354 * @see #setGapThresholdType(UnitType) 355 */ 356 public UnitType getGapThresholdType() { 357 return this.gapThresholdType; 358 } 359 360 /** 361 * Sets the gap threshold type and sends a {@link RendererChangeEvent} to 362 * all registered listeners. 363 * 364 * @param thresholdType the type ({@code null} not permitted). 365 * 366 * @see #getGapThresholdType() 367 */ 368 public void setGapThresholdType(UnitType thresholdType) { 369 Args.nullNotPermitted(thresholdType, "thresholdType"); 370 this.gapThresholdType = thresholdType; 371 fireChangeEvent(); 372 } 373 374 /** 375 * Returns the gap threshold for discontinuous lines. 376 * 377 * @return The gap threshold. 378 * 379 * @see #setGapThreshold(double) 380 */ 381 public double getGapThreshold() { 382 return this.gapThreshold; 383 } 384 385 /** 386 * Sets the gap threshold for discontinuous lines and sends a 387 * {@link RendererChangeEvent} to all registered listeners. 388 * 389 * @param t the threshold. 390 * 391 * @see #getGapThreshold() 392 */ 393 public void setGapThreshold(double t) { 394 this.gapThreshold = t; 395 fireChangeEvent(); 396 } 397 398 /** 399 * Returns true if images are being plotted by the renderer. 400 * 401 * @return {@code true} if images are being plotted by the renderer. 402 * 403 * @see #setPlotImages(boolean) 404 */ 405 public boolean getPlotImages() { 406 return this.plotImages; 407 } 408 409 /** 410 * Sets the flag that controls whether or not an image is drawn at each 411 * data point and sends a {@link RendererChangeEvent} to all registered 412 * listeners. 413 * 414 * @param flag the flag. 415 * 416 * @see #getPlotImages() 417 */ 418 public void setPlotImages(boolean flag) { 419 if (this.plotImages != flag) { 420 this.plotImages = flag; 421 fireChangeEvent(); 422 } 423 } 424 425 /** 426 * Returns a flag that controls whether or not the renderer shows 427 * discontinuous lines. 428 * 429 * @return {@code true} if lines should be discontinuous. 430 */ 431 public boolean getPlotDiscontinuous() { 432 return this.plotDiscontinuous; 433 } 434 435 /** 436 * Sets the flag that controls whether or not the renderer shows 437 * discontinuous lines, and sends a {@link RendererChangeEvent} to all 438 * registered listeners. 439 * 440 * @param flag the new flag value. 441 */ 442 public void setPlotDiscontinuous(boolean flag) { 443 if (this.plotDiscontinuous != flag) { 444 this.plotDiscontinuous = flag; 445 fireChangeEvent(); 446 } 447 } 448 449 /** 450 * Returns a flag that controls whether or not each series is drawn as a 451 * single path. 452 * 453 * @return A boolean. 454 * 455 * @see #setDrawSeriesLineAsPath(boolean) 456 */ 457 public boolean getDrawSeriesLineAsPath() { 458 return this.drawSeriesLineAsPath; 459 } 460 461 /** 462 * Sets the flag that controls whether or not each series is drawn as a 463 * single path. 464 * 465 * @param flag the flag. 466 * 467 * @see #getDrawSeriesLineAsPath() 468 */ 469 public void setDrawSeriesLineAsPath(boolean flag) { 470 this.drawSeriesLineAsPath = flag; 471 } 472 473 /** 474 * Returns the shape used to represent a line in the legend. 475 * 476 * @return The legend line (never {@code null}). 477 * 478 * @see #setLegendLine(Shape) 479 */ 480 public Shape getLegendLine() { 481 return this.legendLine; 482 } 483 484 /** 485 * Sets the shape used as a line in each legend item and sends a 486 * {@link RendererChangeEvent} to all registered listeners. 487 * 488 * @param line the line ({@code null} not permitted). 489 * 490 * @see #getLegendLine() 491 */ 492 public void setLegendLine(Shape line) { 493 Args.nullNotPermitted(line, "line"); 494 this.legendLine = line; 495 fireChangeEvent(); 496 } 497 498 /** 499 * Returns a legend item for a series. 500 * 501 * @param datasetIndex the dataset index (zero-based). 502 * @param series the series index (zero-based). 503 * 504 * @return A legend item for the series. 505 */ 506 @Override 507 public LegendItem getLegendItem(int datasetIndex, int series) { 508 XYPlot plot = getPlot(); 509 if (plot == null) { 510 return null; 511 } 512 LegendItem result = null; 513 XYDataset dataset = plot.getDataset(datasetIndex); 514 if (dataset != null) { 515 if (getItemVisible(series, 0)) { 516 String label = getLegendItemLabelGenerator().generateLabel( 517 dataset, series); 518 String description = label; 519 String toolTipText = null; 520 if (getLegendItemToolTipGenerator() != null) { 521 toolTipText = getLegendItemToolTipGenerator().generateLabel( 522 dataset, series); 523 } 524 String urlText = null; 525 if (getLegendItemURLGenerator() != null) { 526 urlText = getLegendItemURLGenerator().generateLabel( 527 dataset, series); 528 } 529 Shape shape = lookupLegendShape(series); 530 boolean shapeFilled = getItemShapeFilled(series, 0); 531 Paint paint = lookupSeriesPaint(series); 532 Paint linePaint = paint; 533 Stroke lineStroke = lookupSeriesStroke(series); 534 result = new LegendItem(label, description, toolTipText, 535 urlText, this.baseShapesVisible, shape, shapeFilled, 536 paint, !shapeFilled, paint, lineStroke, 537 this.plotLines, this.legendLine, lineStroke, linePaint); 538 result.setLabelFont(lookupLegendTextFont(series)); 539 Paint labelPaint = lookupLegendTextPaint(series); 540 if (labelPaint != null) { 541 result.setLabelPaint(labelPaint); 542 } 543 result.setDataset(dataset); 544 result.setDatasetIndex(datasetIndex); 545 result.setSeriesKey(dataset.getSeriesKey(series)); 546 result.setSeriesIndex(series); 547 } 548 } 549 return result; 550 } 551 552 /** 553 * Records the state for the renderer. This is used to preserve state 554 * information between calls to the drawItem() method for a single chart 555 * drawing. 556 */ 557 public static class State extends XYItemRendererState { 558 559 /** The path for the current series. */ 560 public GeneralPath seriesPath; 561 562 /** The series index. */ 563 private int seriesIndex; 564 565 /** 566 * A flag that indicates if the last (x, y) point was 'good' 567 * (non-null). 568 */ 569 private boolean lastPointGood; 570 571 /** 572 * Creates a new state instance. 573 * 574 * @param info the plot rendering info. 575 */ 576 public State(PlotRenderingInfo info) { 577 super(info); 578 } 579 580 /** 581 * Returns a flag that indicates if the last point drawn (in the 582 * current series) was 'good' (non-null). 583 * 584 * @return A boolean. 585 */ 586 public boolean isLastPointGood() { 587 return this.lastPointGood; 588 } 589 590 /** 591 * Sets a flag that indicates if the last point drawn (in the current 592 * series) was 'good' (non-null). 593 * 594 * @param good the flag. 595 */ 596 public void setLastPointGood(boolean good) { 597 this.lastPointGood = good; 598 } 599 600 /** 601 * Returns the series index for the current path. 602 * 603 * @return The series index for the current path. 604 */ 605 public int getSeriesIndex() { 606 return this.seriesIndex; 607 } 608 609 /** 610 * Sets the series index for the current path. 611 * 612 * @param index the index. 613 */ 614 public void setSeriesIndex(int index) { 615 this.seriesIndex = index; 616 } 617 } 618 619 /** 620 * Initialises the renderer. 621 * <P> 622 * This method will be called before the first item is rendered, giving the 623 * renderer an opportunity to initialise any state information it wants to 624 * maintain. The renderer can do nothing if it chooses. 625 * 626 * @param g2 the graphics device. 627 * @param dataArea the area inside the axes. 628 * @param plot the plot. 629 * @param data the data. 630 * @param info an optional info collection object to return data back to 631 * the caller. 632 * 633 * @return The renderer state. 634 */ 635 @Override 636 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 637 XYPlot plot, XYDataset data, PlotRenderingInfo info) { 638 639 State state = new State(info); 640 state.seriesPath = new GeneralPath(); 641 state.seriesIndex = -1; 642 return state; 643 644 } 645 646 /** 647 * Draws the visual representation of a single data item. 648 * 649 * @param g2 the graphics device. 650 * @param state the renderer state. 651 * @param dataArea the area within which the data is being drawn. 652 * @param info collects information about the drawing. 653 * @param plot the plot (can be used to obtain standard color information 654 * etc). 655 * @param domainAxis the domain axis. 656 * @param rangeAxis the range axis. 657 * @param dataset the dataset. 658 * @param series the series index (zero-based). 659 * @param item the item index (zero-based). 660 * @param crosshairState crosshair information for the plot 661 * ({@code null} permitted). 662 * @param pass the pass index. 663 */ 664 @Override 665 public void drawItem(Graphics2D g2, XYItemRendererState state, 666 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 667 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 668 int series, int item, CrosshairState crosshairState, int pass) { 669 670 boolean itemVisible = getItemVisible(series, item); 671 672 // setup for collecting optional entity info... 673 Shape entityArea = null; 674 EntityCollection entities = null; 675 if (info != null) { 676 entities = info.getOwner().getEntityCollection(); 677 } 678 679 PlotOrientation orientation = plot.getOrientation(); 680 Paint paint = getItemPaint(series, item); 681 Stroke seriesStroke = getItemStroke(series, item); 682 g2.setPaint(paint); 683 g2.setStroke(seriesStroke); 684 685 // get the data point... 686 double x1 = dataset.getXValue(series, item); 687 double y1 = dataset.getYValue(series, item); 688 if (Double.isNaN(x1) || Double.isNaN(y1)) { 689 itemVisible = false; 690 } 691 692 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 693 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 694 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 695 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 696 697 if (getPlotLines()) { 698 if (this.drawSeriesLineAsPath) { 699 State s = (State) state; 700 if (s.getSeriesIndex() != series) { 701 // we are starting a new series path 702 s.seriesPath.reset(); 703 s.lastPointGood = false; 704 s.setSeriesIndex(series); 705 } 706 707 // update path to reflect latest point 708 if (itemVisible && !Double.isNaN(transX1) 709 && !Double.isNaN(transY1)) { 710 float x = (float) transX1; 711 float y = (float) transY1; 712 if (orientation == PlotOrientation.HORIZONTAL) { 713 x = (float) transY1; 714 y = (float) transX1; 715 } 716 if (s.isLastPointGood()) { 717 // TODO: check threshold 718 s.seriesPath.lineTo(x, y); 719 } 720 else { 721 s.seriesPath.moveTo(x, y); 722 } 723 s.setLastPointGood(true); 724 } 725 else { 726 s.setLastPointGood(false); 727 } 728 if (item == dataset.getItemCount(series) - 1) { 729 if (s.seriesIndex == series) { 730 // draw path 731 g2.setStroke(lookupSeriesStroke(series)); 732 g2.setPaint(lookupSeriesPaint(series)); 733 g2.draw(s.seriesPath); 734 } 735 } 736 } 737 738 else if (item != 0 && itemVisible) { 739 // get the previous data point... 740 double x0 = dataset.getXValue(series, item - 1); 741 double y0 = dataset.getYValue(series, item - 1); 742 if (!Double.isNaN(x0) && !Double.isNaN(y0)) { 743 boolean drawLine = true; 744 if (getPlotDiscontinuous()) { 745 // only draw a line if the gap between the current and 746 // previous data point is within the threshold 747 int numX = dataset.getItemCount(series); 748 double minX = dataset.getXValue(series, 0); 749 double maxX = dataset.getXValue(series, numX - 1); 750 if (this.gapThresholdType == UnitType.ABSOLUTE) { 751 drawLine = Math.abs(x1 - x0) <= this.gapThreshold; 752 } 753 else { 754 drawLine = Math.abs(x1 - x0) <= ((maxX - minX) 755 / numX * getGapThreshold()); 756 } 757 } 758 if (drawLine) { 759 double transX0 = domainAxis.valueToJava2D(x0, dataArea, 760 xAxisLocation); 761 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 762 yAxisLocation); 763 764 // only draw if we have good values 765 if (Double.isNaN(transX0) || Double.isNaN(transY0) 766 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 767 return; 768 } 769 770 if (orientation == PlotOrientation.HORIZONTAL) { 771 state.workingLine.setLine(transY0, transX0, 772 transY1, transX1); 773 } 774 else if (orientation == PlotOrientation.VERTICAL) { 775 state.workingLine.setLine(transX0, transY0, 776 transX1, transY1); 777 } 778 779 if (state.workingLine.intersects(dataArea)) { 780 g2.draw(state.workingLine); 781 } 782 } 783 } 784 } 785 } 786 787 // we needed to get this far even for invisible items, to ensure that 788 // seriesPath updates happened, but now there is nothing more we need 789 // to do for non-visible items... 790 if (!itemVisible) { 791 return; 792 } 793 794 if (getBaseShapesVisible()) { 795 796 Shape shape = getItemShape(series, item); 797 if (orientation == PlotOrientation.HORIZONTAL) { 798 shape = ShapeUtils.createTranslatedShape(shape, transY1, 799 transX1); 800 } 801 else if (orientation == PlotOrientation.VERTICAL) { 802 shape = ShapeUtils.createTranslatedShape(shape, transX1, 803 transY1); 804 } 805 if (shape.intersects(dataArea)) { 806 if (getItemShapeFilled(series, item)) { 807 g2.fill(shape); 808 } 809 else { 810 g2.draw(shape); 811 } 812 } 813 entityArea = shape; 814 815 } 816 817 if (getPlotImages()) { 818 Image image = getImage(plot, series, item, transX1, transY1); 819 if (image != null) { 820 Point hotspot = getImageHotspot(plot, series, item, transX1, 821 transY1, image); 822 g2.drawImage(image, (int) (transX1 - hotspot.getX()), 823 (int) (transY1 - hotspot.getY()), null); 824 entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(), 825 transY1 - hotspot.getY(), image.getWidth(null), 826 image.getHeight(null)); 827 } 828 829 } 830 831 double xx = transX1; 832 double yy = transY1; 833 if (orientation == PlotOrientation.HORIZONTAL) { 834 xx = transY1; 835 yy = transX1; 836 } 837 838 // draw the item label if there is one... 839 if (isItemLabelVisible(series, item)) { 840 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 841 (y1 < 0.0)); 842 } 843 844 int datasetIndex = plot.indexOf(dataset); 845 updateCrosshairValues(crosshairState, x1, y1, datasetIndex, 846 transX1, transY1, orientation); 847 848 // add an entity for the item... 849 if (entities != null && ShapeUtils.isPointInRect(dataArea, xx, yy)) { 850 addEntity(entities, entityArea, dataset, series, item, xx, yy); 851 } 852 853 } 854 855 /** 856 * Tests this renderer for equality with another object. 857 * 858 * @param obj the object ({@code null} permitted). 859 * 860 * @return A boolean. 861 */ 862 @Override 863 public boolean equals(Object obj) { 864 865 if (obj == this) { 866 return true; 867 } 868 if (!(obj instanceof StandardXYItemRenderer)) { 869 return false; 870 } 871 StandardXYItemRenderer that = (StandardXYItemRenderer) obj; 872 if (this.baseShapesVisible != that.baseShapesVisible) { 873 return false; 874 } 875 if (this.plotLines != that.plotLines) { 876 return false; 877 } 878 if (this.plotImages != that.plotImages) { 879 return false; 880 } 881 if (this.plotDiscontinuous != that.plotDiscontinuous) { 882 return false; 883 } 884 if (this.gapThresholdType != that.gapThresholdType) { 885 return false; 886 } 887 if (this.gapThreshold != that.gapThreshold) { 888 return false; 889 } 890 if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) { 891 return false; 892 } 893 if (this.baseShapesFilled != that.baseShapesFilled) { 894 return false; 895 } 896 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 897 return false; 898 } 899 if (!ShapeUtils.equal(this.legendLine, that.legendLine)) { 900 return false; 901 } 902 return super.equals(obj); 903 904 } 905 906 /** 907 * Returns a clone of the renderer. 908 * 909 * @return A clone. 910 * 911 * @throws CloneNotSupportedException if the renderer cannot be cloned. 912 */ 913 @Override 914 public Object clone() throws CloneNotSupportedException { 915 StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone(); 916 clone.seriesShapesFilled 917 = (BooleanList) this.seriesShapesFilled.clone(); 918 clone.legendLine = ShapeUtils.clone(this.legendLine); 919 return clone; 920 } 921 922 //////////////////////////////////////////////////////////////////////////// 923 // PROTECTED METHODS 924 // These provide the opportunity to subclass the standard renderer and 925 // create custom effects. 926 //////////////////////////////////////////////////////////////////////////// 927 928 /** 929 * Returns the image used to draw a single data item. 930 * 931 * @param plot the plot (can be used to obtain standard color information 932 * etc). 933 * @param series the series index. 934 * @param item the item index. 935 * @param x the x value of the item. 936 * @param y the y value of the item. 937 * 938 * @return The image. 939 * 940 * @see #getPlotImages() 941 */ 942 protected Image getImage(Plot plot, int series, int item, 943 double x, double y) { 944 // this method must be overridden if you want to display images 945 return null; 946 } 947 948 /** 949 * Returns the hotspot of the image used to draw a single data item. 950 * The hotspot is the point relative to the top left of the image 951 * that should indicate the data item. The default is the center of the 952 * image. 953 * 954 * @param plot the plot (can be used to obtain standard color information 955 * etc). 956 * @param image the image (can be used to get size information about the 957 * image) 958 * @param series the series index 959 * @param item the item index 960 * @param x the x value of the item 961 * @param y the y value of the item 962 * 963 * @return The hotspot used to draw the data item. 964 */ 965 protected Point getImageHotspot(Plot plot, int series, int item, 966 double x, double y, Image image) { 967 968 int height = image.getHeight(null); 969 int width = image.getWidth(null); 970 return new Point(width / 2, height / 2); 971 972 } 973 974 /** 975 * Provides serialization support. 976 * 977 * @param stream the input stream. 978 * 979 * @throws IOException if there is an I/O error. 980 * @throws ClassNotFoundException if there is a classpath problem. 981 */ 982 private void readObject(ObjectInputStream stream) 983 throws IOException, ClassNotFoundException { 984 stream.defaultReadObject(); 985 this.legendLine = SerialUtils.readShape(stream); 986 } 987 988 /** 989 * Provides serialization support. 990 * 991 * @param stream the output stream. 992 * 993 * @throws IOException if there is an I/O error. 994 */ 995 private void writeObject(ObjectOutputStream stream) throws IOException { 996 stream.defaultWriteObject(); 997 SerialUtils.writeShape(this.legendLine, stream); 998 } 999 1000}