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