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