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