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 * BarRenderer.java
029 * ----------------
030 * (C) Copyright 2002-2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Christian W. Zuckschwerdt;
034 *
035 * $Id: BarRenderer.java,v 1.13.2.18 2007/05/18 10:28:27 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 14-Mar-2002 : Version 1 (DG);
040 * 23-May-2002 : Added tooltip generator to renderer (DG);
041 * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
042 * 25-Jun-2002 : Changed constructor to protected and removed redundant
043 * code (DG);
044 * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
045 * clip values (DG);
046 * 24-Sep-2002 : Added getLegendItem() method (DG);
047 * 09-Oct-2002 : Modified constructor to include URL generator (DG);
048 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
049 * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (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 clipping to allow for dual axes and datasets (DG);
053 * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
054 * 12-Jun-2003 : Updates for item labels (DG);
055 * 30-Jul-2003 : Modified entity constructor (CZ);
056 * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
057 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
058 * 07-Oct-2003 : Added renderer state (DG);
059 * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
060 * methods (DG);
061 * 28-Oct-2003 : Added support for gradient paint on bars (DG);
062 * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
063 * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
064 * overriding (DG);
065 * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
066 * label generators. Fixed equals() method (DG);
067 * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
068 * 05-Nov-2004 : Modified drawItem() signature (DG);
069 * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
070 * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
071 * 18-May-2005 : Added configurable base value (DG);
072 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
073 * 01-Dec-2005 : Update legend item to use/not use outline (DG);
074 * ------------: JFreeChart 1.0.x ---------------------------------------------
075 * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
076 * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
077 * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
078 * bars) (DG);
079 * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
080 * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
081 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
082 * 11-May-2007 : Check for visibility in getLegendItem() (DG);
083 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
084 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
085 *
086 */
087
088 package org.jfree.chart.renderer.category;
089
090 import java.awt.BasicStroke;
091 import java.awt.Color;
092 import java.awt.Font;
093 import java.awt.GradientPaint;
094 import java.awt.Graphics2D;
095 import java.awt.Paint;
096 import java.awt.Shape;
097 import java.awt.Stroke;
098 import java.awt.geom.Line2D;
099 import java.awt.geom.Point2D;
100 import java.awt.geom.Rectangle2D;
101 import java.io.Serializable;
102
103 import org.jfree.chart.LegendItem;
104 import org.jfree.chart.axis.CategoryAxis;
105 import org.jfree.chart.axis.ValueAxis;
106 import org.jfree.chart.entity.EntityCollection;
107 import org.jfree.chart.event.RendererChangeEvent;
108 import org.jfree.chart.labels.CategoryItemLabelGenerator;
109 import org.jfree.chart.labels.ItemLabelAnchor;
110 import org.jfree.chart.labels.ItemLabelPosition;
111 import org.jfree.chart.plot.CategoryPlot;
112 import org.jfree.chart.plot.PlotOrientation;
113 import org.jfree.chart.plot.PlotRenderingInfo;
114 import org.jfree.data.Range;
115 import org.jfree.data.category.CategoryDataset;
116 import org.jfree.data.general.DatasetUtilities;
117 import org.jfree.text.TextUtilities;
118 import org.jfree.ui.GradientPaintTransformer;
119 import org.jfree.ui.RectangleEdge;
120 import org.jfree.ui.StandardGradientPaintTransformer;
121 import org.jfree.util.ObjectUtilities;
122 import org.jfree.util.PublicCloneable;
123
124 /**
125 * A {@link CategoryItemRenderer} that draws individual data items as bars.
126 */
127 public class BarRenderer extends AbstractCategoryItemRenderer
128 implements Cloneable, PublicCloneable, Serializable {
129
130 /** For serialization. */
131 private static final long serialVersionUID = 6000649414965887481L;
132
133 /** The default item margin percentage. */
134 public static final double DEFAULT_ITEM_MARGIN = 0.20;
135
136 /**
137 * Constant that controls the minimum width before a bar has an outline
138 * drawn.
139 */
140 public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
141
142 /** The margin between items (bars) within a category. */
143 private double itemMargin;
144
145 /** A flag that controls whether or not bar outlines are drawn. */
146 private boolean drawBarOutline;
147
148 /** The maximum bar width as a percentage of the available space. */
149 private double maximumBarWidth;
150
151 /** The minimum bar length (in Java2D units). */
152 private double minimumBarLength;
153
154 /**
155 * An optional class used to transform gradient paint objects to fit each
156 * bar.
157 */
158 private GradientPaintTransformer gradientPaintTransformer;
159
160 /**
161 * The fallback position if a positive item label doesn't fit inside the
162 * bar.
163 */
164 private ItemLabelPosition positiveItemLabelPositionFallback;
165
166 /**
167 * The fallback position if a negative item label doesn't fit inside the
168 * bar.
169 */
170 private ItemLabelPosition negativeItemLabelPositionFallback;
171
172 /** The upper clip (axis) value for the axis. */
173 private double upperClip;
174 // TODO: this needs to move into the renderer state
175
176 /** The lower clip (axis) value for the axis. */
177 private double lowerClip;
178 // TODO: this needs to move into the renderer state
179
180 /** The base value for the bars (defaults to 0.0). */
181 private double base;
182
183 /**
184 * A flag that controls whether the base value is included in the range
185 * returned by the findRangeBounds() method.
186 */
187 private boolean includeBaseInRange;
188
189 /**
190 * Creates a new bar renderer with default settings.
191 */
192 public BarRenderer() {
193 super();
194 this.base = 0.0;
195 this.includeBaseInRange = true;
196 this.itemMargin = DEFAULT_ITEM_MARGIN;
197 this.drawBarOutline = true;
198 this.maximumBarWidth = 1.0;
199 // 100 percent, so it will not apply unless changed
200 this.positiveItemLabelPositionFallback = null;
201 this.negativeItemLabelPositionFallback = null;
202 this.gradientPaintTransformer = new StandardGradientPaintTransformer();
203 this.minimumBarLength = 0.0;
204 }
205
206 /**
207 * Returns the base value for the bars. The default value is
208 * <code>0.0</code>.
209 *
210 * @return The base value for the bars.
211 *
212 * @see #setBase(double)
213 */
214 public double getBase() {
215 return this.base;
216 }
217
218 /**
219 * Sets the base value for the bars and sends a {@link RendererChangeEvent}
220 * to all registered listeners.
221 *
222 * @param base the new base value.
223 *
224 * @see #getBase()
225 */
226 public void setBase(double base) {
227 this.base = base;
228 notifyListeners(new RendererChangeEvent(this));
229 }
230
231 /**
232 * Returns the item margin as a percentage of the available space for all
233 * bars.
234 *
235 * @return The margin percentage (where 0.10 is ten percent).
236 *
237 * @see #setItemMargin(double)
238 */
239 public double getItemMargin() {
240 return this.itemMargin;
241 }
242
243 /**
244 * Sets the item margin and sends a {@link RendererChangeEvent} to all
245 * registered listeners. The value is expressed as a percentage of the
246 * available width for plotting all the bars, with the resulting amount to
247 * be distributed between all the bars evenly.
248 *
249 * @param percent the margin (where 0.10 is ten percent).
250 *
251 * @see #getItemMargin()
252 */
253 public void setItemMargin(double percent) {
254 this.itemMargin = percent;
255 notifyListeners(new RendererChangeEvent(this));
256 }
257
258 /**
259 * Returns a flag that controls whether or not bar outlines are drawn.
260 *
261 * @return A boolean.
262 *
263 * @see #setDrawBarOutline(boolean)
264 */
265 public boolean isDrawBarOutline() {
266 return this.drawBarOutline;
267 }
268
269 /**
270 * Sets the flag that controls whether or not bar outlines are drawn and
271 * sends a {@link RendererChangeEvent} to all registered listeners.
272 *
273 * @param draw the flag.
274 *
275 * @see #isDrawBarOutline()
276 */
277 public void setDrawBarOutline(boolean draw) {
278 this.drawBarOutline = draw;
279 notifyListeners(new RendererChangeEvent(this));
280 }
281
282 /**
283 * Returns the maximum bar width, as a percentage of the available drawing
284 * space.
285 *
286 * @return The maximum bar width.
287 *
288 * @see #setMaximumBarWidth(double)
289 */
290 public double getMaximumBarWidth() {
291 return this.maximumBarWidth;
292 }
293
294 /**
295 * Sets the maximum bar width, which is specified as a percentage of the
296 * available space for all bars, and sends a {@link RendererChangeEvent} to
297 * all registered listeners.
298 *
299 * @param percent the percent (where 0.05 is five percent).
300 *
301 * @see #getMaximumBarWidth()
302 */
303 public void setMaximumBarWidth(double percent) {
304 this.maximumBarWidth = percent;
305 notifyListeners(new RendererChangeEvent(this));
306 }
307
308 /**
309 * Returns the minimum bar length (in Java2D units).
310 *
311 * @return The minimum bar length.
312 *
313 * @see #setMinimumBarLength(double)
314 */
315 public double getMinimumBarLength() {
316 return this.minimumBarLength;
317 }
318
319 /**
320 * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
321 * all registered listeners. The minimum bar length is specified in Java2D
322 * units, and can be used to prevent bars that represent very small data
323 * values from disappearing when drawn on the screen.
324 *
325 * @param min the minimum bar length (in Java2D units).
326 *
327 * @see #getMinimumBarLength()
328 */
329 public void setMinimumBarLength(double min) {
330 this.minimumBarLength = min;
331 notifyListeners(new RendererChangeEvent(this));
332 }
333
334 /**
335 * Returns the gradient paint transformer (an object used to transform
336 * gradient paint objects to fit each bar).
337 *
338 * @return A transformer (<code>null</code> possible).
339 *
340 * @see #setGradientPaintTransformer(GradientPaintTransformer)
341 */
342 public GradientPaintTransformer getGradientPaintTransformer() {
343 return this.gradientPaintTransformer;
344 }
345
346 /**
347 * Sets the gradient paint transformer and sends a
348 * {@link RendererChangeEvent} to all registered listeners.
349 *
350 * @param transformer the transformer (<code>null</code> permitted).
351 *
352 * @see #getGradientPaintTransformer()
353 */
354 public void setGradientPaintTransformer(
355 GradientPaintTransformer transformer) {
356 this.gradientPaintTransformer = transformer;
357 notifyListeners(new RendererChangeEvent(this));
358 }
359
360 /**
361 * Returns the fallback position for positive item labels that don't fit
362 * within a bar.
363 *
364 * @return The fallback position (<code>null</code> possible).
365 *
366 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
367 */
368 public ItemLabelPosition getPositiveItemLabelPositionFallback() {
369 return this.positiveItemLabelPositionFallback;
370 }
371
372 /**
373 * Sets the fallback position for positive item labels that don't fit
374 * within a bar, and sends a {@link RendererChangeEvent} to all registered
375 * listeners.
376 *
377 * @param position the position (<code>null</code> permitted).
378 *
379 * @see #getPositiveItemLabelPositionFallback()
380 */
381 public void setPositiveItemLabelPositionFallback(
382 ItemLabelPosition position) {
383 this.positiveItemLabelPositionFallback = position;
384 notifyListeners(new RendererChangeEvent(this));
385 }
386
387 /**
388 * Returns the fallback position for negative item labels that don't fit
389 * within a bar.
390 *
391 * @return The fallback position (<code>null</code> possible).
392 *
393 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
394 */
395 public ItemLabelPosition getNegativeItemLabelPositionFallback() {
396 return this.negativeItemLabelPositionFallback;
397 }
398
399 /**
400 * Sets the fallback position for negative item labels that don't fit
401 * within a bar, and sends a {@link RendererChangeEvent} to all registered
402 * listeners.
403 *
404 * @param position the position (<code>null</code> permitted).
405 *
406 * @see #getNegativeItemLabelPositionFallback()
407 */
408 public void setNegativeItemLabelPositionFallback(
409 ItemLabelPosition position) {
410 this.negativeItemLabelPositionFallback = position;
411 notifyListeners(new RendererChangeEvent(this));
412 }
413
414 /**
415 * Returns the flag that controls whether or not the base value for the
416 * bars is included in the range calculated by
417 * {@link #findRangeBounds(CategoryDataset)}.
418 *
419 * @return <code>true</code> if the base is included in the range, and
420 * <code>false</code> otherwise.
421 *
422 * @since 1.0.1
423 *
424 * @see #setIncludeBaseInRange(boolean)
425 */
426 public boolean getIncludeBaseInRange() {
427 return this.includeBaseInRange;
428 }
429
430 /**
431 * Sets the flag that controls whether or not the base value for the bars
432 * is included in the range calculated by
433 * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed,
434 * a {@link RendererChangeEvent} is sent to all registered listeners.
435 *
436 * @param include the new value for the flag.
437 *
438 * @since 1.0.1
439 *
440 * @see #getIncludeBaseInRange()
441 */
442 public void setIncludeBaseInRange(boolean include) {
443 if (this.includeBaseInRange != include) {
444 this.includeBaseInRange = include;
445 notifyListeners(new RendererChangeEvent(this));
446 }
447 }
448
449 /**
450 * Returns the lower clip value. This value is recalculated in the
451 * initialise() method.
452 *
453 * @return The value.
454 */
455 public double getLowerClip() {
456 // TODO: this attribute should be transferred to the renderer state.
457 return this.lowerClip;
458 }
459
460 /**
461 * Returns the upper clip value. This value is recalculated in the
462 * initialise() method.
463 *
464 * @return The value.
465 */
466 public double getUpperClip() {
467 // TODO: this attribute should be transferred to the renderer state.
468 return this.upperClip;
469 }
470
471 /**
472 * Initialises the renderer and returns a state object that will be passed
473 * to subsequent calls to the drawItem method. This method gets called
474 * once at the start of the process of drawing a chart.
475 *
476 * @param g2 the graphics device.
477 * @param dataArea the area in which the data is to be plotted.
478 * @param plot the plot.
479 * @param rendererIndex the renderer index.
480 * @param info collects chart rendering information for return to caller.
481 *
482 * @return The renderer state.
483 */
484 public CategoryItemRendererState initialise(Graphics2D g2,
485 Rectangle2D dataArea,
486 CategoryPlot plot,
487 int rendererIndex,
488 PlotRenderingInfo info) {
489
490 CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
491 rendererIndex, info);
492
493 // get the clipping values...
494 ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
495 this.lowerClip = rangeAxis.getRange().getLowerBound();
496 this.upperClip = rangeAxis.getRange().getUpperBound();
497
498 // calculate the bar width
499 calculateBarWidth(plot, dataArea, rendererIndex, state);
500
501 return state;
502
503 }
504
505 /**
506 * Calculates the bar width and stores it in the renderer state.
507 *
508 * @param plot the plot.
509 * @param dataArea the data area.
510 * @param rendererIndex the renderer index.
511 * @param state the renderer state.
512 */
513 protected void calculateBarWidth(CategoryPlot plot,
514 Rectangle2D dataArea,
515 int rendererIndex,
516 CategoryItemRendererState state) {
517
518 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
519 CategoryDataset dataset = plot.getDataset(rendererIndex);
520 if (dataset != null) {
521 int columns = dataset.getColumnCount();
522 int rows = dataset.getRowCount();
523 double space = 0.0;
524 PlotOrientation orientation = plot.getOrientation();
525 if (orientation == PlotOrientation.HORIZONTAL) {
526 space = dataArea.getHeight();
527 }
528 else if (orientation == PlotOrientation.VERTICAL) {
529 space = dataArea.getWidth();
530 }
531 double maxWidth = space * getMaximumBarWidth();
532 double categoryMargin = 0.0;
533 double currentItemMargin = 0.0;
534 if (columns > 1) {
535 categoryMargin = domainAxis.getCategoryMargin();
536 }
537 if (rows > 1) {
538 currentItemMargin = getItemMargin();
539 }
540 double used = space * (1 - domainAxis.getLowerMargin()
541 - domainAxis.getUpperMargin()
542 - categoryMargin - currentItemMargin);
543 if ((rows * columns) > 0) {
544 state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
545 }
546 else {
547 state.setBarWidth(Math.min(used, maxWidth));
548 }
549 }
550 }
551
552 /**
553 * Calculates the coordinate of the first "side" of a bar. This will be
554 * the minimum x-coordinate for a vertical bar, and the minimum
555 * y-coordinate for a horizontal bar.
556 *
557 * @param plot the plot.
558 * @param orientation the plot orientation.
559 * @param dataArea the data area.
560 * @param domainAxis the domain axis.
561 * @param state the renderer state (has the bar width precalculated).
562 * @param row the row index.
563 * @param column the column index.
564 *
565 * @return The coordinate.
566 */
567 protected double calculateBarW0(CategoryPlot plot,
568 PlotOrientation orientation,
569 Rectangle2D dataArea,
570 CategoryAxis domainAxis,
571 CategoryItemRendererState state,
572 int row,
573 int column) {
574 // calculate bar width...
575 double space = 0.0;
576 if (orientation == PlotOrientation.HORIZONTAL) {
577 space = dataArea.getHeight();
578 }
579 else {
580 space = dataArea.getWidth();
581 }
582 double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
583 dataArea, plot.getDomainAxisEdge());
584 int seriesCount = getRowCount();
585 int categoryCount = getColumnCount();
586 if (seriesCount > 1) {
587 double seriesGap = space * getItemMargin()
588 / (categoryCount * (seriesCount - 1));
589 double seriesW = calculateSeriesWidth(space, domainAxis,
590 categoryCount, seriesCount);
591 barW0 = barW0 + row * (seriesW + seriesGap)
592 + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
593 }
594 else {
595 barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
596 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
597 / 2.0;
598 }
599 return barW0;
600 }
601
602 /**
603 * Calculates the coordinates for the length of a single bar.
604 *
605 * @param value the value represented by the bar.
606 *
607 * @return The coordinates for each end of the bar (or <code>null</code> if
608 * the bar is not visible for the current axis range).
609 */
610 protected double[] calculateBarL0L1(double value) {
611 double lclip = getLowerClip();
612 double uclip = getUpperClip();
613 double barLow = Math.min(this.base, value);
614 double barHigh = Math.max(this.base, value);
615 if (barHigh < lclip) { // bar is not visible
616 return null;
617 }
618 if (barLow > uclip) { // bar is not visible
619 return null;
620 }
621 barLow = Math.max(barLow, lclip);
622 barHigh = Math.min(barHigh, uclip);
623 return new double[] {barLow, barHigh};
624 }
625
626 /**
627 * Returns the range of values the renderer requires to display all the
628 * items from the specified dataset. This takes into account the range
629 * of values in the dataset, plus the flag that determines whether or not
630 * the base value for the bars should be included in the range.
631 *
632 * @param dataset the dataset (<code>null</code> permitted).
633 *
634 * @return The range (or <code>null</code> if the dataset is
635 * <code>null</code> or empty).
636 */
637 public Range findRangeBounds(CategoryDataset dataset) {
638 Range result = DatasetUtilities.findRangeBounds(dataset);
639 if (result != null) {
640 if (this.includeBaseInRange) {
641 result = Range.expandToInclude(result, this.base);
642 }
643 }
644 return result;
645 }
646
647 /**
648 * Returns a legend item for a series.
649 *
650 * @param datasetIndex the dataset index (zero-based).
651 * @param series the series index (zero-based).
652 *
653 * @return The legend item (possibly <code>null</code>).
654 */
655 public LegendItem getLegendItem(int datasetIndex, int series) {
656
657 CategoryPlot cp = getPlot();
658 if (cp == null) {
659 return null;
660 }
661
662 // check that a legend item needs to be displayed...
663 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
664 return null;
665 }
666
667 CategoryDataset dataset = cp.getDataset(datasetIndex);
668 String label = getLegendItemLabelGenerator().generateLabel(dataset,
669 series);
670 String description = label;
671 String toolTipText = null;
672 if (getLegendItemToolTipGenerator() != null) {
673 toolTipText = getLegendItemToolTipGenerator().generateLabel(
674 dataset, series);
675 }
676 String urlText = null;
677 if (getLegendItemURLGenerator() != null) {
678 urlText = getLegendItemURLGenerator().generateLabel(dataset,
679 series);
680 }
681 Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
682 Paint paint = lookupSeriesPaint(series);
683 Paint outlinePaint = lookupSeriesOutlinePaint(series);
684 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
685
686 LegendItem result = new LegendItem(label, description, toolTipText,
687 urlText, true, shape, true, paint, isDrawBarOutline(),
688 outlinePaint, outlineStroke, false, new Line2D.Float(),
689 new BasicStroke(1.0f), Color.black);
690 result.setDataset(dataset);
691 result.setDatasetIndex(datasetIndex);
692 result.setSeriesKey(dataset.getRowKey(series));
693 result.setSeriesIndex(series);
694 if (this.gradientPaintTransformer != null) {
695 result.setFillPaintTransformer(this.gradientPaintTransformer);
696 }
697 return result;
698 }
699
700 /**
701 * Draws the bar for a single (series, category) data item.
702 *
703 * @param g2 the graphics device.
704 * @param state the renderer state.
705 * @param dataArea the data area.
706 * @param plot the plot.
707 * @param domainAxis the domain axis.
708 * @param rangeAxis the range axis.
709 * @param dataset the dataset.
710 * @param row the row index (zero-based).
711 * @param column the column index (zero-based).
712 * @param pass the pass index.
713 */
714 public void drawItem(Graphics2D g2,
715 CategoryItemRendererState state,
716 Rectangle2D dataArea,
717 CategoryPlot plot,
718 CategoryAxis domainAxis,
719 ValueAxis rangeAxis,
720 CategoryDataset dataset,
721 int row,
722 int column,
723 int pass) {
724
725 // nothing is drawn for null values...
726 Number dataValue = dataset.getValue(row, column);
727 if (dataValue == null) {
728 return;
729 }
730
731 double value = dataValue.doubleValue();
732
733 PlotOrientation orientation = plot.getOrientation();
734 double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
735 state, row, column);
736 double[] barL0L1 = calculateBarL0L1(value);
737 if (barL0L1 == null) {
738 return; // the bar is not visible
739 }
740
741 RectangleEdge edge = plot.getRangeAxisEdge();
742 double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
743 double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
744 double barL0 = Math.min(transL0, transL1);
745 double barLength = Math.max(Math.abs(transL1 - transL0),
746 getMinimumBarLength());
747
748 // draw the bar...
749 Rectangle2D bar = null;
750 if (orientation == PlotOrientation.HORIZONTAL) {
751 bar = new Rectangle2D.Double(barL0, barW0, barLength,
752 state.getBarWidth());
753 }
754 else {
755 bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(),
756 barLength);
757 }
758 Paint itemPaint = getItemPaint(row, column);
759 GradientPaintTransformer t = getGradientPaintTransformer();
760 if (t != null && itemPaint instanceof GradientPaint) {
761 itemPaint = t.transform((GradientPaint) itemPaint, bar);
762 }
763 g2.setPaint(itemPaint);
764 g2.fill(bar);
765
766 // draw the outline...
767 if (isDrawBarOutline()
768 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
769 Stroke stroke = getItemOutlineStroke(row, column);
770 Paint paint = getItemOutlinePaint(row, column);
771 if (stroke != null && paint != null) {
772 g2.setStroke(stroke);
773 g2.setPaint(paint);
774 g2.draw(bar);
775 }
776 }
777
778 CategoryItemLabelGenerator generator
779 = getItemLabelGenerator(row, column);
780 if (generator != null && isItemLabelVisible(row, column)) {
781 drawItemLabel(g2, dataset, row, column, plot, generator, bar,
782 (value < 0.0));
783 }
784
785 // add an item entity, if this information is being collected
786 EntityCollection entities = state.getEntityCollection();
787 if (entities != null) {
788 addItemEntity(entities, dataset, row, column, bar);
789 }
790
791 }
792
793 /**
794 * Calculates the available space for each series.
795 *
796 * @param space the space along the entire axis (in Java2D units).
797 * @param axis the category axis.
798 * @param categories the number of categories.
799 * @param series the number of series.
800 *
801 * @return The width of one series.
802 */
803 protected double calculateSeriesWidth(double space, CategoryAxis axis,
804 int categories, int series) {
805 double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
806 - axis.getUpperMargin();
807 if (categories > 1) {
808 factor = factor - axis.getCategoryMargin();
809 }
810 return (space * factor) / (categories * series);
811 }
812
813 /**
814 * Draws an item label. This method is overridden so that the bar can be
815 * used to calculate the label anchor point.
816 *
817 * @param g2 the graphics device.
818 * @param data the dataset.
819 * @param row the row.
820 * @param column the column.
821 * @param plot the plot.
822 * @param generator the label generator.
823 * @param bar the bar.
824 * @param negative a flag indicating a negative value.
825 */
826 protected void drawItemLabel(Graphics2D g2,
827 CategoryDataset data,
828 int row,
829 int column,
830 CategoryPlot plot,
831 CategoryItemLabelGenerator generator,
832 Rectangle2D bar,
833 boolean negative) {
834
835 String label = generator.generateLabel(data, row, column);
836 if (label == null) {
837 return; // nothing to do
838 }
839
840 Font labelFont = getItemLabelFont(row, column);
841 g2.setFont(labelFont);
842 Paint paint = getItemLabelPaint(row, column);
843 g2.setPaint(paint);
844
845 // find out where to place the label...
846 ItemLabelPosition position = null;
847 if (!negative) {
848 position = getPositiveItemLabelPosition(row, column);
849 }
850 else {
851 position = getNegativeItemLabelPosition(row, column);
852 }
853
854 // work out the label anchor point...
855 Point2D anchorPoint = calculateLabelAnchorPoint(
856 position.getItemLabelAnchor(), bar, plot.getOrientation());
857
858 if (isInternalAnchor(position.getItemLabelAnchor())) {
859 Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
860 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
861 position.getTextAnchor(), position.getAngle(),
862 position.getRotationAnchor());
863
864 if (bounds != null) {
865 if (!bar.contains(bounds.getBounds2D())) {
866 if (!negative) {
867 position = getPositiveItemLabelPositionFallback();
868 }
869 else {
870 position = getNegativeItemLabelPositionFallback();
871 }
872 if (position != null) {
873 anchorPoint = calculateLabelAnchorPoint(
874 position.getItemLabelAnchor(), bar,
875 plot.getOrientation());
876 }
877 }
878 }
879
880 }
881
882 if (position != null) {
883 TextUtilities.drawRotatedString(label, g2,
884 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
885 position.getTextAnchor(), position.getAngle(),
886 position.getRotationAnchor());
887 }
888 }
889
890 /**
891 * Calculates the item label anchor point.
892 *
893 * @param anchor the anchor.
894 * @param bar the bar.
895 * @param orientation the plot orientation.
896 *
897 * @return The anchor point.
898 */
899 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
900 Rectangle2D bar,
901 PlotOrientation orientation) {
902
903 Point2D result = null;
904 double offset = getItemLabelAnchorOffset();
905 double x0 = bar.getX() - offset;
906 double x1 = bar.getX();
907 double x2 = bar.getX() + offset;
908 double x3 = bar.getCenterX();
909 double x4 = bar.getMaxX() - offset;
910 double x5 = bar.getMaxX();
911 double x6 = bar.getMaxX() + offset;
912
913 double y0 = bar.getMaxY() + offset;
914 double y1 = bar.getMaxY();
915 double y2 = bar.getMaxY() - offset;
916 double y3 = bar.getCenterY();
917 double y4 = bar.getMinY() + offset;
918 double y5 = bar.getMinY();
919 double y6 = bar.getMinY() - offset;
920
921 if (anchor == ItemLabelAnchor.CENTER) {
922 result = new Point2D.Double(x3, y3);
923 }
924 else if (anchor == ItemLabelAnchor.INSIDE1) {
925 result = new Point2D.Double(x4, y4);
926 }
927 else if (anchor == ItemLabelAnchor.INSIDE2) {
928 result = new Point2D.Double(x4, y4);
929 }
930 else if (anchor == ItemLabelAnchor.INSIDE3) {
931 result = new Point2D.Double(x4, y3);
932 }
933 else if (anchor == ItemLabelAnchor.INSIDE4) {
934 result = new Point2D.Double(x4, y2);
935 }
936 else if (anchor == ItemLabelAnchor.INSIDE5) {
937 result = new Point2D.Double(x4, y2);
938 }
939 else if (anchor == ItemLabelAnchor.INSIDE6) {
940 result = new Point2D.Double(x3, y2);
941 }
942 else if (anchor == ItemLabelAnchor.INSIDE7) {
943 result = new Point2D.Double(x2, y2);
944 }
945 else if (anchor == ItemLabelAnchor.INSIDE8) {
946 result = new Point2D.Double(x2, y2);
947 }
948 else if (anchor == ItemLabelAnchor.INSIDE9) {
949 result = new Point2D.Double(x2, y3);
950 }
951 else if (anchor == ItemLabelAnchor.INSIDE10) {
952 result = new Point2D.Double(x2, y4);
953 }
954 else if (anchor == ItemLabelAnchor.INSIDE11) {
955 result = new Point2D.Double(x2, y4);
956 }
957 else if (anchor == ItemLabelAnchor.INSIDE12) {
958 result = new Point2D.Double(x3, y4);
959 }
960 else if (anchor == ItemLabelAnchor.OUTSIDE1) {
961 result = new Point2D.Double(x5, y6);
962 }
963 else if (anchor == ItemLabelAnchor.OUTSIDE2) {
964 result = new Point2D.Double(x6, y5);
965 }
966 else if (anchor == ItemLabelAnchor.OUTSIDE3) {
967 result = new Point2D.Double(x6, y3);
968 }
969 else if (anchor == ItemLabelAnchor.OUTSIDE4) {
970 result = new Point2D.Double(x6, y1);
971 }
972 else if (anchor == ItemLabelAnchor.OUTSIDE5) {
973 result = new Point2D.Double(x5, y0);
974 }
975 else if (anchor == ItemLabelAnchor.OUTSIDE6) {
976 result = new Point2D.Double(x3, y0);
977 }
978 else if (anchor == ItemLabelAnchor.OUTSIDE7) {
979 result = new Point2D.Double(x1, y0);
980 }
981 else if (anchor == ItemLabelAnchor.OUTSIDE8) {
982 result = new Point2D.Double(x0, y1);
983 }
984 else if (anchor == ItemLabelAnchor.OUTSIDE9) {
985 result = new Point2D.Double(x0, y3);
986 }
987 else if (anchor == ItemLabelAnchor.OUTSIDE10) {
988 result = new Point2D.Double(x0, y5);
989 }
990 else if (anchor == ItemLabelAnchor.OUTSIDE11) {
991 result = new Point2D.Double(x1, y6);
992 }
993 else if (anchor == ItemLabelAnchor.OUTSIDE12) {
994 result = new Point2D.Double(x3, y6);
995 }
996
997 return result;
998
999 }
1000
1001 /**
1002 * Returns <code>true</code> if the specified anchor point is inside a bar.
1003 *
1004 * @param anchor the anchor point.
1005 *
1006 * @return A boolean.
1007 */
1008 private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1009 return anchor == ItemLabelAnchor.CENTER
1010 || anchor == ItemLabelAnchor.INSIDE1
1011 || anchor == ItemLabelAnchor.INSIDE2
1012 || anchor == ItemLabelAnchor.INSIDE3
1013 || anchor == ItemLabelAnchor.INSIDE4
1014 || anchor == ItemLabelAnchor.INSIDE5
1015 || anchor == ItemLabelAnchor.INSIDE6
1016 || anchor == ItemLabelAnchor.INSIDE7
1017 || anchor == ItemLabelAnchor.INSIDE8
1018 || anchor == ItemLabelAnchor.INSIDE9
1019 || anchor == ItemLabelAnchor.INSIDE10
1020 || anchor == ItemLabelAnchor.INSIDE11
1021 || anchor == ItemLabelAnchor.INSIDE12;
1022 }
1023
1024 /**
1025 * Tests this instance for equality with an arbitrary object.
1026 *
1027 * @param obj the object (<code>null</code> permitted).
1028 *
1029 * @return A boolean.
1030 */
1031 public boolean equals(Object obj) {
1032
1033 if (obj == this) {
1034 return true;
1035 }
1036 if (!(obj instanceof BarRenderer)) {
1037 return false;
1038 }
1039 if (!super.equals(obj)) {
1040 return false;
1041 }
1042 BarRenderer that = (BarRenderer) obj;
1043 if (this.base != that.base) {
1044 return false;
1045 }
1046 if (this.itemMargin != that.itemMargin) {
1047 return false;
1048 }
1049 if (this.drawBarOutline != that.drawBarOutline) {
1050 return false;
1051 }
1052 if (this.maximumBarWidth != that.maximumBarWidth) {
1053 return false;
1054 }
1055 if (this.minimumBarLength != that.minimumBarLength) {
1056 return false;
1057 }
1058 if (!ObjectUtilities.equal(this.gradientPaintTransformer,
1059 that.gradientPaintTransformer)) {
1060 return false;
1061 }
1062 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
1063 that.positiveItemLabelPositionFallback)) {
1064 return false;
1065 }
1066 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
1067 that.negativeItemLabelPositionFallback)) {
1068 return false;
1069 }
1070 return true;
1071
1072 }
1073
1074 }