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, XYToolTipGenerator labelGenerator) {
107 super(type, labelGenerator);
108 }
109
110 /**
111 * Creates a new renderer.
112 *
113 * @param type the renderer type.
114 * @param labelGenerator the tooltip generator.
115 * @param urlGenerator the url generator.
116 */
117 public CyclicXYItemRenderer(int type,
118 XYToolTipGenerator labelGenerator,
119 XYURLGenerator urlGenerator) {
120 super(type, labelGenerator, urlGenerator);
121 }
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,
146 XYItemRendererState state,
147 Rectangle2D dataArea,
148 PlotRenderingInfo info,
149 XYPlot plot,
150 ValueAxis domainAxis,
151 ValueAxis rangeAxis,
152 XYDataset dataset,
153 int series,
154 int item,
155 CrosshairState crosshairState,
156 int pass) {
157
158 if ((!getPlotLines()) || ((!(domainAxis instanceof CyclicNumberAxis))
159 && (!(rangeAxis instanceof CyclicNumberAxis))) || (item <= 0)) {
160 super.drawItem(g2, state, dataArea, info, plot, domainAxis,
161 rangeAxis, dataset, series, item, crosshairState, pass);
162 return;
163 }
164
165 // get the previous data point...
166 double xn = dataset.getXValue(series, item - 1);
167 double yn = dataset.getYValue(series, item - 1);
168 // If null, don't draw line => then delegate to parent
169 if (Double.isNaN(yn)) {
170 super.drawItem(g2, state, dataArea, info, plot, domainAxis,
171 rangeAxis, dataset, series, item, crosshairState, pass);
172 return;
173 }
174 double[] x = new double[2];
175 double[] y = new double[2];
176 x[0] = xn;
177 y[0] = yn;
178
179 // get the data point...
180 xn = dataset.getXValue(series, item);
181 yn = dataset.getYValue(series, item);
182 // If null, don't draw line at all
183 if (Double.isNaN(yn)) {
184 return;
185 }
186 x[1] = xn;
187 y[1] = yn;
188
189 // Now split the segment as needed
190 double xcycleBound = Double.NaN;
191 double ycycleBound = Double.NaN;
192 boolean xBoundMapping = false, yBoundMapping = false;
193 CyclicNumberAxis cnax = null, cnay = null;
194
195 if (domainAxis instanceof CyclicNumberAxis) {
196 cnax = (CyclicNumberAxis) domainAxis;
197 xcycleBound = cnax.getCycleBound();
198 xBoundMapping = cnax.isBoundMappedToLastCycle();
199 // If the segment must be splitted, insert a new point
200 // Strict test forces to have real segments (not 2 equal points)
201 // and avoids division by 0
202 if ((x[0] != x[1])
203 && ((xcycleBound >= x[0])
204 && (xcycleBound <= x[1])
205 || (xcycleBound >= x[1])
206 && (xcycleBound <= x[0]))) {
207 double[] nx = new double[3];
208 double[] ny = new double[3];
209 nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
210 nx[1] = xcycleBound;
211 ny[1] = (y[1] - y[0]) * (xcycleBound - x[0])
212 / (x[1] - x[0]) + y[0];
213 x = nx; y = ny;
214 }
215 }
216
217 if (rangeAxis instanceof CyclicNumberAxis) {
218 cnay = (CyclicNumberAxis) rangeAxis;
219 ycycleBound = cnay.getCycleBound();
220 yBoundMapping = cnay.isBoundMappedToLastCycle();
221 // The split may occur in either x splitted segments, if any, but
222 // not in both
223 if ((y[0] != y[1]) && ((ycycleBound >= y[0])
224 && (ycycleBound <= y[1])
225 || (ycycleBound >= y[1]) && (ycycleBound <= y[0]))) {
226 double[] nx = new double[x.length + 1];
227 double[] ny = new double[y.length + 1];
228 nx[0] = x[0]; nx[2] = x[1]; ny[0] = y[0]; ny[2] = y[1];
229 ny[1] = ycycleBound;
230 nx[1] = (x[1] - x[0]) * (ycycleBound - y[0])
231 / (y[1] - y[0]) + x[0];
232 if (x.length == 3) {
233 nx[3] = x[2]; ny[3] = y[2];
234 }
235 x = nx; y = ny;
236 }
237 else if ((x.length == 3) && (y[1] != y[2]) && ((ycycleBound >= y[1])
238 && (ycycleBound <= y[2])
239 || (ycycleBound >= y[2]) && (ycycleBound <= y[1]))) {
240 double[] nx = new double[4];
241 double[] ny = new double[4];
242 nx[0] = x[0]; nx[1] = x[1]; nx[3] = x[2];
243 ny[0] = y[0]; ny[1] = y[1]; ny[3] = y[2];
244 ny[2] = ycycleBound;
245 nx[2] = (x[2] - x[1]) * (ycycleBound - y[1])
246 / (y[2] - y[1]) + x[1];
247 x = nx; y = ny;
248 }
249 }
250
251 // If the line is not wrapping, then parent is OK
252 if (x.length == 2) {
253 super.drawItem(g2, state, dataArea, info, plot, domainAxis,
254 rangeAxis, dataset, series, item, crosshairState, pass);
255 return;
256 }
257
258 OverwriteDataSet newset = new OverwriteDataSet(x, y, dataset);
259
260 if (cnax != null) {
261 if (xcycleBound == x[0]) {
262 cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
263 }
264 if (xcycleBound == x[1]) {
265 cnax.setBoundMappedToLastCycle(x[0] <= xcycleBound);
266 }
267 }
268 if (cnay != null) {
269 if (ycycleBound == y[0]) {
270 cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
271 }
272 if (ycycleBound == y[1]) {
273 cnay.setBoundMappedToLastCycle(y[0] <= ycycleBound);
274 }
275 }
276 super.drawItem(
277 g2, state, dataArea, info, plot, domainAxis, rangeAxis,
278 newset, series, 1, crosshairState, pass
279 );
280
281 if (cnax != null) {
282 if (xcycleBound == x[1]) {
283 cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
284 }
285 if (xcycleBound == x[2]) {
286 cnax.setBoundMappedToLastCycle(x[1] <= xcycleBound);
287 }
288 }
289 if (cnay != null) {
290 if (ycycleBound == y[1]) {
291 cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
292 }
293 if (ycycleBound == y[2]) {
294 cnay.setBoundMappedToLastCycle(y[1] <= ycycleBound);
295 }
296 }
297 super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
298 newset, series, 2, crosshairState, pass);
299
300 if (x.length == 4) {
301 if (cnax != null) {
302 if (xcycleBound == x[2]) {
303 cnax.setBoundMappedToLastCycle(x[3] <= xcycleBound);
304 }
305 if (xcycleBound == x[3]) {
306 cnax.setBoundMappedToLastCycle(x[2] <= xcycleBound);
307 }
308 }
309 if (cnay != null) {
310 if (ycycleBound == y[2]) {
311 cnay.setBoundMappedToLastCycle(y[3] <= ycycleBound);
312 }
313 if (ycycleBound == y[3]) {
314 cnay.setBoundMappedToLastCycle(y[2] <= ycycleBound);
315 }
316 }
317 super.drawItem(g2, state, dataArea, info, plot, domainAxis,
318 rangeAxis, newset, series, 3, crosshairState, pass);
319 }
320
321 if (cnax != null) {
322 cnax.setBoundMappedToLastCycle(xBoundMapping);
323 }
324 if (cnay != null) {
325 cnay.setBoundMappedToLastCycle(yBoundMapping);
326 }
327 }
328
329 /**
330 * A dataset to hold the interpolated points when drawing new lines.
331 */
332 protected static class OverwriteDataSet implements XYDataset {
333
334 /** The delegate dataset. */
335 protected XYDataset delegateSet;
336
337 /** Storage for the x and y values. */
338 Double[] x, y;
339
340 /**
341 * Creates a new dataset.
342 *
343 * @param x the x values.
344 * @param y the y values.
345 * @param delegateSet the dataset.
346 */
347 public OverwriteDataSet(double [] x, double[] y,
348 XYDataset delegateSet) {
349 this.delegateSet = delegateSet;
350 this.x = new Double[x.length]; this.y = new Double[y.length];
351 for (int i = 0; i < x.length; ++i) {
352 this.x[i] = new Double(x[i]);
353 this.y[i] = new Double(y[i]);
354 }
355 }
356
357 /**
358 * Returns the order of the domain (X) values.
359 *
360 * @return The domain order.
361 */
362 public DomainOrder getDomainOrder() {
363 return DomainOrder.NONE;
364 }
365
366 /**
367 * Returns the number of items for the given series.
368 *
369 * @param series the series index (zero-based).
370 *
371 * @return The item count.
372 */
373 public int getItemCount(int series) {
374 return this.x.length;
375 }
376
377 /**
378 * Returns the x-value.
379 *
380 * @param series the series index (zero-based).
381 * @param item the item index (zero-based).
382 *
383 * @return The x-value.
384 */
385 public Number getX(int series, int item) {
386 return this.x[item];
387 }
388
389 /**
390 * Returns the x-value (as a double primitive) for an item within a
391 * series.
392 *
393 * @param series the series (zero-based index).
394 * @param item the item (zero-based index).
395 *
396 * @return The x-value.
397 */
398 public double getXValue(int series, int item) {
399 double result = Double.NaN;
400 Number x = getX(series, item);
401 if (x != null) {
402 result = x.doubleValue();
403 }
404 return result;
405 }
406
407 /**
408 * Returns the y-value.
409 *
410 * @param series the series index (zero-based).
411 * @param item the item index (zero-based).
412 *
413 * @return The y-value.
414 */
415 public Number getY(int series, int item) {
416 return this.y[item];
417 }
418
419 /**
420 * Returns the y-value (as a double primitive) for an item within a
421 * series.
422 *
423 * @param series the series (zero-based index).
424 * @param item the item (zero-based index).
425 *
426 * @return The y-value.
427 */
428 public double getYValue(int series, int item) {
429 double result = Double.NaN;
430 Number y = getY(series, item);
431 if (y != null) {
432 result = y.doubleValue();
433 }
434 return result;
435 }
436
437 /**
438 * Returns the number of series in the dataset.
439 *
440 * @return The series count.
441 */
442 public int getSeriesCount() {
443 return this.delegateSet.getSeriesCount();
444 }
445
446 /**
447 * Returns the name of the given series.
448 *
449 * @param series the series index (zero-based).
450 *
451 * @return The series name.
452 */
453 public Comparable getSeriesKey(int series) {
454 return this.delegateSet.getSeriesKey(series);
455 }
456
457 /**
458 * Returns the index of the named series, or -1.
459 *
460 * @param seriesName the series name.
461 *
462 * @return The index.
463 */
464 public int indexOf(Comparable seriesName) {
465 return this.delegateSet.indexOf(seriesName);
466 }
467
468 /**
469 * Does nothing.
470 *
471 * @param listener ignored.
472 */
473 public void addChangeListener(DatasetChangeListener listener) {
474 // unused in parent
475 }
476
477 /**
478 * Does nothing.
479 *
480 * @param listener ignored.
481 */
482 public void removeChangeListener(DatasetChangeListener listener) {
483 // unused in parent
484 }
485
486 /**
487 * Returns the dataset group.
488 *
489 * @return The dataset group.
490 */
491 public DatasetGroup getGroup() {
492 // unused but must return something, so while we are at it...
493 return this.delegateSet.getGroup();
494 }
495
496 /**
497 * Does nothing.
498 *
499 * @param group ignored.
500 */
501 public void setGroup(DatasetGroup group) {
502 // unused in parent
503 }
504
505 }
506
507 }
508
509