001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2006, 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: * CyclicXYItemRenderer.java
029: * ---------------------------
030: * (C) Copyright 2003-2006, by Nicolas Brodu and Contributors.
031: *
032: * Original Author: Nicolas Brodu;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: CyclicXYItemRenderer.java,v 1.4.2.2 2006/07/06 10:44:54 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 19-Nov-2003 : Initial import to JFreeChart from the JSynoptic project (NB);
040: * 23-Dec-2003 : Added missing Javadocs (DG);
041: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
042: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
043: * getYValue() (DG);
044: * ------------- JFREECHART 1.0.0 ---------------------------------------------
045: * 06-Jul-2006 : Modified to call only dataset methods that return double
046: * primitives (DG);
047: *
048: */
049:
050: package org.jfree.chart.renderer.xy;
051:
052: import java.awt.Graphics2D;
053: import java.awt.geom.Rectangle2D;
054: import java.io.Serializable;
055:
056: import org.jfree.chart.axis.CyclicNumberAxis;
057: import org.jfree.chart.axis.ValueAxis;
058: import org.jfree.chart.labels.XYToolTipGenerator;
059: import org.jfree.chart.plot.CrosshairState;
060: import org.jfree.chart.plot.PlotRenderingInfo;
061: import org.jfree.chart.plot.XYPlot;
062: import org.jfree.chart.urls.XYURLGenerator;
063: import org.jfree.data.DomainOrder;
064: import org.jfree.data.general.DatasetChangeListener;
065: import org.jfree.data.general.DatasetGroup;
066: import org.jfree.data.xy.XYDataset;
067:
068: /**
069: * The Cyclic XY item renderer is specially designed to handle cyclic axis.
070: * While the standard renderer would draw a line across the plot when a cycling
071: * occurs, the cyclic renderer splits the line at each cycle end instead. This
072: * is done by interpolating new points at cycle boundary. Thus, correct
073: * appearance is restored.
074: *
075: * The Cyclic XY item renderer works exactly like a standard XY item renderer
076: * with non-cyclic axis.
077: */
078: public class CyclicXYItemRenderer extends StandardXYItemRenderer
079: implements Serializable {
080:
081: /** For serialization. */
082: private static final long serialVersionUID = 4035912243303764892L;
083:
084: /**
085: * Default constructor.
086: */
087: public CyclicXYItemRenderer() {
088: super ();
089: }
090:
091: /**
092: * Creates a new renderer.
093: *
094: * @param type the renderer type.
095: */
096: public CyclicXYItemRenderer(int type) {
097: super (type);
098: }
099:
100: /**
101: * Creates a new renderer.
102: *
103: * @param type the renderer type.
104: * @param labelGenerator the tooltip generator.
105: */
106: public CyclicXYItemRenderer(int type,
107: XYToolTipGenerator labelGenerator) {
108: super (type, labelGenerator);
109: }
110:
111: /**
112: * Creates a new renderer.
113: *
114: * @param type the renderer type.
115: * @param labelGenerator the tooltip generator.
116: * @param urlGenerator the url generator.
117: */
118: public CyclicXYItemRenderer(int type,
119: XYToolTipGenerator labelGenerator,
120: XYURLGenerator urlGenerator) {
121: super (type, labelGenerator, urlGenerator);
122: }
123:
124: /**
125: * Draws the visual representation of a single data item.
126: * When using cyclic axis, do not draw a line from right to left when
127: * cycling as would a standard XY item renderer, but instead draw a line
128: * from the previous point to the cycle bound in the last cycle, and a line
129: * from the cycle bound to current point in the current cycle.
130: *
131: * @param g2 the graphics device.
132: * @param state the renderer state.
133: * @param dataArea the data area.
134: * @param info the plot rendering info.
135: * @param plot the plot.
136: * @param domainAxis the domain axis.
137: * @param rangeAxis the range axis.
138: * @param dataset the dataset.
139: * @param series the series index.
140: * @param item the item index.
141: * @param crosshairState crosshair information for the plot
142: * (<code>null</code> permitted).
143: * @param pass the current pass index.
144: */
145: public void drawItem(Graphics2D g2, XYItemRendererState state,
146: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
147: ValueAxis domainAxis, ValueAxis rangeAxis,
148: XYDataset dataset, int series, int item,
149: CrosshairState crosshairState, int pass) {
150:
151: if ((!getPlotLines())
152: || ((!(domainAxis instanceof CyclicNumberAxis)) && (!(rangeAxis instanceof CyclicNumberAxis)))
153: || (item <= 0)) {
154: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
155: rangeAxis, dataset, series, item, crosshairState,
156: pass);
157: return;
158: }
159:
160: // get the previous data point...
161: double xn = dataset.getXValue(series, item - 1);
162: double yn = dataset.getYValue(series, item - 1);
163: // If null, don't draw line => then delegate to parent
164: if (Double.isNaN(yn)) {
165: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
166: rangeAxis, dataset, series, item, crosshairState,
167: pass);
168: return;
169: }
170: double[] x = new double[2];
171: double[] y = new double[2];
172: x[0] = xn;
173: y[0] = yn;
174:
175: // get the data point...
176: xn = dataset.getXValue(series, item);
177: yn = dataset.getYValue(series, item);
178: // If null, don't draw line at all
179: if (Double.isNaN(yn)) {
180: return;
181: }
182: x[1] = xn;
183: y[1] = yn;
184:
185: // Now split the segment as needed
186: double xcycleBound = Double.NaN;
187: double ycycleBound = Double.NaN;
188: boolean xBoundMapping = false, yBoundMapping = false;
189: CyclicNumberAxis cnax = null, cnay = null;
190:
191: if (domainAxis instanceof CyclicNumberAxis) {
192: cnax = (CyclicNumberAxis) domainAxis;
193: xcycleBound = cnax.getCycleBound();
194: xBoundMapping = cnax.isBoundMappedToLastCycle();
195: // If the segment must be splitted, insert a new point
196: // Strict test forces to have real segments (not 2 equal points)
197: // and avoids division by 0
198: if ((x[0] != x[1])
199: && ((xcycleBound >= x[0]) && (xcycleBound <= x[1]) || (xcycleBound >= x[1])
200: && (xcycleBound <= x[0]))) {
201: double[] nx = new double[3];
202: double[] ny = new double[3];
203: nx[0] = x[0];
204: nx[2] = x[1];
205: ny[0] = y[0];
206: ny[2] = y[1];
207: nx[1] = xcycleBound;
208: ny[1] = (y[1] - y[0]) * (xcycleBound - x[0])
209: / (x[1] - x[0]) + y[0];
210: x = nx;
211: y = ny;
212: }
213: }
214:
215: if (rangeAxis instanceof CyclicNumberAxis) {
216: cnay = (CyclicNumberAxis) rangeAxis;
217: ycycleBound = cnay.getCycleBound();
218: yBoundMapping = cnay.isBoundMappedToLastCycle();
219: // The split may occur in either x splitted segments, if any, but
220: // not in both
221: if ((y[0] != y[1])
222: && ((ycycleBound >= y[0]) && (ycycleBound <= y[1]) || (ycycleBound >= y[1])
223: && (ycycleBound <= y[0]))) {
224: double[] nx = new double[x.length + 1];
225: double[] ny = new double[y.length + 1];
226: nx[0] = x[0];
227: nx[2] = x[1];
228: ny[0] = y[0];
229: ny[2] = y[1];
230: ny[1] = ycycleBound;
231: nx[1] = (x[1] - x[0]) * (ycycleBound - y[0])
232: / (y[1] - y[0]) + x[0];
233: if (x.length == 3) {
234: nx[3] = x[2];
235: ny[3] = y[2];
236: }
237: x = nx;
238: y = ny;
239: } else if ((x.length == 3)
240: && (y[1] != y[2])
241: && ((ycycleBound >= y[1]) && (ycycleBound <= y[2]) || (ycycleBound >= y[2])
242: && (ycycleBound <= y[1]))) {
243: double[] nx = new double[4];
244: double[] ny = new double[4];
245: nx[0] = x[0];
246: nx[1] = x[1];
247: nx[3] = x[2];
248: ny[0] = y[0];
249: ny[1] = y[1];
250: ny[3] = y[2];
251: ny[2] = ycycleBound;
252: nx[2] = (x[2] - x[1]) * (ycycleBound - y[1])
253: / (y[2] - y[1]) + x[1];
254: x = nx;
255: y = ny;
256: }
257: }
258:
259: // If the line is not wrapping, then parent is OK
260: if (x.length == 2) {
261: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
262: rangeAxis, dataset, series, item, crosshairState,
263: pass);
264: return;
265: }
266:
267: OverwriteDataSet newset = new OverwriteDataSet(x, y, dataset);
268:
269: if (cnax != null) {
270: if (xcycleBound == x[0]) {
271: cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
272: }
273: if (xcycleBound == x[1]) {
274: cnax.setBoundMappedToLastCycle(x[0] <= xcycleBound);
275: }
276: }
277: if (cnay != null) {
278: if (ycycleBound == y[0]) {
279: cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
280: }
281: if (ycycleBound == y[1]) {
282: cnay.setBoundMappedToLastCycle(y[0] <= ycycleBound);
283: }
284: }
285: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
286: rangeAxis, newset, series, 1, crosshairState, pass);
287:
288: if (cnax != null) {
289: if (xcycleBound == x[1]) {
290: cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
291: }
292: if (xcycleBound == x[2]) {
293: cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
294: }
295: }
296: if (cnay != null) {
297: if (ycycleBound == y[1]) {
298: cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
299: }
300: if (ycycleBound == y[2]) {
301: cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
302: }
303: }
304: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
305: rangeAxis, newset, series, 2, crosshairState, pass);
306:
307: if (x.length == 4) {
308: if (cnax != null) {
309: if (xcycleBound == x[2]) {
310: cnax.setBoundMappedToLastCycle(x[3] <= xcycleBound);
311: }
312: if (xcycleBound == x[3]) {
313: cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
314: }
315: }
316: if (cnay != null) {
317: if (ycycleBound == y[2]) {
318: cnay.setBoundMappedToLastCycle(y[3] <= ycycleBound);
319: }
320: if (ycycleBound == y[3]) {
321: cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
322: }
323: }
324: super .drawItem(g2, state, dataArea, info, plot, domainAxis,
325: rangeAxis, newset, series, 3, crosshairState, pass);
326: }
327:
328: if (cnax != null) {
329: cnax.setBoundMappedToLastCycle(xBoundMapping);
330: }
331: if (cnay != null) {
332: cnay.setBoundMappedToLastCycle(yBoundMapping);
333: }
334: }
335:
336: /**
337: * A dataset to hold the interpolated points when drawing new lines.
338: */
339: protected static class OverwriteDataSet implements XYDataset {
340:
341: /** The delegate dataset. */
342: protected XYDataset delegateSet;
343:
344: /** Storage for the x and y values. */
345: Double[] x, y;
346:
347: /**
348: * Creates a new dataset.
349: *
350: * @param x the x values.
351: * @param y the y values.
352: * @param delegateSet the dataset.
353: */
354: public OverwriteDataSet(double[] x, double[] y,
355: XYDataset delegateSet) {
356: this .delegateSet = delegateSet;
357: this .x = new Double[x.length];
358: this .y = new Double[y.length];
359: for (int i = 0; i < x.length; ++i) {
360: this .x[i] = new Double(x[i]);
361: this .y[i] = new Double(y[i]);
362: }
363: }
364:
365: /**
366: * Returns the order of the domain (X) values.
367: *
368: * @return The domain order.
369: */
370: public DomainOrder getDomainOrder() {
371: return DomainOrder.NONE;
372: }
373:
374: /**
375: * Returns the number of items for the given series.
376: *
377: * @param series the series index (zero-based).
378: *
379: * @return The item count.
380: */
381: public int getItemCount(int series) {
382: return this .x.length;
383: }
384:
385: /**
386: * Returns the x-value.
387: *
388: * @param series the series index (zero-based).
389: * @param item the item index (zero-based).
390: *
391: * @return The x-value.
392: */
393: public Number getX(int series, int item) {
394: return this .x[item];
395: }
396:
397: /**
398: * Returns the x-value (as a double primitive) for an item within a
399: * series.
400: *
401: * @param series the series (zero-based index).
402: * @param item the item (zero-based index).
403: *
404: * @return The x-value.
405: */
406: public double getXValue(int series, int item) {
407: double result = Double.NaN;
408: Number x = getX(series, item);
409: if (x != null) {
410: result = x.doubleValue();
411: }
412: return result;
413: }
414:
415: /**
416: * Returns the y-value.
417: *
418: * @param series the series index (zero-based).
419: * @param item the item index (zero-based).
420: *
421: * @return The y-value.
422: */
423: public Number getY(int series, int item) {
424: return this .y[item];
425: }
426:
427: /**
428: * Returns the y-value (as a double primitive) for an item within a
429: * series.
430: *
431: * @param series the series (zero-based index).
432: * @param item the item (zero-based index).
433: *
434: * @return The y-value.
435: */
436: public double getYValue(int series, int item) {
437: double result = Double.NaN;
438: Number y = getY(series, item);
439: if (y != null) {
440: result = y.doubleValue();
441: }
442: return result;
443: }
444:
445: /**
446: * Returns the number of series in the dataset.
447: *
448: * @return The series count.
449: */
450: public int getSeriesCount() {
451: return this .delegateSet.getSeriesCount();
452: }
453:
454: /**
455: * Returns the name of the given series.
456: *
457: * @param series the series index (zero-based).
458: *
459: * @return The series name.
460: */
461: public Comparable getSeriesKey(int series) {
462: return this .delegateSet.getSeriesKey(series);
463: }
464:
465: /**
466: * Returns the index of the named series, or -1.
467: *
468: * @param seriesName the series name.
469: *
470: * @return The index.
471: */
472: public int indexOf(Comparable seriesName) {
473: return this .delegateSet.indexOf(seriesName);
474: }
475:
476: /**
477: * Does nothing.
478: *
479: * @param listener ignored.
480: */
481: public void addChangeListener(DatasetChangeListener listener) {
482: // unused in parent
483: }
484:
485: /**
486: * Does nothing.
487: *
488: * @param listener ignored.
489: */
490: public void removeChangeListener(DatasetChangeListener listener) {
491: // unused in parent
492: }
493:
494: /**
495: * Returns the dataset group.
496: *
497: * @return The dataset group.
498: */
499: public DatasetGroup getGroup() {
500: // unused but must return something, so while we are at it...
501: return this .delegateSet.getGroup();
502: }
503:
504: /**
505: * Does nothing.
506: *
507: * @param group ignored.
508: */
509: public void setGroup(DatasetGroup group) {
510: // unused in parent
511: }
512:
513: }
514:
515: }
|