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