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 * AbstractXYItemRenderer.java
029 * ---------------------------
030 * (C) Copyright 2002-2008, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Richard Atkinson;
034 * Focus Computer Services Limited;
035 * Tim Bardzil;
036 * Sergei Ivanov;
037 *
038 * Changes:
039 * --------
040 * 15-Mar-2002 : Version 1 (DG);
041 * 09-Apr-2002 : Added a getToolTipGenerator() method reflecting the change in
042 * the XYItemRenderer interface (DG);
043 * 05-Aug-2002 : Added a urlGenerator member variable to support HTML image
044 * maps (RA);
045 * 20-Aug-2002 : Added property change events for the tooltip and URL
046 * generators (DG);
047 * 22-Aug-2002 : Moved property change support into AbstractRenderer class (DG);
048 * 23-Sep-2002 : Fixed errors reported by Checkstyle tool (DG);
049 * 18-Nov-2002 : Added methods for drawing grid lines (DG);
050 * 17-Jan-2003 : Moved plot classes into a separate package (DG);
051 * 25-Mar-2003 : Implemented Serializable (DG);
052 * 01-May-2003 : Modified initialise() return type and drawItem() method
053 * signature (DG);
054 * 15-May-2003 : Modified to take into account the plot orientation (DG);
055 * 21-May-2003 : Added labels to markers (DG);
056 * 05-Jun-2003 : Added domain and range grid bands (sponsored by Focus Computer
057 * Services Ltd) (DG);
058 * 27-Jul-2003 : Added getRangeType() to support stacked XY area charts (RA);
059 * 31-Jul-2003 : Deprecated all but the default constructor (DG);
060 * 13-Aug-2003 : Implemented Cloneable (DG);
061 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
062 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
063 * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG);
064 * 11-Feb-2004 : Updated labelling for markers (DG);
065 * 25-Feb-2004 : Added updateCrosshairValues() method. Moved deprecated code
066 * to bottom of source file (DG);
067 * 16-Apr-2004 : Added support for IntervalMarker in drawRangeMarker() method
068 * - thanks to Tim Bardzil (DG);
069 * 05-May-2004 : Fixed bug (948310) where interval markers extend beyond axis
070 * range (DG);
071 * 03-Jun-2004 : Fixed more bugs in drawing interval markers (DG);
072 * 26-Aug-2004 : Added the addEntity() method (DG);
073 * 29-Sep-2004 : Added annotation support (with layers) (DG);
074 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities -->
075 * TextUtilities (DG);
076 * 06-Oct-2004 : Added findDomainBounds() method and renamed
077 * getRangeExtent() --> findRangeBounds() (DG);
078 * 07-Jan-2005 : Removed deprecated code (DG);
079 * 27-Jan-2005 : Modified getLegendItem() to omit hidden series (DG);
080 * 24-Feb-2005 : Added getLegendItems() method (DG);
081 * 08-Mar-2005 : Fixed positioning of marker labels (DG);
082 * 20-Apr-2005 : Renamed XYLabelGenerator --> XYItemLabelGenerator and
083 * added generators for legend labels, tooltips and URLs (DG);
084 * 01-Jun-2005 : Handle one dimension of the marker label adjustment
085 * automatically (DG);
086 * ------------- JFREECHART 1.0.x ---------------------------------------------
087 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
088 * 24-Oct-2006 : Respect alpha setting in markers (see patch 1567843 by Sergei
089 * Ivanov) (DG);
090 * 24-Oct-2006 : Added code to draw outlines for interval markers (DG);
091 * 24-Nov-2006 : Fixed cloning for legend item generators (DG);
092 * 06-Feb-2007 : Added new updateCrosshairValues() method that takes into
093 * account multiple axis plots (see bug 1086307) (DG);
094 * 20-Feb-2007 : Fixed equals() method implementation (DG);
095 * 01-Mar-2007 : Fixed interval marker drawing (patch 1670686 thanks to
096 * Sergei Ivanov) (DG);
097 * 22-Mar-2007 : Modified the tool tip generator look up (DG);
098 * 23-Mar-2007 : Added drawDomainLine() method (DG);
099 * 20-Apr-2007 : Updated getLegendItem() for renderer change, and deprecated
100 * itemLabelGenerator and toolTipGenerator override fields (DG);
101 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
102 * 12-Nov-2007 : Fixed domain and range band drawing methods (DG);
103 * 07-Apr-2008 : Minor API doc update (DG);
104 * 14-May-2008 : Updated addEntity() method to take plot orientation into
105 * account when the incoming area is null (DG);
106 * 02-Jun-2008 : Added isPointInRect() method (DG);
107 *
108 */
109
110 package org.jfree.chart.renderer.xy;
111
112 import java.awt.AlphaComposite;
113 import java.awt.Composite;
114 import java.awt.Font;
115 import java.awt.GradientPaint;
116 import java.awt.Graphics2D;
117 import java.awt.Paint;
118 import java.awt.Shape;
119 import java.awt.Stroke;
120 import java.awt.geom.Ellipse2D;
121 import java.awt.geom.Line2D;
122 import java.awt.geom.Point2D;
123 import java.awt.geom.Rectangle2D;
124 import java.io.Serializable;
125 import java.util.Iterator;
126 import java.util.List;
127
128 import org.jfree.chart.LegendItem;
129 import org.jfree.chart.LegendItemCollection;
130 import org.jfree.chart.annotations.XYAnnotation;
131 import org.jfree.chart.axis.ValueAxis;
132 import org.jfree.chart.entity.EntityCollection;
133 import org.jfree.chart.entity.XYItemEntity;
134 import org.jfree.chart.event.RendererChangeEvent;
135 import org.jfree.chart.labels.ItemLabelPosition;
136 import org.jfree.chart.labels.StandardXYSeriesLabelGenerator;
137 import org.jfree.chart.labels.XYItemLabelGenerator;
138 import org.jfree.chart.labels.XYSeriesLabelGenerator;
139 import org.jfree.chart.labels.XYToolTipGenerator;
140 import org.jfree.chart.plot.CrosshairState;
141 import org.jfree.chart.plot.DrawingSupplier;
142 import org.jfree.chart.plot.IntervalMarker;
143 import org.jfree.chart.plot.Marker;
144 import org.jfree.chart.plot.Plot;
145 import org.jfree.chart.plot.PlotOrientation;
146 import org.jfree.chart.plot.PlotRenderingInfo;
147 import org.jfree.chart.plot.ValueMarker;
148 import org.jfree.chart.plot.XYPlot;
149 import org.jfree.chart.renderer.AbstractRenderer;
150 import org.jfree.chart.urls.XYURLGenerator;
151 import org.jfree.data.Range;
152 import org.jfree.data.general.DatasetUtilities;
153 import org.jfree.data.xy.XYDataset;
154 import org.jfree.text.TextUtilities;
155 import org.jfree.ui.GradientPaintTransformer;
156 import org.jfree.ui.Layer;
157 import org.jfree.ui.LengthAdjustmentType;
158 import org.jfree.ui.RectangleAnchor;
159 import org.jfree.ui.RectangleInsets;
160 import org.jfree.util.ObjectList;
161 import org.jfree.util.ObjectUtilities;
162 import org.jfree.util.PublicCloneable;
163
164 /**
165 * A base class that can be used to create new {@link XYItemRenderer}
166 * implementations.
167 */
168 public abstract class AbstractXYItemRenderer extends AbstractRenderer
169 implements XYItemRenderer, Cloneable, Serializable {
170
171 /** For serialization. */
172 private static final long serialVersionUID = 8019124836026607990L;
173
174 /** The plot. */
175 private XYPlot plot;
176
177 /**
178 * The item label generator for ALL series.
179 *
180 * @deprecated This field is redundant, use itemLabelGeneratorList and
181 * baseItemLabelGenerator instead. Deprecated as of version 1.0.6.
182 */
183 private XYItemLabelGenerator itemLabelGenerator;
184
185 /** A list of item label generators (one per series). */
186 private ObjectList itemLabelGeneratorList;
187
188 /** The base item label generator. */
189 private XYItemLabelGenerator baseItemLabelGenerator;
190
191 /**
192 * The tool tip generator for ALL series.
193 *
194 * @deprecated This field is redundant, use tooltipGeneratorList and
195 * baseToolTipGenerator instead. Deprecated as of version 1.0.6.
196 */
197 private XYToolTipGenerator toolTipGenerator;
198
199 /** A list of tool tip generators (one per series). */
200 private ObjectList toolTipGeneratorList;
201
202 /** The base tool tip generator. */
203 private XYToolTipGenerator baseToolTipGenerator;
204
205 /** The URL text generator. */
206 private XYURLGenerator urlGenerator;
207
208 /**
209 * Annotations to be drawn in the background layer ('underneath' the data
210 * items).
211 */
212 private List backgroundAnnotations;
213
214 /**
215 * Annotations to be drawn in the foreground layer ('on top' of the data
216 * items).
217 */
218 private List foregroundAnnotations;
219
220 /** The default radius for the entity 'hotspot' */
221 private int defaultEntityRadius;
222
223 /** The legend item label generator. */
224 private XYSeriesLabelGenerator legendItemLabelGenerator;
225
226 /** The legend item tool tip generator. */
227 private XYSeriesLabelGenerator legendItemToolTipGenerator;
228
229 /** The legend item URL generator. */
230 private XYSeriesLabelGenerator legendItemURLGenerator;
231
232 /**
233 * Creates a renderer where the tooltip generator and the URL generator are
234 * both <code>null</code>.
235 */
236 protected AbstractXYItemRenderer() {
237 super();
238 this.itemLabelGenerator = null;
239 this.itemLabelGeneratorList = new ObjectList();
240 this.toolTipGenerator = null;
241 this.toolTipGeneratorList = new ObjectList();
242 this.urlGenerator = null;
243 this.backgroundAnnotations = new java.util.ArrayList();
244 this.foregroundAnnotations = new java.util.ArrayList();
245 this.defaultEntityRadius = 3;
246 this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator(
247 "{0}");
248 }
249
250 /**
251 * Returns the number of passes through the data that the renderer requires
252 * in order to draw the chart. Most charts will require a single pass, but
253 * some require two passes.
254 *
255 * @return The pass count.
256 */
257 public int getPassCount() {
258 return 1;
259 }
260
261 /**
262 * Returns the plot that the renderer is assigned to.
263 *
264 * @return The plot (possibly <code>null</code>).
265 */
266 public XYPlot getPlot() {
267 return this.plot;
268 }
269
270 /**
271 * Sets the plot that the renderer is assigned to.
272 *
273 * @param plot the plot (<code>null</code> permitted).
274 */
275 public void setPlot(XYPlot plot) {
276 this.plot = plot;
277 }
278
279 /**
280 * Initialises the renderer and returns a state object that should be
281 * passed to all subsequent calls to the drawItem() method.
282 * <P>
283 * This method will be called before the first item is rendered, giving the
284 * renderer an opportunity to initialise any state information it wants to
285 * maintain. The renderer can do nothing if it chooses.
286 *
287 * @param g2 the graphics device.
288 * @param dataArea the area inside the axes.
289 * @param plot the plot.
290 * @param data the data.
291 * @param info an optional info collection object to return data back to
292 * the caller.
293 *
294 * @return The renderer state (never <code>null</code>).
295 */
296 public XYItemRendererState initialise(Graphics2D g2,
297 Rectangle2D dataArea,
298 XYPlot plot,
299 XYDataset data,
300 PlotRenderingInfo info) {
301
302 XYItemRendererState state = new XYItemRendererState(info);
303 return state;
304
305 }
306
307 // ITEM LABEL GENERATOR
308
309 /**
310 * Returns the label generator for a data item. This implementation simply
311 * passes control to the {@link #getSeriesItemLabelGenerator(int)} method.
312 * If, for some reason, you want a different generator for individual
313 * items, you can override this method.
314 *
315 * @param series the series index (zero based).
316 * @param item the item index (zero based).
317 *
318 * @return The generator (possibly <code>null</code>).
319 */
320 public XYItemLabelGenerator getItemLabelGenerator(int series, int item) {
321 // return the generator for ALL series, if there is one...
322 if (this.itemLabelGenerator != null) {
323 return this.itemLabelGenerator;
324 }
325
326 // otherwise look up the generator table
327 XYItemLabelGenerator generator
328 = (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series);
329 if (generator == null) {
330 generator = this.baseItemLabelGenerator;
331 }
332 return generator;
333 }
334
335 /**
336 * Returns the item label generator for a series.
337 *
338 * @param series the series index (zero based).
339 *
340 * @return The generator (possibly <code>null</code>).
341 */
342 public XYItemLabelGenerator getSeriesItemLabelGenerator(int series) {
343 return (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series);
344 }
345
346 /**
347 * Returns the item label generator override.
348 *
349 * @return The generator (possibly <code>null</code>).
350 *
351 * @since 1.0.5
352 *
353 * @see #setItemLabelGenerator(XYItemLabelGenerator)
354 *
355 * @deprecated As of version 1.0.6, this override setting should not be
356 * used. You can use the base setting instead
357 * ({@link #getBaseItemLabelGenerator()}).
358 */
359 public XYItemLabelGenerator getItemLabelGenerator() {
360 return this.itemLabelGenerator;
361 }
362
363 /**
364 * Sets the item label generator for ALL series and sends a
365 * {@link RendererChangeEvent} to all registered listeners.
366 *
367 * @param generator the generator (<code>null</code> permitted).
368 *
369 * @see #getItemLabelGenerator()
370 *
371 * @deprecated As of version 1.0.6, this override setting should not be
372 * used. You can use the base setting instead
373 * ({@link #setBaseItemLabelGenerator(XYItemLabelGenerator)}).
374 */
375 public void setItemLabelGenerator(XYItemLabelGenerator generator) {
376 this.itemLabelGenerator = generator;
377 fireChangeEvent();
378 }
379
380 /**
381 * Sets the item label generator for a series and sends a
382 * {@link RendererChangeEvent} to all registered listeners.
383 *
384 * @param series the series index (zero based).
385 * @param generator the generator (<code>null</code> permitted).
386 */
387 public void setSeriesItemLabelGenerator(int series,
388 XYItemLabelGenerator generator) {
389 this.itemLabelGeneratorList.set(series, generator);
390 fireChangeEvent();
391 }
392
393 /**
394 * Returns the base item label generator.
395 *
396 * @return The generator (possibly <code>null</code>).
397 */
398 public XYItemLabelGenerator getBaseItemLabelGenerator() {
399 return this.baseItemLabelGenerator;
400 }
401
402 /**
403 * Sets the base item label generator and sends a
404 * {@link RendererChangeEvent} to all registered listeners.
405 *
406 * @param generator the generator (<code>null</code> permitted).
407 */
408 public void setBaseItemLabelGenerator(XYItemLabelGenerator generator) {
409 this.baseItemLabelGenerator = generator;
410 fireChangeEvent();
411 }
412
413 // TOOL TIP GENERATOR
414
415 /**
416 * Returns the tool tip generator for a data item. If, for some reason,
417 * you want a different generator for individual items, you can override
418 * this method.
419 *
420 * @param series the series index (zero based).
421 * @param item the item index (zero based).
422 *
423 * @return The generator (possibly <code>null</code>).
424 */
425 public XYToolTipGenerator getToolTipGenerator(int series, int item) {
426 // return the generator for ALL series, if there is one...
427 if (this.toolTipGenerator != null) {
428 return this.toolTipGenerator;
429 }
430
431 // otherwise look up the generator table
432 XYToolTipGenerator generator
433 = (XYToolTipGenerator) this.toolTipGeneratorList.get(series);
434 if (generator == null) {
435 generator = this.baseToolTipGenerator;
436 }
437 return generator;
438 }
439
440 /**
441 * Returns the override tool tip generator.
442 *
443 * @return The tool tip generator (possible <code>null</code>).
444 *
445 * @since 1.0.5
446 *
447 * @see #setToolTipGenerator(XYToolTipGenerator)
448 *
449 * @deprecated As of version 1.0.6, this override setting should not be
450 * used. You can use the base setting instead
451 * ({@link #getBaseToolTipGenerator()}).
452 */
453 public XYToolTipGenerator getToolTipGenerator() {
454 return this.toolTipGenerator;
455 }
456
457 /**
458 * Sets the tool tip generator for ALL series and sends a
459 * {@link RendererChangeEvent} to all registered listeners.
460 *
461 * @param generator the generator (<code>null</code> permitted).
462 *
463 * @see #getToolTipGenerator()
464 *
465 * @deprecated As of version 1.0.6, this override setting should not be
466 * used. You can use the base setting instead
467 * ({@link #setBaseToolTipGenerator(XYToolTipGenerator)}).
468 */
469 public void setToolTipGenerator(XYToolTipGenerator generator) {
470 this.toolTipGenerator = generator;
471 fireChangeEvent();
472 }
473
474 /**
475 * Returns the tool tip generator for a series.
476 *
477 * @param series the series index (zero based).
478 *
479 * @return The generator (possibly <code>null</code>).
480 */
481 public XYToolTipGenerator getSeriesToolTipGenerator(int series) {
482 return (XYToolTipGenerator) this.toolTipGeneratorList.get(series);
483 }
484
485 /**
486 * Sets the tool tip generator for a series and sends a
487 * {@link RendererChangeEvent} to all registered listeners.
488 *
489 * @param series the series index (zero based).
490 * @param generator the generator (<code>null</code> permitted).
491 */
492 public void setSeriesToolTipGenerator(int series,
493 XYToolTipGenerator generator) {
494 this.toolTipGeneratorList.set(series, generator);
495 fireChangeEvent();
496 }
497
498 /**
499 * Returns the base tool tip generator.
500 *
501 * @return The generator (possibly <code>null</code>).
502 *
503 * @see #setBaseToolTipGenerator(XYToolTipGenerator)
504 */
505 public XYToolTipGenerator getBaseToolTipGenerator() {
506 return this.baseToolTipGenerator;
507 }
508
509 /**
510 * Sets the base tool tip generator and sends a {@link RendererChangeEvent}
511 * to all registered listeners.
512 *
513 * @param generator the generator (<code>null</code> permitted).
514 *
515 * @see #getBaseToolTipGenerator()
516 */
517 public void setBaseToolTipGenerator(XYToolTipGenerator generator) {
518 this.baseToolTipGenerator = generator;
519 fireChangeEvent();
520 }
521
522 // URL GENERATOR
523
524 /**
525 * Returns the URL generator for HTML image maps.
526 *
527 * @return The URL generator (possibly <code>null</code>).
528 */
529 public XYURLGenerator getURLGenerator() {
530 return this.urlGenerator;
531 }
532
533 /**
534 * Sets the URL generator for HTML image maps and sends a
535 * {@link RendererChangeEvent} to all registered listeners.
536 *
537 * @param urlGenerator the URL generator (<code>null</code> permitted).
538 */
539 public void setURLGenerator(XYURLGenerator urlGenerator) {
540 this.urlGenerator = urlGenerator;
541 fireChangeEvent();
542 }
543
544 /**
545 * Adds an annotation and sends a {@link RendererChangeEvent} to all
546 * registered listeners. The annotation is added to the foreground
547 * layer.
548 *
549 * @param annotation the annotation (<code>null</code> not permitted).
550 */
551 public void addAnnotation(XYAnnotation annotation) {
552 // defer argument checking
553 addAnnotation(annotation, Layer.FOREGROUND);
554 }
555
556 /**
557 * Adds an annotation to the specified layer and sends a
558 * {@link RendererChangeEvent} to all registered listeners.
559 *
560 * @param annotation the annotation (<code>null</code> not permitted).
561 * @param layer the layer (<code>null</code> not permitted).
562 */
563 public void addAnnotation(XYAnnotation annotation, Layer layer) {
564 if (annotation == null) {
565 throw new IllegalArgumentException("Null 'annotation' argument.");
566 }
567 if (layer.equals(Layer.FOREGROUND)) {
568 this.foregroundAnnotations.add(annotation);
569 fireChangeEvent();
570 }
571 else if (layer.equals(Layer.BACKGROUND)) {
572 this.backgroundAnnotations.add(annotation);
573 fireChangeEvent();
574 }
575 else {
576 // should never get here
577 throw new RuntimeException("Unknown layer.");
578 }
579 }
580 /**
581 * Removes the specified annotation and sends a {@link RendererChangeEvent}
582 * to all registered listeners.
583 *
584 * @param annotation the annotation to remove (<code>null</code> not
585 * permitted).
586 *
587 * @return A boolean to indicate whether or not the annotation was
588 * successfully removed.
589 */
590 public boolean removeAnnotation(XYAnnotation annotation) {
591 boolean removed = this.foregroundAnnotations.remove(annotation);
592 removed = removed & this.backgroundAnnotations.remove(annotation);
593 fireChangeEvent();
594 return removed;
595 }
596
597 /**
598 * Removes all annotations and sends a {@link RendererChangeEvent}
599 * to all registered listeners.
600 */
601 public void removeAnnotations() {
602 this.foregroundAnnotations.clear();
603 this.backgroundAnnotations.clear();
604 fireChangeEvent();
605 }
606
607 /**
608 * Returns the radius of the circle used for the default entity area
609 * when no area is specified.
610 *
611 * @return A radius.
612 */
613 public int getDefaultEntityRadius() {
614 return this.defaultEntityRadius;
615 }
616
617 /**
618 * Sets the radius of the circle used for the default entity area
619 * when no area is specified.
620 *
621 * @param radius the radius.
622 */
623 public void setDefaultEntityRadius(int radius) {
624 this.defaultEntityRadius = radius;
625 }
626
627 /**
628 * Returns the legend item label generator.
629 *
630 * @return The label generator (never <code>null</code>).
631 *
632 * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator)
633 */
634 public XYSeriesLabelGenerator getLegendItemLabelGenerator() {
635 return this.legendItemLabelGenerator;
636 }
637
638 /**
639 * Sets the legend item label generator and sends a
640 * {@link RendererChangeEvent} to all registered listeners.
641 *
642 * @param generator the generator (<code>null</code> not permitted).
643 *
644 * @see #getLegendItemLabelGenerator()
645 */
646 public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) {
647 if (generator == null) {
648 throw new IllegalArgumentException("Null 'generator' argument.");
649 }
650 this.legendItemLabelGenerator = generator;
651 fireChangeEvent();
652 }
653
654 /**
655 * Returns the legend item tool tip generator.
656 *
657 * @return The tool tip generator (possibly <code>null</code>).
658 *
659 * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator)
660 */
661 public XYSeriesLabelGenerator getLegendItemToolTipGenerator() {
662 return this.legendItemToolTipGenerator;
663 }
664
665 /**
666 * Sets the legend item tool tip generator and sends a
667 * {@link RendererChangeEvent} to all registered listeners.
668 *
669 * @param generator the generator (<code>null</code> permitted).
670 *
671 * @see #getLegendItemToolTipGenerator()
672 */
673 public void setLegendItemToolTipGenerator(
674 XYSeriesLabelGenerator generator) {
675 this.legendItemToolTipGenerator = generator;
676 fireChangeEvent();
677 }
678
679 /**
680 * Returns the legend item URL generator.
681 *
682 * @return The URL generator (possibly <code>null</code>).
683 *
684 * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator)
685 */
686 public XYSeriesLabelGenerator getLegendItemURLGenerator() {
687 return this.legendItemURLGenerator;
688 }
689
690 /**
691 * Sets the legend item URL generator and sends a
692 * {@link RendererChangeEvent} to all registered listeners.
693 *
694 * @param generator the generator (<code>null</code> permitted).
695 *
696 * @see #getLegendItemURLGenerator()
697 */
698 public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) {
699 this.legendItemURLGenerator = generator;
700 fireChangeEvent();
701 }
702
703 /**
704 * Returns the lower and upper bounds (range) of the x-values in the
705 * specified dataset.
706 *
707 * @param dataset the dataset (<code>null</code> permitted).
708 *
709 * @return The range (<code>null</code> if the dataset is <code>null</code>
710 * or empty).
711 */
712 public Range findDomainBounds(XYDataset dataset) {
713 if (dataset != null) {
714 return DatasetUtilities.findDomainBounds(dataset, false);
715 }
716 else {
717 return null;
718 }
719 }
720
721 /**
722 * Returns the range of values the renderer requires to display all the
723 * items from the specified dataset.
724 *
725 * @param dataset the dataset (<code>null</code> permitted).
726 *
727 * @return The range (<code>null</code> if the dataset is <code>null</code>
728 * or empty).
729 */
730 public Range findRangeBounds(XYDataset dataset) {
731 if (dataset != null) {
732 return DatasetUtilities.findRangeBounds(dataset, false);
733 }
734 else {
735 return null;
736 }
737 }
738
739 /**
740 * Returns a (possibly empty) collection of legend items for the series
741 * that this renderer is responsible for drawing.
742 *
743 * @return The legend item collection (never <code>null</code>).
744 */
745 public LegendItemCollection getLegendItems() {
746 if (this.plot == null) {
747 return new LegendItemCollection();
748 }
749 LegendItemCollection result = new LegendItemCollection();
750 int index = this.plot.getIndexOf(this);
751 XYDataset dataset = this.plot.getDataset(index);
752 if (dataset != null) {
753 int seriesCount = dataset.getSeriesCount();
754 for (int i = 0; i < seriesCount; i++) {
755 if (isSeriesVisibleInLegend(i)) {
756 LegendItem item = getLegendItem(index, i);
757 if (item != null) {
758 result.add(item);
759 }
760 }
761 }
762
763 }
764 return result;
765 }
766
767 /**
768 * Returns a default legend item for the specified series. Subclasses
769 * should override this method to generate customised items.
770 *
771 * @param datasetIndex the dataset index (zero-based).
772 * @param series the series index (zero-based).
773 *
774 * @return A legend item for the series.
775 */
776 public LegendItem getLegendItem(int datasetIndex, int series) {
777 LegendItem result = null;
778 XYPlot xyplot = getPlot();
779 if (xyplot != null) {
780 XYDataset dataset = xyplot.getDataset(datasetIndex);
781 if (dataset != null) {
782 String label = this.legendItemLabelGenerator.generateLabel(
783 dataset, series);
784 String description = label;
785 String toolTipText = null;
786 if (getLegendItemToolTipGenerator() != null) {
787 toolTipText = getLegendItemToolTipGenerator().generateLabel(
788 dataset, series);
789 }
790 String urlText = null;
791 if (getLegendItemURLGenerator() != null) {
792 urlText = getLegendItemURLGenerator().generateLabel(
793 dataset, series);
794 }
795 Shape shape = lookupSeriesShape(series);
796 Paint paint = lookupSeriesPaint(series);
797 Paint outlinePaint = lookupSeriesOutlinePaint(series);
798 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
799 result = new LegendItem(label, description, toolTipText,
800 urlText, shape, paint, outlineStroke, outlinePaint);
801 result.setSeriesKey(dataset.getSeriesKey(series));
802 result.setSeriesIndex(series);
803 result.setDataset(dataset);
804 result.setDatasetIndex(datasetIndex);
805 }
806 }
807 return result;
808 }
809
810 /**
811 * Fills a band between two values on the axis. This can be used to color
812 * bands between the grid lines.
813 *
814 * @param g2 the graphics device.
815 * @param plot the plot.
816 * @param axis the domain axis.
817 * @param dataArea the data area.
818 * @param start the start value.
819 * @param end the end value.
820 */
821 public void fillDomainGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis,
822 Rectangle2D dataArea, double start, double end) {
823
824 double x1 = axis.valueToJava2D(start, dataArea,
825 plot.getDomainAxisEdge());
826 double x2 = axis.valueToJava2D(end, dataArea,
827 plot.getDomainAxisEdge());
828 Rectangle2D band;
829 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
830 band = new Rectangle2D.Double(Math.min(x1, x2), dataArea.getMinY(),
831 Math.abs(x2 - x1), dataArea.getWidth());
832 }
833 else {
834 band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(x1, x2),
835 dataArea.getWidth(), Math.abs(x2 - x1));
836 }
837 Paint paint = plot.getDomainTickBandPaint();
838
839 if (paint != null) {
840 g2.setPaint(paint);
841 g2.fill(band);
842 }
843
844 }
845
846 /**
847 * Fills a band between two values on the range axis. This can be used to
848 * color bands between the grid lines.
849 *
850 * @param g2 the graphics device.
851 * @param plot the plot.
852 * @param axis the range axis.
853 * @param dataArea the data area.
854 * @param start the start value.
855 * @param end the end value.
856 */
857 public void fillRangeGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis,
858 Rectangle2D dataArea, double start, double end) {
859
860 double y1 = axis.valueToJava2D(start, dataArea,
861 plot.getRangeAxisEdge());
862 double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge());
863 Rectangle2D band;
864 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
865 band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(y1, y2),
866 dataArea.getWidth(), Math.abs(y2 - y1));
867 }
868 else {
869 band = new Rectangle2D.Double(Math.min(y1, y2), dataArea.getMinY(),
870 Math.abs(y2 - y1), dataArea.getHeight());
871 }
872 Paint paint = plot.getRangeTickBandPaint();
873
874 if (paint != null) {
875 g2.setPaint(paint);
876 g2.fill(band);
877 }
878
879 }
880
881 /**
882 * Draws a grid line against the range axis.
883 *
884 * @param g2 the graphics device.
885 * @param plot the plot.
886 * @param axis the value axis.
887 * @param dataArea the area for plotting data (not yet adjusted for any
888 * 3D effect).
889 * @param value the value at which the grid line should be drawn.
890 */
891 public void drawDomainGridLine(Graphics2D g2,
892 XYPlot plot,
893 ValueAxis axis,
894 Rectangle2D dataArea,
895 double value) {
896
897 Range range = axis.getRange();
898 if (!range.contains(value)) {
899 return;
900 }
901
902 PlotOrientation orientation = plot.getOrientation();
903 double v = axis.valueToJava2D(value, dataArea,
904 plot.getDomainAxisEdge());
905 Line2D line = null;
906 if (orientation == PlotOrientation.HORIZONTAL) {
907 line = new Line2D.Double(dataArea.getMinX(), v,
908 dataArea.getMaxX(), v);
909 }
910 else if (orientation == PlotOrientation.VERTICAL) {
911 line = new Line2D.Double(v, dataArea.getMinY(), v,
912 dataArea.getMaxY());
913 }
914
915 Paint paint = plot.getDomainGridlinePaint();
916 Stroke stroke = plot.getDomainGridlineStroke();
917 g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
918 g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
919 g2.draw(line);
920
921 }
922
923 /**
924 * Draws a line perpendicular to the domain axis.
925 *
926 * @param g2 the graphics device.
927 * @param plot the plot.
928 * @param axis the value axis.
929 * @param dataArea the area for plotting data (not yet adjusted for any 3D
930 * effect).
931 * @param value the value at which the grid line should be drawn.
932 * @param paint the paint.
933 * @param stroke the stroke.
934 *
935 * @since 1.0.5
936 */
937 public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis,
938 Rectangle2D dataArea, double value, Paint paint, Stroke stroke) {
939
940 Range range = axis.getRange();
941 if (!range.contains(value)) {
942 return;
943 }
944
945 PlotOrientation orientation = plot.getOrientation();
946 Line2D line = null;
947 double v = axis.valueToJava2D(value, dataArea,
948 plot.getDomainAxisEdge());
949 if (orientation == PlotOrientation.HORIZONTAL) {
950 line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(),
951 v);
952 }
953 else if (orientation == PlotOrientation.VERTICAL) {
954 line = new Line2D.Double(v, dataArea.getMinY(), v,
955 dataArea.getMaxY());
956 }
957
958 g2.setPaint(paint);
959 g2.setStroke(stroke);
960 g2.draw(line);
961
962 }
963
964 /**
965 * Draws a line perpendicular to the range axis.
966 *
967 * @param g2 the graphics device.
968 * @param plot the plot.
969 * @param axis the value axis.
970 * @param dataArea the area for plotting data (not yet adjusted for any 3D
971 * effect).
972 * @param value the value at which the grid line should be drawn.
973 * @param paint the paint.
974 * @param stroke the stroke.
975 */
976 public void drawRangeLine(Graphics2D g2,
977 XYPlot plot,
978 ValueAxis axis,
979 Rectangle2D dataArea,
980 double value,
981 Paint paint,
982 Stroke stroke) {
983
984 Range range = axis.getRange();
985 if (!range.contains(value)) {
986 return;
987 }
988
989 PlotOrientation orientation = plot.getOrientation();
990 Line2D line = null;
991 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
992 if (orientation == PlotOrientation.HORIZONTAL) {
993 line = new Line2D.Double(v, dataArea.getMinY(), v,
994 dataArea.getMaxY());
995 }
996 else if (orientation == PlotOrientation.VERTICAL) {
997 line = new Line2D.Double(dataArea.getMinX(), v,
998 dataArea.getMaxX(), v);
999 }
1000
1001 g2.setPaint(paint);
1002 g2.setStroke(stroke);
1003 g2.draw(line);
1004
1005 }
1006
1007 /**
1008 * Draws a vertical line on the chart to represent a 'range marker'.
1009 *
1010 * @param g2 the graphics device.
1011 * @param plot the plot.
1012 * @param domainAxis the domain axis.
1013 * @param marker the marker line.
1014 * @param dataArea the axis data area.
1015 */
1016 public void drawDomainMarker(Graphics2D g2,
1017 XYPlot plot,
1018 ValueAxis domainAxis,
1019 Marker marker,
1020 Rectangle2D dataArea) {
1021
1022 if (marker instanceof ValueMarker) {
1023 ValueMarker vm = (ValueMarker) marker;
1024 double value = vm.getValue();
1025 Range range = domainAxis.getRange();
1026 if (!range.contains(value)) {
1027 return;
1028 }
1029
1030 double v = domainAxis.valueToJava2D(value, dataArea,
1031 plot.getDomainAxisEdge());
1032
1033 PlotOrientation orientation = plot.getOrientation();
1034 Line2D line = null;
1035 if (orientation == PlotOrientation.HORIZONTAL) {
1036 line = new Line2D.Double(dataArea.getMinX(), v,
1037 dataArea.getMaxX(), v);
1038 }
1039 else if (orientation == PlotOrientation.VERTICAL) {
1040 line = new Line2D.Double(v, dataArea.getMinY(), v,
1041 dataArea.getMaxY());
1042 }
1043
1044 final Composite originalComposite = g2.getComposite();
1045 g2.setComposite(AlphaComposite.getInstance(
1046 AlphaComposite.SRC_OVER, marker.getAlpha()));
1047 g2.setPaint(marker.getPaint());
1048 g2.setStroke(marker.getStroke());
1049 g2.draw(line);
1050
1051 String label = marker.getLabel();
1052 RectangleAnchor anchor = marker.getLabelAnchor();
1053 if (label != null) {
1054 Font labelFont = marker.getLabelFont();
1055 g2.setFont(labelFont);
1056 g2.setPaint(marker.getLabelPaint());
1057 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1058 g2, orientation, dataArea, line.getBounds2D(),
1059 marker.getLabelOffset(),
1060 LengthAdjustmentType.EXPAND, anchor);
1061 TextUtilities.drawAlignedString(label, g2,
1062 (float) coordinates.getX(), (float) coordinates.getY(),
1063 marker.getLabelTextAnchor());
1064 }
1065 g2.setComposite(originalComposite);
1066 }
1067 else if (marker instanceof IntervalMarker) {
1068 IntervalMarker im = (IntervalMarker) marker;
1069 double start = im.getStartValue();
1070 double end = im.getEndValue();
1071 Range range = domainAxis.getRange();
1072 if (!(range.intersects(start, end))) {
1073 return;
1074 }
1075
1076 double start2d = domainAxis.valueToJava2D(start, dataArea,
1077 plot.getDomainAxisEdge());
1078 double end2d = domainAxis.valueToJava2D(end, dataArea,
1079 plot.getDomainAxisEdge());
1080 double low = Math.min(start2d, end2d);
1081 double high = Math.max(start2d, end2d);
1082
1083 PlotOrientation orientation = plot.getOrientation();
1084 Rectangle2D rect = null;
1085 if (orientation == PlotOrientation.HORIZONTAL) {
1086 // clip top and bottom bounds to data area
1087 low = Math.max(low, dataArea.getMinY());
1088 high = Math.min(high, dataArea.getMaxY());
1089 rect = new Rectangle2D.Double(dataArea.getMinX(),
1090 low, dataArea.getWidth(),
1091 high - low);
1092 }
1093 else if (orientation == PlotOrientation.VERTICAL) {
1094 // clip left and right bounds to data area
1095 low = Math.max(low, dataArea.getMinX());
1096 high = Math.min(high, dataArea.getMaxX());
1097 rect = new Rectangle2D.Double(low,
1098 dataArea.getMinY(), high - low,
1099 dataArea.getHeight());
1100 }
1101
1102 final Composite originalComposite = g2.getComposite();
1103 g2.setComposite(AlphaComposite.getInstance(
1104 AlphaComposite.SRC_OVER, marker.getAlpha()));
1105 Paint p = marker.getPaint();
1106 if (p instanceof GradientPaint) {
1107 GradientPaint gp = (GradientPaint) p;
1108 GradientPaintTransformer t = im.getGradientPaintTransformer();
1109 if (t != null) {
1110 gp = t.transform(gp, rect);
1111 }
1112 g2.setPaint(gp);
1113 }
1114 else {
1115 g2.setPaint(p);
1116 }
1117 g2.fill(rect);
1118
1119 // now draw the outlines, if visible...
1120 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1121 if (orientation == PlotOrientation.VERTICAL) {
1122 Line2D line = new Line2D.Double();
1123 double y0 = dataArea.getMinY();
1124 double y1 = dataArea.getMaxY();
1125 g2.setPaint(im.getOutlinePaint());
1126 g2.setStroke(im.getOutlineStroke());
1127 if (range.contains(start)) {
1128 line.setLine(start2d, y0, start2d, y1);
1129 g2.draw(line);
1130 }
1131 if (range.contains(end)) {
1132 line.setLine(end2d, y0, end2d, y1);
1133 g2.draw(line);
1134 }
1135 }
1136 else { // PlotOrientation.HORIZONTAL
1137 Line2D line = new Line2D.Double();
1138 double x0 = dataArea.getMinX();
1139 double x1 = dataArea.getMaxX();
1140 g2.setPaint(im.getOutlinePaint());
1141 g2.setStroke(im.getOutlineStroke());
1142 if (range.contains(start)) {
1143 line.setLine(x0, start2d, x1, start2d);
1144 g2.draw(line);
1145 }
1146 if (range.contains(end)) {
1147 line.setLine(x0, end2d, x1, end2d);
1148 g2.draw(line);
1149 }
1150 }
1151 }
1152
1153 String label = marker.getLabel();
1154 RectangleAnchor anchor = marker.getLabelAnchor();
1155 if (label != null) {
1156 Font labelFont = marker.getLabelFont();
1157 g2.setFont(labelFont);
1158 g2.setPaint(marker.getLabelPaint());
1159 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1160 g2, orientation, dataArea, rect,
1161 marker.getLabelOffset(), marker.getLabelOffsetType(),
1162 anchor);
1163 TextUtilities.drawAlignedString(label, g2,
1164 (float) coordinates.getX(), (float) coordinates.getY(),
1165 marker.getLabelTextAnchor());
1166 }
1167 g2.setComposite(originalComposite);
1168
1169 }
1170
1171 }
1172
1173 /**
1174 * Calculates the (x, y) coordinates for drawing a marker label.
1175 *
1176 * @param g2 the graphics device.
1177 * @param orientation the plot orientation.
1178 * @param dataArea the data area.
1179 * @param markerArea the rectangle surrounding the marker area.
1180 * @param markerOffset the marker label offset.
1181 * @param labelOffsetType the label offset type.
1182 * @param anchor the label anchor.
1183 *
1184 * @return The coordinates for drawing the marker label.
1185 */
1186 protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1187 PlotOrientation orientation,
1188 Rectangle2D dataArea,
1189 Rectangle2D markerArea,
1190 RectangleInsets markerOffset,
1191 LengthAdjustmentType labelOffsetType,
1192 RectangleAnchor anchor) {
1193
1194 Rectangle2D anchorRect = null;
1195 if (orientation == PlotOrientation.HORIZONTAL) {
1196 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1197 LengthAdjustmentType.CONTRACT, labelOffsetType);
1198 }
1199 else if (orientation == PlotOrientation.VERTICAL) {
1200 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1201 labelOffsetType, LengthAdjustmentType.CONTRACT);
1202 }
1203 return RectangleAnchor.coordinates(anchorRect, anchor);
1204
1205 }
1206
1207 /**
1208 * Draws a horizontal line across the chart to represent a 'range marker'.
1209 *
1210 * @param g2 the graphics device.
1211 * @param plot the plot.
1212 * @param rangeAxis the range axis.
1213 * @param marker the marker line.
1214 * @param dataArea the axis data area.
1215 */
1216 public void drawRangeMarker(Graphics2D g2,
1217 XYPlot plot,
1218 ValueAxis rangeAxis,
1219 Marker marker,
1220 Rectangle2D dataArea) {
1221
1222 if (marker instanceof ValueMarker) {
1223 ValueMarker vm = (ValueMarker) marker;
1224 double value = vm.getValue();
1225 Range range = rangeAxis.getRange();
1226 if (!range.contains(value)) {
1227 return;
1228 }
1229
1230 double v = rangeAxis.valueToJava2D(value, dataArea,
1231 plot.getRangeAxisEdge());
1232 PlotOrientation orientation = plot.getOrientation();
1233 Line2D line = null;
1234 if (orientation == PlotOrientation.HORIZONTAL) {
1235 line = new Line2D.Double(v, dataArea.getMinY(), v,
1236 dataArea.getMaxY());
1237 }
1238 else if (orientation == PlotOrientation.VERTICAL) {
1239 line = new Line2D.Double(dataArea.getMinX(), v,
1240 dataArea.getMaxX(), v);
1241 }
1242
1243 final Composite originalComposite = g2.getComposite();
1244 g2.setComposite(AlphaComposite.getInstance(
1245 AlphaComposite.SRC_OVER, marker.getAlpha()));
1246 g2.setPaint(marker.getPaint());
1247 g2.setStroke(marker.getStroke());
1248 g2.draw(line);
1249
1250 String label = marker.getLabel();
1251 RectangleAnchor anchor = marker.getLabelAnchor();
1252 if (label != null) {
1253 Font labelFont = marker.getLabelFont();
1254 g2.setFont(labelFont);
1255 g2.setPaint(marker.getLabelPaint());
1256 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1257 g2, orientation, dataArea, line.getBounds2D(),
1258 marker.getLabelOffset(),
1259 LengthAdjustmentType.EXPAND, anchor);
1260 TextUtilities.drawAlignedString(label, g2,
1261 (float) coordinates.getX(), (float) coordinates.getY(),
1262 marker.getLabelTextAnchor());
1263 }
1264 g2.setComposite(originalComposite);
1265 }
1266 else if (marker instanceof IntervalMarker) {
1267 IntervalMarker im = (IntervalMarker) marker;
1268 double start = im.getStartValue();
1269 double end = im.getEndValue();
1270 Range range = rangeAxis.getRange();
1271 if (!(range.intersects(start, end))) {
1272 return;
1273 }
1274
1275 double start2d = rangeAxis.valueToJava2D(start, dataArea,
1276 plot.getRangeAxisEdge());
1277 double end2d = rangeAxis.valueToJava2D(end, dataArea,
1278 plot.getRangeAxisEdge());
1279 double low = Math.min(start2d, end2d);
1280 double high = Math.max(start2d, end2d);
1281
1282 PlotOrientation orientation = plot.getOrientation();
1283 Rectangle2D rect = null;
1284 if (orientation == PlotOrientation.HORIZONTAL) {
1285 // clip left and right bounds to data area
1286 low = Math.max(low, dataArea.getMinX());
1287 high = Math.min(high, dataArea.getMaxX());
1288 rect = new Rectangle2D.Double(low,
1289 dataArea.getMinY(), high - low,
1290 dataArea.getHeight());
1291 }
1292 else if (orientation == PlotOrientation.VERTICAL) {
1293 // clip top and bottom bounds to data area
1294 low = Math.max(low, dataArea.getMinY());
1295 high = Math.min(high, dataArea.getMaxY());
1296 rect = new Rectangle2D.Double(dataArea.getMinX(),
1297 low, dataArea.getWidth(),
1298 high - low);
1299 }
1300
1301 final Composite originalComposite = g2.getComposite();
1302 g2.setComposite(AlphaComposite.getInstance(
1303 AlphaComposite.SRC_OVER, marker.getAlpha()));
1304 Paint p = marker.getPaint();
1305 if (p instanceof GradientPaint) {
1306 GradientPaint gp = (GradientPaint) p;
1307 GradientPaintTransformer t = im.getGradientPaintTransformer();
1308 if (t != null) {
1309 gp = t.transform(gp, rect);
1310 }
1311 g2.setPaint(gp);
1312 }
1313 else {
1314 g2.setPaint(p);
1315 }
1316 g2.fill(rect);
1317
1318 // now draw the outlines, if visible...
1319 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1320 if (orientation == PlotOrientation.VERTICAL) {
1321 Line2D line = new Line2D.Double();
1322 double x0 = dataArea.getMinX();
1323 double x1 = dataArea.getMaxX();
1324 g2.setPaint(im.getOutlinePaint());
1325 g2.setStroke(im.getOutlineStroke());
1326 if (range.contains(start)) {
1327 line.setLine(x0, start2d, x1, start2d);
1328 g2.draw(line);
1329 }
1330 if (range.contains(end)) {
1331 line.setLine(x0, end2d, x1, end2d);
1332 g2.draw(line);
1333 }
1334 }
1335 else { // PlotOrientation.HORIZONTAL
1336 Line2D line = new Line2D.Double();
1337 double y0 = dataArea.getMinY();
1338 double y1 = dataArea.getMaxY();
1339 g2.setPaint(im.getOutlinePaint());
1340 g2.setStroke(im.getOutlineStroke());
1341 if (range.contains(start)) {
1342 line.setLine(start2d, y0, start2d, y1);
1343 g2.draw(line);
1344 }
1345 if (range.contains(end)) {
1346 line.setLine(end2d, y0, end2d, y1);
1347 g2.draw(line);
1348 }
1349 }
1350 }
1351
1352 String label = marker.getLabel();
1353 RectangleAnchor anchor = marker.getLabelAnchor();
1354 if (label != null) {
1355 Font labelFont = marker.getLabelFont();
1356 g2.setFont(labelFont);
1357 g2.setPaint(marker.getLabelPaint());
1358 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1359 g2, orientation, dataArea, rect,
1360 marker.getLabelOffset(), marker.getLabelOffsetType(),
1361 anchor);
1362 TextUtilities.drawAlignedString(label, g2,
1363 (float) coordinates.getX(), (float) coordinates.getY(),
1364 marker.getLabelTextAnchor());
1365 }
1366 g2.setComposite(originalComposite);
1367 }
1368 }
1369
1370 /**
1371 * Calculates the (x, y) coordinates for drawing a marker label.
1372 *
1373 * @param g2 the graphics device.
1374 * @param orientation the plot orientation.
1375 * @param dataArea the data area.
1376 * @param markerArea the marker area.
1377 * @param markerOffset the marker offset.
1378 * @param labelOffsetForRange ??
1379 * @param anchor the label anchor.
1380 *
1381 * @return The coordinates for drawing the marker label.
1382 */
1383 private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1384 PlotOrientation orientation,
1385 Rectangle2D dataArea,
1386 Rectangle2D markerArea,
1387 RectangleInsets markerOffset,
1388 LengthAdjustmentType labelOffsetForRange,
1389 RectangleAnchor anchor) {
1390
1391 Rectangle2D anchorRect = null;
1392 if (orientation == PlotOrientation.HORIZONTAL) {
1393 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1394 labelOffsetForRange, LengthAdjustmentType.CONTRACT);
1395 }
1396 else if (orientation == PlotOrientation.VERTICAL) {
1397 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1398 LengthAdjustmentType.CONTRACT, labelOffsetForRange);
1399 }
1400 return RectangleAnchor.coordinates(anchorRect, anchor);
1401
1402 }
1403
1404 /**
1405 * Returns a clone of the renderer.
1406 *
1407 * @return A clone.
1408 *
1409 * @throws CloneNotSupportedException if the renderer does not support
1410 * cloning.
1411 */
1412 protected Object clone() throws CloneNotSupportedException {
1413 AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone();
1414 // 'plot' : just retain reference, not a deep copy
1415
1416 if (this.itemLabelGenerator != null
1417 && this.itemLabelGenerator instanceof PublicCloneable) {
1418 PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator;
1419 clone.itemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1420 }
1421 clone.itemLabelGeneratorList
1422 = (ObjectList) this.itemLabelGeneratorList.clone();
1423 if (this.baseItemLabelGenerator != null
1424 && this.baseItemLabelGenerator instanceof PublicCloneable) {
1425 PublicCloneable pc = (PublicCloneable) this.baseItemLabelGenerator;
1426 clone.baseItemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1427 }
1428
1429 if (this.toolTipGenerator != null
1430 && this.toolTipGenerator instanceof PublicCloneable) {
1431 PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
1432 clone.toolTipGenerator = (XYToolTipGenerator) pc.clone();
1433 }
1434 clone.toolTipGeneratorList
1435 = (ObjectList) this.toolTipGeneratorList.clone();
1436 if (this.baseToolTipGenerator != null
1437 && this.baseToolTipGenerator instanceof PublicCloneable) {
1438 PublicCloneable pc = (PublicCloneable) this.baseToolTipGenerator;
1439 clone.baseToolTipGenerator = (XYToolTipGenerator) pc.clone();
1440 }
1441
1442 if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1443 clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1444 ObjectUtilities.clone(this.legendItemLabelGenerator);
1445 }
1446 if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1447 clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1448 ObjectUtilities.clone(this.legendItemToolTipGenerator);
1449 }
1450 if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1451 clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1452 ObjectUtilities.clone(this.legendItemURLGenerator);
1453 }
1454
1455 clone.foregroundAnnotations = (List) ObjectUtilities.deepClone(
1456 this.foregroundAnnotations);
1457 clone.backgroundAnnotations = (List) ObjectUtilities.deepClone(
1458 this.backgroundAnnotations);
1459
1460 if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1461 clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1462 ObjectUtilities.clone(this.legendItemLabelGenerator);
1463 }
1464 if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1465 clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1466 ObjectUtilities.clone(this.legendItemToolTipGenerator);
1467 }
1468 if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1469 clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1470 ObjectUtilities.clone(this.legendItemURLGenerator);
1471 }
1472
1473 return clone;
1474 }
1475
1476 /**
1477 * Tests this renderer for equality with another object.
1478 *
1479 * @param obj the object (<code>null</code> permitted).
1480 *
1481 * @return <code>true</code> or <code>false</code>.
1482 */
1483 public boolean equals(Object obj) {
1484 if (obj == this) {
1485 return true;
1486 }
1487 if (!(obj instanceof AbstractXYItemRenderer)) {
1488 return false;
1489 }
1490 AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj;
1491 if (!ObjectUtilities.equal(this.itemLabelGenerator,
1492 that.itemLabelGenerator)) {
1493 return false;
1494 }
1495 if (!this.itemLabelGeneratorList.equals(that.itemLabelGeneratorList)) {
1496 return false;
1497 }
1498 if (!ObjectUtilities.equal(this.baseItemLabelGenerator,
1499 that.baseItemLabelGenerator)) {
1500 return false;
1501 }
1502 if (!ObjectUtilities.equal(this.toolTipGenerator,
1503 that.toolTipGenerator)) {
1504 return false;
1505 }
1506 if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) {
1507 return false;
1508 }
1509 if (!ObjectUtilities.equal(this.baseToolTipGenerator,
1510 that.baseToolTipGenerator)) {
1511 return false;
1512 }
1513 if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
1514 return false;
1515 }
1516 if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) {
1517 return false;
1518 }
1519 if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) {
1520 return false;
1521 }
1522 if (this.defaultEntityRadius != that.defaultEntityRadius) {
1523 return false;
1524 }
1525 if (!ObjectUtilities.equal(this.legendItemLabelGenerator,
1526 that.legendItemLabelGenerator)) {
1527 return false;
1528 }
1529 if (!ObjectUtilities.equal(this.legendItemToolTipGenerator,
1530 that.legendItemToolTipGenerator)) {
1531 return false;
1532 }
1533 if (!ObjectUtilities.equal(this.legendItemURLGenerator,
1534 that.legendItemURLGenerator)) {
1535 return false;
1536 }
1537 return super.equals(obj);
1538 }
1539
1540 /**
1541 * Returns the drawing supplier from the plot.
1542 *
1543 * @return The drawing supplier (possibly <code>null</code>).
1544 */
1545 public DrawingSupplier getDrawingSupplier() {
1546 DrawingSupplier result = null;
1547 XYPlot p = getPlot();
1548 if (p != null) {
1549 result = p.getDrawingSupplier();
1550 }
1551 return result;
1552 }
1553
1554 /**
1555 * Considers the current (x, y) coordinate and updates the crosshair point
1556 * if it meets the criteria (usually means the (x, y) coordinate is the
1557 * closest to the anchor point so far).
1558 *
1559 * @param crosshairState the crosshair state (<code>null</code> permitted,
1560 * but the method does nothing in that case).
1561 * @param x the x-value (in data space).
1562 * @param y the y-value (in data space).
1563 * @param transX the x-value translated to Java2D space.
1564 * @param transY the y-value translated to Java2D space.
1565 * @param orientation the plot orientation (<code>null</code> not
1566 * permitted).
1567 *
1568 * @deprecated Use {@link #updateCrosshairValues(CrosshairState, double,
1569 * double, int, int, double, double, PlotOrientation)} -- see bug
1570 * report 1086307.
1571 */
1572 protected void updateCrosshairValues(CrosshairState crosshairState,
1573 double x, double y, double transX, double transY,
1574 PlotOrientation orientation) {
1575 updateCrosshairValues(crosshairState, x, y, 0, 0, transX, transY,
1576 orientation);
1577 }
1578
1579 /**
1580 * Considers the current (x, y) coordinate and updates the crosshair point
1581 * if it meets the criteria (usually means the (x, y) coordinate is the
1582 * closest to the anchor point so far).
1583 *
1584 * @param crosshairState the crosshair state (<code>null</code> permitted,
1585 * but the method does nothing in that case).
1586 * @param x the x-value (in data space).
1587 * @param y the y-value (in data space).
1588 * @param domainAxisIndex the index of the domain axis for the point.
1589 * @param rangeAxisIndex the index of the range axis for the point.
1590 * @param transX the x-value translated to Java2D space.
1591 * @param transY the y-value translated to Java2D space.
1592 * @param orientation the plot orientation (<code>null</code> not
1593 * permitted).
1594 *
1595 * @since 1.0.4
1596 */
1597 protected void updateCrosshairValues(CrosshairState crosshairState,
1598 double x, double y, int domainAxisIndex, int rangeAxisIndex,
1599 double transX, double transY, PlotOrientation orientation) {
1600
1601 if (orientation == null) {
1602 throw new IllegalArgumentException("Null 'orientation' argument.");
1603 }
1604
1605 if (crosshairState != null) {
1606 // do we need to update the crosshair values?
1607 if (this.plot.isDomainCrosshairLockedOnData()) {
1608 if (this.plot.isRangeCrosshairLockedOnData()) {
1609 // both axes
1610 crosshairState.updateCrosshairPoint(x, y, domainAxisIndex,
1611 rangeAxisIndex, transX, transY, orientation);
1612 }
1613 else {
1614 // just the domain axis...
1615 crosshairState.updateCrosshairX(x, domainAxisIndex);
1616 }
1617 }
1618 else {
1619 if (this.plot.isRangeCrosshairLockedOnData()) {
1620 // just the range axis...
1621 crosshairState.updateCrosshairY(y, rangeAxisIndex);
1622 }
1623 }
1624 }
1625
1626 }
1627
1628 /**
1629 * Draws an item label.
1630 *
1631 * @param g2 the graphics device.
1632 * @param orientation the orientation.
1633 * @param dataset the dataset.
1634 * @param series the series index (zero-based).
1635 * @param item the item index (zero-based).
1636 * @param x the x coordinate (in Java2D space).
1637 * @param y the y coordinate (in Java2D space).
1638 * @param negative indicates a negative value (which affects the item
1639 * label position).
1640 */
1641 protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation,
1642 XYDataset dataset, int series, int item, double x, double y,
1643 boolean negative) {
1644
1645 XYItemLabelGenerator generator = getItemLabelGenerator(series, item);
1646 if (generator != null) {
1647 Font labelFont = getItemLabelFont(series, item);
1648 Paint paint = getItemLabelPaint(series, item);
1649 g2.setFont(labelFont);
1650 g2.setPaint(paint);
1651 String label = generator.generateLabel(dataset, series, item);
1652
1653 // get the label position..
1654 ItemLabelPosition position = null;
1655 if (!negative) {
1656 position = getPositiveItemLabelPosition(series, item);
1657 }
1658 else {
1659 position = getNegativeItemLabelPosition(series, item);
1660 }
1661
1662 // work out the label anchor point...
1663 Point2D anchorPoint = calculateLabelAnchorPoint(
1664 position.getItemLabelAnchor(), x, y, orientation);
1665 TextUtilities.drawRotatedString(label, g2,
1666 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1667 position.getTextAnchor(), position.getAngle(),
1668 position.getRotationAnchor());
1669 }
1670
1671 }
1672
1673 /**
1674 * Draws all the annotations for the specified layer.
1675 *
1676 * @param g2 the graphics device.
1677 * @param dataArea the data area.
1678 * @param domainAxis the domain axis.
1679 * @param rangeAxis the range axis.
1680 * @param layer the layer.
1681 * @param info the plot rendering info.
1682 */
1683 public void drawAnnotations(Graphics2D g2,
1684 Rectangle2D dataArea,
1685 ValueAxis domainAxis,
1686 ValueAxis rangeAxis,
1687 Layer layer,
1688 PlotRenderingInfo info) {
1689
1690 Iterator iterator = null;
1691 if (layer.equals(Layer.FOREGROUND)) {
1692 iterator = this.foregroundAnnotations.iterator();
1693 }
1694 else if (layer.equals(Layer.BACKGROUND)) {
1695 iterator = this.backgroundAnnotations.iterator();
1696 }
1697 else {
1698 // should not get here
1699 throw new RuntimeException("Unknown layer.");
1700 }
1701 while (iterator.hasNext()) {
1702 XYAnnotation annotation = (XYAnnotation) iterator.next();
1703 annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis,
1704 0, info);
1705 }
1706
1707 }
1708
1709 /**
1710 * Adds an entity to the collection.
1711 *
1712 * @param entities the entity collection being populated.
1713 * @param area the entity area (if <code>null</code> a default will be
1714 * used).
1715 * @param dataset the dataset.
1716 * @param series the series.
1717 * @param item the item.
1718 * @param entityX the entity's center x-coordinate in user space (only
1719 * used if <code>area</code> is <code>null</code>).
1720 * @param entityY the entity's center y-coordinate in user space (only
1721 * used if <code>area</code> is <code>null</code>).
1722 */
1723 protected void addEntity(EntityCollection entities, Shape area,
1724 XYDataset dataset, int series, int item,
1725 double entityX, double entityY) {
1726 if (!getItemCreateEntity(series, item)) {
1727 return;
1728 }
1729 Shape hotspot = area;
1730 if (hotspot == null) {
1731 double w = this.defaultEntityRadius * 2;
1732 if (getPlot().getOrientation() == PlotOrientation.VERTICAL) {
1733 hotspot = new Ellipse2D.Double(
1734 entityX - this.defaultEntityRadius,
1735 entityY - this.defaultEntityRadius, w, w);
1736 }
1737 else {
1738 hotspot = new Ellipse2D.Double(
1739 entityY - this.defaultEntityRadius,
1740 entityX - this.defaultEntityRadius, w, w);
1741 }
1742 }
1743 String tip = null;
1744 XYToolTipGenerator generator = getToolTipGenerator(series, item);
1745 if (generator != null) {
1746 tip = generator.generateToolTip(dataset, series, item);
1747 }
1748 String url = null;
1749 if (getURLGenerator() != null) {
1750 url = getURLGenerator().generateURL(dataset, series, item);
1751 }
1752 XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item,
1753 tip, url);
1754 entities.add(entity);
1755 }
1756
1757 /**
1758 * Returns <code>true</code> if the specified point (x, y) falls within or
1759 * on the boundary of the specified rectangle.
1760 *
1761 * @param rect the rectangle (<code>null</code> not permitted).
1762 * @param x the x-coordinate.
1763 * @param y the y-coordinate.
1764 *
1765 * @return A boolean.
1766 *
1767 * @since 1.0.10
1768 */
1769 public static boolean isPointInRect(Rectangle2D rect, double x, double y) {
1770 // TODO: For JFreeChart 1.2.0, this method should go in the
1771 // ShapeUtilities class
1772 return (x >= rect.getMinX() && x <= rect.getMaxX()
1773 && y >= rect.getMinY() && y <= rect.getMaxY());
1774 }
1775
1776 }