001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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