KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > renderer > xy > StackedXYAreaRenderer


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * --------------------------
28  * StackedXYAreaRenderer.java
29  * --------------------------
30  * (C) Copyright 2003-2006, by Richard Atkinson and Contributors.
31  *
32  * Original Author: Richard Atkinson;
33  * Contributor(s): Christian W. Zuckschwerdt;
34  * David Gilbert (for Object Refinery Limited);
35  *
36  * $Id: StackedXYAreaRenderer.java,v 1.12.2.7 2006/11/10 11:09:37 mungady Exp $
37  *
38  * Changes:
39  * --------
40  * 27-Jul-2003 : Initial version (RA);
41  * 30-Jul-2003 : Modified entity constructor (CZ);
42  * 18-Aug-2003 : Now handles null values (RA);
43  * 20-Aug-2003 : Implemented Cloneable, PublicCloneable and Serializable (DG);
44  * 22-Sep-2003 : Changed to be a two pass renderer with optional shape Paint
45  * and Stroke (RA);
46  * 07-Oct-2003 : Added renderer state (DG);
47  * 10-Feb-2004 : Updated state object and changed drawItem() method to make
48  * overriding easier (DG);
49  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
50  * XYToolTipGenerator --> XYItemLabelGenerator (DG);
51  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
52  * getYValue() (DG);
53  * 10-Sep-2004 : Removed getRangeType() method (DG);
54  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
55  * 06-Jan-2005 : Override equals() (DG);
56  * 07-Jan-2005 : Update for method name changes in DatasetUtilities (DG);
57  * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
58  * 06-Jun-2005 : Fixed null pointer exception, plus problems with equals() and
59  * serialization (DG);
60  * ------------- JFREECHART 1.0.x ---------------------------------------------
61  * 10-Nov-2006 : Fixed bug 1593156, NullPointerException with line
62  * plotting (DG);
63  *
64  */

65
66 package org.jfree.chart.renderer.xy;
67
68 import java.awt.Graphics2D JavaDoc;
69 import java.awt.Paint JavaDoc;
70 import java.awt.Point JavaDoc;
71 import java.awt.Polygon JavaDoc;
72 import java.awt.Shape JavaDoc;
73 import java.awt.Stroke JavaDoc;
74 import java.awt.geom.Line2D JavaDoc;
75 import java.awt.geom.Rectangle2D JavaDoc;
76 import java.io.IOException JavaDoc;
77 import java.io.ObjectInputStream JavaDoc;
78 import java.io.ObjectOutputStream JavaDoc;
79 import java.io.Serializable JavaDoc;
80 import java.util.Stack JavaDoc;
81
82 import org.jfree.chart.axis.ValueAxis;
83 import org.jfree.chart.entity.EntityCollection;
84 import org.jfree.chart.entity.XYItemEntity;
85 import org.jfree.chart.labels.XYToolTipGenerator;
86 import org.jfree.chart.plot.CrosshairState;
87 import org.jfree.chart.plot.PlotOrientation;
88 import org.jfree.chart.plot.PlotRenderingInfo;
89 import org.jfree.chart.plot.XYPlot;
90 import org.jfree.chart.urls.XYURLGenerator;
91 import org.jfree.data.Range;
92 import org.jfree.data.general.DatasetUtilities;
93 import org.jfree.data.xy.TableXYDataset;
94 import org.jfree.data.xy.XYDataset;
95 import org.jfree.io.SerialUtilities;
96 import org.jfree.util.ObjectUtilities;
97 import org.jfree.util.PaintUtilities;
98 import org.jfree.util.PublicCloneable;
99 import org.jfree.util.ShapeUtilities;
100
101 /**
102  * A stacked area renderer for the {@link XYPlot} class.
103  * <br><br>
104  * SPECIAL NOTE: This renderer does not currently handle negative data values
105  * correctly. This should get fixed at some point, but the current workaround
106  * is to use the {@link StackedXYAreaRenderer2} class instead.
107  */

