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 * XYLineAndShapeRenderer.java 029 * --------------------------- 030 * (C) Copyright 2004, 2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: XYLineAndShapeRenderer.java,v 1.20.2.5 2005/11/28 12:06:35 mungady Exp $ 036 * 037 * Changes: 038 * -------- 039 * 27-Jan-2004 : Version 1 (DG); 040 * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste 041 * overriding easier (DG); 042 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 043 * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG); 044 * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path 045 * (necessary when using a dashed stroke with many data 046 * items) (DG); 047 * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG); 048 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 049 * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG); 050 * 28-Jan-2005 : Added new constructor (DG); 051 * 09-Mar-2005 : Added fillPaint settings (DG); 052 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 053 * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible, 054 * defaultShapesVisible --> baseShapesVisible and 055 * defaultShapesFilled --> baseShapesFilled (DG); 056 * 29-Jul-2005 : Added code to draw item labels (DG); 057 * 058 */ 059 060 package org.jfree.chart.renderer.xy; 061 062 import java.awt.Graphics2D; 063 import java.awt.Paint; 064 import java.awt.Shape; 065 import java.awt.Stroke; 066 import java.awt.geom.GeneralPath; 067 import java.awt.geom.Line2D; 068 import java.awt.geom.Rectangle2D; 069 import java.io.IOException; 070 import java.io.ObjectInputStream; 071 import java.io.ObjectOutputStream; 072 import java.io.Serializable; 073 074 import org.jfree.chart.LegendItem; 075 import org.jfree.chart.axis.ValueAxis; 076 import org.jfree.chart.entity.EntityCollection; 077 import org.jfree.chart.event.RendererChangeEvent; 078 import org.jfree.chart.plot.CrosshairState; 079 import org.jfree.chart.plot.PlotOrientation; 080 import org.jfree.chart.plot.PlotRenderingInfo; 081 import org.jfree.chart.plot.XYPlot; 082 import org.jfree.data.xy.XYDataset; 083 import org.jfree.io.SerialUtilities; 084 import org.jfree.ui.RectangleEdge; 085 import org.jfree.util.BooleanList; 086 import org.jfree.util.BooleanUtilities; 087 import org.jfree.util.ObjectUtilities; 088 import org.jfree.util.PublicCloneable; 089 import org.jfree.util.ShapeUtilities; 090 091 /** 092 * A renderer that can be used with the {@link XYPlot} class. 093 */ 094 public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 095 implements XYItemRenderer, 096 Cloneable, 097 PublicCloneable, 098 Serializable { 099 100 /** For serialization. */ 101 private static final long serialVersionUID = -7435246895986425885L; 102 103 /** A flag that controls whether or not lines are visible for ALL series. */ 104 private Boolean linesVisible; 105 106 /** 107 * A table of flags that control (per series) whether or not lines are 108 * visible. 109 */ 110 private BooleanList seriesLinesVisible; 111 112 /** The default value returned by the getLinesVisible() method. */ 113 private boolean baseLinesVisible; 114 115 /** The shape that is used to represent a line in the legend. */ 116 private transient Shape legendLine; 117 118 /** 119 * A flag that controls whether or not shapes are visible for ALL series. 120 */ 121 private Boolean shapesVisible; 122 123 /** 124 * A table of flags that control (per series) whether or not shapes are 125 * visible. 126 */ 127 private BooleanList seriesShapesVisible; 128 129 /** The default value returned by the getShapeVisible() method. */ 130 private boolean baseShapesVisible; 131 132 /** A flag that controls whether or not shapes are filled for ALL series. */ 133 private Boolean shapesFilled; 134 135 /** 136 * A table of flags that control (per series) whether or not shapes are 137 * filled. 138 */ 139 private BooleanList seriesShapesFilled; 140 141 /** The default value returned by the getShapeFilled() method. */ 142 private boolean baseShapesFilled; 143 144 /** A flag that controls whether outlines are drawn for shapes. */ 145 private boolean drawOutlines; 146 147 /** 148 * A flag that controls whether the fill paint is used for filling 149 * shapes. 150 */ 151 private boolean useFillPaint; 152 153 /** 154 * A flag that controls whether the outline paint is used for drawing shape 155 * outlines. 156 */ 157 private boolean useOutlinePaint; 158 159 /** 160 * A flag that controls whether or not each series is drawn as a single 161 * path. 162 */ 163 private boolean drawSeriesLineAsPath; 164 165 /** 166 * Creates a new renderer with both lines and shapes visible. 167 */ 168 public XYLineAndShapeRenderer() { 169 this(true, true); 170 } 171 172 /** 173 * Creates a new renderer. 174 * 175 * @param lines lines visible? 176 * @param shapes shapes visible? 177 */ 178 public XYLineAndShapeRenderer(boolean lines, boolean shapes) { 179 this.linesVisible = null; 180 this.seriesLinesVisible = new BooleanList(); 181 this.baseLinesVisible = lines; 182 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 183 184 this.shapesVisible = null; 185 this.seriesShapesVisible = new BooleanList(); 186 this.baseShapesVisible = shapes; 187 188 this.shapesFilled = null; 189 this.useFillPaint = false; // use item paint for fills by default 190 this.seriesShapesFilled = new BooleanList(); 191 this.baseShapesFilled = true; 192 193 this.drawOutlines = true; 194 this.useOutlinePaint = false; // use item paint for outlines by 195 // default, not outline paint 196 197 this.drawSeriesLineAsPath = false; 198 } 199 200 /** 201 * Returns a flag that controls whether or not each series is drawn as a 202 * single path. 203 * 204 * @return A boolean. 205 * 206 * @see #setDrawSeriesLineAsPath(boolean) 207 */ 208 public boolean getDrawSeriesLineAsPath() { 209 return this.drawSeriesLineAsPath; 210 } 211 212 /** 213 * Sets the flag that controls whether or not each series is drawn as a 214 * single path. 215 * 216 * @param flag the flag. 217 * 218 * @see #getDrawSeriesLineAsPath() 219 */ 220 public void setDrawSeriesLineAsPath(boolean flag) { 221 if (this.drawSeriesLineAsPath != flag) { 222 this.drawSeriesLineAsPath = flag; 223 notifyListeners(new RendererChangeEvent(this)); 224 } 225 } 226 227 /** 228 * Returns the number of passes through the data that the renderer requires 229 * in order to draw the chart. Most charts will require a single pass, but 230 * some require two passes. 231 * 232 * @return The pass count. 233 */ 234 public int getPassCount() { 235 return 2; 236 } 237 238 // LINES VISIBLE 239 240 /** 241 * Returns the flag used to control whether or not the shape for an item is 242 * visible. 243 * 244 * @param series the series index (zero-based). 245 * @param item the item index (zero-based). 246 * 247 * @return A boolean. 248 */ 249 public boolean getItemLineVisible(int series, int item) { 250 Boolean flag = this.linesVisible; 251 if (flag == null) { 252 flag = getSeriesLinesVisible(series); 253 } 254 if (flag != null) { 255 return flag.booleanValue(); 256 } 257 else { 258 return this.baseLinesVisible; 259 } 260 } 261 262 /** 263 * Returns a flag that controls whether or not lines are drawn for ALL 264 * series. If this flag is <code>null</code>, then the "per series" 265 * settings will apply. 266 * 267 * @return A flag (possibly <code>null</code>). 268 */ 269 public Boolean getLinesVisible() { 270 return this.linesVisible; 271 } 272 273 /** 274 * Sets a flag that controls whether or not lines are drawn between the 275 * items in ALL series, and sends a {@link RendererChangeEvent} to all 276 * registered listeners. You need to set this to <code>null</code> if you 277 * want the "per series" settings to apply. 278 * 279 * @param visible the flag (<code>null</code> permitted). 280 */ 281 public void setLinesVisible(Boolean visible) { 282 this.linesVisible = visible; 283 notifyListeners(new RendererChangeEvent(this)); 284 } 285 286 /** 287 * Sets a flag that controls whether or not lines are drawn between the 288 * items in ALL series, and sends a {@link RendererChangeEvent} to all 289 * registered listeners. 290 * 291 * @param visible the flag. 292 */ 293 public void setLinesVisible(boolean visible) { 294 setLinesVisible(BooleanUtilities.valueOf(visible)); 295 } 296 297 /** 298 * Returns the flag used to control whether or not the lines for a series 299 * are visible. 300 * 301 * @param series the series index (zero-based). 302 * 303 * @return The flag (possibly <code>null</code>). 304 */ 305 public Boolean getSeriesLinesVisible(int series) { 306 return this.seriesLinesVisible.getBoolean(series); 307 } 308 309 /** 310 * Sets the 'lines visible' flag for a series. 311 * 312 * @param series the series index (zero-based). 313 * @param flag the flag (<code>null</code> permitted). 314 */ 315 public void setSeriesLinesVisible(int series, Boolean flag) { 316 this.seriesLinesVisible.setBoolean(series, flag); 317 notifyListeners(new RendererChangeEvent(this)); 318 } 319 320 /** 321 * Sets the 'lines visible' flag for a series. 322 * 323 * @param series the series index (zero-based). 324 * @param visible the flag. 325 */ 326 public void setSeriesLinesVisible(int series, boolean visible) { 327 setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible)); 328 } 329 330 /** 331 * Returns the base 'lines visible' attribute. 332 * 333 * @return The base flag. 334 */ 335 public boolean getBaseLinesVisible() { 336 return this.baseLinesVisible; 337 } 338 339 /** 340 * Sets the base 'lines visible' flag. 341 * 342 * @param flag the flag. 343 */ 344 public void setBaseLinesVisible(boolean flag) { 345 this.baseLinesVisible = flag; 346 notifyListeners(new RendererChangeEvent(this)); 347 } 348 349 /** 350 * Returns the shape used to represent a line in the legend. 351 * 352 * @return The legend line (never <code>null</code>). 353 */ 354 public Shape getLegendLine() { 355 return this.legendLine; 356 } 357 358 /** 359 * Sets the shape used as a line in each legend item and sends a 360 * {@link RendererChangeEvent} to all registered listeners. 361 * 362 * @param line the line (<code>null</code> not permitted). 363 */ 364 public void setLegendLine(Shape line) { 365 if (line == null) { 366 throw new IllegalArgumentException("Null 'line' argument."); 367 } 368 this.legendLine = line; 369 notifyListeners(new RendererChangeEvent(this)); 370 } 371 372 // SHAPES VISIBLE 373 374 /** 375 * Returns the flag used to control whether or not the shape for an item is 376 * visible. 377 * <p> 378 * The default implementation passes control to the 379 * <code>getSeriesShapesVisible</code> method. You can override this method 380 * if you require different behaviour. 381 * 382 * @param series the series index (zero-based). 383 * @param item the item index (zero-based). 384 * 385 * @return A boolean. 386 */ 387 public boolean getItemShapeVisible(int series, int item) { 388 Boolean flag = this.shapesVisible; 389 if (flag == null) { 390 flag = getSeriesShapesVisible(series); 391 } 392 if (flag != null) { 393 return flag.booleanValue(); 394 } 395 else { 396 return this.baseShapesVisible; 397 } 398 } 399 400 /** 401 * Returns the flag that controls whether the shapes are visible for the 402 * items in ALL series. 403 * 404 * @return The flag (possibly <code>null</code>). 405 */ 406 public Boolean getShapesVisible() { 407 return this.shapesVisible; 408 } 409 410 /** 411 * Sets the 'shapes visible' for ALL series and sends a 412 * {@link RendererChangeEvent} to all registered listeners. 413 * 414 * @param visible the flag (<code>null</code> permitted). 415 */ 416 public void setShapesVisible(Boolean visible) { 417 this.shapesVisible = visible; 418 notifyListeners(new RendererChangeEvent(this)); 419 } 420 421 /** 422 * Sets the 'shapes visible' for ALL series and sends a 423 * {@link RendererChangeEvent} to all registered listeners. 424 * 425 * @param visible the flag. 426 */ 427 public void setShapesVisible(boolean visible) { 428 setShapesVisible(BooleanUtilities.valueOf(visible)); 429 } 430 431 /** 432 * Returns the flag used to control whether or not the shapes for a series 433 * are visible. 434 * 435 * @param series the series index (zero-based). 436 * 437 * @return A boolean. 438 */ 439 public Boolean getSeriesShapesVisible(int series) { 440 return this.seriesShapesVisible.getBoolean(series); 441 } 442 443 /** 444 * Sets the 'shapes visible' flag for a series and sends a 445 * {@link RendererChangeEvent} to all registered listeners. 446 * 447 * @param series the series index (zero-based). 448 * @param visible the flag. 449 */ 450 public void setSeriesShapesVisible(int series, boolean visible) { 451 setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible)); 452 } 453 454 /** 455 * Sets the 'shapes visible' flag for a series and sends a 456 * {@link RendererChangeEvent} to all registered listeners. 457 * 458 * @param series the series index (zero-based). 459 * @param flag the flag. 460 */ 461 public void setSeriesShapesVisible(int series, Boolean flag) { 462 this.seriesShapesVisible.setBoolean(series, flag); 463 notifyListeners(new RendererChangeEvent(this)); 464 } 465 466 /** 467 * Returns the base 'shape visible' attribute. 468 * 469 * @return The base flag. 470 */ 471 public boolean getBaseShapesVisible() { 472 return this.baseShapesVisible; 473 } 474 475 /** 476 * Sets the base 'shapes visible' flag. 477 * 478 * @param flag the flag. 479 */ 480 public void setBaseShapesVisible(boolean flag) { 481 this.baseShapesVisible = flag; 482 notifyListeners(new RendererChangeEvent(this)); 483 } 484 485 // SHAPES FILLED 486 487 /** 488 * Returns the flag used to control whether or not the shape for an item 489 * is filled. 490 * <p> 491 * The default implementation passes control to the 492 * <code>getSeriesShapesFilled</code> method. You can override this method 493 * if you require different behaviour. 494 * 495 * @param series the series index (zero-based). 496 * @param item the item index (zero-based). 497 * 498 * @return A boolean. 499 */ 500 public boolean getItemShapeFilled(int series, int item) { 501 Boolean flag = this.shapesFilled; 502 if (flag == null) { 503 flag = getSeriesShapesFilled(series); 504 } 505 if (flag != null) { 506 return flag.booleanValue(); 507 } 508 else { 509 return this.baseShapesFilled; 510 } 511 } 512 513 /** 514 * Sets the 'shapes filled' for ALL series and sends a 515 * {@link RendererChangeEvent} to all registered listeners. 516 * 517 * @param filled the flag. 518 */ 519 public void setShapesFilled(boolean filled) { 520 setShapesFilled(BooleanUtilities.valueOf(filled)); 521 } 522 523 /** 524 * Sets the 'shapes filled' for ALL series and sends a 525 * {@link RendererChangeEvent} to all registered listeners. 526 * 527 * @param filled the flag (<code>null</code> permitted). 528 */ 529 public void setShapesFilled(Boolean filled) { 530 this.shapesFilled = filled; 531 notifyListeners(new RendererChangeEvent(this)); 532 } 533 534 /** 535 * Returns the flag used to control whether or not the shapes for a series 536 * are filled. 537 * 538 * @param series the series index (zero-based). 539 * 540 * @return A boolean. 541 */ 542 public Boolean getSeriesShapesFilled(int series) { 543 return this.seriesShapesFilled.getBoolean(series); 544 } 545 546 /** 547 * Sets the 'shapes filled' flag for a series. 548 * 549 * @param series the series index (zero-based). 550 * @param flag the flag. 551 */ 552 public void setSeriesShapesFilled(int series, boolean flag) { 553 setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag)); 554 } 555 556 /** 557 * Sets the 'shapes filled' flag for a series. 558 * 559 * @param series the series index (zero-based). 560 * @param flag the flag. 561 */ 562 public void setSeriesShapesFilled(int series, Boolean flag) { 563 this.seriesShapesFilled.setBoolean(series, flag); 564 notifyListeners(new RendererChangeEvent(this)); 565 } 566 567 /** 568 * Returns the base 'shape filled' attribute. 569 * 570 * @return The base flag. 571 */ 572 public boolean getBaseShapesFilled() { 573 return this.baseShapesFilled; 574 } 575 576 /** 577 * Sets the base 'shapes filled' flag. 578 * 579 * @param flag the flag. 580 */ 581 public void setBaseShapesFilled(boolean flag) { 582 this.baseShapesFilled = flag; 583 notifyListeners(new RendererChangeEvent(this)); 584 } 585 586 /** 587 * Returns <code>true</code> if outlines should be drawn for shapes, and 588 * <code>false</code> otherwise. 589 * 590 * @return A boolean. 591 */ 592 public boolean getDrawOutlines() { 593 return this.drawOutlines; 594 } 595 596 /** 597 * Sets the flag that controls whether outlines are drawn for 598 * shapes, and sends a {@link RendererChangeEvent} to all registered 599 * listeners. 600 * <P> 601 * In some cases, shapes look better if they do NOT have an outline, but 602 * this flag allows you to set your own preference. 603 * 604 * @param flag the flag. 605 */ 606 public void setDrawOutlines(boolean flag) { 607 this.drawOutlines = flag; 608 notifyListeners(new RendererChangeEvent(this)); 609 } 610 611 /** 612 * Returns <code>true</code> if the renderer should use the fill paint 613 * setting to fill shapes, and <code>false</code> if it should just 614 * use the regular paint. 615 * 616 * @return A boolean. 617 */ 618 public boolean getUseFillPaint() { 619 return this.useFillPaint; 620 } 621 622 /** 623 * Sets the flag that controls whether the fill paint is used to fill 624 * shapes, and sends a {@link RendererChangeEvent} to all 625 * registered listeners. 626 * 627 * @param flag the flag. 628 */ 629 public void setUseFillPaint(boolean flag) { 630 this.useFillPaint = flag; 631 notifyListeners(new RendererChangeEvent(this)); 632 } 633 634 /** 635 * Returns <code>true</code> if the renderer should use the outline paint 636 * setting to draw shape outlines, and <code>false</code> if it should just 637 * use the regular paint. 638 * 639 * @return A boolean. 640 */ 641 public boolean getUseOutlinePaint() { 642 return this.useOutlinePaint; 643 } 644 645 /** 646 * Sets the flag that controls whether the outline paint is used to draw 647 * shape outlines, and sends a {@link RendererChangeEvent} to all 648 * registered listeners. 649 * 650 * @param flag the flag. 651 */ 652 public void setUseOutlinePaint(boolean flag) { 653 this.useOutlinePaint = flag; 654 notifyListeners(new RendererChangeEvent(this)); 655 } 656 657 /** 658 * Records the state for the renderer. This is used to preserve state 659 * information between calls to the drawItem() method for a single chart 660 * drawing. 661 */ 662 public static class State extends XYItemRendererState { 663 664 /** The path for the current series. */ 665 public GeneralPath seriesPath; 666 667 /** 668 * A flag that indicates if the last (x, y) point was 'good' 669 * (non-null). 670 */ 671 private boolean lastPointGood; 672 673 /** 674 * Creates a new state instance. 675 * 676 * @param info the plot rendering info. 677 */ 678 public State(PlotRenderingInfo info) { 679 super(info); 680 } 681 682 /** 683 * Returns a flag that indicates if the last point drawn (in the 684 * current series) was 'good' (non-null). 685 * 686 * @return A boolean. 687 */ 688 public boolean isLastPointGood() { 689 return this.lastPointGood; 690 } 691 692 /** 693 * Sets a flag that indicates if the last point drawn (in the current 694 * series) was 'good' (non-null). 695 * 696 * @param good the flag. 697 */ 698 public void setLastPointGood(boolean good) { 699 this.lastPointGood = good; 700 } 701 } 702 703 /** 704 * Initialises the renderer. 705 * <P> 706 * This method will be called before the first item is rendered, giving the 707 * renderer an opportunity to initialise any state information it wants to 708 * maintain. The renderer can do nothing if it chooses. 709 * 710 * @param g2 the graphics device. 711 * @param dataArea the area inside the axes. 712 * @param plot the plot. 713 * @param data the data. 714 * @param info an optional info collection object to return data back to 715 * the caller. 716 * 717 * @return The renderer state. 718 */ 719 public XYItemRendererState initialise(Graphics2D g2, 720 Rectangle2D dataArea, 721 XYPlot plot, 722 XYDataset data, 723 PlotRenderingInfo info) { 724 725 State state = new State(info); 726 state.seriesPath = new GeneralPath(); 727 return state; 728 729 } 730 731 /** 732 * Draws the visual representation of a single data item. 733 * 734 * @param g2 the graphics device. 735 * @param state the renderer state. 736 * @param dataArea the area within which the data is being drawn. 737 * @param info collects information about the drawing. 738 * @param plot the plot (can be used to obtain standard color 739 * information etc). 740 * @param domainAxis the domain axis. 741 * @param rangeAxis the range axis. 742 * @param dataset the dataset. 743 * @param series the series index (zero-based). 744 * @param item the item index (zero-based). 745 * @param crosshairState crosshair information for the plot 746 * (<code>null</code> permitted). 747 * @param pass the pass index. 748 */ 749 public void drawItem(Graphics2D g2, 750 XYItemRendererState state, 751 Rectangle2D dataArea, 752 PlotRenderingInfo info, 753 XYPlot plot, 754 ValueAxis domainAxis, 755 ValueAxis rangeAxis, 756 XYDataset dataset, 757 int series, 758 int item, 759 CrosshairState crosshairState, 760 int pass) { 761 762 // do nothing if item is not visible 763 if (!getItemVisible(series, item)) { 764 return; 765 } 766 767 // first pass draws the background (lines, for instance) 768 if (isLinePass(pass)) { 769 if (item == 0) { 770 if (this.drawSeriesLineAsPath) { 771 State s = (State) state; 772 s.seriesPath.reset(); 773 s.lastPointGood = false; 774 } 775 } 776 777 if (getItemLineVisible(series, item)) { 778 if (this.drawSeriesLineAsPath) { 779 drawPrimaryLineAsPath( 780 state, g2, plot, dataset, pass, series, item, 781 domainAxis, rangeAxis, dataArea 782 ); 783 } 784 else { 785 drawPrimaryLine( 786 state, g2, plot, dataset, pass, series, item, 787 domainAxis, rangeAxis, dataArea 788 ); 789 } 790 } 791 } 792 // second pass adds shapes where the items are .. 793 else if (isItemPass(pass)) { 794 795 // setup for collecting optional entity info... 796 EntityCollection entities = null; 797 if (info != null) { 798 entities = info.getOwner().getEntityCollection(); 799 } 800 801 drawSecondaryPass( 802 g2, plot, dataset, pass, series, item, domainAxis, dataArea, 803 rangeAxis, crosshairState, entities 804 ); 805 } 806 } 807 808 /** 809 * Returns <code>true</code> if the specified pass is the one for drawing 810 * lines. 811 * 812 * @param pass the pass. 813 * 814 * @return A boolean. 815 */ 816 protected boolean isLinePass(int pass) { 817 return pass == 0; 818 } 819 820 /** 821 * Returns <code>true</code> if the specified pass is the one for drawing 822 * items. 823 * 824 * @param pass the pass. 825 * 826 * @return A boolean. 827 */ 828 protected boolean isItemPass(int pass) { 829 return pass == 1; 830 } 831 832 /** 833 * Draws the item (first pass). This method draws the lines 834 * connecting the items. 835 * 836 * @param g2 the graphics device. 837 * @param state the renderer state. 838 * @param dataArea the area within which the data is being drawn. 839 * @param plot the plot (can be used to obtain standard color 840 * information etc). 841 * @param domainAxis the domain axis. 842 * @param rangeAxis the range axis. 843 * @param dataset the dataset. 844 * @param pass the pass. 845 * @param series the series index (zero-based). 846 * @param item the item index (zero-based). 847 */ 848 protected void drawPrimaryLine(XYItemRendererState state, 849 Graphics2D g2, 850 XYPlot plot, 851 XYDataset dataset, 852 int pass, 853 int series, 854 int item, 855 ValueAxis domainAxis, 856 ValueAxis rangeAxis, 857 Rectangle2D dataArea) { 858 if (item == 0) { 859 return; 860 } 861 862 // get the data point... 863 double x1 = dataset.getXValue(series, item); 864 double y1 = dataset.getYValue(series, item); 865 if (Double.isNaN(y1) || Double.isNaN(x1)) { 866 return; 867 } 868 869 double x0 = dataset.getXValue(series, item - 1); 870 double y0 = dataset.getYValue(series, item - 1); 871 if (Double.isNaN(y0) || Double.isNaN(x0)) { 872 return; 873 } 874 875 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 876 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 877 878 double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 879 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation); 880 881 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 882 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 883 884 // only draw if we have good values 885 if (Double.isNaN(transX0) || Double.isNaN(transY0) 886 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 887 return; 888 } 889 890 PlotOrientation orientation = plot.getOrientation(); 891 if (orientation == PlotOrientation.HORIZONTAL) { 892 state.workingLine.setLine(transY0, transX0, transY1, transX1); 893 } 894 else if (orientation == PlotOrientation.VERTICAL) { 895 state.workingLine.setLine(transX0, transY0, transX1, transY1); 896 } 897 898 if (state.workingLine.intersects(dataArea)) { 899 drawFirstPassShape(g2, pass, series, item, state.workingLine); 900 } 901 } 902 903 /** 904 * Draws the first pass shape. 905 * 906 * @param g2 the graphics device. 907 * @param pass the pass. 908 * @param series the series index. 909 * @param item the item index. 910 * @param shape the shape. 911 */ 912 protected void drawFirstPassShape(Graphics2D g2, 913 int pass, 914 int series, 915 int item, 916 Shape shape) { 917 g2.setStroke(getItemStroke(series, item)); 918 g2.setPaint(getItemPaint(series, item)); 919 g2.draw(shape); 920 } 921 922 923 /** 924 * Draws the item (first pass). This method draws the lines 925 * connecting the items. Instead of drawing separate lines, 926 * a GeneralPath is constructed and drawn at the end of 927 * the series painting. 928 * 929 * @param g2 the graphics device. 930 * @param state the renderer state. 931 * @param plot the plot (can be used to obtain standard color information 932 * etc). 933 * @param dataset the dataset. 934 * @param pass the pass. 935 * @param series the series index (zero-based). 936 * @param item the item index (zero-based). 937 * @param domainAxis the domain axis. 938 * @param rangeAxis the range axis. 939 * @param dataArea the area within which the data is being drawn. 940 */ 941 protected void drawPrimaryLineAsPath(XYItemRendererState state, 942 Graphics2D g2, XYPlot plot, 943 XYDataset dataset, 944 int pass, 945 int series, 946 int item, 947 ValueAxis domainAxis, 948 ValueAxis rangeAxis, 949 Rectangle2D dataArea) { 950 951 952 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 953 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 954 955 // get the data point... 956 double x1 = dataset.getXValue(series, item); 957 double y1 = dataset.getYValue(series, item); 958 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 959 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 960 961 State s = (State) state; 962 // update path to reflect latest point 963 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 964 float x = (float) transX1; 965 float y = (float) transY1; 966 PlotOrientation orientation = plot.getOrientation(); 967 if (orientation == PlotOrientation.HORIZONTAL) { 968 x = (float) transY1; 969 y = (float) transX1; 970 } 971 if (s.isLastPointGood()) { 972 s.seriesPath.lineTo(x, y); 973 } 974 else { 975 s.seriesPath.moveTo(x, y); 976 } 977 s.setLastPointGood(true); 978 } 979 else { 980 s.setLastPointGood(false); 981 } 982 // if this is the last item, draw the path ... 983 if (item == dataset.getItemCount(series) - 1) { 984 // draw path 985 drawFirstPassShape(g2, pass, series, item, s.seriesPath); 986 } 987 } 988 989 /** 990 * Draws the item shapes and adds chart entities (second pass). This method 991 * draws the shapes which mark the item positions. If <code>entities</code> 992 * is not <code>null</code> it will be populated with entity information. 993 * 994 * @param g2 the graphics device. 995 * @param dataArea the area within which the data is being drawn. 996 * @param plot the plot (can be used to obtain standard color 997 * information etc). 998 * @param domainAxis the domain axis. 999 * @param rangeAxis the range axis. 1000 * @param dataset the dataset. 1001 * @param pass the pass. 1002 * @param series the series index (zero-based). 1003 * @param item the item index (zero-based). 1004 * @param crosshairState the crosshair state. 1005 * @param entities the entity collection. 1006 */ 1007 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 1008 XYDataset dataset, 1009 int pass, int series, int item, 1010 ValueAxis domainAxis, 1011 Rectangle2D dataArea, 1012 ValueAxis rangeAxis, 1013 CrosshairState crosshairState, 1014 EntityCollection entities) { 1015 1016 Shape entityArea = null; 1017 1018 // get the data point... 1019 double x1 = dataset.getXValue(series, item); 1020 double y1 = dataset.getYValue(series, item); 1021 if (Double.isNaN(y1) || Double.isNaN(x1)) { 1022 return; 1023 } 1024 1025 PlotOrientation orientation = plot.getOrientation(); 1026 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1027 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1028 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1029 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1030 1031 if (getItemShapeVisible(series, item)) { 1032 Shape shape = getItemShape(series, item); 1033 if (orientation == PlotOrientation.HORIZONTAL) { 1034 shape = ShapeUtilities.createTranslatedShape( 1035 shape, transY1, transX1 1036 ); 1037 } 1038 else if (orientation == PlotOrientation.VERTICAL) { 1039 shape = ShapeUtilities.createTranslatedShape( 1040 shape, transX1, transY1 1041 ); 1042 } 1043 entityArea = shape; 1044 if (shape.intersects(dataArea)) { 1045 if (getItemShapeFilled(series, item)) { 1046 if (this.useFillPaint) { 1047 g2.setPaint(getItemFillPaint(series, item)); 1048 } 1049 else { 1050 g2.setPaint(getItemPaint(series, item)); 1051 } 1052 g2.fill(shape); 1053 } 1054 if (this.drawOutlines) { 1055 if (getUseOutlinePaint()) { 1056 g2.setPaint(getItemOutlinePaint(series, item)); 1057 } 1058 else { 1059 g2.setPaint(getItemPaint(series, item)); 1060 } 1061 g2.setStroke(getItemOutlineStroke(series, item)); 1062 g2.draw(shape); 1063 } 1064 } 1065 } 1066 1067 // draw the item label if there is one... 1068 if (isItemLabelVisible(series, item)) { 1069 double xx = transX1; 1070 double yy = transY1; 1071 if (orientation == PlotOrientation.HORIZONTAL) { 1072 xx = transY1; 1073 yy = transX1; 1074 } 1075 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 1076 (y1 < 0.0)); 1077 } 1078 1079 updateCrosshairValues( 1080 crosshairState, x1, y1, transX1, transY1, plot.getOrientation() 1081 ); 1082 1083 // add an entity for the item... 1084 if (entities != null) { 1085 addEntity( 1086 entities, entityArea, dataset, series, item, transX1, transY1 1087 ); 1088 } 1089 } 1090 1091 1092 /** 1093 * Returns a legend item for the specified series. 1094 * 1095 * @param datasetIndex the dataset index (zero-based). 1096 * @param series the series index (zero-based). 1097 * 1098 * @return A legend item for the series. 1099 */ 1100 public LegendItem getLegendItem(int datasetIndex, int series) { 1101 1102 XYPlot plot = getPlot(); 1103 if (plot == null) { 1104 return null; 1105 } 1106 1107 LegendItem result = null; 1108 XYDataset dataset = plot.getDataset(datasetIndex); 1109 if (dataset != null) { 1110 if (getItemVisible(series, 0)) { 1111 String label = getLegendItemLabelGenerator().generateLabel( 1112 dataset, series 1113 ); 1114 String description = label; 1115 String toolTipText = null; 1116 if (getLegendItemToolTipGenerator() != null) { 1117 toolTipText = getLegendItemToolTipGenerator().generateLabel( 1118 dataset, series 1119 ); 1120 } 1121 String urlText = null; 1122 if (getLegendItemURLGenerator() != null) { 1123 urlText = getLegendItemURLGenerator().generateLabel( 1124 dataset, series 1125 ); 1126 } 1127 boolean shapeIsVisible = getItemShapeVisible(series, 0); 1128 Shape shape = getSeriesShape(series); 1129 boolean shapeIsFilled = getItemShapeFilled(series, 0); 1130 Paint fillPaint = (this.useFillPaint 1131 ? getSeriesFillPaint(series) : getSeriesPaint(series)); 1132 boolean shapeOutlineVisible = this.drawOutlines; 1133 Paint outlinePaint = (this.useOutlinePaint 1134 ? getSeriesOutlinePaint(series) 1135 : getSeriesPaint(series)); 1136 Stroke outlineStroke = getSeriesOutlineStroke(series); 1137 boolean lineVisible = getItemLineVisible(series, 0); 1138 Stroke lineStroke = getSeriesStroke(series); 1139 Paint linePaint = getSeriesPaint(series); 1140 result = new LegendItem(label, description, toolTipText, 1141 urlText, shapeIsVisible, shape, shapeIsFilled, 1142 fillPaint, shapeOutlineVisible, outlinePaint, 1143 outlineStroke, lineVisible, this.legendLine, 1144 lineStroke, linePaint); 1145 } 1146 } 1147 1148 return result; 1149 1150 } 1151 1152 /** 1153 * Returns a clone of the renderer. 1154 * 1155 * @return A clone. 1156 * 1157 * @throws CloneNotSupportedException if the clone cannot be created. 1158 */ 1159 public Object clone() throws CloneNotSupportedException { 1160 return super.clone(); 1161 } 1162 1163 /** 1164 * Tests this renderer for equality with another object. 1165 * 1166 * @param obj the object. 1167 * 1168 * @return <code>true</code> or <code>false</code>. 1169 */ 1170 public boolean equals(Object obj) { 1171 1172 if (obj == this) { 1173 return true; 1174 } 1175 if (!(obj instanceof XYLineAndShapeRenderer)) { 1176 return false; 1177 } 1178 if (!super.equals(obj)) { 1179 return false; 1180 } 1181 XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj; 1182 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1183 return false; 1184 } 1185 if (!ObjectUtilities.equal( 1186 this.seriesLinesVisible, that.seriesLinesVisible) 1187 ) { 1188 return false; 1189 } 1190 if (this.baseLinesVisible != that.baseLinesVisible) { 1191 return false; 1192 } 1193 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1194 return false; 1195 } 1196 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1197 return false; 1198 } 1199 if (!ObjectUtilities.equal( 1200 this.seriesShapesVisible, that.seriesShapesVisible) 1201 ) { 1202 return false; 1203 } 1204 if (this.baseShapesVisible != that.baseShapesVisible) { 1205 return false; 1206 } 1207 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1208 return false; 1209 } 1210 if (!ObjectUtilities.equal( 1211 this.seriesShapesFilled, that.seriesShapesFilled) 1212 ) { 1213 return false; 1214 } 1215 if (this.baseShapesFilled != that.baseShapesFilled) { 1216 return false; 1217 } 1218 if (this.drawOutlines != that.drawOutlines) { 1219 return false; 1220 } 1221 if (this.useOutlinePaint != that.useOutlinePaint) { 1222 return false; 1223 } 1224 1225 return true; 1226 1227 } 1228 1229 /** 1230 * Provides serialization support. 1231 * 1232 * @param stream the input stream. 1233 * 1234 * @throws IOException if there is an I/O error. 1235 * @throws ClassNotFoundException if there is a classpath problem. 1236 */ 1237 private void readObject(ObjectInputStream stream) 1238 throws IOException, ClassNotFoundException { 1239 stream.defaultReadObject(); 1240 this.legendLine = SerialUtilities.readShape(stream); 1241 } 1242 1243 /** 1244 * Provides serialization support. 1245 * 1246 * @param stream the output stream. 1247 * 1248 * @throws IOException if there is an I/O error. 1249 */ 1250 private void writeObject(ObjectOutputStream stream) throws IOException { 1251 stream.defaultWriteObject(); 1252 SerialUtilities.writeShape(this.legendLine, stream); 1253 } 1254 1255 }