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

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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:  * VectorRenderer.java
  29:  * -------------------
  30:  * (C) Copyright 2007, 2008, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 30-Jan-2007 : Version 1 (DG);
  38:  * 24-May-2007 : Updated for method name changes (DG);
  39:  * 25-May-2007 : Moved from experimental to the main source tree (DG);
  40:  * 18-Feb-2008 : Fixed bug 1880114, arrows for horizontal plot
  41:  *               orientation (DG);
  42:  * 22-Apr-2008 : Implemented PublicCloneable (DG);
  43:  *
  44:  */
  45: 
  46: package org.jfree.chart.renderer.xy;
  47: 
  48: import java.awt.Graphics2D;
  49: import java.awt.geom.GeneralPath;
  50: import java.awt.geom.Line2D;
  51: import java.awt.geom.Rectangle2D;
  52: import java.io.Serializable;
  53: 
  54: import org.jfree.chart.axis.ValueAxis;
  55: import org.jfree.chart.plot.CrosshairState;
  56: import org.jfree.chart.plot.PlotOrientation;
  57: import org.jfree.chart.plot.PlotRenderingInfo;
  58: import org.jfree.chart.plot.XYPlot;
  59: import org.jfree.data.Range;
  60: import org.jfree.data.xy.VectorXYDataset;
  61: import org.jfree.data.xy.XYDataset;
  62: import org.jfree.util.PublicCloneable;
  63: 
  64: /**
  65:  * A renderer that represents data from an {@link VectorXYDataset} by drawing a
  66:  * line with an arrow at each (x, y) point.
  67:  *
  68:  * @since 1.0.6
  69:  */
  70: public class VectorRenderer extends AbstractXYItemRenderer
  71:         implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
  72: 
  73:     /** The length of the base. */
  74:     private double baseLength = 0.10;
  75: 
  76:     /** The length of the head. */
  77:     private double headLength = 0.14;
  78: 
  79:     /**
  80:      * Creates a new <code>XYBlockRenderer</code> instance with default
  81:      * attributes.
  82:      */
  83:     public VectorRenderer() {
  84:     }
  85: 
  86:     /**
  87:      * Returns the lower and upper bounds (range) of the x-values in the
  88:      * specified dataset.
  89:      *
  90:      * @param dataset  the dataset (<code>null</code> permitted).
  91:      *
  92:      * @return The range (<code>null</code> if the dataset is <code>null</code>
  93:      *         or empty).
  94:      */
  95:     public Range findDomainBounds(XYDataset dataset) {
  96:         if (dataset == null) {
  97:             throw new IllegalArgumentException("Null 'dataset' argument.");
  98:         }
  99:         double minimum = Double.POSITIVE_INFINITY;
 100:         double maximum = Double.NEGATIVE_INFINITY;
 101:         int seriesCount = dataset.getSeriesCount();
 102:         double lvalue;
 103:         double uvalue;
 104:         if (dataset instanceof VectorXYDataset) {
 105:             VectorXYDataset vdataset = (VectorXYDataset) dataset;
 106:             for (int series = 0; series < seriesCount; series++) {
 107:                 int itemCount = dataset.getItemCount(series);
 108:                 for (int item = 0; item < itemCount; item++) {
 109:                     double delta = vdataset.getVectorXValue(series, item);
 110:                     if (delta < 0.0) {
 111:                         uvalue = vdataset.getXValue(series, item);
 112:                         lvalue = uvalue + delta;
 113:                     }
 114:                     else {
 115:                         lvalue = vdataset.getXValue(series, item);
 116:                         uvalue = lvalue + delta;
 117:                     }
 118:                     minimum = Math.min(minimum, lvalue);
 119:                     maximum = Math.max(maximum, uvalue);
 120:                 }
 121:             }
 122:         }
 123:         else {
 124:             for (int series = 0; series < seriesCount; series++) {
 125:                 int itemCount = dataset.getItemCount(series);
 126:                 for (int item = 0; item < itemCount; item++) {
 127:                     lvalue = dataset.getXValue(series, item);
 128:                     uvalue = lvalue;
 129:                     minimum = Math.min(minimum, lvalue);
 130:                     maximum = Math.max(maximum, uvalue);
 131:                 }
 132:             }
 133:         }
 134:         if (minimum > maximum) {
 135:             return null;
 136:         }
 137:         else {
 138:             return new Range(minimum, maximum);
 139:         }
 140:     }
 141: 
 142:     /**
 143:      * Returns the range of values the renderer requires to display all the
 144:      * items from the specified dataset.
 145:      *
 146:      * @param dataset  the dataset (<code>null</code> permitted).
 147:      *
 148:      * @return The range (<code>null</code> if the dataset is <code>null</code>
 149:      *         or empty).
 150:      */
 151:     public Range findRangeBounds(XYDataset dataset) {
 152:         if (dataset == null) {
 153:             throw new IllegalArgumentException("Null 'dataset' argument.");
 154:         }
 155:         double minimum = Double.POSITIVE_INFINITY;
 156:         double maximum = Double.NEGATIVE_INFINITY;
 157:         int seriesCount = dataset.getSeriesCount();
 158:         double lvalue;
 159:         double uvalue;
 160:         if (dataset instanceof VectorXYDataset) {
 161:             VectorXYDataset vdataset = (VectorXYDataset) dataset;
 162:             for (int series = 0; series < seriesCount; series++) {
 163:                 int itemCount = dataset.getItemCount(series);
 164:                 for (int item = 0; item < itemCount; item++) {
 165:                     double delta = vdataset.getVectorYValue(series, item);
 166:                     if (delta < 0.0) {
 167:                         uvalue = vdataset.getYValue(series, item);
 168:                         lvalue = uvalue + delta;
 169:                     }
 170:                     else {
 171:                         lvalue = vdataset.getYValue(series, item);
 172:                         uvalue = lvalue + delta;
 173:                     }
 174:                     minimum = Math.min(minimum, lvalue);
 175:                     maximum = Math.max(maximum, uvalue);
 176:                 }
 177:             }
 178:         }
 179:         else {
 180:             for (int series = 0; series < seriesCount; series++) {
 181:                 int itemCount = dataset.getItemCount(series);
 182:                 for (int item = 0; item < itemCount; item++) {
 183:                     lvalue = dataset.getYValue(series, item);
 184:                     uvalue = lvalue;
 185:                     minimum = Math.min(minimum, lvalue);
 186:                     maximum = Math.max(maximum, uvalue);
 187:                 }
 188:             }
 189:         }
 190:         if (minimum > maximum) {
 191:             return null;
 192:         }
 193:         else {
 194:             return new Range(minimum, maximum);
 195:         }
 196:     }
 197: 
 198:     /**
 199:      * Draws the block representing the specified item.
 200:      *
 201:      * @param g2  the graphics device.
 202:      * @param state  the state.
 203:      * @param dataArea  the data area.
 204:      * @param info  the plot rendering info.
 205:      * @param plot  the plot.
 206:      * @param domainAxis  the x-axis.
 207:      * @param rangeAxis  the y-axis.
 208:      * @param dataset  the dataset.
 209:      * @param series  the series index.
 210:      * @param item  the item index.
 211:      * @param crosshairState  the crosshair state.
 212:      * @param pass  the pass index.
 213:      */
 214:     public void drawItem(Graphics2D g2, XYItemRendererState state,
 215:             Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
 216:             ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
 217:             int series, int item, CrosshairState crosshairState, int pass) {
 218: 
 219:         double x = dataset.getXValue(series, item);
 220:         double y = dataset.getYValue(series, item);
 221:         double dx = 0.0;
 222:         double dy = 0.0;
 223:         if (dataset instanceof VectorXYDataset) {
 224:             dx = ((VectorXYDataset) dataset).getVectorXValue(series, item);
 225:             dy = ((VectorXYDataset) dataset).getVectorYValue(series, item);
 226:         }
 227:         double xx0 = domainAxis.valueToJava2D(x, dataArea,
 228:                 plot.getDomainAxisEdge());
 229:         double yy0 = rangeAxis.valueToJava2D(y, dataArea,
 230:                 plot.getRangeAxisEdge());
 231:         double xx1 = domainAxis.valueToJava2D(x + dx, dataArea,
 232:                 plot.getDomainAxisEdge());
 233:         double yy1 = rangeAxis.valueToJava2D(y + dy, dataArea,
 234:                 plot.getRangeAxisEdge());
 235:         Line2D line;
 236:         PlotOrientation orientation = plot.getOrientation();
 237:         if (orientation.equals(PlotOrientation.HORIZONTAL)) {
 238:             line = new Line2D.Double(yy0, xx0, yy1, xx1);
 239:         }
 240:         else {
 241:             line = new Line2D.Double(xx0, yy0, xx1, yy1);
 242:         }
 243:         g2.setPaint(getItemPaint(series, item));
 244:         g2.setStroke(getItemStroke(series, item));
 245:         g2.draw(line);
 246: 
 247:         // calculate the arrow head and draw it...
 248:         double dxx = (xx1 - xx0);
 249:         double dyy = (yy1 - yy0);
 250:         double bx = xx0 + (1.0 - this.baseLength) * dxx;
 251:         double by = yy0 + (1.0 - this.baseLength) * dyy;
 252: 
 253:         double cx = xx0 + (1.0 - this.headLength) * dxx;
 254:         double cy = yy0 + (1.0 - this.headLength) * dyy;
 255: 
 256:         double angle = 0.0;
 257:         if (dxx != 0.0) {
 258:             angle = Math.PI / 2.0 - Math.atan(dyy / dxx);
 259:         }
 260:         double deltaX = 2.0 * Math.cos(angle);
 261:         double deltaY = 2.0 * Math.sin(angle);
 262: 
 263:         double leftx = cx + deltaX;
 264:         double lefty = cy - deltaY;
 265:         double rightx = cx - deltaX;
 266:         double righty = cy + deltaY;
 267: 
 268:         GeneralPath p = new GeneralPath();
 269:         if (orientation == PlotOrientation.VERTICAL) {
 270:             p.moveTo((float) xx1, (float) yy1);
 271:             p.lineTo((float) rightx, (float) righty);
 272:             p.lineTo((float) bx, (float) by);
 273:             p.lineTo((float) leftx, (float) lefty);
 274:         }
 275:         else {  // orientation is HORIZONTAL
 276:             p.moveTo((float) yy1, (float) xx1);
 277:             p.lineTo((float) righty, (float) rightx);
 278:             p.lineTo((float) by, (float) bx);
 279:             p.lineTo((float) lefty, (float) leftx);
 280:         }
 281:         p.closePath();
 282:         g2.draw(p);
 283: 
 284: 
 285:     }
 286: 
 287:     /**
 288:      * Tests this <code>VectorRenderer</code> for equality with an arbitrary
 289:      * object.  This method returns <code>true</code> if and only if:
 290:      * <ul>
 291:      * <li><code>obj</code> is an instance of <code>VectorRenderer</code> (not
 292:      *     <code>null</code>);</li>
 293:      * <li><code>obj</code> has the same field values as this
 294:      *     <code>VectorRenderer</code>;</li>
 295:      * </ul>
 296:      *
 297:      * @param obj  the object (<code>null</code> permitted).
 298:      *
 299:      * @return A boolean.
 300:      */
 301:     public boolean equals(Object obj) {
 302:         if (obj == this) {
 303:             return true;
 304:         }
 305:         if (!(obj instanceof VectorRenderer)) {
 306:             return false;
 307:         }
 308:         VectorRenderer that = (VectorRenderer) obj;
 309:         if (this.baseLength != that.baseLength) {
 310:             return false;
 311:         }
 312:         if (this.headLength != that.headLength) {
 313:             return false;
 314:         }
 315:         return super.equals(obj);
 316:     }
 317: 
 318:     /**
 319:      * Returns a clone of this renderer.
 320:      *
 321:      * @return A clone of this renderer.
 322:      *
 323:      * @throws CloneNotSupportedException if there is a problem creating the
 324:      *     clone.
 325:      */
 326:     public Object clone() throws CloneNotSupportedException {
 327:         return super.clone();
 328:     }
 329: 
 330: }