Source for org.jfree.chart.renderer.xy.XYSplineRenderer

   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:         notifyListeners(new RendererChangeEvent(this));
 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: }