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