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: * MinMaxCategoryRenderer.java
029: * ---------------------------
030: * (C) Copyright 2002-2007, by Object Refinery Limited.
031: *
032: * Original Author: Tomer Peretz;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: * Christian W. Zuckschwerdt;
035: * Nicolas Brodu (for Astrium and EADS Corporate Research
036: * Center);
037: *
038: * $Id: MinMaxCategoryRenderer.java,v 1.6.2.8 2007/06/01 15:12:15 mungady Exp $
039: *
040: * Changes:
041: * --------
042: * 29-May-2002 : Version 1 (TP);
043: * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
044: * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
045: * CategoryToolTipGenerator interface (DG);
046: * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
047: * 17-Jan-2003 : Moved plot classes to a separate package (DG);
048: * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
049: * method (DG);
050: * 30-Jul-2003 : Modified entity constructor (CZ);
051: * 08-Sep-2003 : Implemented Serializable (NB);
052: * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
053: * 05-Nov-2004 : Modified drawItem() signature (DG);
054: * 17-Nov-2005 : Added change events and argument checks (DG);
055: * ------------- JFREECHART 1.0.x ---------------------------------------------
056: * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
057: * 09-Mar-2007 : Fixed problem with horizontal rendering (DG);
058: *
059: */
060:
061: package org.jfree.chart.renderer.category;
062:
063: import java.awt.BasicStroke;
064: import java.awt.Color;
065: import java.awt.Component;
066: import java.awt.Graphics;
067: import java.awt.Graphics2D;
068: import java.awt.Paint;
069: import java.awt.Shape;
070: import java.awt.Stroke;
071: import java.awt.geom.AffineTransform;
072: import java.awt.geom.Arc2D;
073: import java.awt.geom.GeneralPath;
074: import java.awt.geom.Line2D;
075: import java.awt.geom.Rectangle2D;
076: import java.io.IOException;
077: import java.io.ObjectInputStream;
078: import java.io.ObjectOutputStream;
079:
080: import javax.swing.Icon;
081:
082: import org.jfree.chart.axis.CategoryAxis;
083: import org.jfree.chart.axis.ValueAxis;
084: import org.jfree.chart.entity.CategoryItemEntity;
085: import org.jfree.chart.entity.EntityCollection;
086: import org.jfree.chart.event.RendererChangeEvent;
087: import org.jfree.chart.labels.CategoryToolTipGenerator;
088: import org.jfree.chart.plot.CategoryPlot;
089: import org.jfree.chart.plot.PlotOrientation;
090: import org.jfree.data.category.CategoryDataset;
091: import org.jfree.io.SerialUtilities;
092:
093: /**
094: * Renderer for drawing min max plot. This renderer draws all the series under
095: * the same category in the same x position using <code>objectIcon</code> and
096: * a line from the maximum value to the minimum value.
097: * <p>
098: * For use with the {@link org.jfree.chart.plot.CategoryPlot} class.
099: */
100: public class MinMaxCategoryRenderer extends
101: AbstractCategoryItemRenderer {
102:
103: /** For serialization. */
104: private static final long serialVersionUID = 2935615937671064911L;
105:
106: /** A flag indicating whether or not lines are drawn between XY points. */
107: private boolean plotLines = false;
108:
109: /**
110: * The paint of the line between the minimum value and the maximum value.
111: */
112: private transient Paint groupPaint = Color.black;
113:
114: /**
115: * The stroke of the line between the minimum value and the maximum value.
116: */
117: private transient Stroke groupStroke = new BasicStroke(1.0f);
118:
119: /** The icon used to indicate the minimum value.*/
120: private transient Icon minIcon = getIcon(new Arc2D.Double(-4, -4,
121: 8, 8, 0, 360, Arc2D.OPEN), null, Color.black);
122:
123: /** The icon used to indicate the maximum value.*/
124: private transient Icon maxIcon = getIcon(new Arc2D.Double(-4, -4,
125: 8, 8, 0, 360, Arc2D.OPEN), null, Color.black);
126:
127: /** The icon used to indicate the values.*/
128: private transient Icon objectIcon = getIcon(new Line2D.Double(-4,
129: 0, 4, 0), false, true);
130:
131: /** The last category. */
132: private int lastCategory = -1;
133:
134: /** The minimum. */
135: private double min;
136:
137: /** The maximum. */
138: private double max;
139:
140: /**
141: * Default constructor.
142: */
143: public MinMaxCategoryRenderer() {
144: super ();
145: }
146:
147: /**
148: * Gets whether or not lines are drawn between category points.
149: *
150: * @return boolean true if line will be drawn between sequenced categories,
151: * otherwise false.
152: *
153: * @see #setDrawLines(boolean)
154: */
155: public boolean isDrawLines() {
156: return this .plotLines;
157: }
158:
159: /**
160: * Sets the flag that controls whether or not lines are drawn to connect
161: * the items within a series and sends a {@link RendererChangeEvent} to
162: * all registered listeners.
163: *
164: * @param draw the new value of the flag.
165: *
166: * @see #isDrawLines()
167: */
168: public void setDrawLines(boolean draw) {
169: if (this .plotLines != draw) {
170: this .plotLines = draw;
171: this .notifyListeners(new RendererChangeEvent(this ));
172: }
173:
174: }
175:
176: /**
177: * Returns the paint used to draw the line between the minimum and maximum
178: * value items in each category.
179: *
180: * @return The paint (never <code>null</code>).
181: *
182: * @see #setGroupPaint(Paint)
183: */
184: public Paint getGroupPaint() {
185: return this .groupPaint;
186: }
187:
188: /**
189: * Sets the paint used to draw the line between the minimum and maximum
190: * value items in each category and sends a {@link RendererChangeEvent} to
191: * all registered listeners.
192: *
193: * @param paint the paint (<code>null</code> not permitted).
194: *
195: * @see #getGroupPaint()
196: */
197: public void setGroupPaint(Paint paint) {
198: if (paint == null) {
199: throw new IllegalArgumentException("Null 'paint' argument.");
200: }
201: this .groupPaint = paint;
202: notifyListeners(new RendererChangeEvent(this ));
203: }
204:
205: /**
206: * Returns the stroke used to draw the line between the minimum and maximum
207: * value items in each category.
208: *
209: * @return The stroke (never <code>null</code>).
210: *
211: * @see #setGroupStroke(Stroke)
212: */
213: public Stroke getGroupStroke() {
214: return this .groupStroke;
215: }
216:
217: /**
218: * Sets the stroke of the line between the minimum value and the maximum
219: * value.
220: *
221: * @param groupStroke The new stroke
222: */
223: public void setGroupStroke(Stroke groupStroke) {
224: this .groupStroke = groupStroke;
225: }
226:
227: /**
228: * Returns the icon drawn for each data item.
229: *
230: * @return The icon (never <code>null</code>).
231: *
232: * @see #setObjectIcon(Icon)
233: */
234: public Icon getObjectIcon() {
235: return this .objectIcon;
236: }
237:
238: /**
239: * Sets the icon drawn for each data item.
240: *
241: * @param icon the icon.
242: *
243: * @see #getObjectIcon()
244: */
245: public void setObjectIcon(Icon icon) {
246: if (icon == null) {
247: throw new IllegalArgumentException("Null 'icon' argument.");
248: }
249: this .objectIcon = icon;
250: notifyListeners(new RendererChangeEvent(this ));
251: }
252:
253: /**
254: * Returns the icon displayed for the maximum value data item within each
255: * category.
256: *
257: * @return The icon (never <code>null</code>).
258: *
259: * @see #setMaxIcon(Icon)
260: */
261: public Icon getMaxIcon() {
262: return this .maxIcon;
263: }
264:
265: /**
266: * Sets the icon displayed for the maximum value data item within each
267: * category and sends a {@link RendererChangeEvent} to all registered
268: * listeners.
269: *
270: * @param icon the icon (<code>null</code> not permitted).
271: *
272: * @see #getMaxIcon()
273: */
274: public void setMaxIcon(Icon icon) {
275: if (icon == null) {
276: throw new IllegalArgumentException("Null 'icon' argument.");
277: }
278: this .maxIcon = icon;
279: notifyListeners(new RendererChangeEvent(this ));
280: }
281:
282: /**
283: * Returns the icon displayed for the minimum value data item within each
284: * category.
285: *
286: * @return The icon (never <code>null</code>).
287: *
288: * @see #setMinIcon(Icon)
289: */
290: public Icon getMinIcon() {
291: return this .minIcon;
292: }
293:
294: /**
295: * Sets the icon displayed for the minimum value data item within each
296: * category and sends a {@link RendererChangeEvent} to all registered
297: * listeners.
298: *
299: * @param icon the icon (<code>null</code> not permitted).
300: *
301: * @see #getMinIcon()
302: */
303: public void setMinIcon(Icon icon) {
304: if (icon == null) {
305: throw new IllegalArgumentException("Null 'icon' argument.");
306: }
307: this .minIcon = icon;
308: notifyListeners(new RendererChangeEvent(this ));
309: }
310:
311: /**
312: * Draw a single data item.
313: *
314: * @param g2 the graphics device.
315: * @param state the renderer state.
316: * @param dataArea the area in which the data is drawn.
317: * @param plot the plot.
318: * @param domainAxis the domain axis.
319: * @param rangeAxis the range axis.
320: * @param dataset the dataset.
321: * @param row the row index (zero-based).
322: * @param column the column index (zero-based).
323: * @param pass the pass index.
324: */
325: public void drawItem(Graphics2D g2,
326: CategoryItemRendererState state, Rectangle2D dataArea,
327: CategoryPlot plot, CategoryAxis domainAxis,
328: ValueAxis rangeAxis, CategoryDataset dataset, int row,
329: int column, int pass) {
330:
331: // first check the number we are plotting...
332: Number value = dataset.getValue(row, column);
333: if (value != null) {
334: // current data point...
335: double x1 = domainAxis.getCategoryMiddle(column,
336: getColumnCount(), dataArea, plot
337: .getDomainAxisEdge());
338: double y1 = rangeAxis.valueToJava2D(value.doubleValue(),
339: dataArea, plot.getRangeAxisEdge());
340: g2.setPaint(getItemPaint(row, column));
341: g2.setStroke(getItemStroke(row, column));
342: Shape shape = null;
343: shape = new Rectangle2D.Double(x1 - 4, y1 - 4, 8.0, 8.0);
344:
345: PlotOrientation orient = plot.getOrientation();
346: if (orient == PlotOrientation.VERTICAL) {
347: this .objectIcon.paintIcon(null, g2, (int) x1, (int) y1);
348: } else {
349: this .objectIcon.paintIcon(null, g2, (int) y1, (int) x1);
350: }
351:
352: if (this .lastCategory == column) {
353: if (this .min > value.doubleValue()) {
354: this .min = value.doubleValue();
355: }
356: if (this .max < value.doubleValue()) {
357: this .max = value.doubleValue();
358: }
359:
360: // last series, so we are ready to draw the min and max
361: if (dataset.getRowCount() - 1 == row) {
362: g2.setPaint(this .groupPaint);
363: g2.setStroke(this .groupStroke);
364: double minY = rangeAxis.valueToJava2D(this .min,
365: dataArea, plot.getRangeAxisEdge());
366: double maxY = rangeAxis.valueToJava2D(this .max,
367: dataArea, plot.getRangeAxisEdge());
368:
369: if (orient == PlotOrientation.VERTICAL) {
370: g2.draw(new Line2D.Double(x1, minY, x1, maxY));
371: this .minIcon.paintIcon(null, g2, (int) x1,
372: (int) minY);
373: this .maxIcon.paintIcon(null, g2, (int) x1,
374: (int) maxY);
375: } else {
376: g2.draw(new Line2D.Double(minY, x1, maxY, x1));
377: this .minIcon.paintIcon(null, g2, (int) minY,
378: (int) x1);
379: this .maxIcon.paintIcon(null, g2, (int) maxY,
380: (int) x1);
381: }
382: }
383: } else { // reset the min and max
384: this .lastCategory = column;
385: this .min = value.doubleValue();
386: this .max = value.doubleValue();
387: }
388:
389: // connect to the previous point
390: if (this .plotLines) {
391: if (column != 0) {
392: Number previousValue = dataset.getValue(row,
393: column - 1);
394: if (previousValue != null) {
395: // previous data point...
396: double previous = previousValue.doubleValue();
397: double x0 = domainAxis.getCategoryMiddle(
398: column - 1, getColumnCount(), dataArea,
399: plot.getDomainAxisEdge());
400: double y0 = rangeAxis.valueToJava2D(previous,
401: dataArea, plot.getRangeAxisEdge());
402: g2.setPaint(getItemPaint(row, column));
403: g2.setStroke(getItemStroke(row, column));
404: Line2D line;
405: if (orient == PlotOrientation.VERTICAL) {
406: line = new Line2D.Double(x0, y0, x1, y1);
407: } else {
408: line = new Line2D.Double(y0, x0, y1, x1);
409: }
410: g2.draw(line);
411: }
412: }
413: }
414:
415: // collect entity and tool tip information...
416: if (state.getInfo() != null) {
417: EntityCollection entities = state.getEntityCollection();
418: if (entities != null && shape != null) {
419: String tip = null;
420: CategoryToolTipGenerator tipster = getToolTipGenerator(
421: row, column);
422: if (tipster != null) {
423: tip = tipster.generateToolTip(dataset, row,
424: column);
425: }
426: CategoryItemEntity entity = new CategoryItemEntity(
427: shape, tip, null, dataset, dataset
428: .getRowKey(row), dataset
429: .getColumnKey(column));
430: entities.add(entity);
431: }
432: }
433: }
434: }
435:
436: /**
437: * Returns an icon.
438: *
439: * @param shape the shape.
440: * @param fillPaint the fill paint.
441: * @param outlinePaint the outline paint.
442: *
443: * @return The icon.
444: */
445: private Icon getIcon(Shape shape, final Paint fillPaint,
446: final Paint outlinePaint) {
447:
448: final int width = shape.getBounds().width;
449: final int height = shape.getBounds().height;
450: final GeneralPath path = new GeneralPath(shape);
451: return new Icon() {
452: public void paintIcon(Component c, Graphics g, int x, int y) {
453: Graphics2D g2 = (Graphics2D) g;
454: path.transform(AffineTransform.getTranslateInstance(x,
455: y));
456: if (fillPaint != null) {
457: g2.setPaint(fillPaint);
458: g2.fill(path);
459: }
460: if (outlinePaint != null) {
461: g2.setPaint(outlinePaint);
462: g2.draw(path);
463: }
464: path.transform(AffineTransform.getTranslateInstance(-x,
465: -y));
466: }
467:
468: public int getIconWidth() {
469: return width;
470: }
471:
472: public int getIconHeight() {
473: return height;
474: }
475:
476: };
477: }
478:
479: /**
480: * Returns an icon.
481: *
482: * @param shape the shape.
483: * @param fill the fill flag.
484: * @param outline the outline flag.
485: *
486: * @return The icon.
487: */
488: private Icon getIcon(Shape shape, final boolean fill,
489: final boolean outline) {
490: final int width = shape.getBounds().width;
491: final int height = shape.getBounds().height;
492: final GeneralPath path = new GeneralPath(shape);
493: return new Icon() {
494: public void paintIcon(Component c, Graphics g, int x, int y) {
495: Graphics2D g2 = (Graphics2D) g;
496: path.transform(AffineTransform.getTranslateInstance(x,
497: y));
498: if (fill) {
499: g2.fill(path);
500: }
501: if (outline) {
502: g2.draw(path);
503: }
504: path.transform(AffineTransform.getTranslateInstance(-x,
505: -y));
506: }
507:
508: public int getIconWidth() {
509: return width;
510: }
511:
512: public int getIconHeight() {
513: return height;
514: }
515: };
516: }
517:
518: /**
519: * Provides serialization support.
520: *
521: * @param stream the output stream.
522: *
523: * @throws IOException if there is an I/O error.
524: */
525: private void writeObject(ObjectOutputStream stream)
526: throws IOException {
527: stream.defaultWriteObject();
528: SerialUtilities.writeStroke(this .groupStroke, stream);
529: SerialUtilities.writePaint(this .groupPaint, stream);
530: }
531:
532: /**
533: * Provides serialization support.
534: *
535: * @param stream the input stream.
536: *
537: * @throws IOException if there is an I/O error.
538: * @throws ClassNotFoundException if there is a classpath problem.
539: */
540: private void readObject(ObjectInputStream stream)
541: throws IOException, ClassNotFoundException {
542: stream.defaultReadObject();
543: this .groupStroke = SerialUtilities.readStroke(stream);
544: this .groupPaint = SerialUtilities.readPaint(stream);
545:
546: this .minIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
547: Arc2D.OPEN), null, Color.black);
548: this .maxIcon = getIcon(new Arc2D.Double(-4, -4, 8, 8, 0, 360,
549: Arc2D.OPEN), null, Color.black);
550: this .objectIcon = getIcon(new Line2D.Double(-4, 0, 4, 0),
551: false, true);
552: }
553:
554: }
|