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