Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * --------------------- 28: * XYSplineRenderer.java 29: * --------------------- 30: * (C) Copyright 2007, by Klaus Rheinwald and Contributors. 31: * 32: * Original Author: Klaus Rheinwald; 33: * Contributor(s): Tobias von Petersdorff (tvp@math.umd.edu, 34: * http://www.wam.umd.edu/~petersd/); 35: * David Gilbert (for Object Refinery Limited); 36: * 37: * Changes: 38: * -------- 39: * 25-Jul-2007 : Version 1, contributed by Klaus Rheinwald (DG); 40: * 03-Aug-2007 : Added new constructor (KR); 41: * 25-Oct-2007 : Prevent duplicate control points (KR); 42: * 43: */ 44: 45: 46: package org.jfree.chart.renderer.xy; 47: 48: import java.awt.Graphics2D; 49: import java.awt.geom.Rectangle2D; 50: import java.util.Vector; 51: 52: import org.jfree.chart.axis.ValueAxis; 53: import org.jfree.chart.event.RendererChangeEvent; 54: import org.jfree.chart.plot.PlotOrientation; 55: import org.jfree.chart.plot.PlotRenderingInfo; 56: import org.jfree.chart.plot.XYPlot; 57: import org.jfree.data.xy.XYDataset; 58: import org.jfree.ui.RectangleEdge; 59: 60: 61: /** 62: * A renderer that connects data points with natural cubic splines and/or 63: * draws shapes at each data point. This renderer is designed for use with 64: * the {@link XYPlot} class. 65: * 66: * @since 1.0.7 67: */ 68: public class XYSplineRenderer extends XYLineAndShapeRenderer { 69: 70: /** 71: * To collect data points for later splining. 72: */ 73: private Vector points; 74: 75: /** 76: * Resolution of splines (number of line segments between points) 77: */ 78: private int precision; 79: 80: /** 81: * Creates a new instance with the 'precision' attribute defaulting to 82: * 5. 83: */ 84: public XYSplineRenderer() { 85: this(5); 86: } 87: 88: /** 89: * Creates a new renderer with the specified precision. 90: * 91: * @param precision the number of points between data items. 92: */ 93: public XYSplineRenderer(int precision) { 94: super(); 95: if (precision <= 0) { 96: throw new IllegalArgumentException("Requires precision > 0."); 97: } 98: this.precision = precision; 99: } 100: 101: /** 102: * Get the resolution of splines. 103: * 104: * @return Number of line segments between points. 105: * 106: * @see #setPrecision(int) 107: */ 108: public int getPrecision() { 109: return this.precision; 110: } 111: 112: /** 113: * Set the resolution of splines and sends a {@link RendererChangeEvent} 114: * to all registered listeners. 115: * 116: * @param p number of line segments between points (must be > 0). 117: * 118: * @see #getPrecision() 119: */ 120: public void setPrecision(int p) { 121: if (p <= 0) { 122: throw new IllegalArgumentException("Requires p > 0."); 123: } 124: this.precision = p; 125: fireChangeEvent(); 126: } 127: 128: /** 129: * Initialises the renderer. 130: * <P> 131: * This method will be called before the first item is rendered, giving the 132: * renderer an opportunity to initialise any state information it wants to 133: * maintain. The renderer can do nothing if it chooses. 134: * 135: * @param g2 the graphics device. 136: * @param dataArea the area inside the axes. 137: * @param plot the plot. 138: * @param data the data. 139: * @param info an optional info collection object to return data back to 140: * the caller. 141: * 142: * @return The renderer state. 143: */ 144: public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 145: XYPlot plot, XYDataset data, PlotRenderingInfo info) { 146: 147: State state = (State) super.initialise(g2, dataArea, plot, data, info); 148: state.setProcessVisibleItemsOnly(false); 149: this.points = new Vector(); 150: setDrawSeriesLineAsPath(true); 151: return state; 152: } 153: 154: /** 155: * Draws the item (first pass). This method draws the lines 156: * connecting the items. Instead of drawing separate lines, 157: * a GeneralPath is constructed and drawn at the end of 158: * the series painting. 159: * 160: * @param g2 the graphics device. 161: * @param state the renderer state. 162: * @param plot the plot (can be used to obtain standard color information 163: * etc). 164: * @param dataset the dataset. 165: * @param pass the pass. 166: * @param series the series index (zero-based). 167: * @param item the item index (zero-based). 168: * @param domainAxis the domain axis. 169: * @param rangeAxis the range axis. 170: * @param dataArea the area within which the data is being drawn. 171: */ 172: protected void drawPrimaryLineAsPath(XYItemRendererState state, 173: Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, 174: int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis, 175: Rectangle2D dataArea) { 176: 177: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 178: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 179: 180: // get the data points 181: double x1 = dataset.getXValue(series, item); 182: double y1 = dataset.getYValue(series, item); 183: double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 184: double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 185: 186: // collect points 187: if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 188: ControlPoint p = new ControlPoint(plot.getOrientation() 189: == PlotOrientation.HORIZONTAL ? (float) transY1 190: : (float) transX1, plot.getOrientation() 191: == PlotOrientation.HORIZONTAL ? (float) transX1 192: : (float) transY1); 193: if (!this.points.contains(p)) { 194: this.points.add(p); 195: } 196: } 197: if (item == dataset.getItemCount(series) - 1) { 198: State s = (State) state; 199: // construct path 200: if (this.points.size() > 1) { 201: // we need at least two points to draw something 202: ControlPoint cp0 = (ControlPoint) this.points.get(0); 203: s.seriesPath.moveTo(cp0.x, cp0.y); 204: if (this.points.size() == 2) { 205: // we need at least 3 points to spline. Draw simple line 206: // for two points 207: ControlPoint cp1 = (ControlPoint) this.points.get(1); 208: s.seriesPath.lineTo(cp1.x, cp1.y); 209: } 210: else { 211: // construct spline 212: int np = this.points.size(); // number of points 213: float[] d = new float[np]; // Newton form coefficients 214: float[] x = new float[np]; // x-coordinates of nodes 215: float y; 216: float t; 217: float oldy = 0; 218: float oldt = 0; 219: 220: float[] a = new float[np]; 221: float t1; 222: float t2; 223: float[] h = new float[np]; 224: 225: for (int i = 0; i < np; i++) { 226: ControlPoint cpi = (ControlPoint) this.points.get(i); 227: x[i] = cpi.x; 228: d[i] = cpi.y; 229: } 230: 231: for (int i = 1; i <= np - 1; i++) { 232: h[i] = x[i] - x[i - 1]; 233: } 234: float[] sub = new float[np - 1]; 235: float[] diag = new float[np - 1]; 236: float[] sup = new float[np - 1]; 237: 238: for (int i = 1; i <= np - 2; i++) { 239: diag[i] = (h[i] + h[i + 1]) / 3; 240: sup[i] = h[i + 1] / 6; 241: sub[i] = h[i] / 6; 242: a[i] = (d[i + 1] - d[i]) / h[i + 1] 243: - (d[i] - d[i - 1]) / h[i]; 244: } 245: solveTridiag(sub, diag, sup, a, np - 2); 246: 247: // note that a[0]=a[np-1]=0 248: // draw 249: oldt = x[0]; 250: oldy = d[0]; 251: s.seriesPath.moveTo(oldt, oldy); 252: for (int i = 1; i <= np - 1; i++) { 253: // loop over intervals between nodes 254: for (int j = 1; j <= this.precision; j++) { 255: t1 = (h[i] * j) / this.precision; 256: t2 = h[i] - t1; 257: y = ((-a[i - 1] / 6 * (t2 + h[i]) * t1 + d[i - 1]) 258: * t2 + (-a[i] / 6 * (t1 + h[i]) * t2 259: + d[i]) * t1) / h[i]; 260: t = x[i - 1] + t1; 261: s.seriesPath.lineTo(t, y); 262: oldt = t; 263: oldy = y; 264: } 265: } 266: } 267: // draw path 268: drawFirstPassShape(g2, pass, series, item, s.seriesPath); 269: } 270: 271: // reset points vector 272: this.points = new Vector(); 273: } 274: } 275: 276: /** 277: * Document me! 278: * 279: * @param sub 280: * @param diag 281: * @param sup 282: * @param b 283: * @param n 284: */ 285: private void solveTridiag(float[] sub, float[] diag, float[] sup, 286: float[] b, int n) { 287: /* solve linear system with tridiagonal n by n matrix a 288: using Gaussian elimination *without* pivoting 289: where a(i,i-1) = sub[i] for 2<=i<=n 290: a(i,i) = diag[i] for 1<=i<=n 291: a(i,i+1) = sup[i] for 1<=i<=n-1 292: (the values sub[1], sup[n] are ignored) 293: right hand side vector b[1:n] is overwritten with solution 294: NOTE: 1...n is used in all arrays, 0 is unused */ 295: int i; 296: /* factorization and forward substitution */ 297: for (i = 2; i <= n; i++) { 298: sub[i] = sub[i] / diag[i - 1]; 299: diag[i] = diag[i] - sub[i] * sup[i - 1]; 300: b[i] = b[i] - sub[i] * b[i - 1]; 301: } 302: b[n] = b[n] / diag[n]; 303: for (i = n - 1; i >= 1; i--) { 304: b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i]; 305: } 306: } 307: 308: /** 309: * Tests this renderer for equality with an arbitrary object. 310: * 311: * @param obj the object (<code>null</code> permitted). 312: * 313: * @return A boolean. 314: */ 315: public boolean equals(Object obj) { 316: if (obj == this) { 317: return true; 318: } 319: if (!(obj instanceof XYSplineRenderer)) { 320: return false; 321: } 322: XYSplineRenderer that = (XYSplineRenderer) obj; 323: if (this.precision != that.precision) { 324: return false; 325: } 326: return super.equals(obj); 327: } 328: 329: /** 330: * Represents a control point. 331: */ 332: class ControlPoint { 333: 334: /** The x-coordinate. */ 335: public float x; 336: 337: /** The y-coordinate. */ 338: public float y; 339: 340: /** 341: * Creates a new control point. 342: * 343: * @param x the x-coordinate. 344: * @param y the y-coordinate. 345: */ 346: public ControlPoint(float x, float y) { 347: this.x = x; 348: this.y = y; 349: } 350: 351: /** 352: * Tests this point for equality with an arbitrary object. 353: * 354: * @param obj the object (<code>null</code> permitted. 355: * 356: * @return A boolean. 357: */ 358: public boolean equals(Object obj) { 359: if (obj == this) { 360: return true; 361: } 362: if (!(obj instanceof ControlPoint)) { 363: return false; 364: } 365: ControlPoint that = (ControlPoint) obj; 366: if (this.x != that.x) { 367: return false; 368: } 369: /*&& y == ((ControlPoint) obj).y*/; 370: return true; 371: } 372: 373: } 374: }