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 under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * ---------------------------
  25. * XYLineAndShapeRenderer.java
  26. * ---------------------------
  27. * (C) Copyright 2004, 2005, by Object Refinery Limited.
  28. *
  29. * Original Author: David Gilbert (for Object Refinery Limited);
  30. * Contributor(s): -;
  31. *
  32. * $Id: XYLineAndShapeRenderer.java,v 1.14 2005/03/10 11:24:30 mungady Exp $
  33. *
  34. * Changes:
  35. * --------
  36. * 27-Jan-2004 : Version 1 (DG);
  37. * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste
  38. * overriding easier (DG);
  39. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  40. * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG);
  41. * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path
  42. * (necessary when using a dashed stroke with many data
  43. * items) (DG);
  44. * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG);
  45. * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  46. * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG);
  47. * 28-Jan-2005 : Added new constructor (DG);
  48. * 09-Mar-2005 : Added fillPaint settings (DG);
  49. *
  50. */
  51. package org.jfree.chart.renderer.xy;
  52. import java.awt.Graphics2D;
  53. import java.awt.Paint;
  54. import java.awt.Shape;
  55. import java.awt.Stroke;
  56. import java.awt.geom.GeneralPath;
  57. import java.awt.geom.Line2D;
  58. import java.awt.geom.Rectangle2D;
  59. import java.io.Serializable;
  60. import org.jfree.chart.LegendItem;
  61. import org.jfree.chart.axis.ValueAxis;
  62. import org.jfree.chart.entity.EntityCollection;
  63. import org.jfree.chart.event.RendererChangeEvent;
  64. import org.jfree.chart.plot.CrosshairState;
  65. import org.jfree.chart.plot.PlotOrientation;
  66. import org.jfree.chart.plot.PlotRenderingInfo;
  67. import org.jfree.chart.plot.XYPlot;
  68. import org.jfree.data.xy.XYDataset;
  69. import org.jfree.ui.RectangleEdge;
  70. import org.jfree.util.BooleanList;
  71. import org.jfree.util.BooleanUtilities;
  72. import org.jfree.util.ObjectUtilities;
  73. import org.jfree.util.PublicCloneable;
  74. import org.jfree.util.ShapeUtilities;
  75. /**
  76. * A renderer that can be used with the {@link XYPlot} class.
  77. */
  78. public class XYLineAndShapeRenderer extends AbstractXYItemRenderer
  79. implements XYItemRenderer,
  80. Cloneable,
  81. PublicCloneable,
  82. Serializable {
  83. /** A flag that controls whether or not lines are visible for ALL series. */
  84. private Boolean linesVisible;
  85. /**
  86. * A table of flags that control (per series) whether or not lines are
  87. * visible.
  88. */
  89. private BooleanList seriesLinesVisible;
  90. /** The default value returned by the getLinesVisible() method. */
  91. private boolean defaultLinesVisible;
  92. /**
  93. * A flag that controls whether or not shapes are visible for ALL series.
  94. */
  95. private Boolean shapesVisible;
  96. /**
  97. * A table of flags that control (per series) whether or not shapes are
  98. * visible.
  99. */
  100. private BooleanList seriesShapesVisible;
  101. /** The default value returned by the getShapeVisible() method. */
  102. private boolean defaultShapesVisible;
  103. /** A flag that controls whether or not shapes are filled for ALL series. */
  104. private Boolean shapesFilled;
  105. /**
  106. * A table of flags that control (per series) whether or not shapes are
  107. * filled.
  108. */
  109. private BooleanList seriesShapesFilled;
  110. /** The default value returned by the getShapeFilled() method. */
  111. private boolean defaultShapesFilled;
  112. /** A flag that controls whether outlines are drawn for filled shapes. */
  113. private boolean drawOutlines;
  114. /**
  115. * A flag that controls whether the fill paint is used for filling
  116. * shapes.
  117. */
  118. private boolean useFillPaint;
  119. /**
  120. * A flag that controls whether the outline paint is used for drawing shape
  121. * outlines.
  122. */
  123. private boolean useOutlinePaint;
  124. /**
  125. * A flag that controls whether or not each series is drawn as a single
  126. * path.
  127. */
  128. private boolean drawSeriesLineAsPath;
  129. /**
  130. * Creates a new renderer with both lines and shapes visible.
  131. */
  132. public XYLineAndShapeRenderer() {
  133. this(true, true);
  134. }
  135. /**
  136. * Creates a new renderer.
  137. *
  138. * @param lines lines visible?
  139. * @param shapes shapes visible?
  140. */
  141. public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
  142. this.linesVisible = null;
  143. this.seriesLinesVisible = new BooleanList();
  144. this.defaultLinesVisible = lines;
  145. this.shapesVisible = null;
  146. this.seriesShapesVisible = new BooleanList();
  147. this.defaultShapesVisible = shapes;
  148. this.shapesFilled = null;
  149. this.seriesShapesFilled = new BooleanList();
  150. this.defaultShapesFilled = true;
  151. this.drawOutlines = true;
  152. this.useFillPaint = false;
  153. this.useOutlinePaint = false; // use item paint for outlines, not
  154. // outline paint
  155. this.drawSeriesLineAsPath = false;
  156. }
  157. /**
  158. * Returns a flag that controls whether or not each series is drawn as a
  159. * single path.
  160. *
  161. * @return A boolean.
  162. */
  163. public boolean getDrawSeriesLineAsPath() {
  164. return this.drawSeriesLineAsPath;
  165. }
  166. /**
  167. * Sets the flag that controls whether or not each series is drawn as a
  168. * single path.
  169. *
  170. * @param flag the flag.
  171. */
  172. public void setDrawSeriesLineAsPath(boolean flag) {
  173. this.drawSeriesLineAsPath = flag;
  174. }
  175. /**
  176. * Returns the number of passes through the data that the renderer requires
  177. * in order to draw the chart. Most charts will require a single pass, but
  178. * some require two passes.
  179. *
  180. * @return The pass count.
  181. */
  182. public int getPassCount() {
  183. return 2;
  184. }
  185. // LINES VISIBLE
  186. /**
  187. * Returns the flag used to control whether or not the shape for an item is
  188. * visible.
  189. *
  190. * @param series the series index (zero-based).
  191. * @param item the item index (zero-based).
  192. *
  193. * @return A boolean.
  194. */
  195. public boolean getItemLineVisible(int series, int item) {
  196. Boolean flag = this.linesVisible;
  197. if (flag == null) {
  198. flag = getSeriesLinesVisible(series);
  199. }
  200. if (flag != null) {
  201. return flag.booleanValue();
  202. }
  203. else {
  204. return this.defaultLinesVisible;
  205. }
  206. }
  207. /**
  208. * Returns a flag that controls whether or not lines are drawn for ALL
  209. * series. If this flag is <code>null</code>, then the "per series"
  210. * settings will apply.
  211. *
  212. * @return A flag (possibly <code>null</code>).
  213. */
  214. public Boolean getLinesVisible() {
  215. return this.linesVisible;
  216. }
  217. /**
  218. * Sets a flag that controls whether or not lines are drawn between the
  219. * items in ALL series, and sends a {@link RendererChangeEvent} to all
  220. * registered listeners. You need to set this to <code>null</code> if you
  221. * want the "per series" settings to apply.
  222. *
  223. * @param visible the flag (<code>null</code> permitted).
  224. */
  225. public void setLinesVisible(Boolean visible) {
  226. this.linesVisible = visible;
  227. notifyListeners(new RendererChangeEvent(this));
  228. }
  229. /**
  230. * Sets a flag that controls whether or not lines are drawn between the
  231. * items in ALL series, and sends a {@link RendererChangeEvent} to all
  232. * registered listeners.
  233. *
  234. * @param visible the flag.
  235. */
  236. public void setLinesVisible(boolean visible) {
  237. setLinesVisible(BooleanUtilities.valueOf(visible));
  238. }
  239. /**
  240. * Returns the flag used to control whether or not the lines for a series
  241. * are visible.
  242. *
  243. * @param series the series index (zero-based).
  244. *
  245. * @return The flag (possibly <code>null</code>).
  246. */
  247. public Boolean getSeriesLinesVisible(int series) {
  248. return this.seriesLinesVisible.getBoolean(series);
  249. }
  250. /**
  251. * Sets the 'lines visible' flag for a series.
  252. *
  253. * @param series the series index (zero-based).
  254. * @param flag the flag (<code>null</code> permitted).
  255. */
  256. public void setSeriesLinesVisible(int series, Boolean flag) {
  257. this.seriesLinesVisible.setBoolean(series, flag);
  258. notifyListeners(new RendererChangeEvent(this));
  259. }
  260. /**
  261. * Sets the 'lines visible' flag for a series.
  262. *
  263. * @param series the series index (zero-based).
  264. * @param visible the flag.
  265. */
  266. public void setSeriesLinesVisible(int series, boolean visible) {
  267. setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
  268. }
  269. /**
  270. * Returns the default 'lines visible' attribute.
  271. *
  272. * @return The default flag.
  273. */
  274. public boolean getDefaultLinesVisible() {
  275. return this.defaultLinesVisible;
  276. }
  277. /**
  278. * Sets the default 'lines visible' flag.
  279. *
  280. * @param flag the flag.
  281. */
  282. public void setDefaultLinesVisible(boolean flag) {
  283. this.defaultLinesVisible = flag;
  284. notifyListeners(new RendererChangeEvent(this));
  285. }
  286. // SHAPES VISIBLE
  287. /**
  288. * Returns the flag used to control whether or not the shape for an item is
  289. * visible.
  290. * <p>
  291. * The default implementation passes control to the
  292. * <code>getSeriesShapesVisible</code> method. You can override this method
  293. * if you require different behaviour.
  294. *
  295. * @param series the series index (zero-based).
  296. * @param item the item index (zero-based).
  297. *
  298. * @return A boolean.
  299. */
  300. public boolean getItemShapeVisible(int series, int item) {
  301. Boolean flag = this.shapesVisible;
  302. if (flag == null) {
  303. flag = getSeriesShapesVisible(series);
  304. }
  305. if (flag != null) {
  306. return flag.booleanValue();
  307. }
  308. else {
  309. return this.defaultShapesVisible;
  310. }
  311. }
  312. /**
  313. * Returns the flag that controls whether the shapes are visible for the
  314. * items in ALL series.
  315. *
  316. * @return The flag (possibly <code>null</code>).
  317. */
  318. public Boolean getShapesVisible() {
  319. return this.shapesVisible;
  320. }
  321. /**
  322. * Sets the 'shapes visible' for ALL series and sends a
  323. * {@link RendererChangeEvent} to all registered listeners.
  324. *
  325. * @param visible the flag (<code>null</code> permitted).
  326. */
  327. public void setShapesVisible(Boolean visible) {
  328. this.shapesVisible = visible;
  329. notifyListeners(new RendererChangeEvent(this));
  330. }
  331. /**
  332. * Sets the 'shapes visible' for ALL series and sends a
  333. * {@link RendererChangeEvent} to all registered listeners.
  334. *
  335. * @param visible the flag.
  336. */
  337. public void setShapesVisible(boolean visible) {
  338. setShapesVisible(BooleanUtilities.valueOf(visible));
  339. }
  340. /**
  341. * Returns the flag used to control whether or not the shapes for a series
  342. * are visible.
  343. *
  344. * @param series the series index (zero-based).
  345. *
  346. * @return A boolean.
  347. */
  348. public Boolean getSeriesShapesVisible(int series) {
  349. return this.seriesShapesVisible.getBoolean(series);
  350. }
  351. /**
  352. * Sets the 'shapes visible' flag for a series and sends a
  353. * {@link RendererChangeEvent} to all registered listeners.
  354. *
  355. * @param series the series index (zero-based).
  356. * @param visible the flag.
  357. */
  358. public void setSeriesShapesVisible(int series, boolean visible) {
  359. setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
  360. }
  361. /**
  362. * Sets the 'shapes visible' flag for a series and sends a
  363. * {@link RendererChangeEvent} to all registered listeners.
  364. *
  365. * @param series the series index (zero-based).
  366. * @param flag the flag.
  367. */
  368. public void setSeriesShapesVisible(int series, Boolean flag) {
  369. this.seriesShapesVisible.setBoolean(series, flag);
  370. notifyListeners(new RendererChangeEvent(this));
  371. }
  372. /**
  373. * Returns the default 'shape visible' attribute.
  374. *
  375. * @return The default flag.
  376. */
  377. public boolean getDefaultShapesVisible() {
  378. return this.defaultShapesVisible;
  379. }
  380. /**
  381. * Sets the default 'shapes visible' flag.
  382. *
  383. * @param flag the flag.
  384. */
  385. public void setDefaultShapesVisible(boolean flag) {
  386. this.defaultShapesVisible = flag;
  387. notifyListeners(new RendererChangeEvent(this));
  388. }
  389. // SHAPES FILLED
  390. /**
  391. * Returns the flag used to control whether or not the shape for an item
  392. * is filled.
  393. * <p>
  394. * The default implementation passes control to the
  395. * <code>getSeriesShapesFilled</code> method. You can override this method
  396. * if you require different behaviour.
  397. *
  398. * @param series the series index (zero-based).
  399. * @param item the item index (zero-based).
  400. *
  401. * @return A boolean.
  402. */
  403. public boolean getItemShapeFilled(int series, int item) {
  404. Boolean flag = this.shapesFilled;
  405. if (flag == null) {
  406. flag = getSeriesShapesFilled(series);
  407. }
  408. if (flag != null) {
  409. return flag.booleanValue();
  410. }
  411. else {
  412. return this.defaultShapesFilled;
  413. }
  414. }
  415. /**
  416. * Sets the 'shapes filled' for ALL series and sends a
  417. * {@link RendererChangeEvent} to all registered listeners.
  418. *
  419. * @param filled the flag.
  420. */
  421. public void setShapesFilled(boolean filled) {
  422. setShapesFilled(BooleanUtilities.valueOf(filled));
  423. }
  424. /**
  425. * Sets the 'shapes filled' for ALL series and sends a
  426. * {@link RendererChangeEvent} to all registered listeners.
  427. *
  428. * @param filled the flag (<code>null</code> permitted).
  429. */
  430. public void setShapesFilled(Boolean filled) {
  431. this.shapesFilled = filled;
  432. notifyListeners(new RendererChangeEvent(this));
  433. }
  434. /**
  435. * Returns the flag used to control whether or not the shapes for a series
  436. * are filled.
  437. *
  438. * @param series the series index (zero-based).
  439. *
  440. * @return A boolean.
  441. */
  442. public Boolean getSeriesShapesFilled(int series) {
  443. return this.seriesShapesFilled.getBoolean(series);
  444. }
  445. /**
  446. * Sets the 'shapes filled' flag for a series.
  447. *
  448. * @param series the series index (zero-based).
  449. * @param flag the flag.
  450. */
  451. public void setSeriesShapesFilled(int series, boolean flag) {
  452. setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag));
  453. }
  454. /**
  455. * Sets the 'shapes filled' flag for a series.
  456. *
  457. * @param series the series index (zero-based).
  458. * @param flag the flag.
  459. */
  460. public void setSeriesShapesFilled(int series, Boolean flag) {
  461. this.seriesShapesFilled.setBoolean(series, flag);
  462. notifyListeners(new RendererChangeEvent(this));
  463. }
  464. /**
  465. * Returns the default 'shape filled' attribute.
  466. *
  467. * @return The default flag.
  468. */
  469. public boolean getDefaultShapesFilled() {
  470. return this.defaultShapesFilled;
  471. }
  472. /**
  473. * Sets the default 'shapes filled' flag.
  474. *
  475. * @param flag the flag.
  476. */
  477. public void setDefaultShapesFilled(boolean flag) {
  478. this.defaultShapesFilled = flag;
  479. notifyListeners(new RendererChangeEvent(this));
  480. }
  481. /**
  482. * Returns <code>true</code> if outlines should be drawn for filled
  483. * shapes, and <code>false</code> otherwise.
  484. *
  485. * @return A boolean.
  486. */
  487. public boolean getDrawOutlines() {
  488. return this.drawOutlines;
  489. }
  490. /**
  491. * Sets the flag that controls whether outlines are drawn for filled
  492. * shapes, and sends a {@link RendererChangeEvent} to all registered
  493. * listeners.
  494. * <P>
  495. * In some cases, shapes look better if they do NOT have an outline, but
  496. * this flag allows you to set your own preference.
  497. *
  498. * @param flag the flag.
  499. */
  500. public void setDrawOutlines(boolean flag) {
  501. this.drawOutlines = flag;
  502. notifyListeners(new RendererChangeEvent(this));
  503. }
  504. /**
  505. * Returns <code>true</code> if the renderer should use the fill paint
  506. * setting to fill shapes, and <code>false</code> if it should just
  507. * use the regular paint.
  508. *
  509. * @return A boolean.
  510. */
  511. public boolean getUseFillPaint() {
  512. return this.useFillPaint;
  513. }
  514. /**
  515. * Sets the flag that controls whether the fill paint is used to fill
  516. * shapes, and sends a {@link RendererChangeEvent} to all
  517. * registered listeners.
  518. *
  519. * @param flag the flag.
  520. */
  521. public void setUseFillPaint(boolean flag) {
  522. this.useFillPaint = flag;
  523. notifyListeners(new RendererChangeEvent(this));
  524. }
  525. /**
  526. * Returns <code>true</code> if the renderer should use the outline paint
  527. * setting to draw shape outlines, and <code>false</code> if it should just
  528. * use the regular paint.
  529. *
  530. * @return A boolean.
  531. */
  532. public boolean getUseOutlinePaint() {
  533. return this.useOutlinePaint;
  534. }
  535. /**
  536. * Sets the flag that controls whether the outline paint is used to draw
  537. * shape outlines, and sends a {@link RendererChangeEvent} to all
  538. * registered listeners.
  539. *
  540. * @param flag the flag.
  541. */
  542. public void setUseOutlinePaint(boolean flag) {
  543. this.useOutlinePaint = flag;
  544. notifyListeners(new RendererChangeEvent(this));
  545. }
  546. /**
  547. * Records the state for the renderer. This is used to preserve state
  548. * information between calls to the drawItem() method for a single chart
  549. * drawing.
  550. */
  551. public static class State extends XYItemRendererState {
  552. /** The path for the current series. */
  553. public GeneralPath seriesPath;
  554. /** A flag that indicates if the last (x, y) point was 'good' (non-null). */
  555. private boolean lastPointGood;
  556. /**
  557. * Creates a new state instance.
  558. *
  559. * @param info the plot rendering info.
  560. */
  561. public State(PlotRenderingInfo info) {
  562. super(info);
  563. }
  564. /**
  565. * Returns a flag that indicates if the last point drawn (in the current series)
  566. * was 'good' (non-null).
  567. *
  568. * @return A boolean.
  569. */
  570. public boolean isLastPointGood() {
  571. return this.lastPointGood;
  572. }
  573. /**
  574. * Sets a flag that indicates if the last point drawn (in the current series)
  575. * was 'good' (non-null).
  576. *
  577. * @param good the flag.
  578. */
  579. public void setLastPointGood(boolean good) {
  580. this.lastPointGood = good;
  581. }
  582. }
  583. /**
  584. * Initialises the renderer.
  585. * <P>
  586. * This method will be called before the first item is rendered, giving the
  587. * renderer an opportunity to initialise any state information it wants to
  588. * maintain. The renderer can do nothing if it chooses.
  589. *
  590. * @param g2 the graphics device.
  591. * @param dataArea the area inside the axes.
  592. * @param plot the plot.
  593. * @param data the data.
  594. * @param info an optional info collection object to return data back to
  595. * the caller.
  596. *
  597. * @return The renderer state.
  598. */
  599. public XYItemRendererState initialise(Graphics2D g2,
  600. Rectangle2D dataArea,
  601. XYPlot plot,
  602. XYDataset data,
  603. PlotRenderingInfo info) {
  604. State state = new State(info);
  605. state.seriesPath = new GeneralPath();
  606. return state;
  607. }
  608. /**
  609. * Draws the visual representation of a single data item.
  610. *
  611. * @param g2 the graphics device.
  612. * @param state the renderer state.
  613. * @param dataArea the area within which the data is being drawn.
  614. * @param info collects information about the drawing.
  615. * @param plot the plot (can be used to obtain standard color
  616. * information etc).
  617. * @param domainAxis the domain axis.
  618. * @param rangeAxis the range axis.
  619. * @param dataset the dataset.
  620. * @param series the series index (zero-based).
  621. * @param item the item index (zero-based).
  622. * @param crosshairState crosshair information for the plot
  623. * (<code>null</code> permitted).
  624. * @param pass the pass index.
  625. */
  626. public void drawItem(Graphics2D g2,
  627. XYItemRendererState state,
  628. Rectangle2D dataArea,
  629. PlotRenderingInfo info,
  630. XYPlot plot,
  631. ValueAxis domainAxis,
  632. ValueAxis rangeAxis,
  633. XYDataset dataset,
  634. int series,
  635. int item,
  636. CrosshairState crosshairState,
  637. int pass) {
  638. // do nothing if item is not visible
  639. if (!getItemVisible(series, item)) {
  640. return;
  641. }
  642. // first pass draws the background (lines, for instance)
  643. if (isLinePass(pass)) {
  644. if (item == 0) {
  645. if (this.drawSeriesLineAsPath) {
  646. State s = (State) state;
  647. s.seriesPath.reset();
  648. s.lastPointGood = false;
  649. }
  650. }
  651. if (getItemLineVisible(series, item)) {
  652. if (this.drawSeriesLineAsPath) {
  653. drawPrimaryLineAsPath(
  654. state, g2, plot, dataset, pass, series, item,
  655. domainAxis, rangeAxis, dataArea
  656. );
  657. }
  658. else {
  659. drawPrimaryLine(
  660. state, g2, plot, dataset, pass, series, item,
  661. domainAxis, rangeAxis, dataArea
  662. );
  663. }
  664. }
  665. }
  666. // second pass adds shapes where the items are ..
  667. else if (isItemPass(pass)) {
  668. // setup for collecting optional entity info...
  669. EntityCollection entities = null;
  670. if (info != null) {
  671. entities = info.getOwner().getEntityCollection();
  672. }
  673. drawSecondaryPass(
  674. g2, plot, dataset, pass, series, item, domainAxis, dataArea,
  675. rangeAxis, crosshairState, entities
  676. );
  677. }
  678. }
  679. /**
  680. * Returns <code>true</code> if the specified pass is the one for drawing
  681. * lines.
  682. *
  683. * @param pass the pass.
  684. *
  685. * @return A boolean.
  686. */
  687. protected boolean isLinePass(int pass) {
  688. return pass == 0;
  689. }
  690. /**
  691. * Returns <code>true</code> if the specified pass is the one for drawing
  692. * items.
  693. *
  694. * @param pass the pass.
  695. *
  696. * @return A boolean.
  697. */
  698. protected boolean isItemPass(int pass) {
  699. return pass == 1;
  700. }
  701. /**
  702. * Draws the item (first pass). This method draws the lines
  703. * connecting the items.
  704. *
  705. * @param g2 the graphics device.
  706. * @param state the renderer state.
  707. * @param dataArea the area within which the data is being drawn.
  708. * @param plot the plot (can be used to obtain standard color
  709. * information etc).
  710. * @param domainAxis the domain axis.
  711. * @param rangeAxis the range axis.
  712. * @param dataset the dataset.
  713. * @param pass the pass.
  714. * @param series the series index (zero-based).
  715. * @param item the item index (zero-based).
  716. */
  717. protected void drawPrimaryLine(XYItemRendererState state,
  718. Graphics2D g2,
  719. XYPlot plot,
  720. XYDataset dataset,
  721. int pass,
  722. int series,
  723. int item,
  724. ValueAxis domainAxis,
  725. ValueAxis rangeAxis,
  726. Rectangle2D dataArea) {
  727. if (item == 0) {
  728. return;
  729. }
  730. // get the data point...
  731. double x1 = dataset.getXValue(series, item);
  732. double y1 = dataset.getYValue(series, item);
  733. if (Double.isNaN(y1) || Double.isNaN(x1)) {
  734. return;
  735. }
  736. double x0 = dataset.getXValue(series, item - 1);
  737. double y0 = dataset.getYValue(series, item - 1);
  738. if (Double.isNaN(y0) || Double.isNaN(x0)) {
  739. return;
  740. }
  741. RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
  742. RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
  743. double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
  744. double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
  745. double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
  746. double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
  747. // only draw if we have good values
  748. if (Double.isNaN(transX0) || Double.isNaN(transY0)
  749. || Double.isNaN(transX1) || Double.isNaN(transY1)) {
  750. return;
  751. }
  752. PlotOrientation orientation = plot.getOrientation();
  753. if (orientation == PlotOrientation.HORIZONTAL) {
  754. state.workingLine.setLine(transY0, transX0, transY1, transX1);
  755. }
  756. else if (orientation == PlotOrientation.VERTICAL) {
  757. state.workingLine.setLine(transX0, transY0, transX1, transY1);
  758. }
  759. if (state.workingLine.intersects(dataArea)) {
  760. drawFirstPassShape(g2, pass, series, item, state.workingLine);
  761. }
  762. }
  763. /**
  764. * Draws the first pass shape.
  765. *
  766. * @param g2 the graphics device.
  767. * @param pass the pass.
  768. * @param series the series index.
  769. * @param item the item index.
  770. * @param shape the shape.
  771. */
  772. protected void drawFirstPassShape(Graphics2D g2,
  773. int pass,
  774. int series,
  775. int item,
  776. Shape shape) {
  777. g2.setStroke(getItemStroke(series, item));
  778. g2.setPaint(getItemPaint(series, item));
  779. g2.draw(shape);
  780. }
  781. /**
  782. * Draws the item (first pass). This method draws the lines
  783. * connecting the items. Instead of drawing separate lines,
  784. * a GeneralPath is constructed and drawn at the end of
  785. * the series painting.
  786. *
  787. * @param g2 the graphics device.
  788. * @param state the renderer state.
  789. * @param plot the plot (can be used to obtain standard color information
  790. * etc).
  791. * @param dataset the dataset.
  792. * @param pass the pass.
  793. * @param series the series index (zero-based).
  794. * @param item the item index (zero-based).
  795. * @param domainAxis the domain axis.
  796. * @param rangeAxis the range axis.
  797. * @param dataArea the area within which the data is being drawn.
  798. */
  799. protected void drawPrimaryLineAsPath(XYItemRendererState state,
  800. Graphics2D g2, XYPlot plot,
  801. XYDataset dataset,
  802. int pass,
  803. int series,
  804. int item,
  805. ValueAxis domainAxis,
  806. ValueAxis rangeAxis,
  807. Rectangle2D dataArea) {
  808. RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
  809. RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
  810. // get the data point...
  811. double x1 = dataset.getXValue(series, item);
  812. double y1 = dataset.getYValue(series, item);
  813. double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
  814. double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
  815. State s = (State) state;
  816. // update path to reflect latest point
  817. if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
  818. float x = (float) transX1;
  819. float y = (float) transY1;
  820. PlotOrientation orientation = plot.getOrientation();
  821. if (orientation == PlotOrientation.HORIZONTAL) {
  822. x = (float) transY1;
  823. y = (float) transX1;
  824. }
  825. if (s.isLastPointGood()) {
  826. s.seriesPath.lineTo(x, y);
  827. }
  828. else {
  829. s.seriesPath.moveTo(x, y);
  830. }
  831. s.setLastPointGood(true);
  832. }
  833. else {
  834. s.setLastPointGood(false);
  835. }
  836. // if this is the last item, draw the path ...
  837. if (item == dataset.getItemCount(series) - 1) {
  838. // draw path
  839. drawFirstPassShape(g2, pass, series, item, s.seriesPath);
  840. }
  841. }
  842. /**
  843. * Draws the item shapes and ChartEntities (second pass). This method draws
  844. * the shapes which mark the item positions. If entities are given, the
  845. * entities are drawn as well.
  846. *
  847. * @param g2 the graphics device.
  848. * @param dataArea the area within which the data is being drawn.
  849. * @param plot the plot (can be used to obtain standard color
  850. * information etc).
  851. * @param domainAxis the domain axis.
  852. * @param rangeAxis the range axis.
  853. * @param dataset the dataset.
  854. * @param pass the pass.
  855. * @param series the series index (zero-based).
  856. * @param item the item index (zero-based).
  857. * @param crosshairState the crosshair state.
  858. * @param entities the entity collection.
  859. */
  860. protected void drawSecondaryPass(Graphics2D g2, XYPlot plot,
  861. XYDataset dataset,
  862. int pass, int series, int item,
  863. ValueAxis domainAxis,
  864. Rectangle2D dataArea,
  865. ValueAxis rangeAxis,
  866. CrosshairState crosshairState,
  867. EntityCollection entities) {
  868. Shape entityArea = null;
  869. // get the data point...
  870. double x1 = dataset.getXValue(series, item);
  871. double y1 = dataset.getYValue(series, item);
  872. if (Double.isNaN(y1) || Double.isNaN(x1)) {
  873. return;
  874. }
  875. RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
  876. RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
  877. double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
  878. double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
  879. if (getItemShapeVisible(series, item)) {
  880. Shape shape = getItemShape(series, item);
  881. PlotOrientation orientation = plot.getOrientation();
  882. if (orientation == PlotOrientation.HORIZONTAL) {
  883. shape = ShapeUtilities.createTranslatedShape(
  884. shape, transY1, transX1
  885. );
  886. }
  887. else if (orientation == PlotOrientation.VERTICAL) {
  888. shape = ShapeUtilities.createTranslatedShape(
  889. shape, transX1, transY1
  890. );
  891. }
  892. entityArea = shape;
  893. if (shape.intersects(dataArea)) {
  894. if (getItemShapeFilled(series, item)) {
  895. if (this.useFillPaint) {
  896. g2.setPaint(getItemFillPaint(series, item));
  897. }
  898. else {
  899. g2.setPaint(getItemPaint(series, item));
  900. }
  901. g2.fill(shape);
  902. if (getDrawOutlines()) {
  903. if (getUseOutlinePaint()) {
  904. g2.setPaint(getItemOutlinePaint(series, item));
  905. }
  906. else {
  907. g2.setPaint(getItemPaint(series, item));
  908. }
  909. g2.draw(shape);
  910. }
  911. }
  912. else {
  913. if (this.drawOutlines) {
  914. if (getUseOutlinePaint()) {
  915. g2.setPaint(getItemOutlinePaint(series, item));
  916. }
  917. else {
  918. g2.setPaint(getItemPaint(series, item));
  919. }
  920. g2.draw(shape);
  921. }
  922. }
  923. }
  924. }
  925. updateCrosshairValues(
  926. crosshairState, x1, y1, transX1, transY1, plot.getOrientation()
  927. );
  928. // add an entity for the item...
  929. if (entities != null) {
  930. addEntity(
  931. entities, entityArea, dataset, series, item, transX1, transY1
  932. );
  933. }
  934. }
  935. /**
  936. * Returns a legend item for the specified series.
  937. *
  938. * @param datasetIndex the dataset index (zero-based).
  939. * @param series the series index (zero-based).
  940. *
  941. * @return A legend item for the series.
  942. */
  943. public LegendItem getLegendItem(int datasetIndex, int series) {
  944. LegendItem result = null;
  945. XYPlot plot = getPlot();
  946. if (plot != null) {
  947. XYDataset dataset = plot.getDataset(datasetIndex);
  948. if (dataset != null) {
  949. if (getItemVisible(series, 0)) {
  950. String label = getLegendItemLabelGenerator().generateLabel(
  951. dataset, series
  952. );
  953. String description = label;
  954. boolean shapeIsVisible = getItemShapeVisible(series, 0);
  955. Shape shape = getSeriesShape(series);
  956. boolean shapeIsFilled = getItemShapeFilled(series, 0);
  957. Paint fillPaint = (this.useFillPaint
  958. ? getSeriesFillPaint(series) : getSeriesPaint(series));
  959. boolean shapeOutlineVisible = this.drawOutlines;
  960. Paint outlinePaint = (this.useOutlinePaint
  961. ? getSeriesOutlinePaint(series)
  962. : getSeriesPaint(series));
  963. Stroke outlineStroke = getSeriesOutlineStroke(series);
  964. boolean lineVisible = getItemLineVisible(series, 0);
  965. Stroke lineStroke = getSeriesStroke(series);
  966. Paint linePaint = getSeriesPaint(series);
  967. result = new LegendItem(
  968. label, description,
  969. shapeIsVisible, shape, shapeIsFilled, fillPaint,
  970. shapeOutlineVisible, outlinePaint, outlineStroke,
  971. lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
  972. lineStroke, linePaint
  973. );
  974. }
  975. }
  976. }
  977. return result;
  978. }
  979. /**
  980. * Returns a clone of the renderer.
  981. *
  982. * @return A clone.
  983. *
  984. * @throws CloneNotSupportedException if the clone cannot be created.
  985. */
  986. public Object clone() throws CloneNotSupportedException {
  987. return super.clone();
  988. }
  989. /**
  990. * Tests this renderer for equality with another object.
  991. *
  992. * @param obj the object.
  993. *
  994. * @return <code>true</code> or <code>false</code>.
  995. */
  996. public boolean equals(Object obj) {
  997. if (obj == this) {
  998. return true;
  999. }
  1000. if (!(obj instanceof XYLineAndShapeRenderer)) {
  1001. return false;
  1002. }
  1003. if (!super.equals(obj)) {
  1004. return false;
  1005. }
  1006. XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
  1007. if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
  1008. return false;
  1009. }
  1010. if (!ObjectUtilities.equal(
  1011. this.seriesLinesVisible, that.seriesLinesVisible)
  1012. ) {
  1013. return false;
  1014. }
  1015. if (this.defaultLinesVisible != that.defaultLinesVisible) {
  1016. return false;
  1017. }
  1018. if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
  1019. return false;
  1020. }
  1021. if (!ObjectUtilities.equal(
  1022. this.seriesShapesVisible, that.seriesShapesVisible)
  1023. ) {
  1024. return false;
  1025. }
  1026. if (this.defaultShapesVisible != that.defaultShapesVisible) {
  1027. return false;
  1028. }
  1029. if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
  1030. return false;
  1031. }
  1032. if (!ObjectUtilities.equal(
  1033. this.seriesShapesFilled, that.seriesShapesFilled)
  1034. ) {
  1035. return false;
  1036. }
  1037. if (this.defaultShapesFilled != that.defaultShapesFilled) {
  1038. return false;
  1039. }
  1040. if (this.drawOutlines != that.drawOutlines) {
  1041. return false;
  1042. }
  1043. if (this.useOutlinePaint != that.useOutlinePaint) {
  1044. return false;
  1045. }
  1046. return true;
  1047. }
  1048. }