001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Christian W. Zuckschwerdt;
034 *
035 * Changes
036 * -------
037 * 14-Mar-2002 : Version 1 (DG);
038 * 23-May-2002 : Added tooltip generator to renderer (DG);
039 * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
040 * 25-Jun-2002 : Changed constructor to protected and removed redundant
041 * code (DG);
042 * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
043 * clip values (DG);
044 * 24-Sep-2002 : Added getLegendItem() method (DG);
045 * 09-Oct-2002 : Modified constructor to include URL generator (DG);
046 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
047 * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
048 * 17-Jan-2003 : Moved plot classes into a separate package (DG);
049 * 25-Mar-2003 : Implemented Serializable (DG);
050 * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
051 * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
052 * 12-Jun-2003 : Updates for item labels (DG);
053 * 30-Jul-2003 : Modified entity constructor (CZ);
054 * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
055 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
056 * 07-Oct-2003 : Added renderer state (DG);
057 * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
058 * methods (DG);
059 * 28-Oct-2003 : Added support for gradient paint on bars (DG);
060 * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
061 * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
062 * overriding (DG);
063 * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
064 * label generators. Fixed equals() method (DG);
065 * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
066 * 05-Nov-2004 : Modified drawItem() signature (DG);
067 * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
068 * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
069 * 18-May-2005 : Added configurable base value (DG);
070 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
071 * 01-Dec-2005 : Update legend item to use/not use outline (DG);
072 * ------------: JFreeChart 1.0.x ---------------------------------------------
073 * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
074 * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
075 * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
076 * bars) (DG);
077 * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
078 * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
079 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
080 * 11-May-2007 : Check for visibility in getLegendItem() (DG);
081 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
082 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
083 * 07-May-2008 : If minimumBarLength is > 0.0, extend the non-base end of the
084 * bar (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 = false;
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 fireChangeEvent();
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 fireChangeEvent();
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 fireChangeEvent();
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 fireChangeEvent();
306 }
307
308 /**
309 * Returns the minimum bar length (in Java2D units). The default value is
310 * 0.0.
311 *
312 * @return The minimum bar length.
313 *
314 * @see #setMinimumBarLength(double)
315 */
316 public double getMinimumBarLength() {
317 return this.minimumBarLength;
318 }
319
320 /**
321 * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
322 * all registered listeners. The minimum bar length is specified in Java2D
323 * units, and can be used to prevent bars that represent very small data
324 * values from disappearing when drawn on the screen. Typically you would
325 * set this to (say) 0.5 or 1.0 Java 2D units. Use this attribute with
326 * caution, however, because setting it to a non-zero value will
327 * artificially increase the length of bars representing small values,
328 * which may misrepresent your data.
329 *
330 * @param min the minimum bar length (in Java2D units, must be >= 0.0).
331 *
332 * @see #getMinimumBarLength()
333 */
334 public void setMinimumBarLength(double min) {
335 if (min < 0.0) {
336 throw new IllegalArgumentException("Requires 'min' >= 0.0");
337 }
338 this.minimumBarLength = min;
339 fireChangeEvent();
340 }
341
342 /**
343 * Returns the gradient paint transformer (an object used to transform
344 * gradient paint objects to fit each bar).
345 *
346 * @return A transformer (<code>null</code> possible).
347 *
348 * @see #setGradientPaintTransformer(GradientPaintTransformer)
349 */
350 public GradientPaintTransformer getGradientPaintTransformer() {
351 return this.gradientPaintTransformer;
352 }
353
354 /**
355 * Sets the gradient paint transformer and sends a
356 * {@link RendererChangeEvent} to all registered listeners.
357 *
358 * @param transformer the transformer (<code>null</code> permitted).
359 *
360 * @see #getGradientPaintTransformer()
361 */
362 public void setGradientPaintTransformer(
363 GradientPaintTransformer transformer) {
364 this.gradientPaintTransformer = transformer;
365 fireChangeEvent();
366 }
367
368 /**
369 * Returns the fallback position for positive item labels that don't fit
370 * within a bar.
371 *
372 * @return The fallback position (<code>null</code> possible).
373 *
374 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
375 */
376 public ItemLabelPosition getPositiveItemLabelPositionFallback() {
377 return this.positiveItemLabelPositionFallback;
378 }
379
380 /**
381 * Sets the fallback position for positive item labels that don't fit
382 * within a bar, and sends a {@link RendererChangeEvent} to all registered
383 * listeners.
384 *
385 * @param position the position (<code>null</code> permitted).
386 *
387 * @see #getPositiveItemLabelPositionFallback()
388 */
389 public void setPositiveItemLabelPositionFallback(
390 ItemLabelPosition position) {
391 this.positiveItemLabelPositionFallback = position;
392 fireChangeEvent();
393 }
394
395 /**
396 * Returns the fallback position for negative item labels that don't fit
397 * within a bar.
398 *
399 * @return The fallback position (<code>null</code> possible).
400 *
401 * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
402 */
403 public ItemLabelPosition getNegativeItemLabelPositionFallback() {
404 return this.negativeItemLabelPositionFallback;
405 }
406
407 /**
408 * Sets the fallback position for negative 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 #getNegativeItemLabelPositionFallback()
415 */
416 public void setNegativeItemLabelPositionFallback(
417 ItemLabelPosition position) {
418 this.negativeItemLabelPositionFallback = position;
419 fireChangeEvent();
420 }
421
422 /**
423 * Returns the flag that controls whether or not the base value for the
424 * bars is included in the range calculated by
425 * {@link #findRangeBounds(CategoryDataset)}.
426 *
427 * @return <code>true</code> if the base is included in the range, and
428 * <code>false</code> otherwise.
429 *
430 * @since 1.0.1
431 *
432 * @see #setIncludeBaseInRange(boolean)
433 */
434 public boolean getIncludeBaseInRange() {
435 return this.includeBaseInRange;
436 }
437
438 /**
439 * Sets the flag that controls whether or not the base value for the bars
440 * is included in the range calculated by
441 * {@link #findRangeBounds(CategoryDataset)}. If the flag is changed,
442 * a {@link RendererChangeEvent} is sent to all registered listeners.
443 *
444 * @param include the new value for the flag.
445 *
446 * @since 1.0.1
447 *
448 * @see #getIncludeBaseInRange()
449 */
450 public void setIncludeBaseInRange(boolean include) {
451 if (this.includeBaseInRange != include) {
452 this.includeBaseInRange = include;
453 fireChangeEvent();
454 }
455 }
456
457 /**
458 * Returns the lower clip value. This value is recalculated in the
459 * initialise() method.
460 *
461 * @return The value.
462 */
463 public double getLowerClip() {
464 // TODO: this attribute should be transferred to the renderer state.
465 return this.lowerClip;
466 }
467
468 /**
469 * Returns the upper clip value. This value is recalculated in the
470 * initialise() method.
471 *
472 * @return The value.
473 */
474 public double getUpperClip() {
475 // TODO: this attribute should be transferred to the renderer state.
476 return this.upperClip;
477 }
478
479 /**
480 * Initialises the renderer and returns a state object that will be passed
481 * to subsequent calls to the drawItem method. This method gets called
482 * once at the start of the process of drawing a chart.
483 *
484 * @param g2 the graphics device.
485 * @param dataArea the area in which the data is to be plotted.
486 * @param plot the plot.
487 * @param rendererIndex the renderer index.
488 * @param info collects chart rendering information for return to caller.
489 *
490 * @return The renderer state.
491 */
492 public CategoryItemRendererState initialise(Graphics2D g2,
493 Rectangle2D dataArea,
494 CategoryPlot plot,
495 int rendererIndex,
496 PlotRenderingInfo info) {
497
498 CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
499 rendererIndex, info);
500
501 // get the clipping values...
502 ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
503 this.lowerClip = rangeAxis.getRange().getLowerBound();
504 this.upperClip = rangeAxis.getRange().getUpperBound();
505
506 // calculate the bar width
507 calculateBarWidth(plot, dataArea, rendererIndex, state);
508
509 return state;
510
511 }
512
513 /**
514 * Calculates the bar width and stores it in the renderer state.
515 *
516 * @param plot the plot.
517 * @param dataArea the data area.
518 * @param rendererIndex the renderer index.
519 * @param state the renderer state.
520 */
521 protected void calculateBarWidth(CategoryPlot plot,
522 Rectangle2D dataArea,
523 int rendererIndex,
524 CategoryItemRendererState state) {
525
526 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
527 CategoryDataset dataset = plot.getDataset(rendererIndex);
528 if (dataset != null) {
529 int columns = dataset.getColumnCount();
530 int rows = dataset.getRowCount();
531 double space = 0.0;
532 PlotOrientation orientation = plot.getOrientation();
533 if (orientation == PlotOrientation.HORIZONTAL) {
534 space = dataArea.getHeight();
535 }
536 else if (orientation == PlotOrientation.VERTICAL) {
537 space = dataArea.getWidth();
538 }
539 double maxWidth = space * getMaximumBarWidth();
540 double categoryMargin = 0.0;
541 double currentItemMargin = 0.0;
542 if (columns > 1) {
543 categoryMargin = domainAxis.getCategoryMargin();
544 }
545 if (rows > 1) {
546 currentItemMargin = getItemMargin();
547 }
548 double used = space * (1 - domainAxis.getLowerMargin()
549 - domainAxis.getUpperMargin()
550 - categoryMargin - currentItemMargin);
551 if ((rows * columns) > 0) {
552 state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
553 }
554 else {
555 state.setBarWidth(Math.min(used, maxWidth));
556 }
557 }
558 }
559
560 /**
561 * Calculates the coordinate of the first "side" of a bar. This will be
562 * the minimum x-coordinate for a vertical bar, and the minimum
563 * y-coordinate for a horizontal bar.
564 *
565 * @param plot the plot.
566 * @param orientation the plot orientation.
567 * @param dataArea the data area.
568 * @param domainAxis the domain axis.
569 * @param state the renderer state (has the bar width precalculated).
570 * @param row the row index.
571 * @param column the column index.
572 *
573 * @return The coordinate.
574 */
575 protected double calculateBarW0(CategoryPlot plot,
576 PlotOrientation orientation,
577 Rectangle2D dataArea,
578 CategoryAxis domainAxis,
579 CategoryItemRendererState state,
580 int row,
581 int column) {
582 // calculate bar width...
583 double space = 0.0;
584 if (orientation == PlotOrientation.HORIZONTAL) {
585 space = dataArea.getHeight();
586 }
587 else {
588 space = dataArea.getWidth();
589 }
590 double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
591 dataArea, plot.getDomainAxisEdge());
592 int seriesCount = getRowCount();
593 int categoryCount = getColumnCount();
594 if (seriesCount > 1) {
595 double seriesGap = space * getItemMargin()
596 / (categoryCount * (seriesCount - 1));
597 double seriesW = calculateSeriesWidth(space, domainAxis,
598 categoryCount, seriesCount);
599 barW0 = barW0 + row * (seriesW + seriesGap)
600 + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
601 }
602 else {
603 barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
604 dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
605 / 2.0;
606 }
607 return barW0;
608 }
609
610 /**
611 * Calculates the coordinates for the length of a single bar.
612 *
613 * @param value the value represented by the bar.
614 *
615 * @return The coordinates for each end of the bar (or <code>null</code> if
616 * the bar is not visible for the current axis range).
617 */
618 protected double[] calculateBarL0L1(double value) {
619 double lclip = getLowerClip();
620 double uclip = getUpperClip();
621 double barLow = Math.min(this.base, value);
622 double barHigh = Math.max(this.base, value);
623 if (barHigh < lclip) { // bar is not visible
624 return null;
625 }
626 if (barLow > uclip) { // bar is not visible
627 return null;
628 }
629 barLow = Math.max(barLow, lclip);
630 barHigh = Math.min(barHigh, uclip);
631 return new double[] {barLow, barHigh};
632 }
633
634 /**
635 * Returns the range of values the renderer requires to display all the
636 * items from the specified dataset. This takes into account the range
637 * of values in the dataset, plus the flag that determines whether or not
638 * the base value for the bars should be included in the range.
639 *
640 * @param dataset the dataset (<code>null</code> permitted).
641 *
642 * @return The range (or <code>null</code> if the dataset is
643 * <code>null</code> or empty).
644 */
645 public Range findRangeBounds(CategoryDataset dataset) {
646 Range result = DatasetUtilities.findRangeBounds(dataset);
647 if (result != null) {
648 if (this.includeBaseInRange) {
649 result = Range.expandToInclude(result, this.base);
650 }
651 }
652 return result;
653 }
654
655 /**
656 * Returns a legend item for a series.
657 *
658 * @param datasetIndex the dataset index (zero-based).
659 * @param series the series index (zero-based).
660 *
661 * @return The legend item (possibly <code>null</code>).
662 */
663 public LegendItem getLegendItem(int datasetIndex, int series) {
664
665 CategoryPlot cp = getPlot();
666 if (cp == null) {
667 return null;
668 }
669
670 // check that a legend item needs to be displayed...
671 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
672 return null;
673 }
674
675 CategoryDataset dataset = cp.getDataset(datasetIndex);
676 String label = getLegendItemLabelGenerator().generateLabel(dataset,
677 series);
678 String description = label;
679 String toolTipText = null;
680 if (getLegendItemToolTipGenerator() != null) {
681 toolTipText = getLegendItemToolTipGenerator().generateLabel(
682 dataset, series);
683 }
684 String urlText = null;
685 if (getLegendItemURLGenerator() != null) {
686 urlText = getLegendItemURLGenerator().generateLabel(dataset,
687 series);
688 }
689 Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
690 Paint paint = lookupSeriesPaint(series);
691 Paint outlinePaint = lookupSeriesOutlinePaint(series);
692 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
693
694 LegendItem result = new LegendItem(label, description, toolTipText,
695 urlText, true, shape, true, paint, isDrawBarOutline(),
696 outlinePaint, outlineStroke, false, new Line2D.Float(),
697 new BasicStroke(1.0f), Color.black);
698 result.setDataset(dataset);
699 result.setDatasetIndex(datasetIndex);
700 result.setSeriesKey(dataset.getRowKey(series));
701 result.setSeriesIndex(series);
702 if (this.gradientPaintTransformer != null) {
703 result.setFillPaintTransformer(this.gradientPaintTransformer);
704 }
705 return result;
706 }
707
708 /**
709 * Draws the bar for a single (series, category) data item.
710 *
711 * @param g2 the graphics device.
712 * @param state the renderer state.
713 * @param dataArea the data area.
714 * @param plot the plot.
715 * @param domainAxis the domain axis.
716 * @param rangeAxis the range axis.
717 * @param dataset the dataset.
718 * @param row the row index (zero-based).
719 * @param column the column index (zero-based).
720 * @param pass the pass index.
721 */
722 public void drawItem(Graphics2D g2,
723 CategoryItemRendererState state,
724 Rectangle2D dataArea,
725 CategoryPlot plot,
726 CategoryAxis domainAxis,
727 ValueAxis rangeAxis,
728 CategoryDataset dataset,
729 int row,
730 int column,
731 int pass) {
732
733 // nothing is drawn for null values...
734 Number dataValue = dataset.getValue(row, column);
735 if (dataValue == null) {
736 return;
737 }
738
739 double value = dataValue.doubleValue();
740 PlotOrientation orientation = plot.getOrientation();
741 double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
742 state, row, column);
743 double[] barL0L1 = calculateBarL0L1(value);
744 if (barL0L1 == null) {
745 return; // the bar is not visible
746 }
747
748 RectangleEdge edge = plot.getRangeAxisEdge();
749 double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
750 double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
751
752 // in the following code, barL0 is (in Java2D coordinates) the LEFT
753 // end of the bar for a horizontal bar chart, and the TOP end of the
754 // bar for a vertical bar chart. Whether this is the BASE of the bar
755 // or not depends also on (a) whether the data value is 'negative'
756 // relative to the base value and (b) whether or not the range axis is
757 // inverted. This only matters if/when we apply the minimumBarLength
758 // attribute, because we should extend the non-base end of the bar
759 boolean positive = (value >= this.base);
760 boolean inverted = rangeAxis.isInverted();
761 double barL0 = Math.min(transL0, transL1);
762 double barLength = Math.abs(transL1 - transL0);
763 double barLengthAdj = 0.0;
764 if (barLength > 0.0 && barLength < getMinimumBarLength()) {
765 barLengthAdj = getMinimumBarLength() - barLength;
766 }
767 double barL0Adj = 0.0;
768 if (orientation == PlotOrientation.HORIZONTAL) {
769 if (positive && inverted || !positive && !inverted) {
770 barL0Adj = barLengthAdj;
771 }
772 }
773 else {
774 if (positive && !inverted || !positive && inverted) {
775 barL0Adj = barLengthAdj;
776 }
777 }
778
779 // draw the bar...
780 Rectangle2D bar = null;
781 if (orientation == PlotOrientation.HORIZONTAL) {
782 bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
783 barLength + barLengthAdj, state.getBarWidth());
784 }
785 else {
786 bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
787 state.getBarWidth(), barLength + barLengthAdj);
788 }
789 Paint itemPaint = getItemPaint(row, column);
790 GradientPaintTransformer t = getGradientPaintTransformer();
791 if (t != null && itemPaint instanceof GradientPaint) {
792 itemPaint = t.transform((GradientPaint) itemPaint, bar);
793 }
794 g2.setPaint(itemPaint);
795 g2.fill(bar);
796
797 // draw the outline...
798 if (isDrawBarOutline()
799 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
800 Stroke stroke = getItemOutlineStroke(row, column);
801 Paint paint = getItemOutlinePaint(row, column);
802 if (stroke != null && paint != null) {
803 g2.setStroke(stroke);
804 g2.setPaint(paint);
805 g2.draw(bar);
806 }
807 }
808
809 CategoryItemLabelGenerator generator
810 = getItemLabelGenerator(row, column);
811 if (generator != null && isItemLabelVisible(row, column)) {
812 drawItemLabel(g2, dataset, row, column, plot, generator, bar,
813 (value < 0.0));
814 }
815
816 // add an item entity, if this information is being collected
817 EntityCollection entities = state.getEntityCollection();
818 if (entities != null) {
819 addItemEntity(entities, dataset, row, column, bar);
820 }
821
822 }
823
824 /**
825 * Calculates the available space for each series.
826 *
827 * @param space the space along the entire axis (in Java2D units).
828 * @param axis the category axis.
829 * @param categories the number of categories.
830 * @param series the number of series.
831 *
832 * @return The width of one series.
833 */
834 protected double calculateSeriesWidth(double space, CategoryAxis axis,
835 int categories, int series) {
836 double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
837 - axis.getUpperMargin();
838 if (categories > 1) {
839 factor = factor - axis.getCategoryMargin();
840 }
841 return (space * factor) / (categories * series);
842 }
843
844 /**
845 * Draws an item label. This method is overridden so that the bar can be
846 * used to calculate the label anchor point.
847 *
848 * @param g2 the graphics device.
849 * @param data the dataset.
850 * @param row the row.
851 * @param column the column.
852 * @param plot the plot.
853 * @param generator the label generator.
854 * @param bar the bar.
855 * @param negative a flag indicating a negative value.
856 */
857 protected void drawItemLabel(Graphics2D g2,
858 CategoryDataset data,
859 int row,
860 int column,
861 CategoryPlot plot,
862 CategoryItemLabelGenerator generator,
863 Rectangle2D bar,
864 boolean negative) {
865
866 String label = generator.generateLabel(data, row, column);
867 if (label == null) {
868 return; // nothing to do
869 }
870
871 Font labelFont = getItemLabelFont(row, column);
872 g2.setFont(labelFont);
873 Paint paint = getItemLabelPaint(row, column);
874 g2.setPaint(paint);
875
876 // find out where to place the label...
877 ItemLabelPosition position = null;
878 if (!negative) {
879 position = getPositiveItemLabelPosition(row, column);
880 }
881 else {
882 position = getNegativeItemLabelPosition(row, column);
883 }
884
885 // work out the label anchor point...
886 Point2D anchorPoint = calculateLabelAnchorPoint(
887 position.getItemLabelAnchor(), bar, plot.getOrientation());
888
889 if (isInternalAnchor(position.getItemLabelAnchor())) {
890 Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
891 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
892 position.getTextAnchor(), position.getAngle(),
893 position.getRotationAnchor());
894
895 if (bounds != null) {
896 if (!bar.contains(bounds.getBounds2D())) {
897 if (!negative) {
898 position = getPositiveItemLabelPositionFallback();
899 }
900 else {
901 position = getNegativeItemLabelPositionFallback();
902 }
903 if (position != null) {
904 anchorPoint = calculateLabelAnchorPoint(
905 position.getItemLabelAnchor(), bar,
906 plot.getOrientation());
907 }
908 }
909 }
910
911 }
912
913 if (position != null) {
914 TextUtilities.drawRotatedString(label, g2,
915 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
916 position.getTextAnchor(), position.getAngle(),
917 position.getRotationAnchor());
918 }
919 }
920
921 /**
922 * Calculates the item label anchor point.
923 *
924 * @param anchor the anchor.
925 * @param bar the bar.
926 * @param orientation the plot orientation.
927 *
928 * @return The anchor point.
929 */
930 private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
931 Rectangle2D bar,
932 PlotOrientation orientation) {
933
934 Point2D result = null;
935 double offset = getItemLabelAnchorOffset();
936 double x0 = bar.getX() - offset;
937 double x1 = bar.getX();
938 double x2 = bar.getX() + offset;
939 double x3 = bar.getCenterX();
940 double x4 = bar.getMaxX() - offset;
941 double x5 = bar.getMaxX();
942 double x6 = bar.getMaxX() + offset;
943
944 double y0 = bar.getMaxY() + offset;
945 double y1 = bar.getMaxY();
946 double y2 = bar.getMaxY() - offset;
947 double y3 = bar.getCenterY();
948 double y4 = bar.getMinY() + offset;
949 double y5 = bar.getMinY();
950 double y6 = bar.getMinY() - offset;
951
952 if (anchor == ItemLabelAnchor.CENTER) {
953 result = new Point2D.Double(x3, y3);
954 }
955 else if (anchor == ItemLabelAnchor.INSIDE1) {
956 result = new Point2D.Double(x4, y4);
957 }
958 else if (anchor == ItemLabelAnchor.INSIDE2) {
959 result = new Point2D.Double(x4, y4);
960 }
961 else if (anchor == ItemLabelAnchor.INSIDE3) {
962 result = new Point2D.Double(x4, y3);
963 }
964 else if (anchor == ItemLabelAnchor.INSIDE4) {
965 result = new Point2D.Double(x4, y2);
966 }
967 else if (anchor == ItemLabelAnchor.INSIDE5) {
968 result = new Point2D.Double(x4, y2);
969 }
970 else if (anchor == ItemLabelAnchor.INSIDE6) {
971 result = new Point2D.Double(x3, y2);
972 }
973 else if (anchor == ItemLabelAnchor.INSIDE7) {
974 result = new Point2D.Double(x2, y2);
975 }
976 else if (anchor == ItemLabelAnchor.INSIDE8) {
977 result = new Point2D.Double(x2, y2);
978 }
979 else if (anchor == ItemLabelAnchor.INSIDE9) {
980 result = new Point2D.Double(x2, y3);
981 }
982 else if (anchor == ItemLabelAnchor.INSIDE10) {
983 result = new Point2D.Double(x2, y4);
984 }
985 else if (anchor == ItemLabelAnchor.INSIDE11) {
986 result = new Point2D.Double(x2, y4);
987 }
988 else if (anchor == ItemLabelAnchor.INSIDE12) {
989 result = new Point2D.Double(x3, y4);
990 }
991 else if (anchor == ItemLabelAnchor.OUTSIDE1) {
992 result = new Point2D.Double(x5, y6);
993 }
994 else if (anchor == ItemLabelAnchor.OUTSIDE2) {
995 result = new Point2D.Double(x6, y5);
996 }
997 else if (anchor == ItemLabelAnchor.OUTSIDE3) {
998 result = new Point2D.Double(x6, y3);
999 }
1000 else if (anchor == ItemLabelAnchor.OUTSIDE4) {
1001 result = new Point2D.Double(x6, y1);
1002 }
1003 else if (anchor == ItemLabelAnchor.OUTSIDE5) {
1004 result = new Point2D.Double(x5, y0);
1005 }
1006 else if (anchor == ItemLabelAnchor.OUTSIDE6) {
1007 result = new Point2D.Double(x3, y0);
1008 }
1009 else if (anchor == ItemLabelAnchor.OUTSIDE7) {
1010 result = new Point2D.Double(x1, y0);
1011 }
1012 else if (anchor == ItemLabelAnchor.OUTSIDE8) {
1013 result = new Point2D.Double(x0, y1);
1014 }
1015 else if (anchor == ItemLabelAnchor.OUTSIDE9) {
1016 result = new Point2D.Double(x0, y3);
1017 }
1018 else if (anchor == ItemLabelAnchor.OUTSIDE10) {
1019 result = new Point2D.Double(x0, y5);
1020 }
1021 else if (anchor == ItemLabelAnchor.OUTSIDE11) {
1022 result = new Point2D.Double(x1, y6);
1023 }
1024 else if (anchor == ItemLabelAnchor.OUTSIDE12) {
1025 result = new Point2D.Double(x3, y6);
1026 }
1027
1028 return result;
1029
1030 }
1031
1032 /**
1033 * Returns <code>true</code> if the specified anchor point is inside a bar.
1034 *
1035 * @param anchor the anchor point.
1036 *
1037 * @return A boolean.
1038 */
1039 private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1040 return anchor == ItemLabelAnchor.CENTER
1041 || anchor == ItemLabelAnchor.INSIDE1
1042 || anchor == ItemLabelAnchor.INSIDE2
1043 || anchor == ItemLabelAnchor.INSIDE3
1044 || anchor == ItemLabelAnchor.INSIDE4
1045 || anchor == ItemLabelAnchor.INSIDE5
1046 || anchor == ItemLabelAnchor.INSIDE6
1047 || anchor == ItemLabelAnchor.INSIDE7
1048 || anchor == ItemLabelAnchor.INSIDE8
1049 || anchor == ItemLabelAnchor.INSIDE9
1050 || anchor == ItemLabelAnchor.INSIDE10
1051 || anchor == ItemLabelAnchor.INSIDE11
1052 || anchor == ItemLabelAnchor.INSIDE12;
1053 }
1054
1055 /**
1056 * Tests this instance for equality with an arbitrary object.
1057 *
1058 * @param obj the object (<code>null</code> permitted).
1059 *
1060 * @return A boolean.
1061 */
1062 public boolean equals(Object obj) {
1063
1064 if (obj == this) {
1065 return true;
1066 }
1067 if (!(obj instanceof BarRenderer)) {
1068 return false;
1069 }
1070 if (!super.equals(obj)) {
1071 return false;
1072 }
1073 BarRenderer that = (BarRenderer) obj;
1074 if (this.base != that.base) {
1075 return false;
1076 }
1077 if (this.itemMargin != that.itemMargin) {
1078 return false;
1079 }
1080 if (this.drawBarOutline != that.drawBarOutline) {
1081 return false;
1082 }
1083 if (this.maximumBarWidth != that.maximumBarWidth) {
1084 return false;
1085 }
1086 if (this.minimumBarLength != that.minimumBarLength) {
1087 return false;
1088 }
1089 if (!ObjectUtilities.equal(this.gradientPaintTransformer,
1090 that.gradientPaintTransformer)) {
1091 return false;
1092 }
1093 if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
1094 that.positiveItemLabelPositionFallback)) {
1095 return false;
1096 }
1097 if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
1098 that.negativeItemLabelPositionFallback)) {
1099 return false;
1100 }
1101 return true;
1102
1103 }
1104
1105 }