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