1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * -------------------------
  27. * XYDifferenceRenderer.java
  28. * -------------------------
  29. * (C) Copyright 2003-2005, by Object Refinery Limited.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): Christian W. Zuckschwerdt;
  33. *
  34. * $Id: XYDifferenceRenderer.java,v 1.6 2005/02/22 12:49:12 mungady Exp $
  35. *
  36. * Changes:
  37. * --------
  38. * 30-Apr-2003 : Version 1 (DG);
  39. * 30-Jul-2003 : Modified entity constructor (CZ);
  40. * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  41. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  42. * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
  43. * 10-Feb-2004 : Added default constructor, setter methods and updated
  44. * Javadocs (DG);
  45. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  46. * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
  47. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
  48. * getYValue() (DG);
  49. * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
  50. * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  51. * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
  52. * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
  53. *
  54. */
  55. package org.jfree.chart.renderer.xy;
  56. import java.awt.Color;
  57. import java.awt.Graphics2D;
  58. import java.awt.Paint;
  59. import java.awt.Shape;
  60. import java.awt.Stroke;
  61. import java.awt.geom.GeneralPath;
  62. import java.awt.geom.Line2D;
  63. import java.awt.geom.Rectangle2D;
  64. import java.io.IOException;
  65. import java.io.ObjectInputStream;
  66. import java.io.ObjectOutputStream;
  67. import java.io.Serializable;
  68. import org.jfree.chart.LegendItem;
  69. import org.jfree.chart.axis.ValueAxis;
  70. import org.jfree.chart.entity.EntityCollection;
  71. import org.jfree.chart.entity.XYItemEntity;
  72. import org.jfree.chart.event.RendererChangeEvent;
  73. import org.jfree.chart.labels.XYToolTipGenerator;
  74. import org.jfree.chart.plot.CrosshairState;
  75. import org.jfree.chart.plot.PlotOrientation;
  76. import org.jfree.chart.plot.PlotRenderingInfo;
  77. import org.jfree.chart.plot.XYPlot;
  78. import org.jfree.data.xy.XYDataset;
  79. import org.jfree.io.SerialUtilities;
  80. import org.jfree.ui.RectangleEdge;
  81. import org.jfree.util.PublicCloneable;
  82. import org.jfree.util.ShapeUtilities;
  83. /**
  84. * A renderer for an {@link XYPlot} that highlights the differences between two
  85. * series. The renderer expects a dataset that:
  86. * <ul>
  87. * <li>has exactly two series;</li>
  88. * <li>each series has the same x-values;</li>
  89. * <li>no <code>null</code> values;
  90. * </ul>
  91. */
  92. public class XYDifferenceRenderer extends AbstractXYItemRenderer
  93. implements XYItemRenderer,
  94. Cloneable,
  95. PublicCloneable,
  96. Serializable {
  97. /** The paint used to highlight positive differences (y(0) > y(1)). */
  98. private transient Paint positivePaint;
  99. /** The paint used to highlight negative differences (y(0) < y(1)). */
  100. private transient Paint negativePaint;
  101. /** Display shapes at each point? */
  102. private boolean plotShapes = true;
  103. /**
  104. * Creates a new renderer with default attributes.
  105. */
  106. public XYDifferenceRenderer() {
  107. this(Color.green, Color.red, false);
  108. }
  109. /**
  110. * Creates a new renderer.
  111. *
  112. * @param positivePaint the highlight color for positive differences
  113. * (<code>null</code> not permitted).
  114. * @param negativePaint the highlight color for negative differences
  115. * (<code>null</code> not permitted).
  116. * @param shapes draw shapes?
  117. */
  118. public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
  119. boolean shapes) {
  120. if (positivePaint == null) {
  121. throw new IllegalArgumentException(
  122. "Null 'positivePaint' argument."
  123. );
  124. }
  125. if (negativePaint == null) {
  126. throw new IllegalArgumentException(
  127. "Null 'negativePaint' argument."
  128. );
  129. }
  130. this.positivePaint = positivePaint;
  131. this.negativePaint = negativePaint;
  132. this.plotShapes = shapes;
  133. }
  134. /**
  135. * Returns the paint used to highlight positive differences.
  136. *
  137. * @return the paint (never <code>null</code>).
  138. */
  139. public Paint getPositivePaint() {
  140. return this.positivePaint;
  141. }
  142. /**
  143. * Sets the paint used to highlight positive differences.
  144. *
  145. * @param paint the paint (<code>null</code> not permitted).
  146. */
  147. public void setPositivePaint(Paint paint) {
  148. if (paint == null) {
  149. throw new IllegalArgumentException("Null 'paint' argument.");
  150. }
  151. this.positivePaint = paint;
  152. notifyListeners(new RendererChangeEvent(this));
  153. }
  154. /**
  155. * Returns the paint used to highlight negative differences.
  156. *
  157. * @return the paint (never <code>null</code>).
  158. */
  159. public Paint getNegativePaint() {
  160. return this.negativePaint;
  161. }
  162. /**
  163. * Sets the paint used to highlight negative differences.
  164. *
  165. * @param paint the paint (<code>null</code> not permitted).
  166. */
  167. public void setNegativePaint(Paint paint) {
  168. if (paint == null) {
  169. throw new IllegalArgumentException("Null 'paint' argument.");
  170. }
  171. this.negativePaint = paint;
  172. notifyListeners(new RendererChangeEvent(this));
  173. }
  174. /**
  175. * Returns a flag that controls whether or not shapes are drawn for each
  176. * data value.
  177. *
  178. * @return a boolean.
  179. */
  180. public boolean getPlotShapes() {
  181. return this.plotShapes;
  182. }
  183. /**
  184. * Sets a flag that controls whether or not shapes are drawn for each
  185. * data value.
  186. *
  187. * @param flag the flag.
  188. */
  189. public void setPlotShapes(boolean flag) {
  190. this.plotShapes = flag;
  191. notifyListeners(new RendererChangeEvent(this));
  192. }
  193. /**
  194. * Initialises the renderer and returns a state object that should be
  195. * passed to subsequent calls to the drawItem() method. This method will
  196. * be called before the first item is rendered, giving the renderer an
  197. * opportunity to initialise any state information it wants to maintain.
  198. * The renderer can do nothing if it chooses.
  199. *
  200. * @param g2 the graphics device.
  201. * @param dataArea the area inside the axes.
  202. * @param plot the plot.
  203. * @param data the data.
  204. * @param info an optional info collection object to return data back to
  205. * the caller.
  206. *
  207. * @return a state object.
  208. */
  209. public XYItemRendererState initialise(Graphics2D g2,
  210. Rectangle2D dataArea,
  211. XYPlot plot,
  212. XYDataset data,
  213. PlotRenderingInfo info) {
  214. return super.initialise(g2, dataArea, plot, data, info);
  215. }
  216. /**
  217. * Returns <code>2</code>, the number of passes required by the renderer.
  218. * The {@link XYPlot} will run through the dataset this number of times.
  219. *
  220. * @return the number of passes required by the renderer.
  221. */
  222. public int getPassCount() {
  223. return 2;
  224. }
  225. /**
  226. * Draws the visual representation of a single data item.
  227. *
  228. * @param g2 the graphics device.
  229. * @param state the renderer state.
  230. * @param dataArea the area within which the data is being drawn.
  231. * @param info collects information about the drawing.
  232. * @param plot the plot (can be used to obtain standard color
  233. * information etc).
  234. * @param domainAxis the domain (horizontal) axis.
  235. * @param rangeAxis the range (vertical) axis.
  236. * @param dataset the dataset.
  237. * @param series the series index (zero-based).
  238. * @param item the item index (zero-based).
  239. * @param crosshairState crosshair information for the plot
  240. * (<code>null</code> permitted).
  241. * @param pass the pass index.
  242. */
  243. public void drawItem(Graphics2D g2,
  244. XYItemRendererState state,
  245. Rectangle2D dataArea,
  246. PlotRenderingInfo info,
  247. XYPlot plot,
  248. ValueAxis domainAxis,
  249. ValueAxis rangeAxis,
  250. XYDataset dataset,
  251. int series,
  252. int item,
  253. CrosshairState crosshairState,
  254. int pass) {
  255. if (pass == 0) {
  256. drawItemPass0(
  257. g2, dataArea, info, plot, domainAxis, rangeAxis, dataset,
  258. series, item, crosshairState
  259. );
  260. }
  261. else if (pass == 1) {
  262. drawItemPass1(
  263. g2, dataArea, info, plot, domainAxis, rangeAxis, dataset,
  264. series, item, crosshairState
  265. );
  266. }
  267. }
  268. /**
  269. * Draws the visual representation of a single data item, first pass.
  270. *
  271. * @param g2 the graphics device.
  272. * @param dataArea the area within which the data is being drawn.
  273. * @param info collects information about the drawing.
  274. * @param plot the plot (can be used to obtain standard color
  275. * information etc).
  276. * @param domainAxis the domain (horizontal) axis.
  277. * @param rangeAxis the range (vertical) axis.
  278. * @param dataset the dataset.
  279. * @param series the series index (zero-based).
  280. * @param item the item index (zero-based).
  281. * @param crosshairState crosshair information for the plot
  282. * (<code>null</code> permitted).
  283. */
  284. protected void drawItemPass0(Graphics2D g2,
  285. Rectangle2D dataArea,
  286. PlotRenderingInfo info,
  287. XYPlot plot,
  288. ValueAxis domainAxis,
  289. ValueAxis rangeAxis,
  290. XYDataset dataset,
  291. int series,
  292. int item,
  293. CrosshairState crosshairState) {
  294. if (series == 0) {
  295. PlotOrientation orientation = plot.getOrientation();
  296. RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
  297. RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
  298. double y0 = dataset.getYValue(0, item);
  299. double x1 = dataset.getXValue(1, item);
  300. double y1 = dataset.getYValue(1, item);
  301. double transY0 = rangeAxis.valueToJava2D(
  302. y0, dataArea, rangeAxisLocation
  303. );
  304. double transX1 = domainAxis.valueToJava2D(
  305. x1, dataArea, domainAxisLocation
  306. );
  307. double transY1 = rangeAxis.valueToJava2D(
  308. y1, dataArea, rangeAxisLocation
  309. );
  310. if (item > 0) {
  311. double prevx0 = dataset.getXValue(0, item - 1);
  312. double prevy0 = dataset.getYValue(0, item - 1);
  313. double prevy1 = dataset.getYValue(1, item - 1);
  314. double prevtransX0 = domainAxis.valueToJava2D(
  315. prevx0, dataArea, domainAxisLocation
  316. );
  317. double prevtransY0 = rangeAxis.valueToJava2D(
  318. prevy0, dataArea, rangeAxisLocation
  319. );
  320. double prevtransY1 = rangeAxis.valueToJava2D(
  321. prevy1, dataArea, rangeAxisLocation
  322. );
  323. Shape positive = getPositiveArea(
  324. (float) prevtransX0, (float) prevtransY0,
  325. (float) prevtransY1,
  326. (float) transX1, (float) transY0, (float) transY1,
  327. orientation
  328. );
  329. if (positive != null) {
  330. g2.setPaint(getPositivePaint());
  331. g2.fill(positive);
  332. }
  333. Shape negative = getNegativeArea(
  334. (float) prevtransX0, (float) prevtransY0,
  335. (float) prevtransY1,
  336. (float) transX1, (float) transY0, (float) transY1,
  337. orientation
  338. );
  339. if (negative != null) {
  340. g2.setPaint(getNegativePaint());
  341. g2.fill(negative);
  342. }
  343. }
  344. }
  345. }
  346. /**
  347. * Draws the visual representation of a single data item, second pass. In
  348. * the second pass, the renderer draws the lines and shapes for the
  349. * individual points in the two series.
  350. *
  351. * @param g2 the graphics device.
  352. * @param dataArea the area within which the data is being drawn.
  353. * @param info collects information about the drawing.
  354. * @param plot the plot (can be used to obtain standard color information
  355. * etc).
  356. * @param domainAxis the domain (horizontal) axis.
  357. * @param rangeAxis the range (vertical) axis.
  358. * @param dataset the dataset.
  359. * @param series the series index (zero-based).
  360. * @param item the item index (zero-based).
  361. * @param crosshairState crosshair information for the plot
  362. * (<code>null</code> permitted).
  363. */
  364. protected void drawItemPass1(Graphics2D g2,
  365. Rectangle2D dataArea,
  366. PlotRenderingInfo info,
  367. XYPlot plot,
  368. ValueAxis domainAxis,
  369. ValueAxis rangeAxis,
  370. XYDataset dataset,
  371. int series,
  372. int item,
  373. CrosshairState crosshairState) {
  374. Shape entityArea = null;
  375. EntityCollection entities = null;
  376. if (info != null) {
  377. entities = info.getOwner().getEntityCollection();
  378. }
  379. Paint seriesPaint = getItemPaint(series, item);
  380. Stroke seriesStroke = getItemStroke(series, item);
  381. g2.setPaint(seriesPaint);
  382. g2.setStroke(seriesStroke);
  383. if (series == 0) {
  384. PlotOrientation orientation = plot.getOrientation();
  385. RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
  386. RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
  387. double x0 = dataset.getXValue(0, item);
  388. double y0 = dataset.getYValue(0, item);
  389. double x1 = dataset.getXValue(1, item);
  390. double y1 = dataset.getYValue(1, item);
  391. double transX0 = domainAxis.valueToJava2D(
  392. x0, dataArea, domainAxisLocation
  393. );
  394. double transY0 = rangeAxis.valueToJava2D(
  395. y0, dataArea, rangeAxisLocation
  396. );
  397. double transX1 = domainAxis.valueToJava2D(
  398. x1, dataArea, domainAxisLocation
  399. );
  400. double transY1 = rangeAxis.valueToJava2D(
  401. y1, dataArea, rangeAxisLocation
  402. );
  403. if (item > 0) {
  404. // get the previous data points...
  405. double prevx0 = dataset.getXValue(0, item - 1);
  406. double prevy0 = dataset.getYValue(0, item - 1);
  407. double prevx1 = dataset.getXValue(1, item - 1);
  408. double prevy1 = dataset.getYValue(1, item - 1);
  409. double prevtransX0 = domainAxis.valueToJava2D(
  410. prevx0, dataArea, domainAxisLocation
  411. );
  412. double prevtransY0 = rangeAxis.valueToJava2D(
  413. prevy0, dataArea, rangeAxisLocation
  414. );
  415. double prevtransX1 = domainAxis.valueToJava2D(
  416. prevx1, dataArea, domainAxisLocation
  417. );
  418. double prevtransY1 = rangeAxis.valueToJava2D(
  419. prevy1, dataArea, rangeAxisLocation
  420. );
  421. Line2D line0 = null;
  422. Line2D line1 = null;
  423. if (orientation == PlotOrientation.HORIZONTAL) {
  424. line0 = new Line2D.Double(
  425. transY0, transX0, prevtransY0, prevtransX0
  426. );
  427. line1 = new Line2D.Double(
  428. transY1, transX1, prevtransY1, prevtransX1
  429. );
  430. }
  431. else if (orientation == PlotOrientation.VERTICAL) {
  432. line0 = new Line2D.Double(
  433. transX0, transY0, prevtransX0, prevtransY0
  434. );
  435. line1 = new Line2D.Double(
  436. transX1, transY1, prevtransX1, prevtransY1
  437. );
  438. }
  439. if (line0 != null && line0.intersects(dataArea)) {
  440. g2.setPaint(getItemPaint(series, item));
  441. g2.draw(line0);
  442. }
  443. if (line1 != null && line1.intersects(dataArea)) {
  444. g2.setPaint(getItemPaint(1, item));
  445. g2.draw(line1);
  446. }
  447. }
  448. if (getPlotShapes()) {
  449. Shape shape0 = getItemShape(series, item);
  450. if (orientation == PlotOrientation.HORIZONTAL) {
  451. shape0 = ShapeUtilities.createTranslatedShape(
  452. shape0, transY0, transX0
  453. );
  454. }
  455. else { // vertical
  456. shape0 = ShapeUtilities.createTranslatedShape(
  457. shape0, transX0, transY0
  458. );
  459. }
  460. if (shape0.intersects(dataArea)) {
  461. g2.setPaint(getItemPaint(series, item));
  462. g2.fill(shape0);
  463. }
  464. entityArea = shape0;
  465. // add an entity for the item...
  466. if (entities != null) {
  467. if (entityArea == null) {
  468. entityArea = new Rectangle2D.Double(
  469. transX0 - 2, transY0 - 2, 4, 4
  470. );
  471. }
  472. String tip = null;
  473. XYToolTipGenerator generator = getToolTipGenerator(
  474. series, item
  475. );
  476. if (generator != null) {
  477. tip = generator.generateToolTip(dataset, series, item);
  478. }
  479. String url = null;
  480. if (getURLGenerator() != null) {
  481. url = getURLGenerator().generateURL(
  482. dataset, series, item
  483. );
  484. }
  485. XYItemEntity entity = new XYItemEntity(
  486. entityArea, dataset, series, item, tip, url
  487. );
  488. entities.add(entity);
  489. }
  490. Shape shape1 = getItemShape(series + 1, item);
  491. shape1 = ShapeUtilities.createTranslatedShape(
  492. shape1, transX1, transY1
  493. );
  494. if (shape1.intersects(dataArea)) {
  495. g2.setPaint(getItemPaint(series + 1, item));
  496. g2.fill(shape1);
  497. }
  498. entityArea = shape1;
  499. // add an entity for the item...
  500. if (entities != null) {
  501. if (entityArea == null) {
  502. entityArea = new Rectangle2D.Double(
  503. transX1 - 2, transY1 - 2, 4, 4
  504. );
  505. }
  506. String tip = null;
  507. XYToolTipGenerator generator = getToolTipGenerator(
  508. series, item
  509. );
  510. if (generator != null) {
  511. tip = generator.generateToolTip(
  512. dataset, series + 1, item
  513. );
  514. }
  515. String url = null;
  516. if (getURLGenerator() != null) {
  517. url = getURLGenerator().generateURL(
  518. dataset, series + 1, item
  519. );
  520. }
  521. XYItemEntity entity = new XYItemEntity(
  522. entityArea, dataset, series + 1, item, tip, url
  523. );
  524. entities.add(entity);
  525. }
  526. }
  527. updateCrosshairValues(
  528. crosshairState, x1, y1, transX1, transY1, orientation
  529. );
  530. }
  531. }
  532. /**
  533. * Returns the positive area for a crossover point.
  534. *
  535. * @param x0 x coordinate.
  536. * @param y0A y coordinate A.
  537. * @param y0B y coordinate B.
  538. * @param x1 x coordinate.
  539. * @param y1A y coordinate A.
  540. * @param y1B y coordinate B.
  541. * @param orientation the plot orientation.
  542. *
  543. * @return The positive area.
  544. */
  545. protected Shape getPositiveArea(float x0, float y0A, float y0B,
  546. float x1, float y1A, float y1B,
  547. PlotOrientation orientation) {
  548. Shape result = null;
  549. boolean startsNegative = (y0A >= y0B);
  550. boolean endsNegative = (y1A >= y1B);
  551. if (orientation == PlotOrientation.HORIZONTAL) {
  552. startsNegative = (y0B >= y0A);
  553. endsNegative = (y1B >= y1A);
  554. }
  555. if (startsNegative) { // starts negative
  556. if (endsNegative) {
  557. // all negative - return null
  558. result = null;
  559. }
  560. else {
  561. // changed from negative to positive
  562. float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
  563. GeneralPath area = new GeneralPath();
  564. if (orientation == PlotOrientation.HORIZONTAL) {
  565. area.moveTo(y1A, x1);
  566. area.lineTo(p[1], p[0]);
  567. area.lineTo(y1B, x1);
  568. area.closePath();
  569. }
  570. else if (orientation == PlotOrientation.VERTICAL) {
  571. area.moveTo(x1, y1A);
  572. area.lineTo(p[0], p[1]);
  573. area.lineTo(x1, y1B);
  574. area.closePath();
  575. }
  576. result = area;
  577. }
  578. }
  579. else { // starts positive
  580. if (endsNegative) {
  581. // changed from positive to negative
  582. float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
  583. GeneralPath area = new GeneralPath();
  584. if (orientation == PlotOrientation.HORIZONTAL) {
  585. area.moveTo(y0A, x0);
  586. area.lineTo(p[1], p[0]);
  587. area.lineTo(y0B, x0);
  588. area.closePath();
  589. }
  590. else if (orientation == PlotOrientation.VERTICAL) {
  591. area.moveTo(x0, y0A);
  592. area.lineTo(p[0], p[1]);
  593. area.lineTo(x0, y0B);
  594. area.closePath();
  595. }
  596. result = area;
  597. }
  598. else {
  599. GeneralPath area = new GeneralPath();
  600. if (orientation == PlotOrientation.HORIZONTAL) {
  601. area.moveTo(y0A, x0);
  602. area.lineTo(y1A, x1);
  603. area.lineTo(y1B, x1);
  604. area.lineTo(y0B, x0);
  605. area.closePath();
  606. }
  607. else if (orientation == PlotOrientation.VERTICAL) {
  608. area.moveTo(x0, y0A);
  609. area.lineTo(x1, y1A);
  610. area.lineTo(x1, y1B);
  611. area.lineTo(x0, y0B);
  612. area.closePath();
  613. }
  614. result = area;
  615. }
  616. }
  617. return result;
  618. }
  619. /**
  620. * Returns the negative area for a cross-over section.
  621. *
  622. * @param x0 x coordinate.
  623. * @param y0A y coordinate A.
  624. * @param y0B y coordinate B.
  625. * @param x1 x coordinate.
  626. * @param y1A y coordinate A.
  627. * @param y1B y coordinate B.
  628. * @param orientation the plot orientation.
  629. *
  630. * @return The negative area.
  631. */
  632. protected Shape getNegativeArea(float x0, float y0A, float y0B,
  633. float x1, float y1A, float y1B,
  634. PlotOrientation orientation) {
  635. Shape result = null;
  636. boolean startsNegative = (y0A >= y0B);
  637. boolean endsNegative = (y1A >= y1B);
  638. if (orientation == PlotOrientation.HORIZONTAL) {
  639. startsNegative = (y0B >= y0A);
  640. endsNegative = (y1B >= y1A);
  641. }
  642. if (startsNegative) { // starts negative
  643. if (endsNegative) { // all negative
  644. GeneralPath area = new GeneralPath();
  645. if (orientation == PlotOrientation.HORIZONTAL) {
  646. area.moveTo(y0A, x0);
  647. area.lineTo(y1A, x1);
  648. area.lineTo(y1B, x1);
  649. area.lineTo(y0B, x0);
  650. area.closePath();
  651. }
  652. else if (orientation == PlotOrientation.VERTICAL) {
  653. area.moveTo(x0, y0A);
  654. area.lineTo(x1, y1A);
  655. area.lineTo(x1, y1B);
  656. area.lineTo(x0, y0B);
  657. area.closePath();
  658. }
  659. result = area;
  660. }
  661. else { // changed from negative to positive
  662. float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
  663. GeneralPath area = new GeneralPath();
  664. if (orientation == PlotOrientation.HORIZONTAL) {
  665. area.moveTo(y0A, x0);
  666. area.lineTo(p[1], p[0]);
  667. area.lineTo(y0B, x0);
  668. area.closePath();
  669. }
  670. else if (orientation == PlotOrientation.VERTICAL) {
  671. area.moveTo(x0, y0A);
  672. area.lineTo(p[0], p[1]);
  673. area.lineTo(x0, y0B);
  674. area.closePath();
  675. }
  676. result = area;
  677. }
  678. }
  679. else {
  680. if (endsNegative) {
  681. // changed from positive to negative
  682. float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
  683. GeneralPath area = new GeneralPath();
  684. if (orientation == PlotOrientation.HORIZONTAL) {
  685. area.moveTo(y1A, x1);
  686. area.lineTo(p[1], p[0]);
  687. area.lineTo(y1B, x1);
  688. area.closePath();
  689. }
  690. else if (orientation == PlotOrientation.VERTICAL) {
  691. area.moveTo(x1, y1A);
  692. area.lineTo(p[0], p[1]);
  693. area.lineTo(x1, y1B);
  694. area.closePath();
  695. }
  696. result = area;
  697. }
  698. else {
  699. // all negative - return null
  700. }
  701. }
  702. return result;
  703. }
  704. /**
  705. * Returns the intersection point of two lines.
  706. *
  707. * @param x1 x1
  708. * @param y1 y1
  709. * @param x2 x2
  710. * @param y2 y2
  711. * @param x3 x3
  712. * @param y3 y3
  713. * @param x4 x4
  714. * @param y4 y4
  715. *
  716. * @return The intersection point.
  717. */
  718. private float[] getIntersection(float x1, float y1, float x2, float y2,
  719. float x3, float y3, float x4, float y4) {
  720. float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  721. float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
  722. float u = n / d;
  723. float[] result = new float[2];
  724. result[0] = x1 + u * (x2 - x1);
  725. result[1] = y1 + u * (y2 - y1);
  726. return result;
  727. }
  728. /**
  729. * Returns a default legend item for the specified series. Subclasses
  730. * should override this method to generate customised items.
  731. *
  732. * @param datasetIndex the dataset index (zero-based).
  733. * @param series the series index (zero-based).
  734. *
  735. * @return A legend item for the series.
  736. */
  737. public LegendItem getLegendItem(int datasetIndex, int series) {
  738. LegendItem result = null;
  739. XYPlot p = getPlot();
  740. if (p != null) {
  741. XYDataset dataset = p.getDataset(datasetIndex);
  742. if (dataset != null) {
  743. if (getItemVisible(series, 0)) {
  744. String label = getLegendItemLabelGenerator().generateLabel(
  745. dataset, series
  746. );
  747. String description = label;
  748. Paint paint = getSeriesPaint(series);
  749. Stroke stroke = getSeriesStroke(series);
  750. // TODO: the following hard-coded line needs generalising
  751. Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
  752. result = new LegendItem(
  753. label, description, line, stroke, paint
  754. );
  755. }
  756. }
  757. }
  758. return result;
  759. }
  760. /**
  761. * Returns a clone of the renderer.
  762. *
  763. * @return A clone.
  764. *
  765. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  766. */
  767. public Object clone() throws CloneNotSupportedException {
  768. return super.clone();
  769. }
  770. /**
  771. * Provides serialization support.
  772. *
  773. * @param stream the output stream.
  774. *
  775. * @throws IOException if there is an I/O error.
  776. */
  777. private void writeObject(ObjectOutputStream stream) throws IOException {
  778. stream.defaultWriteObject();
  779. SerialUtilities.writePaint(this.positivePaint, stream);
  780. SerialUtilities.writePaint(this.negativePaint, stream);
  781. }
  782. /**
  783. * Provides serialization support.
  784. *
  785. * @param stream the input stream.
  786. *
  787. * @throws IOException if there is an I/O error.
  788. * @throws ClassNotFoundException if there is a classpath problem.
  789. */
  790. private void readObject(ObjectInputStream stream)
  791. throws IOException, ClassNotFoundException {
  792. stream.defaultReadObject();
  793. this.positivePaint = SerialUtilities.readPaint(stream);
  794. this.negativePaint = SerialUtilities.readPaint(stream);
  795. }
  796. }