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