001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------------------------------
028 * AbstractCategoryItemRenderer.java
029 * ---------------------------------
030 * (C) Copyright 2002-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Richard Atkinson;
034 *
035 * Changes:
036 * --------
037 * 29-May-2002 : Version 1 (DG);
038 * 06-Jun-2002 : Added accessor methods for the tool tip generator (DG);
039 * 11-Jun-2002 : Made constructors protected (DG);
040 * 26-Jun-2002 : Added axis to initialise method (DG);
041 * 05-Aug-2002 : Added urlGenerator member variable plus accessors (RA);
042 * 22-Aug-2002 : Added categoriesPaint attribute, based on code submitted by
043 * Janet Banks. This can be used when there is only one series,
044 * and you want each category item to have a different color (DG);
045 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
046 * 29-Oct-2002 : Fixed bug where background image for plot was not being
047 * drawn (DG);
048 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
049 * 26-Nov 2002 : Replaced the isStacked() method with getRangeType() (DG);
050 * 09-Jan-2003 : Renamed grid-line methods (DG);
051 * 17-Jan-2003 : Moved plot classes into separate package (DG);
052 * 25-Mar-2003 : Implemented Serializable (DG);
053 * 12-May-2003 : Modified to take into account the plot orientation (DG);
054 * 12-Aug-2003 : Very minor javadoc corrections (DB)
055 * 13-Aug-2003 : Implemented Cloneable (DG);
056 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057 * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG);
058 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
059 * 11-Feb-2004 : Modified labelling for markers (DG);
060 * 12-Feb-2004 : Updated clone() method (DG);
061 * 15-Apr-2004 : Created a new CategoryToolTipGenerator interface (DG);
062 * 05-May-2004 : Fixed bug (948310) where interval markers extend outside axis
063 * range (DG);
064 * 14-Jun-2004 : Fixed bug in drawRangeMarker() method - now uses 'paint' and
065 * 'stroke' rather than 'outlinePaint' and 'outlineStroke' (DG);
066 * 15-Jun-2004 : Interval markers can now use GradientPaint (DG);
067 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
068 * --> TextUtilities (DG);
069 * 01-Oct-2004 : Fixed bug 1029697, problem with label alignment in
070 * drawRangeMarker() method (DG);
071 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG);
072 * 21-Jan-2005 : Modified return type of calculateRangeMarkerTextAnchorPoint()
073 * method (DG);
074 * 08-Mar-2005 : Fixed positioning of marker labels (DG);
075 * 20-Apr-2005 : Added legend label, tooltip and URL generators (DG);
076 * 01-Jun-2005 : Handle one dimension of the marker label adjustment
077 * automatically (DG);
078 * 09-Jun-2005 : Added utility method for adding an item entity (DG);
079 * ------------- JFREECHART 1.0.x ---------------------------------------------
080 * 01-Mar-2006 : Updated getLegendItems() to check seriesVisibleInLegend
081 * flags (DG);
082 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
083 * 23-Oct-2006 : Draw outlines for interval markers (DG);
084 * 24-Oct-2006 : Respect alpha setting in markers, as suggested by Sergei
085 * Ivanov in patch 1567843 (DG);
086 * 30-Nov-2006 : Added a check for series visibility in the getLegendItem()
087 * method (DG);
088 * 07-Dec-2006 : Fix for equals() method (DG);
089 * 22-Feb-2007 : Added createState() method (DG);
090 * 01-Mar-2007 : Fixed interval marker drawing (patch 1670686 thanks to
091 * Sergei Ivanov) (DG);
092 * 20-Apr-2007 : Updated getLegendItem() for renderer change, and deprecated
093 * itemLabelGenerator, toolTipGenerator and itemURLGenerator
094 * override fields (DG);
095 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
096 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
097 * 26-Jun-2008 : Added crosshair support (DG);
098 *
099 */
100
101 package org.jfree.chart.renderer.category;
102
103 import java.awt.AlphaComposite;
104 import java.awt.Composite;
105 import java.awt.Font;
106 import java.awt.GradientPaint;
107 import java.awt.Graphics2D;
108 import java.awt.Paint;
109 import java.awt.Shape;
110 import java.awt.Stroke;
111 import java.awt.geom.Line2D;
112 import java.awt.geom.Point2D;
113 import java.awt.geom.Rectangle2D;
114 import java.io.Serializable;
115
116 import org.jfree.chart.LegendItem;
117 import org.jfree.chart.LegendItemCollection;
118 import org.jfree.chart.axis.CategoryAxis;
119 import org.jfree.chart.axis.ValueAxis;
120 import org.jfree.chart.entity.CategoryItemEntity;
121 import org.jfree.chart.entity.EntityCollection;
122 import org.jfree.chart.event.RendererChangeEvent;
123 import org.jfree.chart.labels.CategoryItemLabelGenerator;
124 import org.jfree.chart.labels.CategorySeriesLabelGenerator;
125 import org.jfree.chart.labels.CategoryToolTipGenerator;
126 import org.jfree.chart.labels.ItemLabelPosition;
127 import org.jfree.chart.labels.StandardCategorySeriesLabelGenerator;
128 import org.jfree.chart.plot.CategoryCrosshairState;
129 import org.jfree.chart.plot.CategoryMarker;
130 import org.jfree.chart.plot.CategoryPlot;
131 import org.jfree.chart.plot.DrawingSupplier;
132 import org.jfree.chart.plot.IntervalMarker;
133 import org.jfree.chart.plot.Marker;
134 import org.jfree.chart.plot.PlotOrientation;
135 import org.jfree.chart.plot.PlotRenderingInfo;
136 import org.jfree.chart.plot.ValueMarker;
137 import org.jfree.chart.renderer.AbstractRenderer;
138 import org.jfree.chart.urls.CategoryURLGenerator;
139 import org.jfree.data.Range;
140 import org.jfree.data.category.CategoryDataset;
141 import org.jfree.data.general.DatasetUtilities;
142 import org.jfree.text.TextUtilities;
143 import org.jfree.ui.GradientPaintTransformer;
144 import org.jfree.ui.LengthAdjustmentType;
145 import org.jfree.ui.RectangleAnchor;
146 import org.jfree.ui.RectangleEdge;
147 import org.jfree.ui.RectangleInsets;
148 import org.jfree.util.ObjectList;
149 import org.jfree.util.ObjectUtilities;
150 import org.jfree.util.PublicCloneable;
151
152 /**
153 * An abstract base class that you can use to implement a new
154 * {@link CategoryItemRenderer}. When you create a new
155 * {@link CategoryItemRenderer} you are not required to extend this class,
156 * but it makes the job easier.
157 */
158 public abstract class AbstractCategoryItemRenderer extends AbstractRenderer
159 implements CategoryItemRenderer, Cloneable, PublicCloneable,
160 Serializable {
161
162 /** For serialization. */
163 private static final long serialVersionUID = 1247553218442497391L;
164
165 /** The plot that the renderer is assigned to. */
166 private CategoryPlot plot;
167
168 /**
169 * The item label generator for ALL series.
170 *
171 * @deprecated This field is redundant and deprecated as of version 1.0.6.
172 */
173 private CategoryItemLabelGenerator itemLabelGenerator;
174
175 /** A list of item label generators (one per series). */
176 private ObjectList itemLabelGeneratorList;
177
178 /** The base item label generator. */
179 private CategoryItemLabelGenerator baseItemLabelGenerator;
180
181 /**
182 * The tool tip generator for ALL series.
183 *
184 * @deprecated This field is redundant and deprecated as of version 1.0.6.
185 */
186 private CategoryToolTipGenerator toolTipGenerator;
187
188 /** A list of tool tip generators (one per series). */
189 private ObjectList toolTipGeneratorList;
190
191 /** The base tool tip generator. */
192 private CategoryToolTipGenerator baseToolTipGenerator;
193
194 /**
195 * The URL generator.
196 *
197 * @deprecated This field is redundant and deprecated as of version 1.0.6.
198 */
199 private CategoryURLGenerator itemURLGenerator;
200
201 /** A list of item label generators (one per series). */
202 private ObjectList itemURLGeneratorList;
203
204 /** The base item label generator. */
205 private CategoryURLGenerator baseItemURLGenerator;
206
207 /** The legend item label generator. */
208 private CategorySeriesLabelGenerator legendItemLabelGenerator;
209
210 /** The legend item tool tip generator. */
211 private CategorySeriesLabelGenerator legendItemToolTipGenerator;
212
213 /** The legend item URL generator. */
214 private CategorySeriesLabelGenerator legendItemURLGenerator;
215
216 /** The number of rows in the dataset (temporary record). */
217 private transient int rowCount;
218
219 /** The number of columns in the dataset (temporary record). */
220 private transient int columnCount;
221
222 /**
223 * Creates a new renderer with no tool tip generator and no URL generator.
224 * The defaults (no tool tip or URL generators) have been chosen to
225 * minimise the processing required to generate a default chart. If you
226 * require tool tips or URLs, then you can easily add the required
227 * generators.
228 */
229 protected AbstractCategoryItemRenderer() {
230 this.itemLabelGenerator = null;
231 this.itemLabelGeneratorList = new ObjectList();
232 this.toolTipGenerator = null;
233 this.toolTipGeneratorList = new ObjectList();
234 this.itemURLGenerator = null;
235 this.itemURLGeneratorList = new ObjectList();
236 this.legendItemLabelGenerator
237 = new StandardCategorySeriesLabelGenerator();
238 }
239
240 /**
241 * Returns the number of passes through the dataset required by the
242 * renderer. This method returns <code>1</code>, subclasses should
243 * override if they need more passes.
244 *
245 * @return The pass count.
246 */
247 public int getPassCount() {
248 return 1;
249 }
250
251 /**
252 * Returns the plot that the renderer has been assigned to (where
253 * <code>null</code> indicates that the renderer is not currently assigned
254 * to a plot).
255 *
256 * @return The plot (possibly <code>null</code>).
257 *
258 * @see #setPlot(CategoryPlot)
259 */
260 public CategoryPlot getPlot() {
261 return this.plot;
262 }
263
264 /**
265 * Sets the plot that the renderer has been assigned to. This method is
266 * usually called by the {@link CategoryPlot}, in normal usage you
267 * shouldn't need to call this method directly.
268 *
269 * @param plot the plot (<code>null</code> not permitted).
270 *
271 * @see #getPlot()
272 */
273 public void setPlot(CategoryPlot plot) {
274 if (plot == null) {
275 throw new IllegalArgumentException("Null 'plot' argument.");
276 }
277 this.plot = plot;
278 }
279
280 // ITEM LABEL GENERATOR
281
282 /**
283 * Returns the item label generator for a data item. This implementation
284 * simply passes control to the {@link #getSeriesItemLabelGenerator(int)}
285 * method. If, for some reason, you want a different generator for
286 * individual items, you can override this method.
287 *
288 * @param row the row index (zero based).
289 * @param column the column index (zero based).
290 *
291 * @return The generator (possibly <code>null</code>).
292 */
293 public CategoryItemLabelGenerator getItemLabelGenerator(int row,
294 int column) {
295 return getSeriesItemLabelGenerator(row);
296 }
297
298 /**
299 * Returns the item label generator for a series.
300 *
301 * @param series the series index (zero based).
302 *
303 * @return The generator (possibly <code>null</code>).
304 *
305 * @see #setSeriesItemLabelGenerator(int, CategoryItemLabelGenerator)
306 */
307 public CategoryItemLabelGenerator getSeriesItemLabelGenerator(int series) {
308
309 // return the generator for ALL series, if there is one...
310 if (this.itemLabelGenerator != null) {
311 return this.itemLabelGenerator;
312 }
313
314 // otherwise look up the generator table
315 CategoryItemLabelGenerator generator = (CategoryItemLabelGenerator)
316 this.itemLabelGeneratorList.get(series);
317 if (generator == null) {
318 generator = this.baseItemLabelGenerator;
319 }
320 return generator;
321
322 }
323
324 /**
325 * Sets the item label generator for ALL series and sends a
326 * {@link RendererChangeEvent} to all registered listeners.
327 *
328 * @param generator the generator (<code>null</code> permitted).
329 *
330 * @deprecated This method should no longer be used (as of version 1.0.6).
331 * It is sufficient to rely on {@link #setSeriesItemLabelGenerator(int,
332 * CategoryItemLabelGenerator)} and
333 * {@link #setBaseItemLabelGenerator(CategoryItemLabelGenerator)}.
334 */
335 public void setItemLabelGenerator(CategoryItemLabelGenerator generator) {
336 this.itemLabelGenerator = generator;
337 fireChangeEvent();
338 }
339
340 /**
341 * Sets the item label generator for a series and sends a
342 * {@link RendererChangeEvent} to all registered listeners.
343 *
344 * @param series the series index (zero based).
345 * @param generator the generator (<code>null</code> permitted).
346 *
347 * @see #getSeriesItemLabelGenerator(int)
348 */
349 public void setSeriesItemLabelGenerator(int series,
350 CategoryItemLabelGenerator generator) {
351 this.itemLabelGeneratorList.set(series, generator);
352 fireChangeEvent();
353 }
354
355 /**
356 * Returns the base item label generator.
357 *
358 * @return The generator (possibly <code>null</code>).
359 *
360 * @see #setBaseItemLabelGenerator(CategoryItemLabelGenerator)
361 */
362 public CategoryItemLabelGenerator getBaseItemLabelGenerator() {
363 return this.baseItemLabelGenerator;
364 }
365
366 /**
367 * Sets the base item label generator and sends a
368 * {@link RendererChangeEvent} to all registered listeners.
369 *
370 * @param generator the generator (<code>null</code> permitted).
371 *
372 * @see #getBaseItemLabelGenerator()
373 */
374 public void setBaseItemLabelGenerator(
375 CategoryItemLabelGenerator generator) {
376 this.baseItemLabelGenerator = generator;
377 fireChangeEvent();
378 }
379
380 // TOOL TIP GENERATOR
381
382 /**
383 * Returns the tool tip generator that should be used for the specified
384 * item. This method looks up the generator using the "three-layer"
385 * approach outlined in the general description of this interface. You
386 * can override this method if you want to return a different generator per
387 * item.
388 *
389 * @param row the row index (zero-based).
390 * @param column the column index (zero-based).
391 *
392 * @return The generator (possibly <code>null</code>).
393 */
394 public CategoryToolTipGenerator getToolTipGenerator(int row, int column) {
395
396 CategoryToolTipGenerator result = null;
397 if (this.toolTipGenerator != null) {
398 result = this.toolTipGenerator;
399 }
400 else {
401 result = getSeriesToolTipGenerator(row);
402 if (result == null) {
403 result = this.baseToolTipGenerator;
404 }
405 }
406 return result;
407 }
408
409 /**
410 * Returns the tool tip generator that will be used for ALL items in the
411 * dataset (the "layer 0" generator).
412 *
413 * @return A tool tip generator (possibly <code>null</code>).
414 *
415 * @see #setToolTipGenerator(CategoryToolTipGenerator)
416 *
417 * @deprecated This method should no longer be used (as of version 1.0.6).
418 * It is sufficient to rely on {@link #getSeriesToolTipGenerator(int)}
419 * and {@link #getBaseToolTipGenerator()}.
420 */
421 public CategoryToolTipGenerator getToolTipGenerator() {
422 return this.toolTipGenerator;
423 }
424
425 /**
426 * Sets the tool tip generator for ALL series and sends a
427 * {@link org.jfree.chart.event.RendererChangeEvent} to all registered
428 * listeners.
429 *
430 * @param generator the generator (<code>null</code> permitted).
431 *
432 * @see #getToolTipGenerator()
433 *
434 * @deprecated This method should no longer be used (as of version 1.0.6).
435 * It is sufficient to rely on {@link #setSeriesToolTipGenerator(int,
436 * CategoryToolTipGenerator)} and
437 * {@link #setBaseToolTipGenerator(CategoryToolTipGenerator)}.
438 */
439 public void setToolTipGenerator(CategoryToolTipGenerator generator) {
440 this.toolTipGenerator = generator;
441 fireChangeEvent();
442 }
443
444 /**
445 * Returns the tool tip generator for the specified series (a "layer 1"
446 * generator).
447 *
448 * @param series the series index (zero-based).
449 *
450 * @return The tool tip generator (possibly <code>null</code>).
451 *
452 * @see #setSeriesToolTipGenerator(int, CategoryToolTipGenerator)
453 */
454 public CategoryToolTipGenerator getSeriesToolTipGenerator(int series) {
455 return (CategoryToolTipGenerator) this.toolTipGeneratorList.get(series);
456 }
457
458 /**
459 * Sets the tool tip generator for a series and sends a
460 * {@link RendererChangeEvent} to all registered listeners.
461 *
462 * @param series the series index (zero-based).
463 * @param generator the generator (<code>null</code> permitted).
464 *
465 * @see #getSeriesToolTipGenerator(int)
466 */
467 public void setSeriesToolTipGenerator(int series,
468 CategoryToolTipGenerator generator) {
469 this.toolTipGeneratorList.set(series, generator);
470 fireChangeEvent();
471 }
472
473 /**
474 * Returns the base tool tip generator (the "layer 2" generator).
475 *
476 * @return The tool tip generator (possibly <code>null</code>).
477 *
478 * @see #setBaseToolTipGenerator(CategoryToolTipGenerator)
479 */
480 public CategoryToolTipGenerator getBaseToolTipGenerator() {
481 return this.baseToolTipGenerator;
482 }
483
484 /**
485 * Sets the base tool tip generator and sends a {@link RendererChangeEvent}
486 * to all registered listeners.
487 *
488 * @param generator the generator (<code>null</code> permitted).
489 *
490 * @see #getBaseToolTipGenerator()
491 */
492 public void setBaseToolTipGenerator(CategoryToolTipGenerator generator) {
493 this.baseToolTipGenerator = generator;
494 fireChangeEvent();
495 }
496
497 // URL GENERATOR
498
499 /**
500 * Returns the URL generator for a data item. This method just calls the
501 * getSeriesItemURLGenerator method, but you can override this behaviour if
502 * you want to.
503 *
504 * @param row the row index (zero based).
505 * @param column the column index (zero based).
506 *
507 * @return The URL generator.
508 */
509 public CategoryURLGenerator getItemURLGenerator(int row, int column) {
510 return getSeriesItemURLGenerator(row);
511 }
512
513 /**
514 * Returns the URL generator for a series.
515 *
516 * @param series the series index (zero based).
517 *
518 * @return The URL generator for the series.
519 *
520 * @see #setSeriesItemURLGenerator(int, CategoryURLGenerator)
521 */
522 public CategoryURLGenerator getSeriesItemURLGenerator(int series) {
523
524 // return the generator for ALL series, if there is one...
525 if (this.itemURLGenerator != null) {
526 return this.itemURLGenerator;
527 }
528
529 // otherwise look up the generator table
530 CategoryURLGenerator generator
531 = (CategoryURLGenerator) this.itemURLGeneratorList.get(series);
532 if (generator == null) {
533 generator = this.baseItemURLGenerator;
534 }
535 return generator;
536
537 }
538
539 /**
540 * Sets the item URL generator for ALL series and sends a
541 * {@link RendererChangeEvent} to all registered listeners.
542 *
543 * @param generator the generator.
544 *
545 * @deprecated This method should no longer be used (as of version 1.0.6).
546 * It is sufficient to rely on {@link #setSeriesItemURLGenerator(int,
547 * CategoryURLGenerator)} and
548 * {@link #setBaseItemURLGenerator(CategoryURLGenerator)}.
549 */
550 public void setItemURLGenerator(CategoryURLGenerator generator) {
551 this.itemURLGenerator = generator;
552 fireChangeEvent();
553 }
554
555 /**
556 * Sets the URL generator for a series and sends a
557 * {@link RendererChangeEvent} to all registered listeners.
558 *
559 * @param series the series index (zero based).
560 * @param generator the generator.
561 *
562 * @see #getSeriesItemURLGenerator(int)
563 */
564 public void setSeriesItemURLGenerator(int series,
565 CategoryURLGenerator generator) {
566 this.itemURLGeneratorList.set(series, generator);
567 fireChangeEvent();
568 }
569
570 /**
571 * Returns the base item URL generator.
572 *
573 * @return The item URL generator.
574 *
575 * @see #setBaseItemURLGenerator(CategoryURLGenerator)
576 */
577 public CategoryURLGenerator getBaseItemURLGenerator() {
578 return this.baseItemURLGenerator;
579 }
580
581 /**
582 * Sets the base item URL generator and sends a
583 * {@link RendererChangeEvent} to all registered listeners.
584 *
585 * @param generator the item URL generator (<code>null</code> permitted).
586 *
587 * @see #getBaseItemURLGenerator()
588 */
589 public void setBaseItemURLGenerator(CategoryURLGenerator generator) {
590 this.baseItemURLGenerator = generator;
591 fireChangeEvent();
592 }
593
594 /**
595 * Returns the number of rows in the dataset. This value is updated in the
596 * {@link AbstractCategoryItemRenderer#initialise} method.
597 *
598 * @return The row count.
599 */
600 public int getRowCount() {
601 return this.rowCount;
602 }
603
604 /**
605 * Returns the number of columns in the dataset. This value is updated in
606 * the {@link AbstractCategoryItemRenderer#initialise} method.
607 *
608 * @return The column count.
609 */
610 public int getColumnCount() {
611 return this.columnCount;
612 }
613
614 /**
615 * Creates a new state instance---this method is called from the
616 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int,
617 * PlotRenderingInfo)} method. Subclasses can override this method if
618 * they need to use a subclass of {@link CategoryItemRendererState}.
619 *
620 * @param info collects plot rendering info (<code>null</code> permitted).
621 *
622 * @return The new state instance (never <code>null</code>).
623 *
624 * @since 1.0.5
625 */
626 protected CategoryItemRendererState createState(PlotRenderingInfo info) {
627 return new CategoryItemRendererState(info);
628 }
629
630 /**
631 * Initialises the renderer and returns a state object that will be used
632 * for the remainder of the drawing process for a single chart. The state
633 * object allows for the fact that the renderer may be used simultaneously
634 * by multiple threads (each thread will work with a separate state object).
635 *
636 * @param g2 the graphics device.
637 * @param dataArea the data area.
638 * @param plot the plot.
639 * @param rendererIndex the renderer index.
640 * @param info an object for returning information about the structure of
641 * the plot (<code>null</code> permitted).
642 *
643 * @return The renderer state.
644 */
645 public CategoryItemRendererState initialise(Graphics2D g2,
646 Rectangle2D dataArea,
647 CategoryPlot plot,
648 int rendererIndex,
649 PlotRenderingInfo info) {
650
651 setPlot(plot);
652 CategoryDataset data = plot.getDataset(rendererIndex);
653 if (data != null) {
654 this.rowCount = data.getRowCount();
655 this.columnCount = data.getColumnCount();
656 }
657 else {
658 this.rowCount = 0;
659 this.columnCount = 0;
660 }
661 return createState(info);
662
663 }
664
665 /**
666 * Returns the range of values the renderer requires to display all the
667 * items from the specified dataset.
668 *
669 * @param dataset the dataset (<code>null</code> permitted).
670 *
671 * @return The range (or <code>null</code> if the dataset is
672 * <code>null</code> or empty).
673 */
674 public Range findRangeBounds(CategoryDataset dataset) {
675 return DatasetUtilities.findRangeBounds(dataset);
676 }
677
678 /**
679 * Returns the Java2D coordinate for the middle of the specified data item.
680 *
681 * @param rowKey the row key.
682 * @param columnKey the column key.
683 * @param dataset the dataset.
684 * @param axis the axis.
685 * @param area the data area.
686 * @param edge the edge along which the axis lies.
687 *
688 * @return The Java2D coordinate for the middle of the item.
689 *
690 * @since 1.0.11
691 */
692 public double getItemMiddle(Comparable rowKey, Comparable columnKey,
693 CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
694 RectangleEdge edge) {
695 return axis.getCategoryMiddle(columnKey, dataset.getColumnKeys(), area,
696 edge);
697 }
698
699 /**
700 * Draws a background for the data area. The default implementation just
701 * gets the plot to draw the background, but some renderers will override
702 * this behaviour.
703 *
704 * @param g2 the graphics device.
705 * @param plot the plot.
706 * @param dataArea the data area.
707 */
708 public void drawBackground(Graphics2D g2,
709 CategoryPlot plot,
710 Rectangle2D dataArea) {
711
712 plot.drawBackground(g2, dataArea);
713
714 }
715
716 /**
717 * Draws an outline for the data area. The default implementation just
718 * gets the plot to draw the outline, but some renderers will override this
719 * behaviour.
720 *
721 * @param g2 the graphics device.
722 * @param plot the plot.
723 * @param dataArea the data area.
724 */
725 public void drawOutline(Graphics2D g2,
726 CategoryPlot plot,
727 Rectangle2D dataArea) {
728
729 plot.drawOutline(g2, dataArea);
730
731 }
732
733 /**
734 * Draws a grid line against the domain axis.
735 * <P>
736 * Note that this default implementation assumes that the horizontal axis
737 * is the domain axis. If this is not the case, you will need to override
738 * this method.
739 *
740 * @param g2 the graphics device.
741 * @param plot the plot.
742 * @param dataArea the area for plotting data (not yet adjusted for any
743 * 3D effect).
744 * @param value the Java2D value at which the grid line should be drawn.
745 *
746 * @see #drawRangeGridline(Graphics2D, CategoryPlot, ValueAxis,
747 * Rectangle2D, double)
748 */
749 public void drawDomainGridline(Graphics2D g2,
750 CategoryPlot plot,
751 Rectangle2D dataArea,
752 double value) {
753
754 Line2D line = null;
755 PlotOrientation orientation = plot.getOrientation();
756
757 if (orientation == PlotOrientation.HORIZONTAL) {
758 line = new Line2D.Double(dataArea.getMinX(), value,
759 dataArea.getMaxX(), value);
760 }
761 else if (orientation == PlotOrientation.VERTICAL) {
762 line = new Line2D.Double(value, dataArea.getMinY(), value,
763 dataArea.getMaxY());
764 }
765
766 Paint paint = plot.getDomainGridlinePaint();
767 if (paint == null) {
768 paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
769 }
770 g2.setPaint(paint);
771
772 Stroke stroke = plot.getDomainGridlineStroke();
773 if (stroke == null) {
774 stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
775 }
776 g2.setStroke(stroke);
777
778 g2.draw(line);
779
780 }
781
782 /**
783 * Draws a grid line against the range axis.
784 *
785 * @param g2 the graphics device.
786 * @param plot the plot.
787 * @param axis the value axis.
788 * @param dataArea the area for plotting data (not yet adjusted for any
789 * 3D effect).
790 * @param value the value at which the grid line should be drawn.
791 *
792 * @see #drawDomainGridline(Graphics2D, CategoryPlot, Rectangle2D, double)
793 *
794 */
795 public void drawRangeGridline(Graphics2D g2,
796 CategoryPlot plot,
797 ValueAxis axis,
798 Rectangle2D dataArea,
799 double value) {
800
801 Range range = axis.getRange();
802 if (!range.contains(value)) {
803 return;
804 }
805
806 PlotOrientation orientation = plot.getOrientation();
807 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
808 Line2D line = null;
809 if (orientation == PlotOrientation.HORIZONTAL) {
810 line = new Line2D.Double(v, dataArea.getMinY(), v,
811 dataArea.getMaxY());
812 }
813 else if (orientation == PlotOrientation.VERTICAL) {
814 line = new Line2D.Double(dataArea.getMinX(), v,
815 dataArea.getMaxX(), v);
816 }
817
818 Paint paint = plot.getRangeGridlinePaint();
819 if (paint == null) {
820 paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
821 }
822 g2.setPaint(paint);
823
824 Stroke stroke = plot.getRangeGridlineStroke();
825 if (stroke == null) {
826 stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
827 }
828 g2.setStroke(stroke);
829
830 g2.draw(line);
831
832 }
833
834 /**
835 * Draws a marker for the domain axis.
836 *
837 * @param g2 the graphics device (not <code>null</code>).
838 * @param plot the plot (not <code>null</code>).
839 * @param axis the range axis (not <code>null</code>).
840 * @param marker the marker to be drawn (not <code>null</code>).
841 * @param dataArea the area inside the axes (not <code>null</code>).
842 *
843 * @see #drawRangeMarker(Graphics2D, CategoryPlot, ValueAxis, Marker,
844 * Rectangle2D)
845 */
846 public void drawDomainMarker(Graphics2D g2,
847 CategoryPlot plot,
848 CategoryAxis axis,
849 CategoryMarker marker,
850 Rectangle2D dataArea) {
851
852 Comparable category = marker.getKey();
853 CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this));
854 int columnIndex = dataset.getColumnIndex(category);
855 if (columnIndex < 0) {
856 return;
857 }
858
859 final Composite savedComposite = g2.getComposite();
860 g2.setComposite(AlphaComposite.getInstance(
861 AlphaComposite.SRC_OVER, marker.getAlpha()));
862
863 PlotOrientation orientation = plot.getOrientation();
864 Rectangle2D bounds = null;
865 if (marker.getDrawAsLine()) {
866 double v = axis.getCategoryMiddle(columnIndex,
867 dataset.getColumnCount(), dataArea,
868 plot.getDomainAxisEdge());
869 Line2D line = null;
870 if (orientation == PlotOrientation.HORIZONTAL) {
871 line = new Line2D.Double(dataArea.getMinX(), v,
872 dataArea.getMaxX(), v);
873 }
874 else if (orientation == PlotOrientation.VERTICAL) {
875 line = new Line2D.Double(v, dataArea.getMinY(), v,
876 dataArea.getMaxY());
877 }
878 g2.setPaint(marker.getPaint());
879 g2.setStroke(marker.getStroke());
880 g2.draw(line);
881 bounds = line.getBounds2D();
882 }
883 else {
884 double v0 = axis.getCategoryStart(columnIndex,
885 dataset.getColumnCount(), dataArea,
886 plot.getDomainAxisEdge());
887 double v1 = axis.getCategoryEnd(columnIndex,
888 dataset.getColumnCount(), dataArea,
889 plot.getDomainAxisEdge());
890 Rectangle2D area = null;
891 if (orientation == PlotOrientation.HORIZONTAL) {
892 area = new Rectangle2D.Double(dataArea.getMinX(), v0,
893 dataArea.getWidth(), (v1 - v0));
894 }
895 else if (orientation == PlotOrientation.VERTICAL) {
896 area = new Rectangle2D.Double(v0, dataArea.getMinY(),
897 (v1 - v0), dataArea.getHeight());
898 }
899 g2.setPaint(marker.getPaint());
900 g2.fill(area);
901 bounds = area;
902 }
903
904 String label = marker.getLabel();
905 RectangleAnchor anchor = marker.getLabelAnchor();
906 if (label != null) {
907 Font labelFont = marker.getLabelFont();
908 g2.setFont(labelFont);
909 g2.setPaint(marker.getLabelPaint());
910 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
911 g2, orientation, dataArea, bounds, marker.getLabelOffset(),
912 marker.getLabelOffsetType(), anchor);
913 TextUtilities.drawAlignedString(label, g2,
914 (float) coordinates.getX(), (float) coordinates.getY(),
915 marker.getLabelTextAnchor());
916 }
917 g2.setComposite(savedComposite);
918 }
919
920 /**
921 * Draws a marker for the range axis.
922 *
923 * @param g2 the graphics device (not <code>null</code>).
924 * @param plot the plot (not <code>null</code>).
925 * @param axis the range axis (not <code>null</code>).
926 * @param marker the marker to be drawn (not <code>null</code>).
927 * @param dataArea the area inside the axes (not <code>null</code>).
928 *
929 * @see #drawDomainMarker(Graphics2D, CategoryPlot, CategoryAxis,
930 * CategoryMarker, Rectangle2D)
931 */
932 public void drawRangeMarker(Graphics2D g2,
933 CategoryPlot plot,
934 ValueAxis axis,
935 Marker marker,
936 Rectangle2D dataArea) {
937
938 if (marker instanceof ValueMarker) {
939 ValueMarker vm = (ValueMarker) marker;
940 double value = vm.getValue();
941 Range range = axis.getRange();
942
943 if (!range.contains(value)) {
944 return;
945 }
946
947 final Composite savedComposite = g2.getComposite();
948 g2.setComposite(AlphaComposite.getInstance(
949 AlphaComposite.SRC_OVER, marker.getAlpha()));
950
951 PlotOrientation orientation = plot.getOrientation();
952 double v = axis.valueToJava2D(value, dataArea,
953 plot.getRangeAxisEdge());
954 Line2D line = null;
955 if (orientation == PlotOrientation.HORIZONTAL) {
956 line = new Line2D.Double(v, dataArea.getMinY(), v,
957 dataArea.getMaxY());
958 }
959 else if (orientation == PlotOrientation.VERTICAL) {
960 line = new Line2D.Double(dataArea.getMinX(), v,
961 dataArea.getMaxX(), v);
962 }
963
964 g2.setPaint(marker.getPaint());
965 g2.setStroke(marker.getStroke());
966 g2.draw(line);
967
968 String label = marker.getLabel();
969 RectangleAnchor anchor = marker.getLabelAnchor();
970 if (label != null) {
971 Font labelFont = marker.getLabelFont();
972 g2.setFont(labelFont);
973 g2.setPaint(marker.getLabelPaint());
974 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
975 g2, orientation, dataArea, line.getBounds2D(),
976 marker.getLabelOffset(), LengthAdjustmentType.EXPAND,
977 anchor);
978 TextUtilities.drawAlignedString(label, g2,
979 (float) coordinates.getX(), (float) coordinates.getY(),
980 marker.getLabelTextAnchor());
981 }
982 g2.setComposite(savedComposite);
983 }
984 else if (marker instanceof IntervalMarker) {
985 IntervalMarker im = (IntervalMarker) marker;
986 double start = im.getStartValue();
987 double end = im.getEndValue();
988 Range range = axis.getRange();
989 if (!(range.intersects(start, end))) {
990 return;
991 }
992
993 final Composite savedComposite = g2.getComposite();
994 g2.setComposite(AlphaComposite.getInstance(
995 AlphaComposite.SRC_OVER, marker.getAlpha()));
996
997 double start2d = axis.valueToJava2D(start, dataArea,
998 plot.getRangeAxisEdge());
999 double end2d = axis.valueToJava2D(end, dataArea,
1000 plot.getRangeAxisEdge());
1001 double low = Math.min(start2d, end2d);
1002 double high = Math.max(start2d, end2d);
1003
1004 PlotOrientation orientation = plot.getOrientation();
1005 Rectangle2D rect = null;
1006 if (orientation == PlotOrientation.HORIZONTAL) {
1007 // clip left and right bounds to data area
1008 low = Math.max(low, dataArea.getMinX());
1009 high = Math.min(high, dataArea.getMaxX());
1010 rect = new Rectangle2D.Double(low,
1011 dataArea.getMinY(), high - low,
1012 dataArea.getHeight());
1013 }
1014 else if (orientation == PlotOrientation.VERTICAL) {
1015 // clip top and bottom bounds to data area
1016 low = Math.max(low, dataArea.getMinY());
1017 high = Math.min(high, dataArea.getMaxY());
1018 rect = new Rectangle2D.Double(dataArea.getMinX(),
1019 low, dataArea.getWidth(),
1020 high - low);
1021 }
1022 Paint p = marker.getPaint();
1023 if (p instanceof GradientPaint) {
1024 GradientPaint gp = (GradientPaint) p;
1025 GradientPaintTransformer t = im.getGradientPaintTransformer();
1026 if (t != null) {
1027 gp = t.transform(gp, rect);
1028 }
1029 g2.setPaint(gp);
1030 }
1031 else {
1032 g2.setPaint(p);
1033 }
1034 g2.fill(rect);
1035
1036 // now draw the outlines, if visible...
1037 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1038 if (orientation == PlotOrientation.VERTICAL) {
1039 Line2D line = new Line2D.Double();
1040 double x0 = dataArea.getMinX();
1041 double x1 = dataArea.getMaxX();
1042 g2.setPaint(im.getOutlinePaint());
1043 g2.setStroke(im.getOutlineStroke());
1044 if (range.contains(start)) {
1045 line.setLine(x0, start2d, x1, start2d);
1046 g2.draw(line);
1047 }
1048 if (range.contains(end)) {
1049 line.setLine(x0, end2d, x1, end2d);
1050 g2.draw(line);
1051 }
1052 }
1053 else { // PlotOrientation.HORIZONTAL
1054 Line2D line = new Line2D.Double();
1055 double y0 = dataArea.getMinY();
1056 double y1 = dataArea.getMaxY();
1057 g2.setPaint(im.getOutlinePaint());
1058 g2.setStroke(im.getOutlineStroke());
1059 if (range.contains(start)) {
1060 line.setLine(start2d, y0, start2d, y1);
1061 g2.draw(line);
1062 }
1063 if (range.contains(end)) {
1064 line.setLine(end2d, y0, end2d, y1);
1065 g2.draw(line);
1066 }
1067 }
1068 }
1069
1070 String label = marker.getLabel();
1071 RectangleAnchor anchor = marker.getLabelAnchor();
1072 if (label != null) {
1073 Font labelFont = marker.getLabelFont();
1074 g2.setFont(labelFont);
1075 g2.setPaint(marker.getLabelPaint());
1076 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1077 g2, orientation, dataArea, rect,
1078 marker.getLabelOffset(), marker.getLabelOffsetType(),
1079 anchor);
1080 TextUtilities.drawAlignedString(label, g2,
1081 (float) coordinates.getX(), (float) coordinates.getY(),
1082 marker.getLabelTextAnchor());
1083 }
1084 g2.setComposite(savedComposite);
1085 }
1086 }
1087
1088 /**
1089 * Calculates the (x, y) coordinates for drawing the label for a marker on
1090 * the range axis.
1091 *
1092 * @param g2 the graphics device.
1093 * @param orientation the plot orientation.
1094 * @param dataArea the data area.
1095 * @param markerArea the rectangle surrounding the marker.
1096 * @param markerOffset the marker offset.
1097 * @param labelOffsetType the label offset type.
1098 * @param anchor the label anchor.
1099 *
1100 * @return The coordinates for drawing the marker label.
1101 */
1102 protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1103 PlotOrientation orientation,
1104 Rectangle2D dataArea,
1105 Rectangle2D markerArea,
1106 RectangleInsets markerOffset,
1107 LengthAdjustmentType labelOffsetType,
1108 RectangleAnchor anchor) {
1109
1110 Rectangle2D anchorRect = null;
1111 if (orientation == PlotOrientation.HORIZONTAL) {
1112 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1113 LengthAdjustmentType.CONTRACT, labelOffsetType);
1114 }
1115 else if (orientation == PlotOrientation.VERTICAL) {
1116 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1117 labelOffsetType, LengthAdjustmentType.CONTRACT);
1118 }
1119 return RectangleAnchor.coordinates(anchorRect, anchor);
1120
1121 }
1122
1123 /**
1124 * Calculates the (x, y) coordinates for drawing a marker label.
1125 *
1126 * @param g2 the graphics device.
1127 * @param orientation the plot orientation.
1128 * @param dataArea the data area.
1129 * @param markerArea the rectangle surrounding the marker.
1130 * @param markerOffset the marker offset.
1131 * @param labelOffsetType the label offset type.
1132 * @param anchor the label anchor.
1133 *
1134 * @return The coordinates for drawing the marker label.
1135 */
1136 protected Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1137 PlotOrientation orientation,
1138 Rectangle2D dataArea,
1139 Rectangle2D markerArea,
1140 RectangleInsets markerOffset,
1141 LengthAdjustmentType labelOffsetType,
1142 RectangleAnchor anchor) {
1143
1144 Rectangle2D anchorRect = null;
1145 if (orientation == PlotOrientation.HORIZONTAL) {
1146 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1147 labelOffsetType, LengthAdjustmentType.CONTRACT);
1148 }
1149 else if (orientation == PlotOrientation.VERTICAL) {
1150 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1151 LengthAdjustmentType.CONTRACT, labelOffsetType);
1152 }
1153 return RectangleAnchor.coordinates(anchorRect, anchor);
1154
1155 }
1156
1157 /**
1158 * Returns a legend item for a series. This default implementation will
1159 * return <code>null</code> if {@link #isSeriesVisible(int)} or
1160 * {@link #isSeriesVisibleInLegend(int)} returns <code>false</code>.
1161 *
1162 * @param datasetIndex the dataset index (zero-based).
1163 * @param series the series index (zero-based).
1164 *
1165 * @return The legend item (possibly <code>null</code>).
1166 *
1167 * @see #getLegendItems()
1168 */
1169 public LegendItem getLegendItem(int datasetIndex, int series) {
1170
1171 CategoryPlot p = getPlot();
1172 if (p == null) {
1173 return null;
1174 }
1175
1176 // check that a legend item needs to be displayed...
1177 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
1178 return null;
1179 }
1180
1181 CategoryDataset dataset = p.getDataset(datasetIndex);
1182 String label = this.legendItemLabelGenerator.generateLabel(dataset,
1183 series);
1184 String description = label;
1185 String toolTipText = null;
1186 if (this.legendItemToolTipGenerator != null) {
1187 toolTipText = this.legendItemToolTipGenerator.generateLabel(
1188 dataset, series);
1189 }
1190 String urlText = null;
1191 if (this.legendItemURLGenerator != null) {
1192 urlText = this.legendItemURLGenerator.generateLabel(dataset,
1193 series);
1194 }
1195 Shape shape = lookupLegendShape(series);
1196 Paint paint = lookupSeriesPaint(series);
1197 Paint outlinePaint = lookupSeriesOutlinePaint(series);
1198 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
1199
1200 LegendItem item = new LegendItem(label, description, toolTipText,
1201 urlText, shape, paint, outlineStroke, outlinePaint);
1202 item.setLabelFont(lookupLegendTextFont(series));
1203 Paint labelPaint = lookupLegendTextPaint(series);
1204 if (labelPaint != null) {
1205 item.setLabelPaint(labelPaint);
1206 }
1207 item.setSeriesKey(dataset.getRowKey(series));
1208 item.setSeriesIndex(series);
1209 item.setDataset(dataset);
1210 item.setDatasetIndex(datasetIndex);
1211 return item;
1212 }
1213
1214 /**
1215 * Tests this renderer for equality with another object.
1216 *
1217 * @param obj the object.
1218 *
1219 * @return <code>true</code> or <code>false</code>.
1220 */
1221 public boolean equals(Object obj) {
1222
1223 if (obj == this) {
1224 return true;
1225 }
1226 if (!(obj instanceof AbstractCategoryItemRenderer)) {
1227 return false;
1228 }
1229 AbstractCategoryItemRenderer that = (AbstractCategoryItemRenderer) obj;
1230
1231 if (!ObjectUtilities.equal(this.itemLabelGenerator,
1232 that.itemLabelGenerator)) {
1233 return false;
1234 }
1235 if (!ObjectUtilities.equal(this.itemLabelGeneratorList,
1236 that.itemLabelGeneratorList)) {
1237 return false;
1238 }
1239 if (!ObjectUtilities.equal(this.baseItemLabelGenerator,
1240 that.baseItemLabelGenerator)) {
1241 return false;
1242 }
1243 if (!ObjectUtilities.equal(this.toolTipGenerator,
1244 that.toolTipGenerator)) {
1245 return false;
1246 }
1247 if (!ObjectUtilities.equal(this.toolTipGeneratorList,
1248 that.toolTipGeneratorList)) {
1249 return false;
1250 }
1251 if (!ObjectUtilities.equal(this.baseToolTipGenerator,
1252 that.baseToolTipGenerator)) {
1253 return false;
1254 }
1255 if (!ObjectUtilities.equal(this.itemURLGenerator,
1256 that.itemURLGenerator)) {
1257 return false;
1258 }
1259 if (!ObjectUtilities.equal(this.itemURLGeneratorList,
1260 that.itemURLGeneratorList)) {
1261 return false;
1262 }
1263 if (!ObjectUtilities.equal(this.baseItemURLGenerator,
1264 that.baseItemURLGenerator)) {
1265 return false;
1266 }
1267 if (!ObjectUtilities.equal(this.legendItemLabelGenerator,
1268 that.legendItemLabelGenerator)) {
1269 return false;
1270 }
1271 if (!ObjectUtilities.equal(this.legendItemToolTipGenerator,
1272 that.legendItemToolTipGenerator)) {
1273 return false;
1274 }
1275 if (!ObjectUtilities.equal(this.legendItemURLGenerator,
1276 that.legendItemURLGenerator)) {
1277 return false;
1278 }
1279 return super.equals(obj);
1280 }
1281
1282 /**
1283 * Returns a hash code for the renderer.
1284 *
1285 * @return The hash code.
1286 */
1287 public int hashCode() {
1288 int result = super.hashCode();
1289 return result;
1290 }
1291
1292 /**
1293 * Returns the drawing supplier from the plot.
1294 *
1295 * @return The drawing supplier (possibly <code>null</code>).
1296 */
1297 public DrawingSupplier getDrawingSupplier() {
1298 DrawingSupplier result = null;
1299 CategoryPlot cp = getPlot();
1300 if (cp != null) {
1301 result = cp.getDrawingSupplier();
1302 }
1303 return result;
1304 }
1305
1306 /**
1307 * Considers the current (x, y) coordinate and updates the crosshair point
1308 * if it meets the criteria (usually means the (x, y) coordinate is the
1309 * closest to the anchor point so far).
1310 *
1311 * @param crosshairState the crosshair state (<code>null</code> permitted,
1312 * but the method does nothing in that case).
1313 * @param rowKey the row key.
1314 * @param columnKey the column key.
1315 * @param value the data value.
1316 * @param datasetIndex the dataset index.
1317 * @param transX the x-value translated to Java2D space.
1318 * @param transY the y-value translated to Java2D space.
1319 * @param orientation the plot orientation (<code>null</code> not
1320 * permitted).
1321 *
1322 * @since 1.0.11
1323 */
1324 protected void updateCrosshairValues(CategoryCrosshairState crosshairState,
1325 Comparable rowKey, Comparable columnKey, double value,
1326 int datasetIndex,
1327 double transX, double transY, PlotOrientation orientation) {
1328
1329 if (orientation == null) {
1330 throw new IllegalArgumentException("Null 'orientation' argument.");
1331 }
1332
1333 if (crosshairState != null) {
1334 if (this.plot.isRangeCrosshairLockedOnData()) {
1335 // both axes
1336 crosshairState.updateCrosshairPoint(rowKey, columnKey, value,
1337 datasetIndex, transX, transY, orientation);
1338 }
1339 else {
1340 crosshairState.updateCrosshairX(rowKey, columnKey,
1341 datasetIndex, transX, orientation);
1342 }
1343 }
1344 }
1345
1346 /**
1347 * Draws an item label.
1348 *
1349 * @param g2 the graphics device.
1350 * @param orientation the orientation.
1351 * @param dataset the dataset.
1352 * @param row the row.
1353 * @param column the column.
1354 * @param x the x coordinate (in Java2D space).
1355 * @param y the y coordinate (in Java2D space).
1356 * @param negative indicates a negative value (which affects the item
1357 * label position).
1358 */
1359 protected void drawItemLabel(Graphics2D g2,
1360 PlotOrientation orientation,
1361 CategoryDataset dataset,
1362 int row, int column,
1363 double x, double y,
1364 boolean negative) {
1365
1366 CategoryItemLabelGenerator generator
1367 = getItemLabelGenerator(row, column);
1368 if (generator != null) {
1369 Font labelFont = getItemLabelFont(row, column);
1370 Paint paint = getItemLabelPaint(row, column);
1371 g2.setFont(labelFont);
1372 g2.setPaint(paint);
1373 String label = generator.generateLabel(dataset, row, column);
1374 ItemLabelPosition position = null;
1375 if (!negative) {
1376 position = getPositiveItemLabelPosition(row, column);
1377 }
1378 else {
1379 position = getNegativeItemLabelPosition(row, column);
1380 }
1381 Point2D anchorPoint = calculateLabelAnchorPoint(
1382 position.getItemLabelAnchor(), x, y, orientation);
1383 TextUtilities.drawRotatedString(label, g2,
1384 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1385 position.getTextAnchor(),
1386 position.getAngle(), position.getRotationAnchor());
1387 }
1388
1389 }
1390
1391 /**
1392 * Returns an independent copy of the renderer. The <code>plot</code>
1393 * reference is shallow copied.
1394 *
1395 * @return A clone.
1396 *
1397 * @throws CloneNotSupportedException can be thrown if one of the objects
1398 * belonging to the renderer does not support cloning (for example,
1399 * an item label generator).
1400 */
1401 public Object clone() throws CloneNotSupportedException {
1402
1403 AbstractCategoryItemRenderer clone
1404 = (AbstractCategoryItemRenderer) super.clone();
1405
1406 if (this.itemLabelGenerator != null) {
1407 if (this.itemLabelGenerator instanceof PublicCloneable) {
1408 PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator;
1409 clone.itemLabelGenerator
1410 = (CategoryItemLabelGenerator) pc.clone();
1411 }
1412 else {
1413 throw new CloneNotSupportedException(
1414 "ItemLabelGenerator not cloneable.");
1415 }
1416 }
1417
1418 if (this.itemLabelGeneratorList != null) {
1419 clone.itemLabelGeneratorList
1420 = (ObjectList) this.itemLabelGeneratorList.clone();
1421 }
1422
1423 if (this.baseItemLabelGenerator != null) {
1424 if (this.baseItemLabelGenerator instanceof PublicCloneable) {
1425 PublicCloneable pc
1426 = (PublicCloneable) this.baseItemLabelGenerator;
1427 clone.baseItemLabelGenerator
1428 = (CategoryItemLabelGenerator) pc.clone();
1429 }
1430 else {
1431 throw new CloneNotSupportedException(
1432 "ItemLabelGenerator not cloneable.");
1433 }
1434 }
1435
1436 if (this.toolTipGenerator != null) {
1437 if (this.toolTipGenerator instanceof PublicCloneable) {
1438 PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
1439 clone.toolTipGenerator = (CategoryToolTipGenerator) pc.clone();
1440 }
1441 else {
1442 throw new CloneNotSupportedException(
1443 "Tool tip generator not cloneable.");
1444 }
1445 }
1446
1447 if (this.toolTipGeneratorList != null) {
1448 clone.toolTipGeneratorList
1449 = (ObjectList) this.toolTipGeneratorList.clone();
1450 }
1451
1452 if (this.baseToolTipGenerator != null) {
1453 if (this.baseToolTipGenerator instanceof PublicCloneable) {
1454 PublicCloneable pc
1455 = (PublicCloneable) this.baseToolTipGenerator;
1456 clone.baseToolTipGenerator
1457 = (CategoryToolTipGenerator) pc.clone();
1458 }
1459 else {
1460 throw new CloneNotSupportedException(
1461 "Base tool tip generator not cloneable.");
1462 }
1463 }
1464
1465 if (this.itemURLGenerator != null) {
1466 if (this.itemURLGenerator instanceof PublicCloneable) {
1467 PublicCloneable pc = (PublicCloneable) this.itemURLGenerator;
1468 clone.itemURLGenerator = (CategoryURLGenerator) pc.clone();
1469 }
1470 else {
1471 throw new CloneNotSupportedException(
1472 "Item URL generator not cloneable.");
1473 }
1474 }
1475
1476 if (this.itemURLGeneratorList != null) {
1477 clone.itemURLGeneratorList
1478 = (ObjectList) this.itemURLGeneratorList.clone();
1479 }
1480
1481 if (this.baseItemURLGenerator != null) {
1482 if (this.baseItemURLGenerator instanceof PublicCloneable) {
1483 PublicCloneable pc
1484 = (PublicCloneable) this.baseItemURLGenerator;
1485 clone.baseItemURLGenerator = (CategoryURLGenerator) pc.clone();
1486 }
1487 else {
1488 throw new CloneNotSupportedException(
1489 "Base item URL generator not cloneable.");
1490 }
1491 }
1492
1493 if (this.legendItemLabelGenerator instanceof PublicCloneable) {
1494 clone.legendItemLabelGenerator = (CategorySeriesLabelGenerator)
1495 ObjectUtilities.clone(this.legendItemLabelGenerator);
1496 }
1497 if (this.legendItemToolTipGenerator instanceof PublicCloneable) {
1498 clone.legendItemToolTipGenerator = (CategorySeriesLabelGenerator)
1499 ObjectUtilities.clone(this.legendItemToolTipGenerator);
1500 }
1501 if (this.legendItemURLGenerator instanceof PublicCloneable) {
1502 clone.legendItemURLGenerator = (CategorySeriesLabelGenerator)
1503 ObjectUtilities.clone(this.legendItemURLGenerator);
1504 }
1505 return clone;
1506 }
1507
1508 /**
1509 * Returns a domain axis for a plot.
1510 *
1511 * @param plot the plot.
1512 * @param index the axis index.
1513 *
1514 * @return A domain axis.
1515 */
1516 protected CategoryAxis getDomainAxis(CategoryPlot plot, int index) {
1517 CategoryAxis result = plot.getDomainAxis(index);
1518 if (result == null) {
1519 result = plot.getDomainAxis();
1520 }
1521 return result;
1522 }
1523
1524 /**
1525 * Returns a range axis for a plot.
1526 *
1527 * @param plot the plot.
1528 * @param index the axis index.
1529 *
1530 * @return A range axis.
1531 */
1532 protected ValueAxis getRangeAxis(CategoryPlot plot, int index) {
1533 ValueAxis result = plot.getRangeAxis(index);
1534 if (result == null) {
1535 result = plot.getRangeAxis();
1536 }
1537 return result;
1538 }
1539
1540 /**
1541 * Returns a (possibly empty) collection of legend items for the series
1542 * that this renderer is responsible for drawing.
1543 *
1544 * @return The legend item collection (never <code>null</code>).
1545 *
1546 * @see #getLegendItem(int, int)
1547 */
1548 public LegendItemCollection getLegendItems() {
1549 if (this.plot == null) {
1550 return new LegendItemCollection();
1551 }
1552 LegendItemCollection result = new LegendItemCollection();
1553 int index = this.plot.getIndexOf(this);
1554 CategoryDataset dataset = this.plot.getDataset(index);
1555 if (dataset != null) {
1556 int seriesCount = dataset.getRowCount();
1557 for (int i = 0; i < seriesCount; i++) {
1558 if (isSeriesVisibleInLegend(i)) {
1559 LegendItem item = getLegendItem(index, i);
1560 if (item != null) {
1561 result.add(item);
1562 }
1563 }
1564 }
1565
1566 }
1567 return result;
1568 }
1569
1570 /**
1571 * Returns the legend item label generator.
1572 *
1573 * @return The label generator (never <code>null</code>).
1574 *
1575 * @see #setLegendItemLabelGenerator(CategorySeriesLabelGenerator)
1576 */
1577 public CategorySeriesLabelGenerator getLegendItemLabelGenerator() {
1578 return this.legendItemLabelGenerator;
1579 }
1580
1581 /**
1582 * Sets the legend item label generator and sends a
1583 * {@link RendererChangeEvent} to all registered listeners.
1584 *
1585 * @param generator the generator (<code>null</code> not permitted).
1586 *
1587 * @see #getLegendItemLabelGenerator()
1588 */
1589 public void setLegendItemLabelGenerator(
1590 CategorySeriesLabelGenerator generator) {
1591 if (generator == null) {
1592 throw new IllegalArgumentException("Null 'generator' argument.");
1593 }
1594 this.legendItemLabelGenerator = generator;
1595 fireChangeEvent();
1596 }
1597
1598 /**
1599 * Returns the legend item tool tip generator.
1600 *
1601 * @return The tool tip generator (possibly <code>null</code>).
1602 *
1603 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1604 */
1605 public CategorySeriesLabelGenerator getLegendItemToolTipGenerator() {
1606 return this.legendItemToolTipGenerator;
1607 }
1608
1609 /**
1610 * Sets the legend item tool tip generator and sends a
1611 * {@link RendererChangeEvent} to all registered listeners.
1612 *
1613 * @param generator the generator (<code>null</code> permitted).
1614 *
1615 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1616 */
1617 public void setLegendItemToolTipGenerator(
1618 CategorySeriesLabelGenerator generator) {
1619 this.legendItemToolTipGenerator = generator;
1620 fireChangeEvent();
1621 }
1622
1623 /**
1624 * Returns the legend item URL generator.
1625 *
1626 * @return The URL generator (possibly <code>null</code>).
1627 *
1628 * @see #setLegendItemURLGenerator(CategorySeriesLabelGenerator)
1629 */
1630 public CategorySeriesLabelGenerator getLegendItemURLGenerator() {
1631 return this.legendItemURLGenerator;
1632 }
1633
1634 /**
1635 * Sets the legend item URL generator and sends a
1636 * {@link RendererChangeEvent} to all registered listeners.
1637 *
1638 * @param generator the generator (<code>null</code> permitted).
1639 *
1640 * @see #getLegendItemURLGenerator()
1641 */
1642 public void setLegendItemURLGenerator(
1643 CategorySeriesLabelGenerator generator) {
1644 this.legendItemURLGenerator = generator;
1645 fireChangeEvent();
1646 }
1647
1648 /**
1649 * Adds an entity with the specified hotspot.
1650 *
1651 * @param entities the entity collection.
1652 * @param dataset the dataset.
1653 * @param row the row index.
1654 * @param column the column index.
1655 * @param hotspot the hotspot.
1656 */
1657 protected void addItemEntity(EntityCollection entities,
1658 CategoryDataset dataset, int row, int column,
1659 Shape hotspot) {
1660
1661 String tip = null;
1662 CategoryToolTipGenerator tipster = getToolTipGenerator(row, column);
1663 if (tipster != null) {
1664 tip = tipster.generateToolTip(dataset, row, column);
1665 }
1666 String url = null;
1667 CategoryURLGenerator urlster = getItemURLGenerator(row, column);
1668 if (urlster != null) {
1669 url = urlster.generateURL(dataset, row, column);
1670 }
1671 CategoryItemEntity entity = new CategoryItemEntity(hotspot, tip, url,
1672 dataset, dataset.getRowKey(row), dataset.getColumnKey(column));
1673 entities.add(entity);
1674
1675 }
1676
1677 }