108 public class StackedXYAreaRenderer extends XYAreaRenderer
109                                    implements Cloneable JavaDoc,
110                                               PublicCloneable,
111                                               Serializable JavaDoc {
112     
113     /** For serialization. */
114     private static final long serialVersionUID = 5217394318178570889L;
115      
116      /**
117      * A state object for use by this renderer.
118      */

119     static class StackedXYAreaRendererState extends XYItemRendererState {
120         
121         /** The area for the current series. */
122         private Polygon JavaDoc seriesArea;
123         
124         /** The line. */
125         private Line2D JavaDoc line;
126         
127         /** The points from the last series. */
128         private Stack JavaDoc lastSeriesPoints;
129         
130         /** The points for the current series. */
131         private Stack JavaDoc currentSeriesPoints;
132         
133         /**
134          * Creates a new state for the renderer.
135          *
136          * @param info the plot rendering info.
137          */

138         public StackedXYAreaRendererState(PlotRenderingInfo info) {
139             super(info);
140             this.seriesArea = null;
141             this.line = new Line2D.Double JavaDoc();
142             this.lastSeriesPoints = new Stack JavaDoc();
143             this.currentSeriesPoints = new Stack JavaDoc();
144         }
145         
146         /**
147          * Returns the series area.
148          *
149          * @return The series area.
150          */

151         public Polygon JavaDoc getSeriesArea() {
152             return this.seriesArea;
153         }
154         
155         /**
156          * Sets the series area.
157          *
158          * @param area the area.
159          */

160         public void setSeriesArea(Polygon JavaDoc area) {
161             this.seriesArea = area;
162         }
163         
164         /**
165          * Returns the working line.
166          *
167          * @return The working line.
168          */

169         public Line2D JavaDoc getLine() {
170             return this.line;
171         }
172         
173         /**
174          * Returns the current series points.
175          *
176          * @return The current series points.
177          */

178         public Stack JavaDoc getCurrentSeriesPoints() {
179             return this.currentSeriesPoints;
180         }
181         
182         /**
183          * Sets the current series points.
184          *
185          * @param points the points.
186          */

187         public void setCurrentSeriesPoints(Stack JavaDoc points) {
188             this.currentSeriesPoints = points;
189         }
190     
191         /**
192          * Returns the last series points.
193          *
194          * @return The last series points.
195          */

196         public Stack JavaDoc getLastSeriesPoints() {
197             return this.lastSeriesPoints;
198         }
199         
200         /**
201          * Sets the last series points.
202          *
203          * @param points the points.
204          */

205         public void setLastSeriesPoints(Stack JavaDoc points) {
206             this.lastSeriesPoints = points;
207         }
208     
209     }
210
211     /**
212      * Custom Paint for drawing all shapes, if null defaults to series shapes
213      */

214     private transient Paint JavaDoc shapePaint = null;
215
216     /**
217      * Custom Stroke for drawing all shapes, if null defaults to series
218      * strokes.
219      */

220     private transient Stroke JavaDoc shapeStroke = null;
221
222     /**
223      * Creates a new renderer.
224      */

225     public StackedXYAreaRenderer() {
226         this(AREA);
227     }
228     /**
229      * Constructs a new renderer.
230      *
231      * @param type the type of the renderer.
232      */

233     public StackedXYAreaRenderer(int type) {
234         this(type, null, null);
235     }
236
237     /**
238      * Constructs a new renderer. To specify the type of renderer, use one of
239      * the constants: <code>SHAPES</code>, <code>LINES</code>,
240      * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
241      * <code>AREA_AND_SHAPES</code>.
242      *
243      * @param type the type of renderer.
244      * @param labelGenerator the tool tip generator to use (<code>null</code>
245      * is none).
246      * @param urlGenerator the URL generator (<code>null</code> permitted).
247      */

248     public StackedXYAreaRenderer(int type,
249                                  XYToolTipGenerator labelGenerator,
250                                  XYURLGenerator urlGenerator) {
251
252         super(type, labelGenerator, urlGenerator);
253     }
254
255     /**
256      * Returns the paint used for rendering shapes, or <code>null</code> if
257      * using series paints.
258      *
259      * @return The Paint.
260      */

261     public Paint JavaDoc getShapePaint() {
262         return this.shapePaint;
263     }
264
265     /**
266      * Returns the stroke used for rendering shapes, or <code>null</code> if
267      * using series strokes.
268      *
269      * @return The stroke.
270      */

271     public Stroke JavaDoc getShapeStroke() {
272         return this.shapeStroke;
273     }
274
275     /**
276      * Sets the paint for rendering shapes.
277      *
278      * @param shapePaint the paint (<code>null</code> permitted).
279      */

280     public void setShapePaint(Paint JavaDoc shapePaint) {
281         this.shapePaint = shapePaint;
282     }
283
284     /**
285      * Sets the stroke for rendering shapes.
286      *
287      * @param shapeStroke the stroke (<code>null</code> permitted).
288      */

289     public void setShapeStroke(Stroke JavaDoc shapeStroke) {
290         this.shapeStroke = shapeStroke;
291     }
292
293     /**
294      * Initialises the renderer. This method will be called before the first
295      * item is rendered, giving the renderer an opportunity to initialise any
296      * state information it wants to maintain.
297      *
298      * @param g2 the graphics device.
299      * @param dataArea the area inside the axes.
300      * @param plot the plot.
301      * @param data the data.
302      * @param info an optional info collection object to return data back to
303      * the caller.
304      *
305      * @return A state object that should be passed to subsequent calls to the
306      * drawItem() method.
307      */

308     public XYItemRendererState initialise(Graphics2D JavaDoc g2,
309                                           Rectangle2D JavaDoc dataArea,
310                                           XYPlot plot,
311                                           XYDataset data,
312                                           PlotRenderingInfo info) {
313
314         return new StackedXYAreaRendererState(info);
315
316     }
317
318     /**
319      * Returns the number of passes required by the renderer.
320      *
321      * @return 2.
322      */

323     public int getPassCount() {
324         return 2;
325     }
326
327     /**
328      * Returns the range of values the renderer requires to display all the
329      * items from the specified dataset.
330      *
331      * @param dataset the dataset (<code>null</code> permitted).
332      *
333      * @return The range ([0.0, 0.0] if the dataset contains no values, and
334      * <code>null</code> if the dataset is <code>null</code>).
335      *
336      * @throws ClassCastException if <code>dataset</code> is not an instance
337      * of {@link TableXYDataset}.
338      */

339     public Range findRangeBounds(XYDataset dataset) {
340         if (dataset != null) {
341             return DatasetUtilities.findStackedRangeBounds(
342                 (TableXYDataset) dataset);
343         }
344         else {
345             return null;
346         }
347     }
348
349     /**
350      * Draws the visual representation of a single data item.
351      *
352      * @param g2 the graphics device.
353      * @param state the renderer state.
354      * @param dataArea the area within which the data is being drawn.
355      * @param info collects information about the drawing.
356      * @param plot the plot (can be used to obtain standard color information
357      * etc).
358      * @param domainAxis the domain axis.
359      * @param rangeAxis the range axis.
360      * @param dataset the dataset.
361      * @param series the series index (zero-based).
362      * @param item the item index (zero-based).
363      * @param crosshairState information about crosshairs on a plot.
364      * @param pass the pass index.
365      *
366      * @throws ClassCastException if <code>state</code> is not an instance of
367      * <code>StackedXYAreaRendererState</code> or <code>dataset</code>
368      * is not an instance of {@link TableXYDataset}.
369      */

370     public void drawItem(Graphics2D JavaDoc g2,
371                          XYItemRendererState state,
372                          Rectangle2D JavaDoc dataArea,
373                          PlotRenderingInfo info,
374                          XYPlot plot,
375                          ValueAxis domainAxis,
376                          ValueAxis rangeAxis,
377                          XYDataset dataset,
378                          int series,
379                          int item,
380                          CrosshairState crosshairState,
381                          int pass) {
382
383         PlotOrientation orientation = plot.getOrientation();
384         StackedXYAreaRendererState areaState
385             = (StackedXYAreaRendererState) state;
386         // Get the item count for the series, so that we can know which is the
387
// end of the series.
388
TableXYDataset tdataset = (TableXYDataset) dataset;
389         int itemCount = tdataset.getItemCount();
390
391         // get the data point...
392
double x1 = dataset.getXValue(series, item);
393         double y1 = dataset.getYValue(series, item);
394         boolean nullPoint = false;
395         if (Double.isNaN(y1)) {
396             y1 = 0.0;
397             nullPoint = true;
398         }
399
400         // Get height adjustment based on stack and translate to Java2D values
401
double ph1 = getPreviousHeight(tdataset, series, item);
402         double transX1 = domainAxis.valueToJava2D(x1, dataArea,
403                 plot.getDomainAxisEdge());
404         double transY1 = rangeAxis.valueToJava2D(y1 + ph1, dataArea,
405                 plot.getRangeAxisEdge());
406
407         // Get series Paint and Stroke
408
Paint JavaDoc seriesPaint = getItemPaint(series, item);
409         Stroke JavaDoc seriesStroke = getItemStroke(series, item);
410
411         if (pass == 0) {
412             // On first pass render the areas, line and outlines
413

414             if (item == 0) {
415                 // Create a new Area for the series
416
areaState.setSeriesArea(new Polygon JavaDoc());
417                 areaState.setLastSeriesPoints(
418                         areaState.getCurrentSeriesPoints());
419                 areaState.setCurrentSeriesPoints(new Stack JavaDoc());
420
421                 // start from previous height (ph1)
422
double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
423                         plot.getRangeAxisEdge());
424
425                 // The first point is (x, 0)
426
if (orientation == PlotOrientation.VERTICAL) {
427                     areaState.getSeriesArea().addPoint((int) transX1,
428                             (int) transY2);
429                 }
430                 else if (orientation == PlotOrientation.HORIZONTAL) {
431                     areaState.getSeriesArea().addPoint((int) transY2,
432                             (int) transX1);
433                 }
434             }
435
436             // Add each point to Area (x, y)
437
if (orientation == PlotOrientation.VERTICAL) {
438                 Point JavaDoc point = new Point JavaDoc((int) transX1, (int) transY1);
439                 areaState.getSeriesArea().addPoint((int) point.getX(),
440                         (int) point.getY());
441                 areaState.getCurrentSeriesPoints().push(point);
442             }
443             else if (orientation == PlotOrientation.HORIZONTAL) {
444                 areaState.getSeriesArea().addPoint((int) transY1,
445                         (int) transX1);
446             }
447
448             if (getPlotLines()) {
449                 if (item > 0) {
450                     // get the previous data point...
451
double x0 = dataset.getXValue(series, item - 1);
452                     double y0 = dataset.getYValue(series, item - 1);
453                     double ph0 = getPreviousHeight(tdataset, series, item - 1);
454                     double transX0 = domainAxis.valueToJava2D(x0, dataArea,
455                             plot.getDomainAxisEdge());
456                     double transY0 = rangeAxis.valueToJava2D(y0 + ph0,
457                             dataArea, plot.getRangeAxisEdge());
458
459                     if (orientation == PlotOrientation.VERTICAL) {
460                         areaState.getLine().setLine(transX0, transY0, transX1,
461                                 transY1);
462                     }
463                     else if (orientation == PlotOrientation.HORIZONTAL) {
464                         areaState.getLine().setLine(transY0, transX0, transY1,
465                                 transX1);
466                     }
467                     g2.draw(areaState.getLine());
468                 }
469             }
470
471             // Check if the item is the last item for the series and number of
472
// items > 0. We can't draw an area for a single point.
473
if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
474
475                 double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
476                         plot.getRangeAxisEdge());
477
478                 if (orientation == PlotOrientation.VERTICAL) {
479                     // Add the last point (x,0)
480
areaState.getSeriesArea().addPoint((int) transX1,
481                             (int) transY2);
482                 }
483                 else if (orientation == PlotOrientation.HORIZONTAL) {
484                     // Add the last point (x,0)
485
areaState.getSeriesArea().addPoint((int) transY2,
486                             (int) transX1);
487                 }
488
489                 // Add points from last series to complete the base of the
490
// polygon
491
if (series != 0) {
492                     Stack JavaDoc points = areaState.getLastSeriesPoints();
493                     while (!points.empty()) {
494                         Point JavaDoc point = (Point JavaDoc) points.pop();
495                         areaState.getSeriesArea().addPoint((int) point.getX(),
496                                 (int) point.getY());
497                     }
498                 }
499
500                 // Fill the polygon
501
g2.setPaint(seriesPaint);
502                 g2.setStroke(seriesStroke);
503                 g2.fill(areaState.getSeriesArea());
504
505                 // Draw an outline around the Area.
506
if (isOutline()) {
507                     g2.setStroke(getSeriesOutlineStroke(series));
508                     g2.setPaint(getSeriesOutlinePaint(series));
509                     g2.draw(areaState.getSeriesArea());
510                 }
511             }
512
513             updateCrosshairValues(crosshairState, x1, y1, transX1, transY1,
514                     orientation);
515
516         }
517         else if (pass == 1) {
518             // On second pass render shapes and collect entity and tooltip
519
// information
520

521             Shape JavaDoc shape = null;
522             if (getPlotShapes()) {
523                 shape = getItemShape(series, item);
524                 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
525                     shape = ShapeUtilities.createTranslatedShape(shape,
526                             transX1, transY1);
527                 }
528                 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
529                     shape = ShapeUtilities.createTranslatedShape(shape,
530                             transY1, transX1);
531                 }
532                 if (!nullPoint) {
533                     if (getShapePaint() != null) {
534                         g2.setPaint(getShapePaint());
535                     }
536                     else {
537                         g2.setPaint(seriesPaint);
538                     }
539                     if (getShapeStroke() != null) {
540                         g2.setStroke(getShapeStroke());
541                     }
542                     else {
543                         g2.setStroke(seriesStroke);
544                     }
545                     g2.draw(shape);
546                 }
547             }
548             else {
549                 if (plot.getOrientation() == PlotOrientation.VERTICAL) {
550                     shape = new Rectangle2D.Double JavaDoc(transX1 - 3, transY1 - 3,
551                             6.0, 6.0);
552                 }
553                 else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
554                     shape = new Rectangle2D.Double JavaDoc(transY1 - 3, transX1 - 3,
555                             6.0, 6.0);
556                 }
557             }
558
559             // collect entity and tool tip information...
560
if (state.getInfo() != null) {
561                 EntityCollection entities = state.getEntityCollection();
562                 if (entities != null && shape != null && !nullPoint) {
563                     String JavaDoc tip = null;
564                     XYToolTipGenerator generator
565                         = getToolTipGenerator(series, item);
566                     if (generator != null) {
567                         tip = generator.generateToolTip(dataset, series, item);
568                     }
569                     String JavaDoc url = null;
570                     if (getURLGenerator() != null) {
571                         url = getURLGenerator().generateURL(dataset, series,
572                                 item);
573                     }
574                     XYItemEntity entity = new XYItemEntity(shape, dataset,
575                             series, item, tip, url);
576                     entities.add(entity);
577                 }
578             }
579
580         }
581     }
582
583     /**
584      * Calculates the stacked value of the all series up to, but not including
585      * <code>series</code> for the specified item. It returns 0.0 if
586      * <code>series</code> is the first series, i.e. 0.
587      *
588      * @param dataset the dataset.
589      * @param series the series.
590      * @param index the index.
591      *
592      * @return The cumulative value for all series' values up to but excluding
593      * <code>series</code> for <code>index</code>.
594      */

