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 * XYDifferenceRenderer.java 029 * ------------------------- 030 * (C) Copyright 2003-2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Christian W. Zuckschwerdt; 034 * 035 * $Id: XYDifferenceRenderer.java,v 1.12.2.5 2005/11/28 12:06:35 mungady Exp $ 036 * 037 * Changes: 038 * -------- 039 * 30-Apr-2003 : Version 1 (DG); 040 * 30-Jul-2003 : Modified entity constructor (CZ); 041 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 042 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 043 * 09-Feb-2004 : Updated to support horizontal plot orientation (DG); 044 * 10-Feb-2004 : Added default constructor, setter methods and updated 045 * Javadocs (DG); 046 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 047 * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG); 048 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 049 * getYValue() (DG); 050 * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG); 051 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 052 * 19-Jan-2005 : Now accesses only primitive values from dataset (DG); 053 * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG); 054 * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG); 055 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 056 * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() --> 057 * get/setShapesVisible (DG); 058 * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG); 059 * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG); 060 * 061 */ 062 063 package org.jfree.chart.renderer.xy; 064 065 import java.awt.Color; 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.entity.XYItemEntity; 082 import org.jfree.chart.event.RendererChangeEvent; 083 import org.jfree.chart.labels.XYToolTipGenerator; 084 import org.jfree.chart.plot.CrosshairState; 085 import org.jfree.chart.plot.PlotOrientation; 086 import org.jfree.chart.plot.PlotRenderingInfo; 087 import org.jfree.chart.plot.XYPlot; 088 import org.jfree.data.xy.XYDataset; 089 import org.jfree.io.SerialUtilities; 090 import org.jfree.ui.RectangleEdge; 091 import org.jfree.util.PaintUtilities; 092 import org.jfree.util.PublicCloneable; 093 import org.jfree.util.ShapeUtilities; 094 095 /** 096 * A renderer for an {@link XYPlot} that highlights the differences between two 097 * series. The renderer expects a dataset that: 098 * <ul> 099 * <li>has exactly two series;</li> 100 * <li>each series has the same x-values;</li> 101 * <li>no <code>null</code> values; 102 * </ul> 103 */ 104 public class XYDifferenceRenderer extends AbstractXYItemRenderer 105 implements XYItemRenderer, 106 Cloneable, 107 PublicCloneable, 108 Serializable { 109 110 /** For serialization. */ 111 private static final long serialVersionUID = -8447915602375584857L; 112 113 /** The paint used to highlight positive differences (y(0) > y(1)). */ 114 private transient Paint positivePaint; 115 116 /** The paint used to highlight negative differences (y(0) < y(1)). */ 117 private transient Paint negativePaint; 118 119 /** Display shapes at each point? */ 120 private boolean shapesVisible; 121 122 /** The shape to display in the legend item. */ 123 private transient Shape legendLine; 124 125 /** 126 * Creates a new renderer with default attributes. 127 */ 128 public XYDifferenceRenderer() { 129 this(Color.green, Color.red, false); 130 } 131 132 /** 133 * Creates a new renderer. 134 * 135 * @param positivePaint the highlight color for positive differences 136 * (<code>null</code> not permitted). 137 * @param negativePaint the highlight color for negative differences 138 * (<code>null</code> not permitted). 139 * @param shapes draw shapes? 140 */ 141 public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint, 142 boolean shapes) { 143 if (positivePaint == null) { 144 throw new IllegalArgumentException( 145 "Null 'positivePaint' argument." 146 ); 147 } 148 if (negativePaint == null) { 149 throw new IllegalArgumentException( 150 "Null 'negativePaint' argument." 151 ); 152 } 153 this.positivePaint = positivePaint; 154 this.negativePaint = negativePaint; 155 this.shapesVisible = shapes; 156 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 157 } 158 159 /** 160 * Returns the paint used to highlight positive differences. 161 * 162 * @return The paint (never <code>null</code>). 163 */ 164 public Paint getPositivePaint() { 165 return this.positivePaint; 166 } 167 168 /** 169 * Sets the paint used to highlight positive differences. 170 * 171 * @param paint the paint (<code>null</code> not permitted). 172 */ 173 public void setPositivePaint(Paint paint) { 174 if (paint == null) { 175 throw new IllegalArgumentException("Null 'paint' argument."); 176 } 177 this.positivePaint = paint; 178 notifyListeners(new RendererChangeEvent(this)); 179 } 180 181 /** 182 * Returns the paint used to highlight negative differences. 183 * 184 * @return The paint (never <code>null</code>). 185 */ 186 public Paint getNegativePaint() { 187 return this.negativePaint; 188 } 189 190 /** 191 * Sets the paint used to highlight negative differences. 192 * 193 * @param paint the paint (<code>null</code> not permitted). 194 */ 195 public void setNegativePaint(Paint paint) { 196 if (paint == null) { 197 throw new IllegalArgumentException("Null 'paint' argument."); 198 } 199 this.negativePaint = paint; 200 notifyListeners(new RendererChangeEvent(this)); 201 } 202 203 /** 204 * Returns a flag that controls whether or not shapes are drawn for each 205 * data value. 206 * 207 * @return A boolean. 208 */ 209 public boolean getShapesVisible() { 210 return this.shapesVisible; 211 } 212 213 /** 214 * Sets a flag that controls whether or not shapes are drawn for each 215 * data value. 216 * 217 * @param flag the flag. 218 */ 219 public void setShapesVisible(boolean flag) { 220 this.shapesVisible = flag; 221 notifyListeners(new RendererChangeEvent(this)); 222 } 223 224 /** 225 * Returns the shape used to represent a line in the legend. 226 * 227 * @return The legend line (never <code>null</code>). 228 */ 229 public Shape getLegendLine() { 230 return this.legendLine; 231 } 232 233 /** 234 * Sets the shape used as a line in each legend item and sends a 235 * {@link RendererChangeEvent} to all registered listeners. 236 * 237 * @param line the line (<code>null</code> not permitted). 238 */ 239 public void setLegendLine(Shape line) { 240 if (line == null) { 241 throw new IllegalArgumentException("Null 'line' argument."); 242 } 243 this.legendLine = line; 244 notifyListeners(new RendererChangeEvent(this)); 245 } 246 247 /** 248 * Initialises the renderer and returns a state object that should be 249 * passed to subsequent calls to the drawItem() method. This method will 250 * be called before the first item is rendered, giving the renderer an 251 * opportunity to initialise any state information it wants to maintain. 252 * The renderer can do nothing if it chooses. 253 * 254 * @param g2 the graphics device. 255 * @param dataArea the area inside the axes. 256 * @param plot the plot. 257 * @param data the data. 258 * @param info an optional info collection object to return data back to 259 * the caller. 260 * 261 * @return A state object. 262 */ 263 public XYItemRendererState initialise(Graphics2D g2, 264 Rectangle2D dataArea, 265 XYPlot plot, 266 XYDataset data, 267 PlotRenderingInfo info) { 268 269 return super.initialise(g2, dataArea, plot, data, info); 270 271 } 272 273 /** 274 * Returns <code>2</code>, the number of passes required by the renderer. 275 * The {@link XYPlot} will run through the dataset this number of times. 276 * 277 * @return The number of passes required by the renderer. 278 */ 279 public int getPassCount() { 280 return 2; 281 } 282 283 /** 284 * Draws the visual representation of a single data item. 285 * 286 * @param g2 the graphics device. 287 * @param state the renderer state. 288 * @param dataArea the area within which the data is being drawn. 289 * @param info collects information about the drawing. 290 * @param plot the plot (can be used to obtain standard color 291 * information etc). 292 * @param domainAxis the domain (horizontal) axis. 293 * @param rangeAxis the range (vertical) axis. 294 * @param dataset the dataset. 295 * @param series the series index (zero-based). 296 * @param item the item index (zero-based). 297 * @param crosshairState crosshair information for the plot 298 * (<code>null</code> permitted). 299 * @param pass the pass index. 300 */ 301 public void drawItem(Graphics2D g2, 302 XYItemRendererState state, 303 Rectangle2D dataArea, 304 PlotRenderingInfo info, 305 XYPlot plot, 306 ValueAxis domainAxis, 307 ValueAxis rangeAxis, 308 XYDataset dataset, 309 int series, 310 int item, 311 CrosshairState crosshairState, 312 int pass) { 313 314 if (pass == 0) { 315 drawItemPass0( 316 g2, dataArea, info, plot, domainAxis, rangeAxis, dataset, 317 series, item, crosshairState 318 ); 319 } 320 else if (pass == 1) { 321 drawItemPass1( 322 g2, dataArea, info, plot, domainAxis, rangeAxis, dataset, 323 series, item, crosshairState 324 ); 325 } 326 327 } 328 329 /** 330 * Draws the visual representation of a single data item, first pass. 331 * 332 * @param g2 the graphics device. 333 * @param dataArea the area within which the data is being drawn. 334 * @param info collects information about the drawing. 335 * @param plot the plot (can be used to obtain standard color 336 * information etc). 337 * @param domainAxis the domain (horizontal) axis. 338 * @param rangeAxis the range (vertical) axis. 339 * @param dataset the dataset. 340 * @param series the series index (zero-based). 341 * @param item the item index (zero-based). 342 * @param crosshairState crosshair information for the plot 343 * (<code>null</code> permitted). 344 */ 345 protected void drawItemPass0(Graphics2D g2, 346 Rectangle2D dataArea, 347 PlotRenderingInfo info, 348 XYPlot plot, 349 ValueAxis domainAxis, 350 ValueAxis rangeAxis, 351 XYDataset dataset, 352 int series, 353 int item, 354 CrosshairState crosshairState) { 355 356 if (series == 0) { 357 358 PlotOrientation orientation = plot.getOrientation(); 359 RectangleEdge domainAxisLocation = plot.getDomainAxisEdge(); 360 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 361 362 double y0 = dataset.getYValue(0, item); 363 double x1 = dataset.getXValue(1, item); 364 double y1 = dataset.getYValue(1, item); 365 366 double transY0 = rangeAxis.valueToJava2D( 367 y0, dataArea, rangeAxisLocation 368 ); 369 double transX1 = domainAxis.valueToJava2D( 370 x1, dataArea, domainAxisLocation 371 ); 372 double transY1 = rangeAxis.valueToJava2D( 373 y1, dataArea, rangeAxisLocation 374 ); 375 376 if (item > 0) { 377 double prevx0 = dataset.getXValue(0, item - 1); 378 double prevy0 = dataset.getYValue(0, item - 1); 379 double prevy1 = dataset.getYValue(1, item - 1); 380 381 double prevtransX0 = domainAxis.valueToJava2D( 382 prevx0, dataArea, domainAxisLocation 383 ); 384 double prevtransY0 = rangeAxis.valueToJava2D( 385 prevy0, dataArea, rangeAxisLocation 386 ); 387 double prevtransY1 = rangeAxis.valueToJava2D( 388 prevy1, dataArea, rangeAxisLocation 389 ); 390 391 Shape positive = getPositiveArea( 392 (float) prevtransX0, (float) prevtransY0, 393 (float) prevtransY1, 394 (float) transX1, (float) transY0, (float) transY1, 395 orientation 396 ); 397 if (positive != null) { 398 g2.setPaint(getPositivePaint()); 399 g2.fill(positive); 400 } 401 402 Shape negative = getNegativeArea( 403 (float) prevtransX0, (float) prevtransY0, 404 (float) prevtransY1, 405 (float) transX1, (float) transY0, (float) transY1, 406 orientation 407 ); 408 409 if (negative != null) { 410 g2.setPaint(getNegativePaint()); 411 g2.fill(negative); 412 } 413 } 414 } 415 416 } 417 418 /** 419 * Draws the visual representation of a single data item, second pass. In 420 * the second pass, the renderer draws the lines and shapes for the 421 * individual points in the two series. 422 * 423 * @param g2 the graphics device. 424 * @param dataArea the area within which the data is being drawn. 425 * @param info collects information about the drawing. 426 * @param plot the plot (can be used to obtain standard color information 427 * etc). 428 * @param domainAxis the domain (horizontal) axis. 429 * @param rangeAxis the range (vertical) axis. 430 * @param dataset the dataset. 431 * @param series the series index (zero-based). 432 * @param item the item index (zero-based). 433 * @param crosshairState crosshair information for the plot 434 * (<code>null</code> permitted). 435 */ 436 protected void drawItemPass1(Graphics2D g2, 437 Rectangle2D dataArea, 438 PlotRenderingInfo info, 439 XYPlot plot, 440 ValueAxis domainAxis, 441 ValueAxis rangeAxis, 442 XYDataset dataset, 443 int series, 444 int item, 445 CrosshairState crosshairState) { 446 447 Shape entityArea = null; 448 EntityCollection entities = null; 449 if (info != null) { 450 entities = info.getOwner().getEntityCollection(); 451 } 452 453 Paint seriesPaint = getItemPaint(series, item); 454 Stroke seriesStroke = getItemStroke(series, item); 455 g2.setPaint(seriesPaint); 456 g2.setStroke(seriesStroke); 457 458 if (series == 0) { 459 460 PlotOrientation orientation = plot.getOrientation(); 461 RectangleEdge domainAxisLocation = plot.getDomainAxisEdge(); 462 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 463 464 double x0 = dataset.getXValue(0, item); 465 double y0 = dataset.getYValue(0, item); 466 double x1 = dataset.getXValue(1, item); 467 double y1 = dataset.getYValue(1, item); 468 469 double transX0 = domainAxis.valueToJava2D( 470 x0, dataArea, domainAxisLocation 471 ); 472 double transY0 = rangeAxis.valueToJava2D( 473 y0, dataArea, rangeAxisLocation 474 ); 475 double transX1 = domainAxis.valueToJava2D( 476 x1, dataArea, domainAxisLocation 477 ); 478 double transY1 = rangeAxis.valueToJava2D( 479 y1, dataArea, rangeAxisLocation 480 ); 481 482 if (item > 0) { 483 // get the previous data points... 484 double prevx0 = dataset.getXValue(0, item - 1); 485 double prevy0 = dataset.getYValue(0, item - 1); 486 double prevx1 = dataset.getXValue(1, item - 1); 487 double prevy1 = dataset.getYValue(1, item - 1); 488 489 double prevtransX0 = domainAxis.valueToJava2D( 490 prevx0, dataArea, domainAxisLocation 491 ); 492 double prevtransY0 = rangeAxis.valueToJava2D( 493 prevy0, dataArea, rangeAxisLocation 494 ); 495 double prevtransX1 = domainAxis.valueToJava2D( 496 prevx1, dataArea, domainAxisLocation 497 ); 498 double prevtransY1 = rangeAxis.valueToJava2D( 499 prevy1, dataArea, rangeAxisLocation 500 ); 501 502 Line2D line0 = null; 503 Line2D line1 = null; 504 if (orientation == PlotOrientation.HORIZONTAL) { 505 line0 = new Line2D.Double( 506 transY0, transX0, prevtransY0, prevtransX0 507 ); 508 line1 = new Line2D.Double( 509 transY1, transX1, prevtransY1, prevtransX1 510 ); 511 } 512 else if (orientation == PlotOrientation.VERTICAL) { 513 line0 = new Line2D.Double( 514 transX0, transY0, prevtransX0, prevtransY0 515 ); 516 line1 = new Line2D.Double( 517 transX1, transY1, prevtransX1, prevtransY1 518 ); 519 } 520 if (line0 != null && line0.intersects(dataArea)) { 521 g2.setPaint(getItemPaint(series, item)); 522 g2.setStroke(getItemStroke(series, item)); 523 g2.draw(line0); 524 } 525 if (line1 != null && line1.intersects(dataArea)) { 526 g2.setPaint(getItemPaint(1, item)); 527 g2.setStroke(getItemStroke(1, item)); 528 g2.draw(line1); 529 } 530 } 531 532 if (getShapesVisible()) { 533 Shape shape0 = getItemShape(series, item); 534 if (orientation == PlotOrientation.HORIZONTAL) { 535 shape0 = ShapeUtilities.createTranslatedShape( 536 shape0, transY0, transX0 537 ); 538 } 539 else { // vertical 540 shape0 = ShapeUtilities.createTranslatedShape( 541 shape0, transX0, transY0 542 ); 543 } 544 if (shape0.intersects(dataArea)) { 545 g2.setPaint(getItemPaint(series, item)); 546 g2.fill(shape0); 547 } 548 entityArea = shape0; 549 550 // add an entity for the item... 551 if (entities != null) { 552 if (entityArea == null) { 553 entityArea = new Rectangle2D.Double( 554 transX0 - 2, transY0 - 2, 4, 4 555 ); 556 } 557 String tip = null; 558 XYToolTipGenerator generator = getToolTipGenerator( 559 series, item 560 ); 561 if (generator != null) { 562 tip = generator.generateToolTip(dataset, series, item); 563 } 564 String url = null; 565 if (getURLGenerator() != null) { 566 url = getURLGenerator().generateURL( 567 dataset, series, item 568 ); 569 } 570 XYItemEntity entity = new XYItemEntity( 571 entityArea, dataset, series, item, tip, url 572 ); 573 entities.add(entity); 574 } 575 576 Shape shape1 = getItemShape(series + 1, item); 577 if (orientation == PlotOrientation.HORIZONTAL) { 578 shape1 = ShapeUtilities.createTranslatedShape( 579 shape1, transY1, transX1 580 ); 581 } 582 else { // vertical 583 shape1 = ShapeUtilities.createTranslatedShape( 584 shape1, transX1, transY1 585 ); 586 } 587 if (shape1.intersects(dataArea)) { 588 g2.setPaint(getItemPaint(series + 1, item)); 589 g2.fill(shape1); 590 } 591 entityArea = shape1; 592 593 // add an entity for the item... 594 if (entities != null) { 595 if (entityArea == null) { 596 entityArea = new Rectangle2D.Double( 597 transX1 - 2, transY1 - 2, 4, 4 598 ); 599 } 600 String tip = null; 601 XYToolTipGenerator generator = getToolTipGenerator( 602 series, item 603 ); 604 if (generator != null) { 605 tip = generator.generateToolTip( 606 dataset, series + 1, item 607 ); 608 } 609 String url = null; 610 if (getURLGenerator() != null) { 611 url = getURLGenerator().generateURL( 612 dataset, series + 1, item 613 ); 614 } 615 XYItemEntity entity = new XYItemEntity( 616 entityArea, dataset, series + 1, item, tip, url 617 ); 618 entities.add(entity); 619 } 620 } 621 updateCrosshairValues( 622 crosshairState, x1, y1, transX1, transY1, orientation 623 ); 624 } 625 626 } 627 628 /** 629 * Returns the positive area for a crossover point. 630 * 631 * @param x0 x coordinate. 632 * @param y0A y coordinate A. 633 * @param y0B y coordinate B. 634 * @param x1 x coordinate. 635 * @param y1A y coordinate A. 636 * @param y1B y coordinate B. 637 * @param orientation the plot orientation. 638 * 639 * @return The positive area. 640 */ 641 protected Shape getPositiveArea(float x0, float y0A, float y0B, 642 float x1, float y1A, float y1B, 643 PlotOrientation orientation) { 644 645 Shape result = null; 646 647 boolean startsNegative = (y0A >= y0B); 648 boolean endsNegative = (y1A >= y1B); 649 if (orientation == PlotOrientation.HORIZONTAL) { 650 startsNegative = (y0B >= y0A); 651 endsNegative = (y1B >= y1A); 652 } 653 654 if (startsNegative) { // starts negative 655 if (endsNegative) { 656 // all negative - return null 657 result = null; 658 } 659 else { 660 // changed from negative to positive 661 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B); 662 GeneralPath area = new GeneralPath(); 663 if (orientation == PlotOrientation.HORIZONTAL) { 664 area.moveTo(y1A, x1); 665 area.lineTo(p[1], p[0]); 666 area.lineTo(y1B, x1); 667 area.closePath(); 668 } 669 else if (orientation == PlotOrientation.VERTICAL) { 670 area.moveTo(x1, y1A); 671 area.lineTo(p[0], p[1]); 672 area.lineTo(x1, y1B); 673 area.closePath(); 674 } 675 result = area; 676 } 677 } 678 else { // starts positive 679 if (endsNegative) { 680 // changed from positive to negative 681 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B); 682 GeneralPath area = new GeneralPath(); 683 if (orientation == PlotOrientation.HORIZONTAL) { 684 area.moveTo(y0A, x0); 685 area.lineTo(p[1], p[0]); 686 area.lineTo(y0B, x0); 687 area.closePath(); 688 } 689 else if (orientation == PlotOrientation.VERTICAL) { 690 area.moveTo(x0, y0A); 691 area.lineTo(p[0], p[1]); 692 area.lineTo(x0, y0B); 693 area.closePath(); 694 } 695 result = area; 696 697 } 698 else { 699 GeneralPath area = new GeneralPath(); 700 if (orientation == PlotOrientation.HORIZONTAL) { 701 area.moveTo(y0A, x0); 702 area.lineTo(y1A, x1); 703 area.lineTo(y1B, x1); 704 area.lineTo(y0B, x0); 705 area.closePath(); 706 } 707 else if (orientation == PlotOrientation.VERTICAL) { 708 area.moveTo(x0, y0A); 709 area.lineTo(x1, y1A); 710 area.lineTo(x1, y1B); 711 area.lineTo(x0, y0B); 712 area.closePath(); 713 } 714 result = area; 715 } 716 717 } 718 719 return result; 720 721 } 722 723 /** 724 * Returns the negative area for a cross-over section. 725 * 726 * @param x0 x coordinate. 727 * @param y0A y coordinate A. 728 * @param y0B y coordinate B. 729 * @param x1 x coordinate. 730 * @param y1A y coordinate A. 731 * @param y1B y coordinate B. 732 * @param orientation the plot orientation. 733 * 734 * @return The negative area. 735 */ 736 protected Shape getNegativeArea(float x0, float y0A, float y0B, 737 float x1, float y1A, float y1B, 738 PlotOrientation orientation) { 739 740 Shape result = null; 741 742 boolean startsNegative = (y0A >= y0B); 743 boolean endsNegative = (y1A >= y1B); 744 if (orientation == PlotOrientation.HORIZONTAL) { 745 startsNegative = (y0B >= y0A); 746 endsNegative = (y1B >= y1A); 747 } 748 if (startsNegative) { // starts negative 749 if (endsNegative) { // all negative 750 GeneralPath area = new GeneralPath(); 751 if (orientation == PlotOrientation.HORIZONTAL) { 752 area.moveTo(y0A, x0); 753 area.lineTo(y1A, x1); 754 area.lineTo(y1B, x1); 755 area.lineTo(y0B, x0); 756 area.closePath(); 757 } 758 else if (orientation == PlotOrientation.VERTICAL) { 759 area.moveTo(x0, y0A); 760 area.lineTo(x1, y1A); 761 area.lineTo(x1, y1B); 762 area.lineTo(x0, y0B); 763 area.closePath(); 764 } 765 result = area; 766 } 767 else { // changed from negative to positive 768 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B); 769 GeneralPath area = new GeneralPath(); 770 if (orientation == PlotOrientation.HORIZONTAL) { 771 area.moveTo(y0A, x0); 772 area.lineTo(p[1], p[0]); 773 area.lineTo(y0B, x0); 774 area.closePath(); 775 } 776 else if (orientation == PlotOrientation.VERTICAL) { 777 area.moveTo(x0, y0A); 778 area.lineTo(p[0], p[1]); 779 area.lineTo(x0, y0B); 780 area.closePath(); 781 } 782 result = area; 783 } 784 } 785 else { 786 if (endsNegative) { 787 // changed from positive to negative 788 float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B); 789 GeneralPath area = new GeneralPath(); 790 if (orientation == PlotOrientation.HORIZONTAL) { 791 area.moveTo(y1A, x1); 792 area.lineTo(p[1], p[0]); 793 area.lineTo(y1B, x1); 794 area.closePath(); 795 } 796 else if (orientation == PlotOrientation.VERTICAL) { 797 area.moveTo(x1, y1A); 798 area.lineTo(p[0], p[1]); 799 area.lineTo(x1, y1B); 800 area.closePath(); 801 } 802 result = area; 803 } 804 else { 805 // all negative - return null 806 } 807 808 } 809 810 return result; 811 812 } 813 814 /** 815 * Returns the intersection point of two lines. 816 * 817 * @param x1 x1 818 * @param y1 y1 819 * @param x2 x2 820 * @param y2 y2 821 * @param x3 x3 822 * @param y3 y3 823 * @param x4 x4 824 * @param y4 y4 825 * 826 * @return The intersection point. 827 */ 828 private float[] getIntersection(float x1, float y1, float x2, float y2, 829 float x3, float y3, float x4, float y4) { 830 831 float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); 832 float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); 833 float u = n / d; 834 835 float[] result = new float[2]; 836 result[0] = x1 + u * (x2 - x1); 837 result[1] = y1 + u * (y2 - y1); 838 return result; 839 840 } 841 842 /** 843 * Returns a default legend item for the specified series. Subclasses 844 * should override this method to generate customised items. 845 * 846 * @param datasetIndex the dataset index (zero-based). 847 * @param series the series index (zero-based). 848 * 849 * @return A legend item for the series. 850 */ 851 public LegendItem getLegendItem(int datasetIndex, int series) { 852 LegendItem result = null; 853 XYPlot p = getPlot(); 854 if (p != null) { 855 XYDataset dataset = p.getDataset(datasetIndex); 856 if (dataset != null) { 857 if (getItemVisible(series, 0)) { 858 String label = getLegendItemLabelGenerator().generateLabel( 859 dataset, series 860 ); 861 String description = label; 862 String toolTipText = null; 863 if (getLegendItemToolTipGenerator() != null) { 864 toolTipText = getLegendItemToolTipGenerator().generateLabel( 865 dataset, series 866 ); 867 } 868 String urlText = null; 869 if (getLegendItemURLGenerator() != null) { 870 urlText = getLegendItemURLGenerator().generateLabel( 871 dataset, series 872 ); 873 } 874 Paint paint = getSeriesPaint(series); 875 Stroke stroke = getSeriesStroke(series); 876 // TODO: the following hard-coded line needs generalising 877 Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 878 result = new LegendItem(label, description, 879 toolTipText, urlText, line, stroke, paint); 880 } 881 } 882 883 } 884 885 return result; 886 887 } 888 889 /** 890 * Tests this renderer for equality with an arbitrary object. 891 * 892 * @param obj the object (<code>null</code> permitted). 893 * 894 * @return A boolean. 895 */ 896 public boolean equals(Object obj) { 897 if (obj == this) { 898 return true; 899 } 900 if (!(obj instanceof XYDifferenceRenderer)) { 901 return false; 902 } 903 if (!super.equals(obj)) { 904 return false; 905 } 906 XYDifferenceRenderer that = (XYDifferenceRenderer) obj; 907 if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) { 908 return false; 909 } 910 if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) { 911 return false; 912 } 913 if (this.shapesVisible != that.shapesVisible) { 914 return false; 915 } 916 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 917 return false; 918 } 919 return true; 920 } 921 922 /** 923 * Returns a clone of the renderer. 924 * 925 * @return A clone. 926 * 927 * @throws CloneNotSupportedException if the renderer cannot be cloned. 928 */ 929 public Object clone() throws CloneNotSupportedException { 930 return super.clone(); 931 } 932 933 /** 934 * Provides serialization support. 935 * 936 * @param stream the output stream. 937 * 938 * @throws IOException if there is an I/O error. 939 */ 940 private void writeObject(ObjectOutputStream stream) throws IOException { 941 stream.defaultWriteObject(); 942 SerialUtilities.writePaint(this.positivePaint, stream); 943 SerialUtilities.writePaint(this.negativePaint, stream); 944 SerialUtilities.writeShape(this.legendLine, stream); 945 } 946 947 /** 948 * Provides serialization support. 949 * 950 * @param stream the input stream. 951 * 952 * @throws IOException if there is an I/O error. 953 * @throws ClassNotFoundException if there is a classpath problem. 954 */ 955 private void readObject(ObjectInputStream stream) 956 throws IOException, ClassNotFoundException { 957 stream.defaultReadObject(); 958 this.positivePaint = SerialUtilities.readPaint(stream); 959 this.negativePaint = SerialUtilities.readPaint(stream); 960 this.legendLine = SerialUtilities.readShape(stream); 961 } 962 963 }