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. * StandardXYItemRenderer.java
  28. * ---------------------------
  29. * (C) Copyright 2001-2005, by Object Refinery Limited and Contributors.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): Mark Watson (www.markwatson.com);
  33. * Jonathan Nash;
  34. * Andreas Schneider;
  35. * Norbert Kiesel (for TBD Networks);
  36. * Christian W. Zuckschwerdt;
  37. * Bill Kelemen;
  38. * Nicolas Brodu (for Astrium and EADS Corporate Research
  39. * Center);
  40. *
  41. * $Id: StandardXYItemRenderer.java,v 1.10 2005/03/10 11:23:55 mungady Exp $
  42. *
  43. * Changes:
  44. * --------
  45. * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
  46. * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  47. * 21-Dec-2001 : Added working line instance to improve performance (DG);
  48. * 22-Jan-2002 : Added code to lock crosshairs to data points. Based on code
  49. * by Jonathan Nash (DG);
  50. * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
  51. * 28-Mar-2002 : Added a property change listener mechanism so that the
  52. * renderer no longer needs to be immutable (DG);
  53. * 02-Apr-2002 : Modified to handle null values (DG);
  54. * 09-Apr-2002 : Modified draw method to return void. Removed the translated
  55. * zero from the drawItem method. Override the initialise()
  56. * method to calculate it (DG);
  57. * 13-May-2002 : Added code from Andreas Schneider to allow changing
  58. * shapes/colors per item (DG);
  59. * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  60. * 25-Jun-2002 : Removed redundant code (DG);
  61. * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
  62. * 08-Aug-2002 : Added discontinuous lines option contributed by
  63. * Norbert Kiesel (DG);
  64. * 20-Aug-2002 : Added user definable default values to be returned by
  65. * protected methods unless overridden by a subclass (DG);
  66. * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
  67. * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  68. * 25-Mar-2003 : Implemented Serializable (DG);
  69. * 01-May-2003 : Modified drawItem(...) method signature (DG);
  70. * 15-May-2003 : Modified to take into account the plot orientation (DG);
  71. * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
  72. * 30-Jul-2003 : Modified entity constructor (CZ);
  73. * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  74. * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
  75. * 08-Sep-2003 : Fixed serialization (NB);
  76. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  77. * 21-Jan-2004 : Override for getLegendItem() method (DG);
  78. * 27-Jan-2004 : Moved working line into state object (DG);
  79. * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding
  80. * easier (DG);
  81. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
  82. * XYToolTipGenerator --> XYItemLabelGenerator (DG);
  83. * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
  84. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
  85. * getYValue() (DG);
  86. * 25-Aug-2004 : Created addEntity() method in superclass (DG);
  87. * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
  88. * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  89. * 23-Feb-2005 : Fixed getLegendItem() method to show lines. Fixed bug
  90. * 1077108 (shape not visible for first item in series) (DG);
  91. *
  92. */
  93. package org.jfree.chart.renderer.xy;
  94. import java.awt.Graphics2D;
  95. import java.awt.Image;
  96. import java.awt.Paint;
  97. import java.awt.Point;
  98. import java.awt.Shape;
  99. import java.awt.Stroke;
  100. import java.awt.geom.GeneralPath;
  101. import java.awt.geom.Line2D;
  102. import java.awt.geom.Rectangle2D;
  103. import java.io.Serializable;
  104. import org.jfree.chart.LegendItem;
  105. import org.jfree.chart.axis.ValueAxis;
  106. import org.jfree.chart.entity.EntityCollection;
  107. import org.jfree.chart.event.RendererChangeEvent;
  108. import org.jfree.chart.labels.XYToolTipGenerator;
  109. import org.jfree.chart.plot.CrosshairState;
  110. import org.jfree.chart.plot.Plot;
  111. import org.jfree.chart.plot.PlotOrientation;
  112. import org.jfree.chart.plot.PlotRenderingInfo;
  113. import org.jfree.chart.plot.XYPlot;
  114. import org.jfree.chart.urls.XYURLGenerator;
  115. import org.jfree.data.xy.XYDataset;
  116. import org.jfree.ui.RectangleEdge;
  117. import org.jfree.util.BooleanList;
  118. import org.jfree.util.BooleanUtilities;
  119. import org.jfree.util.ObjectUtilities;
  120. import org.jfree.util.PublicCloneable;
  121. import org.jfree.util.ShapeUtilities;
  122. import org.jfree.util.UnitType;
  123. /**
  124. * Standard item renderer for an {@link XYPlot}. This class can draw (a)
  125. * shapes at each point, or (b) lines between points, or (c) both shapes and
  126. * lines.
  127. */
  128. public class StandardXYItemRenderer extends AbstractXYItemRenderer
  129. implements XYItemRenderer,
  130. Cloneable,
  131. PublicCloneable,
  132. Serializable {
  133. /** Constant for the type of rendering (shapes only). */
  134. public static final int SHAPES = 1;
  135. /** Constant for the type of rendering (lines only). */
  136. public static final int LINES = 2;
  137. /** Constant for the type of rendering (shapes and lines). */
  138. public static final int SHAPES_AND_LINES = SHAPES | LINES;
  139. /** Constant for the type of rendering (images only). */
  140. public static final int IMAGES = 4;
  141. /** Constant for the type of rendering (discontinuous lines). */
  142. public static final int DISCONTINUOUS = 8;
  143. /** Constant for the type of rendering (discontinuous lines). */
  144. public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
  145. /** A flag indicating whether or not shapes are drawn at each XY point. */
  146. private boolean plotShapes;
  147. /** A flag indicating whether or not lines are drawn between XY points. */
  148. private boolean plotLines;
  149. /** A flag indicating whether or not images are drawn between XY points. */
  150. private boolean plotImages;
  151. /** A flag controlling whether or not discontinuous lines are used. */
  152. private boolean plotDiscontinuous;
  153. /** Specifies how the gap threshold value is interpreted. */
  154. private UnitType gapThresholdType = UnitType.RELATIVE;
  155. /** Threshold for deciding when to discontinue a line. */
  156. private double gapThreshold = 1.0;
  157. /** A flag that controls whether or not shapes are filled for ALL series. */
  158. private Boolean shapesFilled;
  159. /**
  160. * A table of flags that control (per series) whether or not shapes are
  161. * filled.
  162. */
  163. private BooleanList seriesShapesFilled;
  164. /** The default value returned by the getShapeFilled() method. */
  165. private Boolean defaultShapesFilled;
  166. /**
  167. * A flag that controls whether or not each series is drawn as a single
  168. * path.
  169. */
  170. private boolean drawSeriesLineAsPath;
  171. /**
  172. * Constructs a new renderer.
  173. */
  174. public StandardXYItemRenderer() {
  175. this(LINES, null);
  176. }
  177. /**
  178. * Constructs a new renderer.
  179. * <p>
  180. * To specify the type of renderer, use one of the constants: SHAPES, LINES
  181. * or SHAPES_AND_LINES.
  182. *
  183. * @param type the type.
  184. */
  185. public StandardXYItemRenderer(int type) {
  186. this(type, null);
  187. }
  188. /**
  189. * Constructs a new renderer.
  190. * <p>
  191. * To specify the type of renderer, use one of the constants: SHAPES, LINES
  192. * or SHAPES_AND_LINES.
  193. *
  194. * @param type the type of renderer.
  195. * @param toolTipGenerator the item label generator (<code>null</code>
  196. * permitted).
  197. */
  198. public StandardXYItemRenderer(int type,
  199. XYToolTipGenerator toolTipGenerator) {
  200. this(type, toolTipGenerator, null);
  201. }
  202. /**
  203. * Constructs a new renderer.
  204. * <p>
  205. * To specify the type of renderer, use one of the constants: SHAPES, LINES
  206. * or SHAPES_AND_LINES.
  207. *
  208. * @param type the type of renderer.
  209. * @param toolTipGenerator the item label generator (<code>null</code>
  210. * permitted).
  211. * @param urlGenerator the URL generator.
  212. */
  213. public StandardXYItemRenderer(int type,
  214. XYToolTipGenerator toolTipGenerator,
  215. XYURLGenerator urlGenerator) {
  216. super();
  217. setToolTipGenerator(toolTipGenerator);
  218. setURLGenerator(urlGenerator);
  219. if ((type & SHAPES) != 0) {
  220. this.plotShapes = true;
  221. }
  222. if ((type & LINES) != 0) {
  223. this.plotLines = true;
  224. }
  225. if ((type & IMAGES) != 0) {
  226. this.plotImages = true;
  227. }
  228. if ((type & DISCONTINUOUS) != 0) {
  229. this.plotDiscontinuous = true;
  230. }
  231. this.shapesFilled = null;
  232. this.seriesShapesFilled = new BooleanList();
  233. this.defaultShapesFilled = Boolean.TRUE;
  234. this.drawSeriesLineAsPath = false;
  235. }
  236. /**
  237. * Returns true if shapes are being plotted by the renderer.
  238. *
  239. * @return <code>true</code> if shapes are being plotted by the renderer.
  240. */
  241. public boolean getDefaultShapesVisible() {
  242. return this.plotShapes;
  243. }
  244. /**
  245. * Sets the flag that controls whether or not a shape is plotted at each
  246. * data point.
  247. *
  248. * @param flag the flag.
  249. */
  250. public void setDefaultShapesVisible(boolean flag) {
  251. if (this.plotShapes != flag) {
  252. this.plotShapes = flag;
  253. notifyListeners(new RendererChangeEvent(this));
  254. }
  255. }
  256. /**
  257. * Returns true if shapes are being plotted by the renderer.
  258. *
  259. * @return <code>true</code> if shapes are being plotted by the renderer.
  260. */
  261. public boolean getPlotShapes() {
  262. return this.plotShapes;
  263. }
  264. /**
  265. * Sets the flag that controls whether or not a shape is plotted at each
  266. * data point.
  267. *
  268. * @param flag the flag.
  269. */
  270. public void setPlotShapes(boolean flag) {
  271. if (this.plotShapes != flag) {
  272. this.plotShapes = flag;
  273. notifyListeners(new RendererChangeEvent(this));
  274. }
  275. }
  276. // SHAPES FILLED
  277. /**
  278. * Returns the flag used to control whether or not the shape for an item is
  279. * filled.
  280. * <p>
  281. * The default implementation passes control to the
  282. * <code>getSeriesShapesFilled</code> method. You can override this method
  283. * if you require different behaviour.
  284. *
  285. * @param series the series index (zero-based).
  286. * @param item the item index (zero-based).
  287. *
  288. * @return A boolean.
  289. */
  290. public boolean getItemShapeFilled(int series, int item) {
  291. return getSeriesShapesFilled(series);
  292. }
  293. /**
  294. * Returns the flag used to control whether or not the shapes for a series
  295. * are filled.
  296. *
  297. * @param series the series index (zero-based).
  298. *
  299. * @return A boolean.
  300. */
  301. public boolean getSeriesShapesFilled(int series) {
  302. // return the overall setting, if there is one...
  303. if (this.shapesFilled != null) {
  304. return this.shapesFilled.booleanValue();
  305. }
  306. // otherwise look up the paint table
  307. Boolean flag = this.seriesShapesFilled.getBoolean(series);
  308. if (flag != null) {
  309. return flag.booleanValue();
  310. }
  311. else {
  312. return this.defaultShapesFilled.booleanValue();
  313. }
  314. }
  315. /**
  316. * Sets the 'shapes filled' for ALL series.
  317. *
  318. * @param filled the flag.
  319. */
  320. public void setShapesFilled(boolean filled) {
  321. // here we use BooleanUtilities to remain compatible with JDKs < 1.4
  322. setShapesFilled(BooleanUtilities.valueOf(filled));
  323. }
  324. /**
  325. * Sets the 'shapes filled' for ALL series.
  326. *
  327. * @param filled the flag (<code>null</code> permitted).
  328. */
  329. public void setShapesFilled(Boolean filled) {
  330. this.shapesFilled = filled;
  331. }
  332. /**
  333. * Sets the 'shapes filled' flag for a series.
  334. *
  335. * @param series the series index (zero-based).
  336. * @param flag the flag.
  337. */
  338. public void setSeriesShapesFilled(int series, Boolean flag) {
  339. this.seriesShapesFilled.setBoolean(series, flag);
  340. }
  341. /**
  342. * Returns the default 'shape filled' attribute.
  343. *
  344. * @return The default flag.
  345. */
  346. public Boolean getDefaultShapesFilled() {
  347. return this.defaultShapesFilled;
  348. }
  349. /**
  350. * Sets the default 'shapes filled' flag.
  351. *
  352. * @param flag the flag.
  353. */
  354. public void setDefaultShapesFilled(Boolean flag) {
  355. this.defaultShapesFilled = flag;
  356. }
  357. /**
  358. * Returns true if lines are being plotted by the renderer.
  359. *
  360. * @return <code>true</code> if lines are being plotted by the renderer.
  361. */
  362. public boolean getPlotLines() {
  363. return this.plotLines;
  364. }
  365. /**
  366. * Sets the flag that controls whether or not a line is plotted between
  367. * each data point.
  368. *
  369. * @param flag the flag.
  370. */
  371. public void setPlotLines(boolean flag) {
  372. if (this.plotLines != flag) {
  373. this.plotLines = flag;
  374. notifyListeners(new RendererChangeEvent(this));
  375. }
  376. }
  377. /**
  378. * Returns the gap threshold type (relative or absolute).
  379. *
  380. * @return The type.
  381. */
  382. public UnitType getGapThresholdType() {
  383. return this.gapThresholdType;
  384. }
  385. /**
  386. * Sets the gap threshold type.
  387. *
  388. * @param thresholdType the type (<code>null</code> not permitted).
  389. */
  390. public void setGapThresholdType(UnitType thresholdType) {
  391. if (thresholdType == null) {
  392. throw new IllegalArgumentException(
  393. "Null 'thresholdType' argument."
  394. );
  395. }
  396. this.gapThresholdType = thresholdType;
  397. notifyListeners(new RendererChangeEvent(this));
  398. }
  399. /**
  400. * Returns the gap threshold for discontinuous lines.
  401. *
  402. * @return The gap threshold.
  403. */
  404. public double getGapThreshold() {
  405. return this.gapThreshold;
  406. }
  407. /**
  408. * Sets the gap threshold for discontinuous lines.
  409. *
  410. * @param t the threshold.
  411. */
  412. public void setGapThreshold(double t) {
  413. this.gapThreshold = t;
  414. notifyListeners(new RendererChangeEvent(this));
  415. }
  416. /**
  417. * Returns true if images are being plotted by the renderer.
  418. *
  419. * @return <code>true</code> if images are being plotted by the renderer.
  420. */
  421. public boolean getPlotImages() {
  422. return this.plotImages;
  423. }
  424. /**
  425. * Sets the flag that controls whether or not an image is drawn at each
  426. * data point.
  427. *
  428. * @param flag the flag.
  429. */
  430. public void setPlotImages(boolean flag) {
  431. if (this.plotImages != flag) {
  432. this.plotImages = flag;
  433. notifyListeners(new RendererChangeEvent(this));
  434. }
  435. }
  436. /**
  437. * Returns true if lines should be discontinuous.
  438. *
  439. * @return <code>true</code> if lines should be discontinuous.
  440. */
  441. public boolean getPlotDiscontinuous() {
  442. return this.plotDiscontinuous;
  443. }
  444. /**
  445. * Returns a flag that controls whether or not each series is drawn as a
  446. * single path.
  447. *
  448. * @return A boolean.
  449. */
  450. public boolean getDrawSeriesLineAsPath() {
  451. return this.drawSeriesLineAsPath;
  452. }
  453. /**
  454. * Sets the flag that controls whether or not each series is drawn as a
  455. * single path.
  456. *
  457. * @param flag the flag.
  458. */
  459. public void setDrawSeriesLineAsPath(boolean flag) {
  460. this.drawSeriesLineAsPath = flag;
  461. }
  462. /**
  463. * Returns a legend item for a series.
  464. *
  465. * @param datasetIndex the dataset index (zero-based).
  466. * @param series the series index (zero-based).
  467. *
  468. * @return A legend item for the series.
  469. */
  470. public LegendItem getLegendItem(int datasetIndex, int series) {
  471. LegendItem result = null;
  472. XYPlot plot = getPlot();
  473. if (plot != null) {
  474. XYDataset dataset = plot.getDataset(datasetIndex);
  475. if (dataset != null) {
  476. if (getItemVisible(series, 0)) {
  477. String label = dataset.getSeriesName(series);
  478. String description = label;
  479. Shape shape = getSeriesShape(series);
  480. boolean shapeFilled = getSeriesShapesFilled(series);
  481. Paint paint = getSeriesPaint(series);
  482. Paint outlinePaint = getSeriesOutlinePaint(series);
  483. Stroke outlineStroke = getSeriesOutlineStroke(series);
  484. Paint linePaint = paint;
  485. Stroke lineStroke = getSeriesStroke(series);
  486. result = new LegendItem(
  487. label, description, this.plotShapes, shape, shapeFilled,
  488. paint, true, outlinePaint, outlineStroke,
  489. this.plotLines,
  490. new Line2D.Float(-7.0f, 0.0f, 7.0f, 0.0f),
  491. lineStroke, linePaint
  492. );
  493. }
  494. }
  495. }
  496. return result;
  497. }
  498. /**
  499. * Records the state for the renderer. This is used to preserve state
  500. * information between calls to the drawItem() method for a single chart
  501. * drawing.
  502. */
  503. public static class State extends XYItemRendererState {
  504. /** The path for the current series. */
  505. public GeneralPath seriesPath;
  506. /** A flag that indicates if the last (x, y) point was 'good' (non-null). */
  507. private boolean lastPointGood;
  508. /**
  509. * Creates a new state instance.
  510. *
  511. * @param info the plot rendering info.
  512. */
  513. public State(PlotRenderingInfo info) {
  514. super(info);
  515. }
  516. /**
  517. * Returns a flag that indicates if the last point drawn (in the current series)
  518. * was 'good' (non-null).
  519. *
  520. * @return A boolean.
  521. */
  522. public boolean isLastPointGood() {
  523. return this.lastPointGood;
  524. }
  525. /**
  526. * Sets a flag that indicates if the last point drawn (in the current series)
  527. * was 'good' (non-null).
  528. *
  529. * @param good the flag.
  530. */
  531. public void setLastPointGood(boolean good) {
  532. this.lastPointGood = good;
  533. }
  534. }
  535. /**
  536. * Initialises the renderer.
  537. * <P>
  538. * This method will be called before the first item is rendered, giving the
  539. * renderer an opportunity to initialise any state information it wants to
  540. * maintain. The renderer can do nothing if it chooses.
  541. *
  542. * @param g2 the graphics device.
  543. * @param dataArea the area inside the axes.
  544. * @param plot the plot.
  545. * @param data the data.
  546. * @param info an optional info collection object to return data back to
  547. * the caller.
  548. *
  549. * @return The renderer state.
  550. */
  551. public XYItemRendererState initialise(Graphics2D g2,
  552. Rectangle2D dataArea,
  553. XYPlot plot,
  554. XYDataset data,
  555. PlotRenderingInfo info) {
  556. State state = new State(info);
  557. state.seriesPath = new GeneralPath();
  558. return state;
  559. }
  560. /**
  561. * Draws the visual representation of a single data item.
  562. *
  563. * @param g2 the graphics device.
  564. * @param state the renderer state.
  565. * @param dataArea the area within which the data is being drawn.
  566. * @param info collects information about the drawing.
  567. * @param plot the plot (can be used to obtain standard color information
  568. * etc).
  569. * @param domainAxis the domain axis.
  570. * @param rangeAxis the range axis.
  571. * @param dataset the dataset.
  572. * @param series the series index (zero-based).
  573. * @param item the item index (zero-based).
  574. * @param crosshairState crosshair information for the plot
  575. * (<code>null</code> permitted).
  576. * @param pass the pass index.
  577. */
  578. public void drawItem(Graphics2D g2,
  579. XYItemRendererState state,
  580. Rectangle2D dataArea,
  581. PlotRenderingInfo info,
  582. XYPlot plot,
  583. ValueAxis domainAxis,
  584. ValueAxis rangeAxis,
  585. XYDataset dataset,
  586. int series,
  587. int item,
  588. CrosshairState crosshairState,
  589. int pass) {
  590. if (!getItemVisible(series, item)) {
  591. return;
  592. }
  593. // setup for collecting optional entity info...
  594. Shape entityArea = null;
  595. EntityCollection entities = null;
  596. if (info != null) {
  597. entities = info.getOwner().getEntityCollection();
  598. }
  599. PlotOrientation orientation = plot.getOrientation();
  600. Paint paint = getItemPaint(series, item);
  601. Stroke seriesStroke = getItemStroke(series, item);
  602. g2.setPaint(paint);
  603. g2.setStroke(seriesStroke);
  604. // get the data point...
  605. double x1 = dataset.getXValue(series, item);
  606. double y1 = dataset.getYValue(series, item);
  607. if (Double.isNaN(x1) || Double.isNaN(y1)) {
  608. return;
  609. }
  610. RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
  611. RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
  612. double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
  613. double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
  614. if (getPlotLines()) {
  615. if (item == 0) {
  616. if (this.drawSeriesLineAsPath) {
  617. State s = (State) state;
  618. s.seriesPath.reset();
  619. s.lastPointGood = false;
  620. }
  621. }
  622. if (this.drawSeriesLineAsPath) {
  623. State s = (State) state;
  624. // update path to reflect latest point
  625. if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
  626. float x = (float) transX1;
  627. float y = (float) transY1;
  628. if (orientation == PlotOrientation.HORIZONTAL) {
  629. x = (float) transY1;
  630. y = (float) transX1;
  631. }
  632. if (s.isLastPointGood()) {
  633. // TODO: check threshold
  634. s.seriesPath.lineTo(x, y);
  635. }
  636. else {
  637. s.seriesPath.moveTo(x, y);
  638. }
  639. s.setLastPointGood(true);
  640. }
  641. else {
  642. s.setLastPointGood(false);
  643. }
  644. if (item == dataset.getItemCount(series) - 1) {
  645. // draw path
  646. g2.setStroke(getSeriesStroke(series));
  647. g2.setPaint(getSeriesPaint(series));
  648. g2.draw(s.seriesPath);
  649. }
  650. }
  651. else if (item != 0) {
  652. // get the previous data point...
  653. double x0 = dataset.getXValue(series, item - 1);
  654. double y0 = dataset.getYValue(series, item - 1);
  655. if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
  656. boolean drawLine = true;
  657. if (getPlotDiscontinuous()) {
  658. // only draw a line if the gap between the current and
  659. // previous data point is within the threshold
  660. int numX = dataset.getItemCount(series);
  661. double minX = dataset.getXValue(series, 0);
  662. double maxX = dataset.getXValue(series, numX - 1);
  663. if (this.gapThresholdType == UnitType.ABSOLUTE) {
  664. drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
  665. }
  666. else {
  667. drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
  668. / numX * getGapThreshold());
  669. }
  670. }
  671. if (drawLine) {
  672. double transX0 = domainAxis.valueToJava2D(
  673. x0, dataArea, xAxisLocation
  674. );
  675. double transY0 = rangeAxis.valueToJava2D(
  676. y0, dataArea, yAxisLocation
  677. );
  678. // only draw if we have good values
  679. if (Double.isNaN(transX0) || Double.isNaN(transY0)
  680. || Double.isNaN(transX1) || Double.isNaN(transY1)) {
  681. return;
  682. }
  683. if (orientation == PlotOrientation.HORIZONTAL) {
  684. state.workingLine.setLine(
  685. transY0, transX0, transY1, transX1
  686. );
  687. }
  688. else if (orientation == PlotOrientation.VERTICAL) {
  689. state.workingLine.setLine(
  690. transX0, transY0, transX1, transY1
  691. );
  692. }
  693. if (state.workingLine.intersects(dataArea)) {
  694. g2.draw(state.workingLine);
  695. }
  696. }
  697. }
  698. }
  699. }
  700. if (getPlotShapes()) {
  701. Shape shape = getItemShape(series, item);
  702. if (orientation == PlotOrientation.HORIZONTAL) {
  703. shape = ShapeUtilities.createTranslatedShape(
  704. shape, transY1, transX1
  705. );
  706. }
  707. else if (orientation == PlotOrientation.VERTICAL) {
  708. shape = ShapeUtilities.createTranslatedShape(
  709. shape, transX1, transY1
  710. );
  711. }
  712. if (shape.intersects(dataArea)) {
  713. if (getItemShapeFilled(series, item)) {
  714. g2.fill(shape);
  715. }
  716. else {
  717. g2.draw(shape);
  718. }
  719. }
  720. entityArea = shape;
  721. }
  722. if (getPlotImages()) {
  723. Image image = getImage(plot, series, item, transX1, transY1);
  724. if (image != null) {
  725. Point hotspot = getImageHotspot(
  726. plot, series, item, transX1, transY1, image
  727. );
  728. g2.drawImage(
  729. image, (int) (transX1 - hotspot.getX()),
  730. (int) (transY1 - hotspot.getY()), null
  731. );
  732. entityArea = new Rectangle2D.Double(
  733. transX1 - hotspot.getX(), transY1 - hotspot.getY(),
  734. image.getWidth(null), image.getHeight(null)
  735. );
  736. }
  737. }
  738. // draw the item label if there is one...
  739. if (isItemLabelVisible(series, item)) {
  740. drawItemLabel(
  741. g2, orientation, dataset, series, item, transX1, transY1,
  742. (y1 < 0.0)
  743. );
  744. }
  745. updateCrosshairValues(
  746. crosshairState, x1, y1, transX1, transY1, orientation
  747. );
  748. // add an entity for the item...
  749. if (entities != null) {
  750. addEntity(
  751. entities, entityArea, dataset, series, item, transX1, transY1
  752. );
  753. }
  754. }
  755. /**
  756. * Tests this renderer for equality with another object.
  757. *
  758. * @param obj the object (<code>null</code> permitted).
  759. *
  760. * @return A boolean.
  761. */
  762. public boolean equals(Object obj) {
  763. if (obj == this) {
  764. return true;
  765. }
  766. if (!(obj instanceof StandardXYItemRenderer)) {
  767. return false;
  768. }
  769. if (!super.equals(obj)) {
  770. return false;
  771. }
  772. StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
  773. if (this.plotShapes != that.plotShapes) {
  774. return false;
  775. }
  776. if (this.plotLines != that.plotLines) {
  777. return false;
  778. }
  779. if (this.plotImages != that.plotImages) {
  780. return false;
  781. }
  782. if (this.plotDiscontinuous != that.plotDiscontinuous) {
  783. return false;
  784. }
  785. if (this.gapThresholdType != that.gapThresholdType) {
  786. return false;
  787. }
  788. if (this.gapThreshold != that.gapThreshold) {
  789. return false;
  790. }
  791. if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
  792. return false;
  793. }
  794. return true;
  795. }
  796. /**
  797. * Returns a clone of the renderer.
  798. *
  799. * @return A clone.
  800. *
  801. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  802. */
  803. public Object clone() throws CloneNotSupportedException {
  804. return super.clone();
  805. }
  806. ////////////////////////////////////////////////////////////////////////////
  807. // PROTECTED METHODS
  808. // These provide the opportunity to subclass the standard renderer and
  809. // create custom effects.
  810. ////////////////////////////////////////////////////////////////////////////
  811. /**
  812. * Returns the image used to draw a single data item.
  813. *
  814. * @param plot the plot (can be used to obtain standard color information
  815. * etc).
  816. * @param series the series index.
  817. * @param item the item index.
  818. * @param x the x value of the item.
  819. * @param y the y value of the item.
  820. *
  821. * @return The image.
  822. */
  823. protected Image getImage(Plot plot, int series, int item,
  824. double x, double y) {
  825. // should this be added to the plot as well ?
  826. // return plot.getShape(series, item, x, y, scale);
  827. // or should this be left to the user - like this:
  828. return null;
  829. }
  830. /**
  831. * Returns the hotspot of the image used to draw a single data item.
  832. * The hotspot is the point relative to the top left of the image
  833. * that should indicate the data item. The default is the center of the
  834. * image.
  835. *
  836. * @param plot the plot (can be used to obtain standard color information
  837. * etc).
  838. * @param image the image (can be used to get size information about the
  839. * image)
  840. * @param series the series index
  841. * @param item the item index
  842. * @param x the x value of the item
  843. * @param y the y value of the item
  844. *
  845. * @return The hotspot used to draw the data item.
  846. */
  847. protected Point getImageHotspot(Plot plot, int series, int item,
  848. double x, double y, Image image) {
  849. int height = image.getHeight(null);
  850. int width = image.getWidth(null);
  851. return new Point(width / 2, height / 2);
  852. }
  853. }