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