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