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: * XYStepAreaRenderer.java
029: * -----------------------
030: * (C) Copyright 2003-2007, by Matthias Rose and Contributors.
031: *
032: * Original Author: Matthias Rose (based on XYAreaRenderer.java);
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: XYStepAreaRenderer.java,v 1.7.2.7 2007/05/04 11:12:16 mungady Exp $
036: *
037: * Changes:
038: * --------
039: * 07-Oct-2003 : Version 1, contributed by Matthias Rose (DG);
040: * 10-Feb-2004 : Added some getter and setter methods (DG);
041: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
042: * XYToolTipGenerator --> XYItemLabelGenerator (DG);
043: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
044: * getYValue() (DG);
045: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
046: * 06-Jul-2005 : Renamed get/setPlotShapes() --> get/setShapesVisible() (DG);
047: * ------------- JFREECHART 1.0.x ---------------------------------------------
048: * 06-Jul-2006 : Modified to call dataset methods that return double
049: * primitives only (DG);
050: * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
051: * 14-Feb-2007 : Added equals() method override (DG);
052: * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
053: *
054: */
055:
056: package org.jfree.chart.renderer.xy;
057:
058: import java.awt.Graphics2D;
059: import java.awt.Paint;
060: import java.awt.Polygon;
061: import java.awt.Shape;
062: import java.awt.Stroke;
063: import java.awt.geom.Rectangle2D;
064: import java.io.Serializable;
065:
066: import org.jfree.chart.axis.ValueAxis;
067: import org.jfree.chart.entity.EntityCollection;
068: import org.jfree.chart.entity.XYItemEntity;
069: import org.jfree.chart.event.RendererChangeEvent;
070: import org.jfree.chart.labels.XYToolTipGenerator;
071: import org.jfree.chart.plot.CrosshairState;
072: import org.jfree.chart.plot.PlotOrientation;
073: import org.jfree.chart.plot.PlotRenderingInfo;
074: import org.jfree.chart.plot.XYPlot;
075: import org.jfree.chart.urls.XYURLGenerator;
076: import org.jfree.data.xy.XYDataset;
077: import org.jfree.util.PublicCloneable;
078: import org.jfree.util.ShapeUtilities;
079:
080: /**
081: * A step chart renderer that fills the area between the step and the x-axis.
082: */
083: public class XYStepAreaRenderer extends AbstractXYItemRenderer
084: implements XYItemRenderer, Cloneable, PublicCloneable,
085: Serializable {
086:
087: /** For serialization. */
088: private static final long serialVersionUID = -7311560779702649635L;
089:
090: /** Useful constant for specifying the type of rendering (shapes only). */
091: public static final int SHAPES = 1;
092:
093: /** Useful constant for specifying the type of rendering (area only). */
094: public static final int AREA = 2;
095:
096: /**
097: * Useful constant for specifying the type of rendering (area and shapes).
098: */
099: public static final int AREA_AND_SHAPES = 3;
100:
101: /** A flag indicating whether or not shapes are drawn at each XY point. */
102: private boolean shapesVisible;
103:
104: /** A flag that controls whether or not shapes are filled for ALL series. */
105: private boolean shapesFilled;
106:
107: /** A flag indicating whether or not Area are drawn at each XY point. */
108: private boolean plotArea;
109:
110: /** A flag that controls whether or not the outline is shown. */
111: private boolean showOutline;
112:
113: /** Area of the complete series */
114: protected transient Polygon pArea = null;
115:
116: /**
117: * The value on the range axis which defines the 'lower' border of the
118: * area.
119: */
120: private double rangeBase;
121:
122: /**
123: * Constructs a new renderer.
124: */
125: public XYStepAreaRenderer() {
126: this (AREA);
127: }
128:
129: /**
130: * Constructs a new renderer.
131: *
132: * @param type the type of the renderer.
133: */
134: public XYStepAreaRenderer(int type) {
135: this (type, null, null);
136: }
137:
138: /**
139: * Constructs a new renderer.
140: * <p>
141: * To specify the type of renderer, use one of the constants:
142: * AREA, SHAPES or AREA_AND_SHAPES.
143: *
144: * @param type the type of renderer.
145: * @param toolTipGenerator the tool tip generator to use
146: * (<code>null</code> permitted).
147: * @param urlGenerator the URL generator (<code>null</code> permitted).
148: */
149: public XYStepAreaRenderer(int type,
150: XYToolTipGenerator toolTipGenerator,
151: XYURLGenerator urlGenerator) {
152:
153: super ();
154: setBaseToolTipGenerator(toolTipGenerator);
155: setURLGenerator(urlGenerator);
156:
157: if (type == AREA) {
158: this .plotArea = true;
159: } else if (type == SHAPES) {
160: this .shapesVisible = true;
161: } else if (type == AREA_AND_SHAPES) {
162: this .plotArea = true;
163: this .shapesVisible = true;
164: }
165: this .showOutline = false;
166: }
167:
168: /**
169: * Returns a flag that controls whether or not outlines of the areas are
170: * drawn.
171: *
172: * @return The flag.
173: *
174: * @see #setOutline(boolean)
175: */
176: public boolean isOutline() {
177: return this .showOutline;
178: }
179:
180: /**
181: * Sets a flag that controls whether or not outlines of the areas are
182: * drawn, and sends a {@link RendererChangeEvent} to all registered
183: * listeners.
184: *
185: * @param show the flag.
186: *
187: * @see #isOutline()
188: */
189: public void setOutline(boolean show) {
190: this .showOutline = show;
191: notifyListeners(new RendererChangeEvent(this ));
192: }
193:
194: /**
195: * Returns true if shapes are being plotted by the renderer.
196: *
197: * @return <code>true</code> if shapes are being plotted by the renderer.
198: *
199: * @see #setShapesVisible(boolean)
200: */
201: public boolean getShapesVisible() {
202: return this .shapesVisible;
203: }
204:
205: /**
206: * Sets the flag that controls whether or not shapes are displayed for each
207: * data item, and sends a {@link RendererChangeEvent} to all registered
208: * listeners.
209: *
210: * @param flag the flag.
211: *
212: * @see #getShapesVisible()
213: */
214: public void setShapesVisible(boolean flag) {
215: this .shapesVisible = flag;
216: notifyListeners(new RendererChangeEvent(this ));
217: }
218:
219: /**
220: * Returns the flag that controls whether or not the shapes are filled.
221: *
222: * @return A boolean.
223: *
224: * @see #setShapesFilled(boolean)
225: */
226: public boolean isShapesFilled() {
227: return this .shapesFilled;
228: }
229:
230: /**
231: * Sets the 'shapes filled' for ALL series.
232: *
233: * @param filled the flag.
234: *
235: * @see #isShapesFilled()
236: */
237: public void setShapesFilled(boolean filled) {
238: this .shapesFilled = filled;
239: notifyListeners(new RendererChangeEvent(this ));
240: }
241:
242: /**
243: * Returns true if Area is being plotted by the renderer.
244: *
245: * @return <code>true</code> if Area is being plotted by the renderer.
246: *
247: * @see #setPlotArea(boolean)
248: */
249: public boolean getPlotArea() {
250: return this .plotArea;
251: }
252:
253: /**
254: * Sets a flag that controls whether or not areas are drawn for each data
255: * item.
256: *
257: * @param flag the flag.
258: *
259: * @see #getPlotArea()
260: */
261: public void setPlotArea(boolean flag) {
262: this .plotArea = flag;
263: notifyListeners(new RendererChangeEvent(this ));
264: }
265:
266: /**
267: * Returns the value on the range axis which defines the 'lower' border of
268: * the area.
269: *
270: * @return <code>double</code> the value on the range axis which defines
271: * the 'lower' border of the area.
272: *
273: * @see #setRangeBase(double)
274: */
275: public double getRangeBase() {
276: return this .rangeBase;
277: }
278:
279: /**
280: * Sets the value on the range axis which defines the default border of the
281: * area. E.g. setRangeBase(Double.NEGATIVE_INFINITY) lets areas always
282: * reach the lower border of the plotArea.
283: *
284: * @param val the value on the range axis which defines the default border
285: * of the area.
286: *
287: * @see #getRangeBase()
288: */
289: public void setRangeBase(double val) {
290: this .rangeBase = val;
291: notifyListeners(new RendererChangeEvent(this ));
292: }
293:
294: /**
295: * Initialises the renderer. Here we calculate the Java2D y-coordinate for
296: * zero, since all the bars have their bases fixed at zero.
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 The number of passes required by the renderer.
306: */
307: public XYItemRendererState initialise(Graphics2D g2,
308: Rectangle2D dataArea, XYPlot plot, XYDataset data,
309: PlotRenderingInfo info) {
310:
311: XYItemRendererState state = super .initialise(g2, dataArea,
312: plot, data, info);
313: // disable visible items optimisation - it doesn't work for this
314: // renderer...
315: state.setProcessVisibleItemsOnly(false);
316: return state;
317:
318: }
319:
320: /**
321: * Draws the visual representation of a single data item.
322: *
323: * @param g2 the graphics device.
324: * @param state the renderer state.
325: * @param dataArea the area within which the data is being drawn.
326: * @param info collects information about the drawing.
327: * @param plot the plot (can be used to obtain standard color information
328: * etc).
329: * @param domainAxis the domain axis.
330: * @param rangeAxis the range axis.
331: * @param dataset the dataset.
332: * @param series the series index (zero-based).
333: * @param item the item index (zero-based).
334: * @param crosshairState crosshair information for the plot
335: * (<code>null</code> permitted).
336: * @param pass the pass index.
337: */
338: public void drawItem(Graphics2D g2, XYItemRendererState state,
339: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
340: ValueAxis domainAxis, ValueAxis rangeAxis,
341: XYDataset dataset, int series, int item,
342: CrosshairState crosshairState, int pass) {
343:
344: PlotOrientation orientation = plot.getOrientation();
345:
346: // Get the item count for the series, so that we can know which is the
347: // end of the series.
348: int itemCount = dataset.getItemCount(series);
349:
350: Paint paint = getItemPaint(series, item);
351: Stroke seriesStroke = getItemStroke(series, item);
352: g2.setPaint(paint);
353: g2.setStroke(seriesStroke);
354:
355: // get the data point...
356: double x1 = dataset.getXValue(series, item);
357: double y1 = dataset.getYValue(series, item);
358: double x = x1;
359: double y = Double.isNaN(y1) ? getRangeBase() : y1;
360: double transX1 = domainAxis.valueToJava2D(x, dataArea, plot
361: .getDomainAxisEdge());
362: double transY1 = rangeAxis.valueToJava2D(y, dataArea, plot
363: .getRangeAxisEdge());
364:
365: // avoid possible sun.dc.pr.PRException: endPath: bad path
366: transY1 = restrictValueToDataArea(transY1, plot, dataArea);
367:
368: if (this .pArea == null && !Double.isNaN(y1)) {
369:
370: // Create a new Area for the series
371: this .pArea = new Polygon();
372:
373: // start from Y = rangeBase
374: double transY2 = rangeAxis.valueToJava2D(getRangeBase(),
375: dataArea, plot.getRangeAxisEdge());
376:
377: // avoid possible sun.dc.pr.PRException: endPath: bad path
378: transY2 = restrictValueToDataArea(transY2, plot, dataArea);
379:
380: // The first point is (x, this.baseYValue)
381: if (orientation == PlotOrientation.VERTICAL) {
382: this .pArea.addPoint((int) transX1, (int) transY2);
383: } else if (orientation == PlotOrientation.HORIZONTAL) {
384: this .pArea.addPoint((int) transY2, (int) transX1);
385: }
386: }
387:
388: double transX0 = 0;
389: double transY0 = restrictValueToDataArea(getRangeBase(), plot,
390: dataArea);
391:
392: double x0;
393: double y0;
394: if (item > 0) {
395: // get the previous data point...
396: x0 = dataset.getXValue(series, item - 1);
397: y0 = Double.isNaN(y1) ? y1 : dataset.getYValue(series,
398: item - 1);
399:
400: x = x0;
401: y = Double.isNaN(y0) ? getRangeBase() : y0;
402: transX0 = domainAxis.valueToJava2D(x, dataArea, plot
403: .getDomainAxisEdge());
404: transY0 = rangeAxis.valueToJava2D(y, dataArea, plot
405: .getRangeAxisEdge());
406:
407: // avoid possible sun.dc.pr.PRException: endPath: bad path
408: transY0 = restrictValueToDataArea(transY0, plot, dataArea);
409:
410: if (Double.isNaN(y1)) {
411: // NULL value -> insert point on base line
412: // instead of 'step point'
413: transX1 = transX0;
414: transY0 = transY1;
415: }
416: if (transY0 != transY1) {
417: // not just a horizontal bar but need to perform a 'step'.
418: if (orientation == PlotOrientation.VERTICAL) {
419: this .pArea.addPoint((int) transX1, (int) transY0);
420: } else if (orientation == PlotOrientation.HORIZONTAL) {
421: this .pArea.addPoint((int) transY0, (int) transX1);
422: }
423: }
424: }
425:
426: Shape shape = null;
427: if (!Double.isNaN(y1)) {
428: // Add each point to Area (x, y)
429: if (orientation == PlotOrientation.VERTICAL) {
430: this .pArea.addPoint((int) transX1, (int) transY1);
431: } else if (orientation == PlotOrientation.HORIZONTAL) {
432: this .pArea.addPoint((int) transY1, (int) transX1);
433: }
434:
435: if (getShapesVisible()) {
436: shape = getItemShape(series, item);
437: if (orientation == PlotOrientation.VERTICAL) {
438: shape = ShapeUtilities.createTranslatedShape(shape,
439: transX1, transY1);
440: } else if (orientation == PlotOrientation.HORIZONTAL) {
441: shape = ShapeUtilities.createTranslatedShape(shape,
442: transY1, transX1);
443: }
444: if (isShapesFilled()) {
445: g2.fill(shape);
446: } else {
447: g2.draw(shape);
448: }
449: } else {
450: if (orientation == PlotOrientation.VERTICAL) {
451: shape = new Rectangle2D.Double(transX1 - 2,
452: transY1 - 2, 4.0, 4.0);
453: } else if (orientation == PlotOrientation.HORIZONTAL) {
454: shape = new Rectangle2D.Double(transY1 - 2,
455: transX1 - 2, 4.0, 4.0);
456: }
457: }
458: }
459:
460: // Check if the item is the last item for the series or if it
461: // is a NULL value and number of items > 0. We can't draw an area for
462: // a single point.
463: if (getPlotArea() && item > 0 && this .pArea != null
464: && (item == (itemCount - 1) || Double.isNaN(y1))) {
465:
466: double transY2 = rangeAxis.valueToJava2D(getRangeBase(),
467: dataArea, plot.getRangeAxisEdge());
468:
469: // avoid possible sun.dc.pr.PRException: endPath: bad path
470: transY2 = restrictValueToDataArea(transY2, plot, dataArea);
471:
472: if (orientation == PlotOrientation.VERTICAL) {
473: // Add the last point (x,0)
474: this .pArea.addPoint((int) transX1, (int) transY2);
475: } else if (orientation == PlotOrientation.HORIZONTAL) {
476: // Add the last point (x,0)
477: this .pArea.addPoint((int) transY2, (int) transX1);
478: }
479:
480: // fill the polygon
481: g2.fill(this .pArea);
482:
483: // draw an outline around the Area.
484: if (isOutline()) {
485: g2.setStroke(plot.getOutlineStroke());
486: g2.setPaint(plot.getOutlinePaint());
487: g2.draw(this .pArea);
488: }
489:
490: // start new area when needed (see above)
491: this .pArea = null;
492: }
493:
494: // do we need to update the crosshair values?
495: if (!Double.isNaN(y1)) {
496: int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
497: int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
498: updateCrosshairValues(crosshairState, x1, y1,
499: domainAxisIndex, rangeAxisIndex, transX1, transY1,
500: orientation);
501: }
502:
503: // collect entity and tool tip information...
504: if (state.getInfo() != null) {
505: EntityCollection entities = state.getEntityCollection();
506: if (entities != null && shape != null) {
507: String tip = null;
508: XYToolTipGenerator generator = getToolTipGenerator(
509: series, item);
510: if (generator != null) {
511: tip = generator.generateToolTip(dataset, series,
512: item);
513: }
514: String url = null;
515: if (getURLGenerator() != null) {
516: url = getURLGenerator().generateURL(dataset,
517: series, item);
518: }
519: XYItemEntity entity = new XYItemEntity(shape, dataset,
520: series, item, tip, url);
521: entities.add(entity);
522: }
523: }
524: }
525:
526: /**
527: * Tests this renderer for equality with an arbitrary object.
528: *
529: * @param obj the object (<code>null</code> permitted).
530: *
531: * @return A boolean.
532: */
533: public boolean equals(Object obj) {
534: if (obj == this ) {
535: return true;
536: }
537: if (!(obj instanceof XYStepAreaRenderer)) {
538: return false;
539: }
540: XYStepAreaRenderer that = (XYStepAreaRenderer) obj;
541: if (this .showOutline != that.showOutline) {
542: return false;
543: }
544: if (this .shapesVisible != that.shapesVisible) {
545: return false;
546: }
547: if (this .shapesFilled != that.shapesFilled) {
548: return false;
549: }
550: if (this .plotArea != that.plotArea) {
551: return false;
552: }
553: if (this .rangeBase != that.rangeBase) {
554: return false;
555: }
556: return super .equals(obj);
557: }
558:
559: /**
560: * Returns a clone of the renderer.
561: *
562: * @return A clone.
563: *
564: * @throws CloneNotSupportedException if the renderer cannot be cloned.
565: */
566: public Object clone() throws CloneNotSupportedException {
567: return super .clone();
568: }
569:
570: /**
571: * Helper method which returns a value if it lies
572: * inside the visible dataArea and otherwise the corresponding
573: * coordinate on the border of the dataArea. The PlotOrientation
574: * is taken into account.
575: * Useful to avoid possible sun.dc.pr.PRException: endPath: bad path
576: * which occurs when trying to draw lines/shapes which in large part
577: * lie outside of the visible dataArea.
578: *
579: * @param value the value which shall be
580: * @param dataArea the area within which the data is being drawn.
581: * @param plot the plot (can be used to obtain standard color
582: * information etc).
583: * @return <code>double</code> value inside the data area.
584: */
585: protected static double restrictValueToDataArea(double value,
586: XYPlot plot, Rectangle2D dataArea) {
587: double min = 0;
588: double max = 0;
589: if (plot.getOrientation() == PlotOrientation.VERTICAL) {
590: min = dataArea.getMinY();
591: max = dataArea.getMaxY();
592: } else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
593: min = dataArea.getMinX();
594: max = dataArea.getMaxX();
595: }
596: if (value < min) {
597: value = min;
598: } else if (value > max) {
599: value = max;
600: }
601: return value;
602: }
603:
604: }
|