595     protected double getPreviousHeight(TableXYDataset dataset,
596                                        int series, int index) {
597         double result = 0.0;
598         for (int i = 0; i < series; i++) {
599             double value = dataset.getYValue(i, index);
600             if (!Double.isNaN(value)) {
601                 result += value;
602             }
603         }
604         return result;
605     }
606     
607     /**
608      * Tests the renderer for equality with an arbitrary object.
609      *
610      * @param obj the object (<code>null</code> permitted).
611      *
612      * @return A boolean.
613      */

614     public boolean equals(Object JavaDoc obj) {
615         if (obj == this) {
616             return true;
617         }
618         if (!(obj instanceof StackedXYAreaRenderer) || !super.equals(obj)) {
619             return false;
620         }
621         StackedXYAreaRenderer that = (StackedXYAreaRenderer) obj;
622         if (!PaintUtilities.equal(this.shapePaint, that.shapePaint)) {
623             return false;
624         }
625         if (!ObjectUtilities.equal(this.shapeStroke, that.shapeStroke)) {
626             return false;
627         }
628         return true;
629     }
630
631     /**
632      * Returns a clone of the renderer.
633      *
634      * @return A clone.
635      *
636      * @throws CloneNotSupportedException if the renderer cannot be cloned.
637      */

638     public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
639         return super.clone();
640     }
641     
642     /**
643      * Provides serialization support.
644      *
645      * @param stream the input stream.
646      *
647      * @throws IOException if there is an I/O error.
648      * @throws ClassNotFoundException if there is a classpath problem.
649      */

650     private void readObject(ObjectInputStream JavaDoc stream)
651             throws IOException JavaDoc, ClassNotFoundException JavaDoc {
652         stream.defaultReadObject();
653         this.shapePaint = SerialUtilities.readPaint(stream);
654         this.shapeStroke = SerialUtilities.readStroke(stream);
655     }
656     
657     /**
658      * Provides serialization support.
659      *
660      * @param stream the output stream.
661      *
662      * @throws IOException if there is an I/O error.
663      */

664     private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
665         stream.defaultWriteObject();
666         SerialUtilities.writePaint(this.shapePaint, stream);
667         SerialUtilities.writeStroke(this.shapeStroke, stream);
668     }
669
670 }
671
Popular Tags