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