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