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