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 * XYBarRenderer.java
029 * ------------------
030 * (C) Copyright 2001-2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Richard Atkinson;
034 * Christian W. Zuckschwerdt;
035 * Bill Kelemen;
036 *
037 * $Id: XYBarRenderer.java,v 1.14.2.17 2007/06/15 12:43:24 mungady Exp $
038 *
039 * Changes
040 * -------
041 * 13-Dec-2001 : Version 1, makes VerticalXYBarPlot class redundant (DG);
042 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
043 * 09-Apr-2002 : Removed the translated zero from the drawItem method. Override
044 * the initialise() method to calculate it (DG);
045 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
046 * 25-Jun-2002 : Removed redundant import (DG);
047 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
048 * image maps (RA);
049 * 25-Mar-2003 : Implemented Serializable (DG);
050 * 01-May-2003 : Modified drawItem() method signature (DG);
051 * 30-Jul-2003 : Modified entity constructor (CZ);
052 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
053 * 24-Aug-2003 : Added null checks in drawItem (BK);
054 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
055 * 07-Oct-2003 : Added renderer state (DG);
056 * 05-Dec-2003 : Changed call to obtain outline paint (DG);
057 * 10-Feb-2004 : Added state class, updated drawItem() method to make
058 * cut-and-paste overriding easier, and replaced property change
059 * with RendererChangeEvent (DG);
060 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
061 * 26-Apr-2004 : Added gradient paint transformer (DG);
062 * 19-May-2004 : Fixed bug (879709) with bar zero value for secondary axis (DG);
063 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
064 * getYValue() (DG);
065 * 01-Sep-2004 : Added a flag to control whether or not the bar outlines are
066 * drawn (DG);
067 * 03-Sep-2004 : Added option to use y-interval from dataset to determine the
068 * length of the bars (DG);
069 * 08-Sep-2004 : Added equals() method and updated clone() method (DG);
070 * 26-Jan-2005 : Added override for getLegendItem() method (DG);
071 * 20-Apr-2005 : Use generators for label tooltips and URLs (DG);
072 * 19-May-2005 : Added minimal item label implementation - needs improving (DG);
073 * 14-Oct-2005 : Fixed rendering problem with inverted axes (DG);
074 * ------------- JFREECHART 1.0.x ---------------------------------------------
075 * 21-Jun-2006 : Improved item label handling - see bug 1501768 (DG);
076 * 24-Aug-2006 : Added crosshair support (DG);
077 * 13-Dec-2006 : Updated getLegendItems() to return gradient paint
078 * transformer (DG);
079 * 02-Feb-2007 : Changed setUseYInterval() to only notify when the flag
080 * changes (DG);
081 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
082 * 09-Feb-2007 : Updated getLegendItem() to observe drawBarOutline flag (DG);
083 * 05-Mar-2007 : Applied patch 1671126 by Sergei Ivanov, to fix rendering with
084 * LogarithmicAxis (DG);
085 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
086 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
087 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
088 * 15-Jun-2007 : Changed default for drawBarOutline to false (DG);
089 *
090 */
091
092 package org.jfree.chart.renderer.xy;
093
094 import java.awt.Font;
095 import java.awt.GradientPaint;
096 import java.awt.Graphics2D;
097 import java.awt.Paint;
098 import java.awt.Shape;
099 import java.awt.Stroke;
100 import java.awt.geom.Point2D;
101 import java.awt.geom.Rectangle2D;
102 import java.io.IOException;
103 import java.io.ObjectInputStream;
104 import java.io.ObjectOutputStream;
105 import java.io.Serializable;
106
107 import org.jfree.chart.LegendItem;
108 import org.jfree.chart.axis.ValueAxis;
109 import org.jfree.chart.entity.EntityCollection;
110 import org.jfree.chart.entity.XYItemEntity;
111 import org.jfree.chart.event.RendererChangeEvent;
112 import org.jfree.chart.labels.ItemLabelAnchor;
113 import org.jfree.chart.labels.ItemLabelPosition;
114 import org.jfree.chart.labels.XYItemLabelGenerator;
115 import org.jfree.chart.labels.XYSeriesLabelGenerator;
116 import org.jfree.chart.labels.XYToolTipGenerator;
117 import org.jfree.chart.plot.CrosshairState;
118 import org.jfree.chart.plot.PlotOrientation;
119 import org.jfree.chart.plot.PlotRenderingInfo;
120 import org.jfree.chart.plot.XYPlot;
121 import org.jfree.data.Range;
122 import org.jfree.data.general.DatasetUtilities;
123 import org.jfree.data.xy.IntervalXYDataset;
124 import org.jfree.data.xy.XYDataset;
125 import org.jfree.io.SerialUtilities;
126 import org.jfree.text.TextUtilities;
127 import org.jfree.ui.GradientPaintTransformer;
128 import org.jfree.ui.RectangleEdge;
129 import org.jfree.ui.StandardGradientPaintTransformer;
130 import org.jfree.util.ObjectUtilities;
131 import org.jfree.util.PublicCloneable;
132 import org.jfree.util.ShapeUtilities;
133
134 /**
135 * A renderer that draws bars on an {@link XYPlot} (requires an
136 * {@link IntervalXYDataset}).
137 */
138 public class XYBarRenderer extends AbstractXYItemRenderer
139 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
140
141 /** For serialization. */
142 private static final long serialVersionUID = 770559577251370036L;
143
144 /**
145 * The state class used by this renderer.
146 */
147 protected class XYBarRendererState extends XYItemRendererState {
148
149 /** Base for bars against the range axis, in Java 2D space. */
150 private double g2Base;
151
152 /**
153 * Creates a new state object.
154 *
155 * @param info the plot rendering info.
156 */
157 public XYBarRendererState(PlotRenderingInfo info) {
158 super(info);
159 }
160
161 /**
162 * Returns the base (range) value in Java 2D space.
163 *
164 * @return The base value.
165 */
166 public double getG2Base() {
167 return this.g2Base;
168 }
169
170 /**
171 * Sets the range axis base in Java2D space.
172 *
173 * @param value the value.
174 */
175 public void setG2Base(double value) {
176 this.g2Base = value;
177 }
178 }
179
180 /** The default base value for the bars. */
181 private double base;
182
183 /**
184 * A flag that controls whether the bars use the y-interval supplied by the
185 * dataset.
186 */
187 private boolean useYInterval;
188
189 /** Percentage margin (to reduce the width of bars). */
190 private double margin;
191
192 /** A flag that controls whether or not bar outlines are drawn. */
193 private boolean drawBarOutline;
194
195 /**
196 * An optional class used to transform gradient paint objects to fit each
197 * bar.
198 */
199 private GradientPaintTransformer gradientPaintTransformer;
200
201 /**
202 * The shape used to represent a bar in each legend item (this should never
203 * be <code>null</code>).
204 */
205 private transient Shape legendBar;
206
207 /**
208 * The fallback position if a positive item label doesn't fit inside the
209 * bar.
210 */
211 private ItemLabelPosition positiveItemLabelPositionFallback;
212
213 /**
214 * The fallback position if a negative item label doesn't fit inside the
215 * bar.
216 */
217 private ItemLabelPosition negativeItemLabelPositionFallback;
218
219 /**
220 * The default constructor.
221 */
222 public XYBarRenderer() {
223 this(0.0);
224 }
225
226 /**
227 * Constructs a new renderer.
228 *
229 * @param margin the percentage amount to trim from the width of each bar.
230 */
231 public XYBarRenderer(double margin) {
232 super();
233 this.margin = margin;
234 this.base = 0.0;
235 this.useYInterval = false;
236 this.gradientPaintTransformer = new StandardGradientPaintTransformer();
237 this.drawBarOutline = false;
238 this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0);
239 }
240
241 /**
242 * Returns the base value for the bars.
243 *
244 * @return The base value for the bars.
245 *
246 * @see #setBase(double)
247 */
248 public double getBase() {
249 return this.base;
250 }
251
252 /**
253 * Sets the base value for the bars and sends a {@link RendererChangeEvent}
254 * to all registered listeners. The base value is not used if the dataset's
255 * y-interval is being used to determine the bar length.
256 *
257 * @param base the new base value.
258 *
259 * @see #getBase()
260 * @see #getUseYInterval()
261 */
262 public void setBase(double base) {
263 this.base = base;
264 notifyListeners(new RendererChangeEvent(this));
265 }
266
267 /**
268 * Returns a flag that determines whether the y-interval from the dataset is
269 * used to calculate the length of each bar.
270 *
271 * @return A boolean.
272 *
273 * @see #setUseYInterval(boolean)
274 */
275 public boolean getUseYInterval() {
276 return this.useYInterval;
277 }
278
279 /**
280 * Sets the flag that determines whether the y-interval from the dataset is
281 * used to calculate the length of each bar, and sends a
282 * {@link RendererChangeEvent} to all registered listeners.
283 *
284 * @param use the flag.
285 *
286 * @see #getUseYInterval()
287 */
288 public void setUseYInterval(boolean use) {
289 if (this.useYInterval != use) {
290 this.useYInterval = use;
291 notifyListeners(new RendererChangeEvent(this));
292 }
293 }
294
295 /**
296 * Returns the margin which is a percentage amount by which the bars are
297 * trimmed.
298 *
299 * @return The margin.
300 *
301 * @see #setMargin(double)
302 */
303 public double getMargin() {
304 return this.margin;
305 }
306
307 /**
308 * Sets the percentage amount by which the bars are trimmed and sends a
309 * {@link RendererChangeEvent} to all registered listeners.
310 *
311 * @param margin the new margin.
312 *
313 * @see #getMargin()
314 */
315 public void setMargin(double margin) {
316 this.margin = margin;
317 notifyListeners(new RendererChangeEvent(this));
318 }
319
320 /**
321 * Returns a flag that controls whether or not bar outlines are drawn.
322 *
323 * @return A boolean.
324 *
325 * @see #setDrawBarOutline(boolean)
326 */
327 public boolean isDrawBarOutline() {
328 return this.drawBarOutline;
329 }
330
331 /**
332 * Sets the flag that controls whether or not bar outlines are drawn and
333 * sends a {@link RendererChangeEvent} to all registered listeners.
334 *
335 * @param draw the flag.
336 *
337 * @see #isDrawBarOutline()
338 */
339 public void setDrawBarOutline(boolean draw) {
340 this.drawBarOutline = draw;
341 notifyListeners(new RendererChangeEvent(this));
342 }
343
344 /**
345 * Returns the gradient paint transformer (an object used to transform
346 * gradient paint objects to fit each bar).
347 *
348 * @return A transformer (<code>null</code> possible).
349 *
350 * @see #setGradientPaintTransformer(GradientPaintTransformer)
351 */
352 public GradientPaintTransformer getGradientPaintTransformer() {
353 return this.gradientPaintTransformer;
354 }
355
356 /**
357 * Sets the gradient paint transformer and sends a
358 * {@link RendererChangeEvent} to all registered listeners.
359 *
360 * @param transformer the transformer (<code>null</code> permitted).
361 *
362 * @see #getGradientPaintTransformer()
363 */
364 public void setGradientPaintTransformer(
365 GradientPaintTransformer transformer) {
366 this.gradientPaintTransformer = transformer;
367 notifyListeners(new RendererChangeEvent(this));
368 }
369
370 /**
371 * Returns the shape used to represent bars in each legend item.
372 *
373 * @return The shape used to represent bars in each legend item (never
374 * <code>null</code>).
375 *
376 * @see #setLegendBar(Shape)
377 */
378 public Shape getLegendBar() {
379 return this.legendBar;
380 }
381
382 /**
383 * Sets the shape used to represent bars in each legend item and sends a
384 * {@link RendererChangeEvent} to all registered listeners.
385 *
386 * @param bar the bar shape (<code>null</code> not permitted).
387 *
388 * @see #getLegendBar()
389 */
390 public void setLegendBar(Shape bar) {
391 if (bar == null) {
392 throw new IllegalArgumentException("Null 'bar' argument.");
393 }
394 this.legendBar = bar;
395 notifyListeners(new RendererChangeEvent(this));
396 }
397
398 /**
399 * Returns the fallback position for positive item labels that don't fit
400 * within a bar.
401 *
402 * @return The fallback position (<code>null</code> possible).
403 *
404 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
405 * @since 1.0.2
406 */
407 public ItemLabelPosition getPositiveItemLabelPositionFallback() {
408 return this.positiveItemLabelPositionFallback;
409 }
410
411 /**
412 * Sets the fallback position for positive item labels that don't fit
413 * within a bar, and sends a {@link RendererChangeEvent} to all registered
414 * listeners.
415 *
416 * @param position the position (<code>null</code> permitted).
417 *
418 * @see #getPositiveItemLabelPositionFallback()
419 * @since 1.0.2
420 */
421 public void setPositiveItemLabelPositionFallback(
422 ItemLabelPosition position) {
423 this.positiveItemLabelPositionFallback = position;
424 notifyListeners(new RendererChangeEvent(this));
425 }
426
427 /**
428 * Returns the fallback position for negative item labels that don't fit
429 * within a bar.
430 *
431 * @return The fallback position (<code>null</code> possible).
432 *
433 * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition)
434 * @since 1.0.2
435 */
436 public ItemLabelPosition getNegativeItemLabelPositionFallback() {
437 return this.negativeItemLabelPositionFallback;
438 }
439
440 /**
441 * Sets the fallback position for negative item labels that don't fit
442 * within a bar, and sends a {@link RendererChangeEvent} to all registered
443 * listeners.
444 *
445 * @param position the position (<code>null</code> permitted).
446 *
447 * @see #getNegativeItemLabelPositionFallback()
448 * @since 1.0.2
449 */
450 public void setNegativeItemLabelPositionFallback(
451 ItemLabelPosition position) {
452 this.negativeItemLabelPositionFallback = position;
453 notifyListeners(new RendererChangeEvent(this));
454 }
455
456 /**
457 * Initialises the renderer and returns a state object that should be
458 * passed to all subsequent calls to the drawItem() method. Here we
459 * calculate the Java2D y-coordinate for zero, since all the bars have
460 * their bases fixed at zero.
461 *
462 * @param g2 the graphics device.
463 * @param dataArea the area inside the axes.
464 * @param plot the plot.
465 * @param dataset the data.
466 * @param info an optional info collection object to return data back to
467 * the caller.
468 *
469 * @return A state object.
470 */
471 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
472 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
473
474 XYBarRendererState state = new XYBarRendererState(info);
475 ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf(
476 dataset));
477 state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea,
478 plot.getRangeAxisEdge()));
479 return state;
480
481 }
482
483 /**
484 * Returns a default legend item for the specified series. Subclasses
485 * should override this method to generate customised items.
486 *
487 * @param datasetIndex the dataset index (zero-based).
488 * @param series the series index (zero-based).
489 *
490 * @return A legend item for the series.
491 */
492 public LegendItem getLegendItem(int datasetIndex, int series) {
493 LegendItem result = null;
494 XYPlot xyplot = getPlot();
495 if (xyplot != null) {
496 XYDataset dataset = xyplot.getDataset(datasetIndex);
497 if (dataset != null) {
498 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
499 String label = lg.generateLabel(dataset, series);
500 String description = label;
501 String toolTipText = null;
502 if (getLegendItemToolTipGenerator() != null) {
503 toolTipText = getLegendItemToolTipGenerator().generateLabel(
504 dataset, series);
505 }
506 String urlText = null;
507 if (getLegendItemURLGenerator() != null) {
508 urlText = getLegendItemURLGenerator().generateLabel(
509 dataset, series);
510 }
511 Shape shape = this.legendBar;
512 Paint paint = lookupSeriesPaint(series);
513 Paint outlinePaint = lookupSeriesOutlinePaint(series);
514 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
515 if (this.drawBarOutline) {
516 result = new LegendItem(label, description, toolTipText,
517 urlText, shape, paint, outlineStroke, outlinePaint);
518 }
519 else {
520 result = new LegendItem(label, description, toolTipText,
521 urlText, shape, paint);
522 }
523 result.setDataset(dataset);
524 result.setDatasetIndex(datasetIndex);
525 result.setSeriesKey(dataset.getSeriesKey(series));
526 result.setSeriesIndex(series);
527 if (getGradientPaintTransformer() != null) {
528 result.setFillPaintTransformer(
529 getGradientPaintTransformer());
530 }
531 }
532 }
533 return result;
534 }
535
536 /**
537 * Draws the visual representation of a single data item.
538 *
539 * @param g2 the graphics device.
540 * @param state the renderer state.
541 * @param dataArea the area within which the plot is being drawn.
542 * @param info collects information about the drawing.
543 * @param plot the plot (can be used to obtain standard color
544 * information etc).
545 * @param domainAxis the domain axis.
546 * @param rangeAxis the range axis.
547 * @param dataset the dataset.
548 * @param series the series index (zero-based).
549 * @param item the item index (zero-based).
550 * @param crosshairState crosshair information for the plot
551 * (<code>null</code> permitted).
552 * @param pass the pass index.
553 */
554 public void drawItem(Graphics2D g2,
555 XYItemRendererState state,
556 Rectangle2D dataArea,
557 PlotRenderingInfo info,
558 XYPlot plot,
559 ValueAxis domainAxis,
560 ValueAxis rangeAxis,
561 XYDataset dataset,
562 int series,
563 int item,
564 CrosshairState crosshairState,
565 int pass) {
566
567 if (!getItemVisible(series, item)) {
568 return;
569 }
570 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
571
572 double value0;
573 double value1;
574 if (this.useYInterval) {
575 value0 = intervalDataset.getStartYValue(series, item);
576 value1 = intervalDataset.getEndYValue(series, item);
577 }
578 else {
579 value0 = this.base;
580 value1 = intervalDataset.getYValue(series, item);
581 }
582 if (Double.isNaN(value0) || Double.isNaN(value1)) {
583 return;
584 }
585 if (value0 <= value1) {
586 if (!rangeAxis.getRange().intersects(value0, value1)) {
587 return;
588 }
589 }
590 else {
591 if (!rangeAxis.getRange().intersects(value1, value0)) {
592 return;
593 }
594 }
595
596 double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea,
597 plot.getRangeAxisEdge());
598 double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea,
599 plot.getRangeAxisEdge());
600 double bottom = Math.min(translatedValue0, translatedValue1);
601 double top = Math.max(translatedValue0, translatedValue1);
602
603 double startX = intervalDataset.getStartXValue(series, item);
604 if (Double.isNaN(startX)) {
605 return;
606 }
607 double endX = intervalDataset.getEndXValue(series, item);
608 if (Double.isNaN(endX)) {
609 return;
610 }
611 if (startX <= endX) {
612 if (!domainAxis.getRange().intersects(startX, endX)) {
613 return;
614 }
615 }
616 else {
617 if (!domainAxis.getRange().intersects(endX, startX)) {
618 return;
619 }
620 }
621
622 RectangleEdge location = plot.getDomainAxisEdge();
623 double translatedStartX = domainAxis.valueToJava2D(startX, dataArea,
624 location);
625 double translatedEndX = domainAxis.valueToJava2D(endX, dataArea,
626 location);
627
628 double translatedWidth = Math.max(1, Math.abs(translatedEndX
629 - translatedStartX));
630
631 if (getMargin() > 0.0) {
632 double cut = translatedWidth * getMargin();
633 translatedWidth = translatedWidth - cut;
634 translatedStartX = translatedStartX + cut / 2;
635 }
636
637 Rectangle2D bar = null;
638 PlotOrientation orientation = plot.getOrientation();
639 if (orientation == PlotOrientation.HORIZONTAL) {
640 // clip left and right bounds to data area
641 bottom = Math.max(bottom, dataArea.getMinX());
642 top = Math.min(top, dataArea.getMaxX());
643 bar = new Rectangle2D.Double(
644 bottom,
645 Math.min(translatedStartX, translatedEndX),
646 top - bottom, translatedWidth);
647 }
648 else if (orientation == PlotOrientation.VERTICAL) {
649 // clip top and bottom bounds to data area
650 bottom = Math.max(bottom, dataArea.getMinY());
651 top = Math.min(top, dataArea.getMaxY());
652 bar = new Rectangle2D.Double(
653 Math.min(translatedStartX, translatedEndX),
654 bottom,
655 translatedWidth, top - bottom);
656 }
657
658 Paint itemPaint = getItemPaint(series, item);
659 if (getGradientPaintTransformer()
660 != null && itemPaint instanceof GradientPaint) {
661 GradientPaint gp = (GradientPaint) itemPaint;
662 itemPaint = getGradientPaintTransformer().transform(gp, bar);
663 }
664 g2.setPaint(itemPaint);
665 g2.fill(bar);
666 if (isDrawBarOutline()
667 && Math.abs(translatedEndX - translatedStartX) > 3) {
668 Stroke stroke = getItemOutlineStroke(series, item);
669 Paint paint = getItemOutlinePaint(series, item);
670 if (stroke != null && paint != null) {
671 g2.setStroke(stroke);
672 g2.setPaint(paint);
673 g2.draw(bar);
674 }
675 }
676
677 if (isItemLabelVisible(series, item)) {
678 XYItemLabelGenerator generator = getItemLabelGenerator(series,
679 item);
680 drawItemLabel(g2, dataset, series, item, plot, generator, bar,
681 value1 < 0.0);
682 }
683
684 // update the crosshair point
685 double x1 = (startX + endX) / 2.0;
686 double y1 = dataset.getYValue(series, item);
687 double transX1 = domainAxis.valueToJava2D(x1, dataArea, location);
688 double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
689 plot.getRangeAxisEdge());
690 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
691 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
692 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
693 rangeAxisIndex, transX1, transY1, plot.getOrientation());
694
695 // add an entity for the item...
696 if (info != null) {
697 EntityCollection entities = info.getOwner().getEntityCollection();
698 if (entities != null) {
699 String tip = null;
700 XYToolTipGenerator generator = getToolTipGenerator(series,
701 item);
702 if (generator != null) {
703 tip = generator.generateToolTip(dataset, series, item);
704 }
705 String url = null;
706 if (getURLGenerator() != null) {
707 url = getURLGenerator().generateURL(dataset, series, item);
708 }
709 XYItemEntity entity = new XYItemEntity(bar, dataset, series,
710 item, tip, url);
711 entities.add(entity);
712 }
713 }
714
715 }
716
717 /**
718 * Draws an item label. This method is overridden so that the bar can be
719 * used to calculate the label anchor point.
720 *
721 * @param g2 the graphics device.
722 * @param dataset the dataset.
723 * @param series the series index.
724 * @param item the item index.
725 * @param plot the plot.
726 * @param generator the label generator.
727 * @param bar the bar.
728 * @param negative a flag indicating a negative value.
729 */
730 protected void drawItemLabel(Graphics2D g2, XYDataset dataset,
731 int series, int item, XYPlot plot, XYItemLabelGenerator generator,
732 Rectangle2D bar, boolean negative) {
733
734 String label = generator.generateLabel(dataset, series, item);
735 if (label == null) {
736 return; // nothing to do
737 }
738
739 Font labelFont = getItemLabelFont(series, item);
740 g2.setFont(labelFont);
741 Paint paint = getItemLabelPaint(series, item);
742 g2.setPaint(paint);
743
744 // find out where to place the label...
745 ItemLabelPosition position = null;
746 if (!negative) {
747 position = getPositiveItemLabelPosition(series, item);
748 }
749 else {
750 position = getNegativeItemLabelPosition(series, item);
751 }
752
753 // work out the label anchor point...
754 Point2D anchorPoint = calculateLabelAnchorPoint(
755 position.getItemLabelAnchor(), bar, plot.getOrientation());
756
757 if (isInternalAnchor(position.getItemLabelAnchor())) {
758 Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
759 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
760 position.getTextAnchor(), position.getAngle(),
761 position.getRotationAnchor());
762
763 if (bounds != null) {
764 if (!bar.contains(bounds.getBounds2D())) {
765 if (!negative) {
766 position = getPositiveItemLabelPositionFallback();
767 }
768 else {
769 position = getNegativeItemLabelPositionFallback();
770 }
771 if (position != null) {
772 anchorPoint = calculateLabelAnchorPoint(
773 position.getItemLabelAnchor(), bar,
774 plot.getOrientation());
775 }
776 }
777 }
778
779 }
780
781 if (position != null) {
782 TextUtilities.drawRotatedString(label, g2,
783 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
784 position.getTextAnchor(), position.getAngle(),
785 position.getRotationAnchor());
786 }
787 }
788
789 /**
790 * Calculates the item label anchor point.
791 *
792 * @param anchor the anchor.
793 * @param bar the bar.
794 * @param orientation the plot orientation.
795 *
796 * @return The anchor point.
797 */
798 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
799 Rectangle2D bar, PlotOrientation orientation) {
800
801 Point2D result = null;
802 double offset = getItemLabelAnchorOffset();
803 double x0 = bar.getX() - offset;
804 double x1 = bar.getX();
805 double x2 = bar.getX() + offset;
806 double x3 = bar.getCenterX();
807 double x4 = bar.getMaxX() - offset;
808 double x5 = bar.getMaxX();
809 double x6 = bar.getMaxX() + offset;
810
811 double y0 = bar.getMaxY() + offset;
812 double y1 = bar.getMaxY();
813 double y2 = bar.getMaxY() - offset;
814 double y3 = bar.getCenterY();
815 double y4 = bar.getMinY() + offset;
816 double y5 = bar.getMinY();
817 double y6 = bar.getMinY() - offset;
818
819 if (anchor == ItemLabelAnchor.CENTER) {
820 result = new Point2D.Double(x3, y3);
821 }
822 else if (anchor == ItemLabelAnchor.INSIDE1) {
823 result = new Point2D.Double(x4, y4);
824 }
825 else if (anchor == ItemLabelAnchor.INSIDE2) {
826 result = new Point2D.Double(x4, y4);
827 }
828 else if (anchor == ItemLabelAnchor.INSIDE3) {
829 result = new Point2D.Double(x4, y3);
830 }
831 else if (anchor == ItemLabelAnchor.INSIDE4) {
832 result = new Point2D.Double(x4, y2);
833 }
834 else if (anchor == ItemLabelAnchor.INSIDE5) {
835 result = new Point2D.Double(x4, y2);
836 }
837 else if (anchor == ItemLabelAnchor.INSIDE6) {
838 result = new Point2D.Double(x3, y2);
839 }
840 else if (anchor == ItemLabelAnchor.INSIDE7) {
841 result = new Point2D.Double(x2, y2);
842 }
843 else if (anchor == ItemLabelAnchor.INSIDE8) {
844 result = new Point2D.Double(x2, y2);
845 }
846 else if (anchor == ItemLabelAnchor.INSIDE9) {
847 result = new Point2D.Double(x2, y3);
848 }
849 else if (anchor == ItemLabelAnchor.INSIDE10) {
850 result = new Point2D.Double(x2, y4);
851 }
852 else if (anchor == ItemLabelAnchor.INSIDE11) {
853 result = new Point2D.Double(x2, y4);
854 }
855 else if (anchor == ItemLabelAnchor.INSIDE12) {
856 result = new Point2D.Double(x3, y4);
857 }
858 else if (anchor == ItemLabelAnchor.OUTSIDE1) {
859 result = new Point2D.Double(x5, y6);
860 }
861 else if (anchor == ItemLabelAnchor.OUTSIDE2) {
862 result = new Point2D.Double(x6, y5);
863 }
864 else if (anchor == ItemLabelAnchor.OUTSIDE3) {
865 result = new Point2D.Double(x6, y3);
866 }
867 else if (anchor == ItemLabelAnchor.OUTSIDE4) {
868 result = new Point2D.Double(x6, y1);
869 }
870 else if (anchor == ItemLabelAnchor.OUTSIDE5) {
871 result = new Point2D.Double(x5, y0);
872 }
873 else if (anchor == ItemLabelAnchor.OUTSIDE6) {
874 result = new Point2D.Double(x3, y0);
875 }
876 else if (anchor == ItemLabelAnchor.OUTSIDE7) {
877 result = new Point2D.Double(x1, y0);
878 }
879 else if (anchor == ItemLabelAnchor.OUTSIDE8) {
880 result = new Point2D.Double(x0, y1);
881 }
882 else if (anchor == ItemLabelAnchor.OUTSIDE9) {
883 result = new Point2D.Double(x0, y3);
884 }
885 else if (anchor == ItemLabelAnchor.OUTSIDE10) {
886 result = new Point2D.Double(x0, y5);
887 }
888 else if (anchor == ItemLabelAnchor.OUTSIDE11) {
889 result = new Point2D.Double(x1, y6);
890 }
891 else if (anchor == ItemLabelAnchor.OUTSIDE12) {
892 result = new Point2D.Double(x3, y6);
893 }
894
895 return result;
896
897 }
898
899 /**
900 * Returns <code>true</code> if the specified anchor point is inside a bar.
901 *
902 * @param anchor the anchor point.
903 *
904 * @return A boolean.
905 */
906 private boolean isInternalAnchor(ItemLabelAnchor anchor) {
907 return anchor == ItemLabelAnchor.CENTER
908 || anchor == ItemLabelAnchor.INSIDE1
909 || anchor == ItemLabelAnchor.INSIDE2
910 || anchor == ItemLabelAnchor.INSIDE3
911 || anchor == ItemLabelAnchor.INSIDE4
912 || anchor == ItemLabelAnchor.INSIDE5
913 || anchor == ItemLabelAnchor.INSIDE6
914 || anchor == ItemLabelAnchor.INSIDE7
915 || anchor == ItemLabelAnchor.INSIDE8
916 || anchor == ItemLabelAnchor.INSIDE9
917 || anchor == ItemLabelAnchor.INSIDE10
918 || anchor == ItemLabelAnchor.INSIDE11
919 || anchor == ItemLabelAnchor.INSIDE12;
920 }
921
922 /**
923 * Returns the lower and upper bounds (range) of the x-values in the
924 * specified dataset. Since this renderer uses the x-interval in the
925 * dataset, this is taken into account for the range.
926 *
927 * @param dataset the dataset (<code>null</code> permitted).
928 *
929 * @return The range (<code>null</code> if the dataset is
930 * <code>null</code> or empty).
931 */
932 public Range findDomainBounds(XYDataset dataset) {
933 if (dataset != null) {
934 return DatasetUtilities.findDomainBounds(dataset, true);
935 }
936 else {
937 return null;
938 }
939 }
940
941 /**
942 * Returns a clone of the renderer.
943 *
944 * @return A clone.
945 *
946 * @throws CloneNotSupportedException if the renderer cannot be cloned.
947 */
948 public Object clone() throws CloneNotSupportedException {
949 XYBarRenderer result = (XYBarRenderer) super.clone();
950 if (this.gradientPaintTransformer != null) {
951 result.gradientPaintTransformer = (GradientPaintTransformer)
952 ObjectUtilities.clone(this.gradientPaintTransformer);
953 }
954 result.legendBar = ShapeUtilities.clone(this.legendBar);
955 return result;
956 }
957
958 /**
959 * Tests this renderer for equality with an arbitrary object.
960 *
961 * @param obj the object to test against (<code>null</code> permitted).
962 *
963 * @return A boolean.
964 */
965 public boolean equals(Object obj) {
966 if (obj == this) {
967 return true;
968 }
969 if (!(obj instanceof XYBarRenderer)) {
970 return false;
971 }
972 if (!super.equals(obj)) {
973 return false;
974 }
975 XYBarRenderer that = (XYBarRenderer) obj;
976 if (this.base != that.base) {
977 return false;
978 }
979 if (this.drawBarOutline != that.drawBarOutline) {
980 return false;
981 }
982 if (this.margin != that.margin) {
983 return false;
984 }
985 if (this.useYInterval != that.useYInterval) {
986 return false;
987 }
988 if (!ObjectUtilities.equal(
989 this.gradientPaintTransformer, that.gradientPaintTransformer)
990 ) {
991 return false;
992 }
993 if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) {
994 return false;
995 }
996 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
997 that.positiveItemLabelPositionFallback)) {
998 return false;
999 }
1000 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
1001 that.negativeItemLabelPositionFallback)) {
1002 return false;
1003 }
1004 return true;
1005 }
1006
1007 /**
1008 * Provides serialization support.
1009 *
1010 * @param stream the input stream.
1011 *
1012 * @throws IOException if there is an I/O error.
1013 * @throws ClassNotFoundException if there is a classpath problem.
1014 */
1015 private void readObject(ObjectInputStream stream)
1016 throws IOException, ClassNotFoundException {
1017 stream.defaultReadObject();
1018 this.legendBar = SerialUtilities.readShape(stream);
1019 }
1020
1021 /**
1022 * Provides serialization support.
1023 *
1024 * @param stream the output stream.
1025 *
1026 * @throws IOException if there is an I/O error.
1027 */
1028 private void writeObject(ObjectOutputStream stream) throws IOException {
1029 stream.defaultWriteObject();
1030 SerialUtilities.writeShape(this.legendBar, stream);
1031 }
1032
1033 }