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: * LineRenderer3D.java
029: * -------------------
030: * (C) Copyright 2004-2007, by Tobias Selb and Contributors.
031: *
032: * Original Author: Tobias Selb (http://www.uepselon.com);
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: LineRenderer3D.java,v 1.10.2.8 2007/04/03 15:59:18 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 15-Oct-2004 : Version 1 (TS);
040: * 05-Nov-2004 : Modified drawItem() signature (DG);
041: * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
042: * 26-Jan-2005 : Update for changes in super class (DG);
043: * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
044: * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
045: * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
046: * ------------- JFREECHART 1.0.x ---------------------------------------------
047: * 01-Dec-2006 : Fixed equals() and serialization (DG);
048: * 17-Jan-2007 : Fixed bug in drawDomainGridline() method and added
049: * argument check to setWallPaint() (DG);
050: * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG);
051: *
052: */
053:
054: package org.jfree.chart.renderer.category;
055:
056: import java.awt.AlphaComposite;
057: import java.awt.Color;
058: import java.awt.Composite;
059: import java.awt.Graphics2D;
060: import java.awt.Image;
061: import java.awt.Paint;
062: import java.awt.Shape;
063: import java.awt.Stroke;
064: import java.awt.geom.GeneralPath;
065: import java.awt.geom.Line2D;
066: import java.awt.geom.Rectangle2D;
067: import java.io.IOException;
068: import java.io.ObjectInputStream;
069: import java.io.ObjectOutputStream;
070: import java.io.Serializable;
071:
072: import org.jfree.chart.Effect3D;
073: import org.jfree.chart.axis.CategoryAxis;
074: import org.jfree.chart.axis.ValueAxis;
075: import org.jfree.chart.entity.EntityCollection;
076: import org.jfree.chart.event.RendererChangeEvent;
077: import org.jfree.chart.plot.CategoryPlot;
078: import org.jfree.chart.plot.Marker;
079: import org.jfree.chart.plot.PlotOrientation;
080: import org.jfree.chart.plot.ValueMarker;
081: import org.jfree.data.Range;
082: import org.jfree.data.category.CategoryDataset;
083: import org.jfree.io.SerialUtilities;
084: import org.jfree.util.PaintUtilities;
085: import org.jfree.util.ShapeUtilities;
086:
087: /**
088: * A line renderer with a 3D effect.
089: */
090: public class LineRenderer3D extends LineAndShapeRenderer implements
091: Effect3D, Serializable {
092:
093: /** For serialization. */
094: private static final long serialVersionUID = 5467931468380928736L;
095:
096: /** The default x-offset for the 3D effect. */
097: public static final double DEFAULT_X_OFFSET = 12.0;
098:
099: /** The default y-offset for the 3D effect. */
100: public static final double DEFAULT_Y_OFFSET = 8.0;
101:
102: /** The default wall paint. */
103: public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD,
104: 0xDD, 0xDD);
105:
106: /** The size of x-offset for the 3D effect. */
107: private double xOffset;
108:
109: /** The size of y-offset for the 3D effect. */
110: private double yOffset;
111:
112: /** The paint used to shade the left and lower 3D wall. */
113: private transient Paint wallPaint;
114:
115: /**
116: * Creates a new renderer.
117: */
118: public LineRenderer3D() {
119: super (true, false); //Create a line renderer only
120: this .xOffset = DEFAULT_X_OFFSET;
121: this .yOffset = DEFAULT_Y_OFFSET;
122: this .wallPaint = DEFAULT_WALL_PAINT;
123: }
124:
125: /**
126: * Returns the x-offset for the 3D effect.
127: *
128: * @return The x-offset.
129: *
130: * @see #setXOffset(double)
131: * @see #getYOffset()
132: */
133: public double getXOffset() {
134: return this .xOffset;
135: }
136:
137: /**
138: * Returns the y-offset for the 3D effect.
139: *
140: * @return The y-offset.
141: *
142: * @see #setYOffset(double)
143: * @see #getXOffset()
144: */
145: public double getYOffset() {
146: return this .yOffset;
147: }
148:
149: /**
150: * Sets the x-offset and sends a {@link RendererChangeEvent} to all
151: * registered listeners.
152: *
153: * @param xOffset the x-offset.
154: *
155: * @see #getXOffset()
156: */
157: public void setXOffset(double xOffset) {
158: this .xOffset = xOffset;
159: notifyListeners(new RendererChangeEvent(this ));
160: }
161:
162: /**
163: * Sets the y-offset and sends a {@link RendererChangeEvent} to all
164: * registered listeners.
165: *
166: * @param yOffset the y-offset.
167: *
168: * @see #getYOffset()
169: */
170: public void setYOffset(double yOffset) {
171: this .yOffset = yOffset;
172: notifyListeners(new RendererChangeEvent(this ));
173: }
174:
175: /**
176: * Returns the paint used to highlight the left and bottom wall in the plot
177: * background.
178: *
179: * @return The paint.
180: *
181: * @see #setWallPaint(Paint)
182: */
183: public Paint getWallPaint() {
184: return this .wallPaint;
185: }
186:
187: /**
188: * Sets the paint used to hightlight the left and bottom walls in the plot
189: * background, and sends a {@link RendererChangeEvent} to all
190: * registered listeners.
191: *
192: * @param paint the paint (<code>null</code> not permitted).
193: *
194: * @see #getWallPaint()
195: */
196: public void setWallPaint(Paint paint) {
197: if (paint == null) {
198: throw new IllegalArgumentException("Null 'paint' argument.");
199: }
200: this .wallPaint = paint;
201: notifyListeners(new RendererChangeEvent(this ));
202: }
203:
204: /**
205: * Draws the background for the plot.
206: *
207: * @param g2 the graphics device.
208: * @param plot the plot.
209: * @param dataArea the area inside the axes.
210: */
211: public void drawBackground(Graphics2D g2, CategoryPlot plot,
212: Rectangle2D dataArea) {
213:
214: float x0 = (float) dataArea.getX();
215: float x1 = x0 + (float) Math.abs(this .xOffset);
216: float x3 = (float) dataArea.getMaxX();
217: float x2 = x3 - (float) Math.abs(this .xOffset);
218:
219: float y0 = (float) dataArea.getMaxY();
220: float y1 = y0 - (float) Math.abs(this .yOffset);
221: float y3 = (float) dataArea.getMinY();
222: float y2 = y3 + (float) Math.abs(this .yOffset);
223:
224: GeneralPath clip = new GeneralPath();
225: clip.moveTo(x0, y0);
226: clip.lineTo(x0, y2);
227: clip.lineTo(x1, y3);
228: clip.lineTo(x3, y3);
229: clip.lineTo(x3, y1);
230: clip.lineTo(x2, y0);
231: clip.closePath();
232:
233: Composite originalComposite = g2.getComposite();
234: g2.setComposite(AlphaComposite.getInstance(
235: AlphaComposite.SRC_OVER, plot.getBackgroundAlpha()));
236:
237: // fill background...
238: Paint backgroundPaint = plot.getBackgroundPaint();
239: if (backgroundPaint != null) {
240: g2.setPaint(backgroundPaint);
241: g2.fill(clip);
242: }
243:
244: GeneralPath leftWall = new GeneralPath();
245: leftWall.moveTo(x0, y0);
246: leftWall.lineTo(x0, y2);
247: leftWall.lineTo(x1, y3);
248: leftWall.lineTo(x1, y1);
249: leftWall.closePath();
250: g2.setPaint(getWallPaint());
251: g2.fill(leftWall);
252:
253: GeneralPath bottomWall = new GeneralPath();
254: bottomWall.moveTo(x0, y0);
255: bottomWall.lineTo(x1, y1);
256: bottomWall.lineTo(x3, y1);
257: bottomWall.lineTo(x2, y0);
258: bottomWall.closePath();
259: g2.setPaint(getWallPaint());
260: g2.fill(bottomWall);
261:
262: // higlight the background corners...
263: g2.setPaint(Color.lightGray);
264: Line2D corner = new Line2D.Double(x0, y0, x1, y1);
265: g2.draw(corner);
266: corner.setLine(x1, y1, x1, y3);
267: g2.draw(corner);
268: corner.setLine(x1, y1, x3, y1);
269: g2.draw(corner);
270:
271: // draw background image, if there is one...
272: Image backgroundImage = plot.getBackgroundImage();
273: if (backgroundImage != null) {
274: Rectangle2D adjusted = new Rectangle2D.Double(dataArea
275: .getX()
276: + getXOffset(), dataArea.getY(), dataArea
277: .getWidth()
278: - getXOffset(), dataArea.getHeight() - getYOffset());
279: plot.drawBackgroundImage(g2, adjusted);
280: }
281:
282: g2.setComposite(originalComposite);
283:
284: }
285:
286: /**
287: * Draws the outline for the plot.
288: *
289: * @param g2 the graphics device.
290: * @param plot the plot.
291: * @param dataArea the area inside the axes.
292: */
293: public void drawOutline(Graphics2D g2, CategoryPlot plot,
294: Rectangle2D dataArea) {
295:
296: float x0 = (float) dataArea.getX();
297: float x1 = x0 + (float) Math.abs(this .xOffset);
298: float x3 = (float) dataArea.getMaxX();
299: float x2 = x3 - (float) Math.abs(this .xOffset);
300:
301: float y0 = (float) dataArea.getMaxY();
302: float y1 = y0 - (float) Math.abs(this .yOffset);
303: float y3 = (float) dataArea.getMinY();
304: float y2 = y3 + (float) Math.abs(this .yOffset);
305:
306: GeneralPath clip = new GeneralPath();
307: clip.moveTo(x0, y0);
308: clip.lineTo(x0, y2);
309: clip.lineTo(x1, y3);
310: clip.lineTo(x3, y3);
311: clip.lineTo(x3, y1);
312: clip.lineTo(x2, y0);
313: clip.closePath();
314:
315: // put an outline around the data area...
316: Stroke outlineStroke = plot.getOutlineStroke();
317: Paint outlinePaint = plot.getOutlinePaint();
318: if ((outlineStroke != null) && (outlinePaint != null)) {
319: g2.setStroke(outlineStroke);
320: g2.setPaint(outlinePaint);
321: g2.draw(clip);
322: }
323:
324: }
325:
326: /**
327: * Draws a grid line against the domain axis.
328: *
329: * @param g2 the graphics device.
330: * @param plot the plot.
331: * @param dataArea the area for plotting data (not yet adjusted for any
332: * 3D effect).
333: * @param value the Java2D value at which the grid line should be drawn.
334: *
335: */
336: public void drawDomainGridline(Graphics2D g2, CategoryPlot plot,
337: Rectangle2D dataArea, double value) {
338:
339: Line2D line1 = null;
340: Line2D line2 = null;
341: PlotOrientation orientation = plot.getOrientation();
342: if (orientation == PlotOrientation.HORIZONTAL) {
343: double y0 = value;
344: double y1 = value - getYOffset();
345: double x0 = dataArea.getMinX();
346: double x1 = x0 + getXOffset();
347: double x2 = dataArea.getMaxX();
348: line1 = new Line2D.Double(x0, y0, x1, y1);
349: line2 = new Line2D.Double(x1, y1, x2, y1);
350: } else if (orientation == PlotOrientation.VERTICAL) {
351: double x0 = value;
352: double x1 = value + getXOffset();
353: double y0 = dataArea.getMaxY();
354: double y1 = y0 - getYOffset();
355: double y2 = dataArea.getMinY();
356: line1 = new Line2D.Double(x0, y0, x1, y1);
357: line2 = new Line2D.Double(x1, y1, x1, y2);
358: }
359: g2.setPaint(plot.getDomainGridlinePaint());
360: g2.setStroke(plot.getDomainGridlineStroke());
361: g2.draw(line1);
362: g2.draw(line2);
363:
364: }
365:
366: /**
367: * Draws a grid line against the range axis.
368: *
369: * @param g2 the graphics device.
370: * @param plot the plot.
371: * @param axis the value axis.
372: * @param dataArea the area for plotting data (not yet adjusted for any
373: * 3D effect).
374: * @param value the value at which the grid line should be drawn.
375: *
376: */
377: public void drawRangeGridline(Graphics2D g2, CategoryPlot plot,
378: ValueAxis axis, Rectangle2D dataArea, double value) {
379:
380: Range range = axis.getRange();
381:
382: if (!range.contains(value)) {
383: return;
384: }
385:
386: Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
387: dataArea.getY() + getYOffset(), dataArea.getWidth()
388: - getXOffset(), dataArea.getHeight()
389: - getYOffset());
390:
391: Line2D line1 = null;
392: Line2D line2 = null;
393: PlotOrientation orientation = plot.getOrientation();
394: if (orientation == PlotOrientation.HORIZONTAL) {
395: double x0 = axis.valueToJava2D(value, adjusted, plot
396: .getRangeAxisEdge());
397: double x1 = x0 + getXOffset();
398: double y0 = dataArea.getMaxY();
399: double y1 = y0 - getYOffset();
400: double y2 = dataArea.getMinY();
401: line1 = new Line2D.Double(x0, y0, x1, y1);
402: line2 = new Line2D.Double(x1, y1, x1, y2);
403: } else if (orientation == PlotOrientation.VERTICAL) {
404: double y0 = axis.valueToJava2D(value, adjusted, plot
405: .getRangeAxisEdge());
406: double y1 = y0 - getYOffset();
407: double x0 = dataArea.getMinX();
408: double x1 = x0 + getXOffset();
409: double x2 = dataArea.getMaxX();
410: line1 = new Line2D.Double(x0, y0, x1, y1);
411: line2 = new Line2D.Double(x1, y1, x2, y1);
412: }
413: g2.setPaint(plot.getRangeGridlinePaint());
414: g2.setStroke(plot.getRangeGridlineStroke());
415: g2.draw(line1);
416: g2.draw(line2);
417:
418: }
419:
420: /**
421: * Draws a range marker.
422: *
423: * @param g2 the graphics device.
424: * @param plot the plot.
425: * @param axis the value axis.
426: * @param marker the marker.
427: * @param dataArea the area for plotting data (not including 3D effect).
428: */
429: public void drawRangeMarker(Graphics2D g2, CategoryPlot plot,
430: ValueAxis axis, Marker marker, Rectangle2D dataArea) {
431:
432: if (marker instanceof ValueMarker) {
433: ValueMarker vm = (ValueMarker) marker;
434: double value = vm.getValue();
435: Range range = axis.getRange();
436: if (!range.contains(value)) {
437: return;
438: }
439:
440: Rectangle2D adjusted = new Rectangle2D.Double(dataArea
441: .getX(), dataArea.getY() + getYOffset(), dataArea
442: .getWidth()
443: - getXOffset(), dataArea.getHeight() - getYOffset());
444:
445: GeneralPath path = null;
446: PlotOrientation orientation = plot.getOrientation();
447: if (orientation == PlotOrientation.HORIZONTAL) {
448: float x = (float) axis.valueToJava2D(value, adjusted,
449: plot.getRangeAxisEdge());
450: float y = (float) adjusted.getMaxY();
451: path = new GeneralPath();
452: path.moveTo(x, y);
453: path.lineTo((float) (x + getXOffset()), y
454: - (float) getYOffset());
455: path.lineTo((float) (x + getXOffset()),
456: (float) (adjusted.getMinY() - getYOffset()));
457: path.lineTo(x, (float) adjusted.getMinY());
458: path.closePath();
459: } else if (orientation == PlotOrientation.VERTICAL) {
460: float y = (float) axis.valueToJava2D(value, adjusted,
461: plot.getRangeAxisEdge());
462: float x = (float) dataArea.getX();
463: path = new GeneralPath();
464: path.moveTo(x, y);
465: path.lineTo(x + (float) this .xOffset, y
466: - (float) this .yOffset);
467: path.lineTo(
468: (float) (adjusted.getMaxX() + this .xOffset), y
469: - (float) this .yOffset);
470: path.lineTo((float) (adjusted.getMaxX()), y);
471: path.closePath();
472: }
473: g2.setPaint(marker.getPaint());
474: g2.fill(path);
475: g2.setPaint(marker.getOutlinePaint());
476: g2.draw(path);
477: }
478: }
479:
480: /**
481: * Draw a single data item.
482: *
483: * @param g2 the graphics device.
484: * @param state the renderer state.
485: * @param dataArea the area in which the data is drawn.
486: * @param plot the plot.
487: * @param domainAxis the domain axis.
488: * @param rangeAxis the range axis.
489: * @param dataset the dataset.
490: * @param row the row index (zero-based).
491: * @param column the column index (zero-based).
492: * @param pass the pass index.
493: */
494: public void drawItem(Graphics2D g2,
495: CategoryItemRendererState state, Rectangle2D dataArea,
496: CategoryPlot plot, CategoryAxis domainAxis,
497: ValueAxis rangeAxis, CategoryDataset dataset, int row,
498: int column, int pass) {
499:
500: if (!getItemVisible(row, column)) {
501: return;
502: }
503:
504: // nothing is drawn for null...
505: Number v = dataset.getValue(row, column);
506: if (v == null) {
507: return;
508: }
509:
510: Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
511: dataArea.getY() + getYOffset(), dataArea.getWidth()
512: - getXOffset(), dataArea.getHeight()
513: - getYOffset());
514:
515: PlotOrientation orientation = plot.getOrientation();
516:
517: // current data point...
518: double x1 = domainAxis.getCategoryMiddle(column,
519: getColumnCount(), adjusted, plot.getDomainAxisEdge());
520: double value = v.doubleValue();
521: double y1 = rangeAxis.valueToJava2D(value, adjusted, plot
522: .getRangeAxisEdge());
523:
524: Shape shape = getItemShape(row, column);
525: if (orientation == PlotOrientation.HORIZONTAL) {
526: shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
527: } else if (orientation == PlotOrientation.VERTICAL) {
528: shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
529: }
530:
531: if (getItemLineVisible(row, column)) {
532: if (column != 0) {
533:
534: Number previousValue = dataset
535: .getValue(row, column - 1);
536: if (previousValue != null) {
537:
538: // previous data point...
539: double previous = previousValue.doubleValue();
540: double x0 = domainAxis.getCategoryMiddle(
541: column - 1, getColumnCount(), adjusted,
542: plot.getDomainAxisEdge());
543: double y0 = rangeAxis.valueToJava2D(previous,
544: adjusted, plot.getRangeAxisEdge());
545:
546: double x2 = x0 + getXOffset();
547: double y2 = y0 - getYOffset();
548: double x3 = x1 + getXOffset();
549: double y3 = y1 - getYOffset();
550:
551: GeneralPath clip = new GeneralPath();
552:
553: if (orientation == PlotOrientation.HORIZONTAL) {
554: clip.moveTo((float) y0, (float) x0);
555: clip.lineTo((float) y1, (float) x1);
556: clip.lineTo((float) y3, (float) x3);
557: clip.lineTo((float) y2, (float) x2);
558: clip.lineTo((float) y0, (float) x0);
559: clip.closePath();
560: } else if (orientation == PlotOrientation.VERTICAL) {
561: clip.moveTo((float) x0, (float) y0);
562: clip.lineTo((float) x1, (float) y1);
563: clip.lineTo((float) x3, (float) y3);
564: clip.lineTo((float) x2, (float) y2);
565: clip.lineTo((float) x0, (float) y0);
566: clip.closePath();
567: }
568:
569: g2.setPaint(getItemPaint(row, column));
570: g2.fill(clip);
571: g2.setStroke(getItemOutlineStroke(row, column));
572: g2.setPaint(getItemOutlinePaint(row, column));
573: g2.draw(clip);
574: }
575: }
576: }
577:
578: // draw the item label if there is one...
579: if (isItemLabelVisible(row, column)) {
580: drawItemLabel(g2, orientation, dataset, row, column, x1,
581: y1, (value < 0.0));
582: }
583:
584: // add an item entity, if this information is being collected
585: EntityCollection entities = state.getEntityCollection();
586: if (entities != null) {
587: addItemEntity(entities, dataset, row, column, shape);
588: }
589:
590: }
591:
592: /**
593: * Checks this renderer for equality with an arbitrary object.
594: *
595: * @param obj the object (<code>null</code> permitted).
596: *
597: * @return A boolean.
598: */
599: public boolean equals(Object obj) {
600: if (obj == this ) {
601: return true;
602: }
603: if (!(obj instanceof LineRenderer3D)) {
604: return false;
605: }
606: LineRenderer3D that = (LineRenderer3D) obj;
607: if (this .xOffset != that.xOffset) {
608: return false;
609: }
610: if (this .yOffset != that.yOffset) {
611: return false;
612: }
613: if (!PaintUtilities.equal(this .wallPaint, that.wallPaint)) {
614: return false;
615: }
616: return super .equals(obj);
617: }
618:
619: /**
620: * Provides serialization support.
621: *
622: * @param stream the output stream.
623: *
624: * @throws IOException if there is an I/O error.
625: */
626: private void writeObject(ObjectOutputStream stream)
627: throws IOException {
628: stream.defaultWriteObject();
629: SerialUtilities.writePaint(this .wallPaint, stream);
630: }
631:
632: /**
633: * Provides serialization support.
634: *
635: * @param stream the input stream.
636: *
637: * @throws IOException if there is an I/O error.
638: * @throws ClassNotFoundException if there is a classpath problem.
639: */
640: private void readObject(ObjectInputStream stream)
641: throws IOException, ClassNotFoundException {
642: stream.defaultReadObject();
643: this.wallPaint = SerialUtilities.readPaint(stream);
644: }
645:
646: }
|