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. * AbstractCategoryItemRenderer.java
  28. * ---------------------------------
  29. * (C) Copyright 2002-2005, by Object Refinery Limited.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): Richard Atkinson;
  33. *
  34. * $Id: AbstractCategoryItemRenderer.java,v 1.12 2005/03/08 15:53:25 mungady Exp $
  35. *
  36. * Changes:
  37. * --------
  38. * 29-May-2002 : Version 1 (DG);
  39. * 06-Jun-2002 : Added accessor methods for the tool tip generator (DG);
  40. * 11-Jun-2002 : Made constructors protected (DG);
  41. * 26-Jun-2002 : Added axis to initialise method (DG);
  42. * 05-Aug-2002 : Added urlGenerator member variable plus accessors (RA);
  43. * 22-Aug-2002 : Added categoriesPaint attribute, based on code submitted by
  44. * Janet Banks. This can be used when there is only one series,
  45. * and you want each category item to have a different color (DG);
  46. * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  47. * 29-Oct-2002 : Fixed bug where background image for plot was not being
  48. * drawn (DG);
  49. * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
  50. * 26-Nov 2002 : Replaced the isStacked() method with getRangeType() (DG);
  51. * 09-Jan-2003 : Renamed grid-line methods (DG);
  52. * 17-Jan-2003 : Moved plot classes into separate package (DG);
  53. * 25-Mar-2003 : Implemented Serializable (DG);
  54. * 12-May-2003 : Modified to take into account the plot orientation (DG);
  55. * 12-Aug-2003 : Very minor javadoc corrections (DB)
  56. * 13-Aug-2003 : Implemented Cloneable (DG);
  57. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  58. * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG);
  59. * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
  60. * 11-Feb-2004 : Modified labelling for markers (DG);
  61. * 12-Feb-2004 : Updated clone() method (DG);
  62. * 15-Apr-2004 : Created a new CategoryToolTipGenerator interface (DG);
  63. * 05-May-2004 : Fixed bug (948310) where interval markers extend outside axis
  64. * range (DG);
  65. * 14-Jun-2004 : Fixed bug in drawRangeMarker() method - now uses 'paint' and
  66. * 'stroke' rather than 'outlinePaint' and 'outlineStroke' (DG);
  67. * 15-Jun-2004 : Interval markers can now use GradientPaint (DG);
  68. * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
  69. * --> TextUtilities (DG);
  70. * 01-Oct-2004 : Fixed bug 1029697, problem with label alignment in
  71. * drawRangeMarker() method (DG);
  72. * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG);
  73. * 21-Jan-2005 : Modified return type of calculateRangeMarkerTextAnchorPoint()
  74. * method (DG);
  75. * 08-Mar-2005 : Fixed positioning of marker labels (DG);
  76. *
  77. */
  78. package org.jfree.chart.renderer.category;
  79. import java.awt.Font;
  80. import java.awt.GradientPaint;
  81. import java.awt.Graphics2D;
  82. import java.awt.Paint;
  83. import java.awt.Shape;
  84. import java.awt.Stroke;
  85. import java.awt.geom.Line2D;
  86. import java.awt.geom.Point2D;
  87. import java.awt.geom.Rectangle2D;
  88. import java.io.Serializable;
  89. import org.jfree.chart.LegendItem;
  90. import org.jfree.chart.LegendItemCollection;
  91. import org.jfree.chart.axis.CategoryAxis;
  92. import org.jfree.chart.axis.ValueAxis;
  93. import org.jfree.chart.event.RendererChangeEvent;
  94. import org.jfree.chart.labels.CategoryLabelGenerator;
  95. import org.jfree.chart.labels.CategoryToolTipGenerator;
  96. import org.jfree.chart.labels.ItemLabelPosition;
  97. import org.jfree.chart.plot.CategoryPlot;
  98. import org.jfree.chart.plot.DrawingSupplier;
  99. import org.jfree.chart.plot.IntervalMarker;
  100. import org.jfree.chart.plot.Marker;
  101. import org.jfree.chart.plot.PlotOrientation;
  102. import org.jfree.chart.plot.PlotRenderingInfo;
  103. import org.jfree.chart.plot.ValueMarker;
  104. import org.jfree.chart.renderer.AbstractRenderer;
  105. import org.jfree.chart.urls.CategoryURLGenerator;
  106. import org.jfree.data.Range;
  107. import org.jfree.data.category.CategoryDataset;
  108. import org.jfree.data.general.DatasetUtilities;
  109. import org.jfree.text.TextUtilities;
  110. import org.jfree.ui.GradientPaintTransformer;
  111. import org.jfree.ui.LengthAdjustmentType;
  112. import org.jfree.ui.RectangleAnchor;
  113. import org.jfree.ui.RectangleInsets;
  114. import org.jfree.util.ObjectList;
  115. import org.jfree.util.ObjectUtilities;
  116. import org.jfree.util.PublicCloneable;
  117. /**
  118. * An abstract base class that you can use to implement a new
  119. * {@link CategoryItemRenderer}. When you create a new
  120. * {@link CategoryItemRenderer} you are not required to extend this class,
  121. * but it makes the job easier.
  122. */
  123. public abstract class AbstractCategoryItemRenderer extends AbstractRenderer
  124. implements CategoryItemRenderer, Cloneable, PublicCloneable, Serializable {
  125. /** The plot that the renderer is assigned to. */
  126. private CategoryPlot plot;
  127. /** The label generator for ALL series. */
  128. private CategoryLabelGenerator labelGenerator;
  129. /** A list of item label generators (one per series). */
  130. private ObjectList labelGeneratorList;
  131. /** The base item label generator. */
  132. private CategoryLabelGenerator baseLabelGenerator;
  133. /** The tool tip generator for ALL series. */
  134. private CategoryToolTipGenerator toolTipGenerator;
  135. /** A list of tool tip generators (one per series). */
  136. private ObjectList toolTipGeneratorList;
  137. /** The base tool tip generator. */
  138. private CategoryToolTipGenerator baseToolTipGenerator;
  139. /** The URL generator. */
  140. private CategoryURLGenerator itemURLGenerator;
  141. /** A list of item label generators (one per series). */
  142. private ObjectList itemURLGeneratorList;
  143. /** The base item label generator. */
  144. private CategoryURLGenerator baseItemURLGenerator;
  145. /** The number of rows in the dataset (temporary record). */
  146. private transient int rowCount;
  147. /** The number of columns in the dataset (temporary record). */
  148. private transient int columnCount;
  149. /**
  150. * Creates a new renderer with no tool tip generator and no URL generator.
  151. * The defaults (no tool tip or URL generators) have been chosen to
  152. * minimise the processing required to generate a default chart. If you
  153. * require tool tips or URLs, then you can easily add the required
  154. * generators.
  155. */
  156. protected AbstractCategoryItemRenderer() {
  157. this.labelGenerator = null;
  158. this.labelGeneratorList = new ObjectList();
  159. this.toolTipGenerator = null;
  160. this.toolTipGeneratorList = new ObjectList();
  161. this.itemURLGenerator = null;
  162. this.itemURLGeneratorList = new ObjectList();
  163. }
  164. /**
  165. * Returns the number of passes through the dataset required by the
  166. * renderer. This method returns <code>1</code>, subclasses should
  167. * override if they need more passes.
  168. *
  169. * @return The pass count.
  170. */
  171. public int getPassCount() {
  172. return 1;
  173. }
  174. /**
  175. * Returns the plot that the renderer has been assigned to (where
  176. * <code>null</code> indicates that the renderer is not currently assigned
  177. * to a plot).
  178. *
  179. * @return The plot (possibly <code>null</code>).
  180. */
  181. public CategoryPlot getPlot() {
  182. return this.plot;
  183. }
  184. /**
  185. * Sets the plot that the renderer has been assigned to. This method is
  186. * usually called by the {@link CategoryPlot}, in normal usage you
  187. * shouldn't need to call this method directly.
  188. *
  189. * @param plot the plot (<code>null</code> not permitted).
  190. */
  191. public void setPlot(CategoryPlot plot) {
  192. if (plot == null) {
  193. throw new IllegalArgumentException("Null 'plot' argument.");
  194. }
  195. this.plot = plot;
  196. }
  197. // LABEL GENERATOR
  198. /**
  199. * Returns the label generator for a data item. This implementation simply
  200. * passes control to the {@link #getSeriesLabelGenerator(int)} method.
  201. * If, for some reason, you want a different generator for individual
  202. * items, you can override this method.
  203. *
  204. * @param row the row index (zero based).
  205. * @param column the column index (zero based).
  206. *
  207. * @return the generator (possibly <code>null</code>).
  208. */
  209. public CategoryLabelGenerator getLabelGenerator(int row, int column) {
  210. return getSeriesLabelGenerator(row);
  211. }
  212. /**
  213. * Returns the label generator for a series.
  214. *
  215. * @param series the series index (zero based).
  216. *
  217. * @return the generator (possibly <code>null</code>).
  218. */
  219. public CategoryLabelGenerator getSeriesLabelGenerator(int series) {
  220. // return the generator for ALL series, if there is one...
  221. if (this.labelGenerator != null) {
  222. return this.labelGenerator;
  223. }
  224. // otherwise look up the generator table
  225. CategoryLabelGenerator generator
  226. = (CategoryLabelGenerator) this.labelGeneratorList.get(series);
  227. if (generator == null) {
  228. generator = this.baseLabelGenerator;
  229. }
  230. return generator;
  231. }
  232. /**
  233. * Sets the label generator for ALL series and sends a
  234. * {@link RendererChangeEvent} to all registered listeners.
  235. *
  236. * @param generator the generator (<code>null</code> permitted).
  237. */
  238. public void setLabelGenerator(CategoryLabelGenerator generator) {
  239. this.labelGenerator = generator;
  240. notifyListeners(new RendererChangeEvent(this));
  241. }
  242. /**
  243. * Sets the label generator for a series and sends a
  244. * {@link RendererChangeEvent} to all registered listeners.
  245. *
  246. * @param series the series index (zero based).
  247. * @param generator the generator (<code>null</code> permitted).
  248. */
  249. public void setSeriesLabelGenerator(int series,
  250. CategoryLabelGenerator generator) {
  251. this.labelGeneratorList.set(series, generator);
  252. notifyListeners(new RendererChangeEvent(this));
  253. }
  254. /**
  255. * Returns the base label generator.
  256. *
  257. * @return The generator (possibly <code>null</code>).
  258. */
  259. public CategoryLabelGenerator getBaseLabelGenerator() {
  260. return this.baseLabelGenerator;
  261. }
  262. /**
  263. * Sets the base label generator and sends a {@link RendererChangeEvent}
  264. * to all registered listeners.
  265. *
  266. * @param generator the generator (<code>null</code> permitted).
  267. */
  268. public void setBaseLabelGenerator(CategoryLabelGenerator generator) {
  269. this.baseLabelGenerator = generator;
  270. notifyListeners(new RendererChangeEvent(this));
  271. }
  272. // TOOL TIP GENERATOR
  273. /**
  274. * Returns the tool tip generator that should be used for the specified
  275. * item. This method looks up the generator using the "three-layer"
  276. * approach outlined in the general description of this interface. You
  277. * can override this method if you want to return a different generator per
  278. * item.
  279. *
  280. * @param row the row index (zero-based).
  281. * @param column the column index (zero-based).
  282. *
  283. * @return The generator (possibly <code>null</code>).
  284. */
  285. public CategoryToolTipGenerator getToolTipGenerator(int row, int column) {
  286. CategoryToolTipGenerator result = null;
  287. if (this.toolTipGenerator != null) {
  288. result = this.toolTipGenerator;
  289. }
  290. else {
  291. result = getSeriesToolTipGenerator(row);
  292. if (result == null) {
  293. result = this.baseToolTipGenerator;
  294. }
  295. }
  296. return result;
  297. }
  298. /**
  299. * Returns the tool tip generator that will be used for ALL items in the
  300. * dataset (the "layer 0" generator).
  301. *
  302. * @return A tool tip generator (possibly <code>null</code>).
  303. */
  304. public CategoryToolTipGenerator getToolTipGenerator() {
  305. return this.toolTipGenerator;
  306. }
  307. /**
  308. * Sets the tool tip generator for ALL series and sends a
  309. * {@link org.jfree.chart.event.RendererChangeEvent} to all registered
  310. * listeners.
  311. *
  312. * @param generator the generator (<code>null</code> permitted).
  313. */
  314. public void setToolTipGenerator(CategoryToolTipGenerator generator) {
  315. this.toolTipGenerator = generator;
  316. notifyListeners(new RendererChangeEvent(this));
  317. }
  318. /**
  319. * Returns the tool tip generator for the specified series (a "layer 1"
  320. * generator).
  321. *
  322. * @param series the series index (zero-based).
  323. *
  324. * @return The tool tip generator (possibly <code>null</code>).
  325. */
  326. public CategoryToolTipGenerator getSeriesToolTipGenerator(int series) {
  327. return (CategoryToolTipGenerator) this.toolTipGeneratorList.get(series);
  328. }
  329. /**
  330. * Sets the tool tip generator for a series and sends a
  331. * {@link org.jfree.chart.event.RendererChangeEvent} to all registered
  332. * listeners.
  333. *
  334. * @param series the series index (zero-based).
  335. * @param generator the generator (<code>null</code> permitted).
  336. */
  337. public void setSeriesToolTipGenerator(int series,
  338. CategoryToolTipGenerator generator) {
  339. this.toolTipGeneratorList.set(series, generator);
  340. notifyListeners(new RendererChangeEvent(this));
  341. }
  342. /**
  343. * Returns the base tool tip generator (the "layer 2" generator).
  344. *
  345. * @return The tool tip generator (possibly <code>null</code>).
  346. */
  347. public CategoryToolTipGenerator getBaseToolTipGenerator() {
  348. return this.baseToolTipGenerator;
  349. }
  350. /**
  351. * Sets the base tool tip generator and sends a
  352. * {@link org.jfree.chart.event.RendererChangeEvent} to all registered
  353. * listeners.
  354. *
  355. * @param generator the generator (<code>null</code> permitted).
  356. */
  357. public void setBaseToolTipGenerator(CategoryToolTipGenerator generator) {
  358. this.baseToolTipGenerator = generator;
  359. notifyListeners(new RendererChangeEvent(this));
  360. }
  361. // URL GENERATOR
  362. /**
  363. * Returns the URL generator for a data item. This method just calls the
  364. * getSeriesItemURLGenerator method, but you can override this behaviour if
  365. * you want to.
  366. *
  367. * @param row the row index (zero based).
  368. * @param column the column index (zero based).
  369. *
  370. * @return The URL generator.
  371. */
  372. public CategoryURLGenerator getItemURLGenerator(int row, int column) {
  373. return getSeriesItemURLGenerator(row);
  374. }
  375. /**
  376. * Returns the URL generator for a series.
  377. *
  378. * @param series the series index (zero based).
  379. *
  380. * @return The URL generator for the series.
  381. */
  382. public CategoryURLGenerator getSeriesItemURLGenerator(int series) {
  383. // return the generator for ALL series, if there is one...
  384. if (this.itemURLGenerator != null) {
  385. return this.itemURLGenerator;
  386. }
  387. // otherwise look up the generator table
  388. CategoryURLGenerator generator
  389. = (CategoryURLGenerator) this.itemURLGeneratorList.get(series);
  390. if (generator == null) {
  391. generator = this.baseItemURLGenerator;
  392. }
  393. return generator;
  394. }
  395. /**
  396. * Sets the item URL generator for ALL series.
  397. *
  398. * @param generator the generator.
  399. */
  400. public void setItemURLGenerator(CategoryURLGenerator generator) {
  401. this.itemURLGenerator = generator;
  402. }
  403. /**
  404. * Sets the URL generator for a series.
  405. *
  406. * @param series the series index (zero based).
  407. * @param generator the generator.
  408. */
  409. public void setSeriesItemURLGenerator(int series,
  410. CategoryURLGenerator generator) {
  411. this.itemURLGeneratorList.set(series, generator);
  412. }
  413. /**
  414. * Returns the base item URL generator.
  415. *
  416. * @return The item URL generator.
  417. */
  418. public CategoryURLGenerator getBaseItemURLGenerator() {
  419. return this.baseItemURLGenerator;
  420. }
  421. /**
  422. * Sets the base item URL generator.
  423. *
  424. * @param generator the item URL generator.
  425. */
  426. public void setBaseItemURLGenerator(CategoryURLGenerator generator) {
  427. this.baseItemURLGenerator = generator;
  428. }
  429. /**
  430. * Returns the number of rows in the dataset. This value is updated in the
  431. * {@link AbstractCategoryItemRenderer#initialise} method.
  432. *
  433. * @return the row count.
  434. */
  435. public int getRowCount() {
  436. return this.rowCount;
  437. }
  438. /**
  439. * Returns the number of columns in the dataset. This value is updated in
  440. * the {@link AbstractCategoryItemRenderer#initialise} method.
  441. *
  442. * @return the column count.
  443. */
  444. public int getColumnCount() {
  445. return this.columnCount;
  446. }
  447. /**
  448. * Initialises the renderer and returns a state object that will be used
  449. * for the remainder of the drawing process for a single chart. The state
  450. * object allows for the fact that the renderer may be used simultaneously
  451. * by multiple threads (each thread will work with a separate state object).
  452. * <P>
  453. * Stores a reference to the {@link PlotRenderingInfo} object (which might
  454. * be <code>null</code>), and then sets the useCategoriesPaint flag
  455. * according to the special case conditions a) there is only one series
  456. * and b) the categoriesPaint array is not null.
  457. *
  458. * @param g2 the graphics device.
  459. * @param dataArea the data area.
  460. * @param plot the plot.
  461. * @param rendererIndex the renderer index.
  462. * @param info an object for returning information about the structure of
  463. * the plot (<code>null</code> permitted).
  464. *
  465. * @return the renderer state.
  466. *
  467. */
  468. public CategoryItemRendererState initialise(Graphics2D g2,
  469. Rectangle2D dataArea,
  470. CategoryPlot plot,
  471. int rendererIndex,
  472. PlotRenderingInfo info) {
  473. setPlot(plot);
  474. CategoryDataset data = plot.getDataset(rendererIndex);
  475. if (data != null) {
  476. this.rowCount = data.getRowCount();
  477. this.columnCount = data.getColumnCount();
  478. }
  479. else {
  480. this.rowCount = 0;
  481. this.columnCount = 0;
  482. }
  483. return new CategoryItemRendererState(info);
  484. }
  485. /**
  486. * Returns the range of values the renderer requires to display all the
  487. * items from the specified dataset.
  488. *
  489. * @param dataset the dataset (<code>null</code> permitted).
  490. *
  491. * @return The range (or <code>null</code> if the dataset is
  492. * <code>null</code> or empty).
  493. */
  494. public Range findRangeBounds(CategoryDataset dataset) {
  495. return DatasetUtilities.findRangeBounds(dataset);
  496. }
  497. /**
  498. * Draws a background for the data area. The default implementation just
  499. * gets the plot to draw the outline, but some renderers will override this
  500. * behaviour.
  501. *
  502. * @param g2 the graphics device.
  503. * @param plot the plot.
  504. * @param dataArea the data area.
  505. */
  506. public void drawBackground(Graphics2D g2,
  507. CategoryPlot plot,
  508. Rectangle2D dataArea) {
  509. plot.drawBackground(g2, dataArea);
  510. }
  511. /**
  512. * Draws an outline for the data area. The default implementation just
  513. * gets the plot to draw the outline, but some renderers will override this
  514. * behaviour.
  515. *
  516. * @param g2 the graphics device.
  517. * @param plot the plot.
  518. * @param dataArea the data area.
  519. */
  520. public void drawOutline(Graphics2D g2,
  521. CategoryPlot plot,
  522. Rectangle2D dataArea) {
  523. plot.drawOutline(g2, dataArea);
  524. }
  525. /**
  526. * Draws a grid line against the domain axis.
  527. * <P>
  528. * Note that this default implementation assumes that the horizontal axis
  529. * is the domain axis. If this is not the case, you will need to override
  530. * this method.
  531. *
  532. * @param g2 the graphics device.
  533. * @param plot the plot.
  534. * @param dataArea the area for plotting data (not yet adjusted for any
  535. * 3D effect).
  536. * @param value the Java2D value at which the grid line should be drawn.
  537. */
  538. public void drawDomainGridline(Graphics2D g2,
  539. CategoryPlot plot,
  540. Rectangle2D dataArea,
  541. double value) {
  542. Line2D line = null;
  543. PlotOrientation orientation = plot.getOrientation();
  544. if (orientation == PlotOrientation.HORIZONTAL) {
  545. line = new Line2D.Double(
  546. dataArea.getMinX(), value, dataArea.getMaxX(), value
  547. );
  548. }
  549. else if (orientation == PlotOrientation.VERTICAL) {
  550. line = new Line2D.Double(
  551. value, dataArea.getMinY(), value, dataArea.getMaxY()
  552. );
  553. }
  554. Paint paint = plot.getDomainGridlinePaint();
  555. if (paint == null) {
  556. paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
  557. }
  558. g2.setPaint(paint);
  559. Stroke stroke = plot.getDomainGridlineStroke();
  560. if (stroke == null) {
  561. stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
  562. }
  563. g2.setStroke(stroke);
  564. g2.draw(line);
  565. }
  566. /**
  567. * Draws a grid line against the range axis.
  568. *
  569. * @param g2 the graphics device.
  570. * @param plot the plot.
  571. * @param axis the value axis.
  572. * @param dataArea the area for plotting data (not yet adjusted for any
  573. * 3D effect).
  574. * @param value the value at which the grid line should be drawn.
  575. *
  576. */
  577. public void drawRangeGridline(Graphics2D g2,
  578. CategoryPlot plot,
  579. ValueAxis axis,
  580. Rectangle2D dataArea,
  581. double value) {
  582. Range range = axis.getRange();
  583. if (!range.contains(value)) {
  584. return;
  585. }
  586. PlotOrientation orientation = plot.getOrientation();
  587. double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
  588. Line2D line = null;
  589. if (orientation == PlotOrientation.HORIZONTAL) {
  590. line = new Line2D.Double(
  591. v, dataArea.getMinY(), v, dataArea.getMaxY()
  592. );
  593. }
  594. else if (orientation == PlotOrientation.VERTICAL) {
  595. line = new Line2D.Double(
  596. dataArea.getMinX(), v, dataArea.getMaxX(), v
  597. );
  598. }
  599. Paint paint = plot.getRangeGridlinePaint();
  600. if (paint == null) {
  601. paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
  602. }
  603. g2.setPaint(paint);
  604. Stroke stroke = plot.getRangeGridlineStroke();
  605. if (stroke == null) {
  606. stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
  607. }
  608. g2.setStroke(stroke);
  609. g2.draw(line);
  610. }
  611. /**
  612. * Draws a marker for the range axis.
  613. *
  614. * @param g2 the graphics device (not <code>null</code>).
  615. * @param plot the plot (not <code>null</code>).
  616. * @param axis the range axis (not <code>null</code>).
  617. * @param marker the marker to be drawn (not <code>null</code>).
  618. * @param dataArea the area inside the axes (not <code>null</code>).
  619. */
  620. public void drawRangeMarker(Graphics2D g2,
  621. CategoryPlot plot,
  622. ValueAxis axis,
  623. Marker marker,
  624. Rectangle2D dataArea) {
  625. if (marker instanceof ValueMarker) {
  626. ValueMarker vm = (ValueMarker) marker;
  627. double value = vm.getValue();
  628. Range range = axis.getRange();
  629. if (!range.contains(value)) {
  630. return;
  631. }
  632. PlotOrientation orientation = plot.getOrientation();
  633. double v = axis.valueToJava2D(
  634. value, dataArea, plot.getRangeAxisEdge()
  635. );
  636. Line2D line = null;
  637. if (orientation == PlotOrientation.HORIZONTAL) {
  638. line = new Line2D.Double(
  639. v, dataArea.getMinY(), v, dataArea.getMaxY()
  640. );
  641. }
  642. else if (orientation == PlotOrientation.VERTICAL) {
  643. line = new Line2D.Double(
  644. dataArea.getMinX(), v, dataArea.getMaxX(), v
  645. );
  646. }
  647. g2.setPaint(marker.getPaint());
  648. g2.setStroke(marker.getStroke());
  649. g2.draw(line);
  650. String label = marker.getLabel();
  651. RectangleAnchor anchor = marker.getLabelAnchor();
  652. if (label != null) {
  653. Font labelFont = marker.getLabelFont();
  654. g2.setFont(labelFont);
  655. g2.setPaint(marker.getLabelPaint());
  656. Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
  657. g2, orientation, dataArea, line.getBounds2D(),
  658. marker.getLabelOffset(),
  659. marker.getLabelOffsetTypeForDomain(),
  660. marker.getLabelOffsetTypeForRange(), anchor
  661. );
  662. TextUtilities.drawAlignedString(
  663. label, g2,
  664. (float) coordinates.getX(), (float) coordinates.getY(),
  665. marker.getLabelTextAnchor()
  666. );
  667. }
  668. }
  669. else if (marker instanceof IntervalMarker) {
  670. IntervalMarker im = (IntervalMarker) marker;
  671. double start = im.getStartValue();
  672. double end = im.getEndValue();
  673. Range range = axis.getRange();
  674. if (!(range.intersects(start, end))) {
  675. return;
  676. }
  677. // don't draw beyond the axis range...
  678. start = range.constrain(start);
  679. end = range.constrain(end);
  680. double v0 = axis.valueToJava2D(
  681. start, dataArea, plot.getRangeAxisEdge()
  682. );
  683. double v1 = axis.valueToJava2D(
  684. end, dataArea, plot.getRangeAxisEdge()
  685. );
  686. PlotOrientation orientation = plot.getOrientation();
  687. Rectangle2D rect = null;
  688. if (orientation == PlotOrientation.HORIZONTAL) {
  689. rect = new Rectangle2D.Double(
  690. v0, dataArea.getMinY(), v1 - v0, dataArea.getHeight()
  691. );
  692. }
  693. else if (orientation == PlotOrientation.VERTICAL) {
  694. rect = new Rectangle2D.Double(
  695. dataArea.getMinX(), Math.min(v0, v1),
  696. dataArea.getWidth(), Math.abs(v1 - v0)
  697. );
  698. }
  699. Paint p = marker.getPaint();
  700. if (p instanceof GradientPaint) {
  701. GradientPaint gp = (GradientPaint) p;
  702. GradientPaintTransformer t = im.getGradientPaintTransformer();
  703. if (t != null) {
  704. gp = t.transform(gp, rect);
  705. }
  706. g2.setPaint(gp);
  707. }
  708. else {
  709. g2.setPaint(p);
  710. }
  711. g2.fill(rect);
  712. String label = marker.getLabel();
  713. RectangleAnchor anchor = marker.getLabelAnchor();
  714. if (label != null) {
  715. Font labelFont = marker.getLabelFont();
  716. g2.setFont(labelFont);
  717. g2.setPaint(marker.getLabelPaint());
  718. Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
  719. g2, orientation, dataArea,
  720. rect, marker.getLabelOffset(),
  721. marker.getLabelOffsetTypeForDomain(),
  722. marker.getLabelOffsetTypeForRange(), anchor
  723. );
  724. TextUtilities.drawAlignedString(
  725. label, g2,
  726. (float) coordinates.getX(), (float) coordinates.getY(),
  727. marker.getLabelTextAnchor()
  728. );
  729. }
  730. }
  731. }
  732. /**
  733. * Calculates the (x, y) coordinates for drawing a marker label.
  734. *
  735. * @param g2 the graphics device.
  736. * @param orientation the plot orientation.
  737. * @param dataArea the data area.
  738. * @param markerArea the rectangle surrounding the marker.
  739. * @param markerOffset the marker offset.
  740. * @param anchor the label anchor.
  741. *
  742. * @return The coordinates for drawing the marker label.
  743. */
  744. private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
  745. PlotOrientation orientation,
  746. Rectangle2D dataArea,
  747. Rectangle2D markerArea,
  748. RectangleInsets markerOffset,
  749. LengthAdjustmentType labelOffsetForDomain,
  750. LengthAdjustmentType labelOffsetForRange,
  751. RectangleAnchor anchor) {
  752. Rectangle2D anchorRect = null;
  753. if (orientation == PlotOrientation.HORIZONTAL) {
  754. anchorRect = markerOffset.createAdjustedRectangle(
  755. markerArea, labelOffsetForRange, labelOffsetForDomain
  756. );
  757. }
  758. else if (orientation == PlotOrientation.VERTICAL) {
  759. anchorRect = markerOffset.createAdjustedRectangle(
  760. markerArea, labelOffsetForDomain, labelOffsetForRange
  761. );
  762. }
  763. return RectangleAnchor.coordinates(anchorRect, anchor);
  764. }
  765. /**
  766. * Returns a legend item for a series.
  767. *
  768. * @param datasetIndex the dataset index (zero-based).
  769. * @param series the series index (zero-based).
  770. *
  771. * @return The legend item.
  772. */
  773. public LegendItem getLegendItem(int datasetIndex, int series) {
  774. CategoryPlot cp = getPlot();
  775. if (cp == null) {
  776. return null;
  777. }
  778. CategoryDataset dataset;
  779. dataset = cp.getDataset(datasetIndex);
  780. String label = dataset.getRowKey(series).toString();
  781. String description = label;
  782. Shape shape = getSeriesShape(series);
  783. Paint paint = getSeriesPaint(series);
  784. Paint outlinePaint = getSeriesOutlinePaint(series);
  785. Stroke outlineStroke = getSeriesOutlineStroke(series);
  786. return new LegendItem(
  787. label, description, shape, paint, outlineStroke, outlinePaint
  788. );
  789. }
  790. /**
  791. * Tests this renderer for equality with another object.
  792. *
  793. * @param obj the object.
  794. *
  795. * @return <code>true</code> or <code>false</code>.
  796. */
  797. public boolean equals(Object obj) {
  798. if (obj == this) {
  799. return true;
  800. }
  801. if (!(obj instanceof AbstractCategoryItemRenderer)) {
  802. return false;
  803. }
  804. if (!super.equals(obj)) {
  805. return false;
  806. }
  807. AbstractCategoryItemRenderer that = (AbstractCategoryItemRenderer) obj;
  808. if (!ObjectUtilities.equal(this.labelGenerator, that.labelGenerator)) {
  809. return false;
  810. }
  811. if (!ObjectUtilities.equal(
  812. this.labelGeneratorList, that.labelGeneratorList
  813. )) {
  814. return false;
  815. }
  816. if (!ObjectUtilities.equal(
  817. this.baseLabelGenerator, that.baseLabelGenerator
  818. )) {
  819. return false;
  820. }
  821. if (!ObjectUtilities.equal(
  822. this.toolTipGenerator, that.toolTipGenerator
  823. )) {
  824. return false;
  825. }
  826. if (!ObjectUtilities.equal(
  827. this.toolTipGeneratorList, that.toolTipGeneratorList
  828. )) {
  829. return false;
  830. }
  831. if (!ObjectUtilities.equal(
  832. this.baseToolTipGenerator, that.baseToolTipGenerator
  833. )) {
  834. return false;
  835. }
  836. if (!ObjectUtilities.equal(
  837. this.itemURLGenerator, that.itemURLGenerator
  838. )) {
  839. return false;
  840. }
  841. if (!ObjectUtilities.equal(
  842. this.itemURLGeneratorList, that.itemURLGeneratorList
  843. )) {
  844. return false;
  845. }
  846. if (!ObjectUtilities.equal(
  847. this.baseItemURLGenerator, that.baseItemURLGenerator
  848. )) {
  849. return false;
  850. }
  851. return true;
  852. }
  853. /**
  854. * Returns a hash code for the renderer.
  855. *
  856. * @return The hash code.
  857. */
  858. public int hashCode() {
  859. int result = super.hashCode();
  860. return result;
  861. }
  862. /**
  863. * Returns the drawing supplier from the plot.
  864. *
  865. * @return The drawing supplier (possibly <code>null</code>).
  866. */
  867. public DrawingSupplier getDrawingSupplier() {
  868. DrawingSupplier result = null;
  869. CategoryPlot cp = getPlot();
  870. if (cp != null) {
  871. result = cp.getDrawingSupplier();
  872. }
  873. return result;
  874. }
  875. /**
  876. * Draws an item label.
  877. *
  878. * @param g2 the graphics device.
  879. * @param orientation the orientation.
  880. * @param dataset the dataset.
  881. * @param row the row.
  882. * @param column the column.
  883. * @param x the x coordinate (in Java2D space).
  884. * @param y the y coordinate (in Java2D space).
  885. * @param negative indicates a negative value (which affects the item
  886. * label position).
  887. */
  888. protected void drawItemLabel(Graphics2D g2,
  889. PlotOrientation orientation,
  890. CategoryDataset dataset,
  891. int row, int column,
  892. double x, double y,
  893. boolean negative) {
  894. CategoryLabelGenerator generator = getLabelGenerator(row, column);
  895. if (generator != null) {
  896. Font labelFont = getItemLabelFont(row, column);
  897. Paint paint = getItemLabelPaint(row, column);
  898. g2.setFont(labelFont);
  899. g2.setPaint(paint);
  900. String label = generator.generateLabel(dataset, row, column);
  901. ItemLabelPosition position = null;
  902. if (!negative) {
  903. position = getPositiveItemLabelPosition(row, column);
  904. }
  905. else {
  906. position = getNegativeItemLabelPosition(row, column);
  907. }
  908. Point2D anchorPoint = calculateLabelAnchorPoint(
  909. position.getItemLabelAnchor(), x, y, orientation
  910. );
  911. TextUtilities.drawRotatedString(
  912. label, g2,
  913. (float) anchorPoint.getX(), (float) anchorPoint.getY(),
  914. position.getTextAnchor(),
  915. position.getAngle(), position.getRotationAnchor()
  916. );
  917. }
  918. }
  919. /**
  920. * Returns an independent copy of the renderer. The <code>plot</code>
  921. * reference is shallow copied.
  922. *
  923. * @return A clone.
  924. *
  925. * @throws CloneNotSupportedException can be thrown if one of the objects
  926. * belonging to the renderer does not support cloning (for example,
  927. * an item label generator).
  928. */
  929. public Object clone() throws CloneNotSupportedException {
  930. AbstractCategoryItemRenderer clone
  931. = (AbstractCategoryItemRenderer) super.clone();
  932. if (this.labelGenerator != null) {
  933. if (this.labelGenerator instanceof PublicCloneable) {
  934. PublicCloneable pc = (PublicCloneable) this.labelGenerator;
  935. clone.labelGenerator = (CategoryLabelGenerator) pc.clone();
  936. }
  937. else {
  938. throw new CloneNotSupportedException(
  939. "ItemLabelGenerator not cloneable."
  940. );
  941. }
  942. }
  943. if (this.labelGeneratorList != null) {
  944. clone.labelGeneratorList
  945. = (ObjectList) this.labelGeneratorList.clone();
  946. }
  947. if (this.baseLabelGenerator != null) {
  948. if (this.baseLabelGenerator instanceof PublicCloneable) {
  949. PublicCloneable pc = (PublicCloneable) this.baseLabelGenerator;
  950. clone.baseLabelGenerator = (CategoryLabelGenerator) pc.clone();
  951. }
  952. else {
  953. throw new CloneNotSupportedException(
  954. "ItemLabelGenerator not cloneable."
  955. );
  956. }
  957. }
  958. if (this.toolTipGenerator != null) {
  959. if (this.toolTipGenerator instanceof PublicCloneable) {
  960. PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
  961. clone.toolTipGenerator = (CategoryToolTipGenerator) pc.clone();
  962. }
  963. else {
  964. throw new CloneNotSupportedException(
  965. "Tool tip generator not cloneable."
  966. );
  967. }
  968. }
  969. if (this.toolTipGeneratorList != null) {
  970. clone.toolTipGeneratorList
  971. = (ObjectList) this.toolTipGeneratorList.clone();
  972. }
  973. if (this.baseToolTipGenerator != null) {
  974. if (this.baseToolTipGenerator instanceof PublicCloneable) {
  975. PublicCloneable pc
  976. = (PublicCloneable) this.baseToolTipGenerator;
  977. clone.baseToolTipGenerator
  978. = (CategoryToolTipGenerator) pc.clone();
  979. }
  980. else {
  981. throw new CloneNotSupportedException(
  982. "Base tool tip generator not cloneable."
  983. );
  984. }
  985. }
  986. if (this.itemURLGenerator != null) {
  987. if (this.itemURLGenerator instanceof PublicCloneable) {
  988. PublicCloneable pc = (PublicCloneable) this.itemURLGenerator;
  989. clone.itemURLGenerator = (CategoryURLGenerator) pc.clone();
  990. }
  991. else {
  992. throw new CloneNotSupportedException(
  993. "Item URL generator not cloneable."
  994. );
  995. }
  996. }
  997. if (this.itemURLGeneratorList != null) {
  998. clone.itemURLGeneratorList
  999. = (ObjectList) this.itemURLGeneratorList.clone();
  1000. }
  1001. if (this.baseItemURLGenerator != null) {
  1002. if (this.baseItemURLGenerator instanceof PublicCloneable) {
  1003. PublicCloneable pc = (PublicCloneable) this.baseItemURLGenerator;
  1004. clone.baseItemURLGenerator = (CategoryURLGenerator) pc.clone();
  1005. }
  1006. else {
  1007. throw new CloneNotSupportedException(
  1008. "Base item URL generator not cloneable."
  1009. );
  1010. }
  1011. }
  1012. return clone;
  1013. }
  1014. /**
  1015. * Returns a domain axis for a plot.
  1016. *
  1017. * @param plot the plot.
  1018. * @param index the axis index.
  1019. *
  1020. * @return A domain axis.
  1021. */
  1022. protected CategoryAxis getDomainAxis(CategoryPlot plot, int index) {
  1023. CategoryAxis result = plot.getDomainAxis(index);
  1024. if (result == null) {
  1025. result = plot.getDomainAxis();
  1026. }
  1027. return result;
  1028. }
  1029. /**
  1030. * Returns a range axis for a plot.
  1031. *
  1032. * @param plot the plot.
  1033. * @param index the axis index (<code>null</code> for the primary axis).
  1034. *
  1035. * @return A range axis.
  1036. */
  1037. protected ValueAxis getRangeAxis(CategoryPlot plot, int index) {
  1038. ValueAxis result = plot.getRangeAxis(index);
  1039. if (result == null) {
  1040. result = plot.getRangeAxis();
  1041. }
  1042. return result;
  1043. }
  1044. /**
  1045. * Returns a (possibly empty) collection of legend items for the series
  1046. * that this renderer is responsible for drawing.
  1047. *
  1048. * @return The legend item collection (never <code>null</code>).
  1049. */
  1050. public LegendItemCollection getLegendItems() {
  1051. if (this.plot == null) {
  1052. return new LegendItemCollection();
  1053. }
  1054. LegendItemCollection result = new LegendItemCollection();
  1055. int index = this.plot.getIndexOf(this);
  1056. CategoryDataset dataset = this.plot.getDataset(index);
  1057. if (dataset != null) {
  1058. int seriesCount = dataset.getRowCount();
  1059. for (int i = 0; i < seriesCount; i++) {
  1060. LegendItem item = getLegendItem(index, i);
  1061. if (item != null) {
  1062. result.add(item);
  1063. }
  1064. }
  1065. }
  1066. return result;
  1067. }
  1068. }