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