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