001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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 * VectorRenderer.java 029 * ------------------- 030 * (C) Copyright 2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 30-Jan-2007 : Version 1 (DG); 038 * 24-May-2007 : Updated for method name changes (DG); 039 * 25-May-2007 : Moved from experimental to the main source tree (DG); 040 * 041 */ 042 043 package org.jfree.chart.renderer.xy; 044 045 import java.awt.Graphics2D; 046 import java.awt.geom.GeneralPath; 047 import java.awt.geom.Line2D; 048 import java.awt.geom.Rectangle2D; 049 import java.io.Serializable; 050 051 import org.jfree.chart.axis.ValueAxis; 052 import org.jfree.chart.plot.CrosshairState; 053 import org.jfree.chart.plot.PlotOrientation; 054 import org.jfree.chart.plot.PlotRenderingInfo; 055 import org.jfree.chart.plot.XYPlot; 056 import org.jfree.data.Range; 057 import org.jfree.data.xy.VectorXYDataset; 058 import org.jfree.data.xy.XYDataset; 059 060 /** 061 * A renderer that represents data from an {@link VectorXYDataset} by drawing a 062 * line with an arrow at each (x, y) point. 063 * 064 * @since 1.0.6 065 */ 066 public class VectorRenderer extends AbstractXYItemRenderer 067 implements XYItemRenderer, Cloneable, Serializable { 068 069 /** The length of the base. */ 070 private double baseLength = 0.10; 071 072 /** The length of the head. */ 073 private double headLength = 0.14; 074 075 076 /** 077 * Creates a new <code>XYBlockRenderer</code> instance with default 078 * attributes. 079 */ 080 public VectorRenderer() { 081 } 082 083 /** 084 * Returns the lower and upper bounds (range) of the x-values in the 085 * specified dataset. 086 * 087 * @param dataset the dataset (<code>null</code> permitted). 088 * 089 * @return The range (<code>null</code> if the dataset is <code>null</code> 090 * or empty). 091 */ 092 public Range findDomainBounds(XYDataset dataset) { 093 if (dataset == null) { 094 throw new IllegalArgumentException("Null 'dataset' argument."); 095 } 096 double minimum = Double.POSITIVE_INFINITY; 097 double maximum = Double.NEGATIVE_INFINITY; 098 int seriesCount = dataset.getSeriesCount(); 099 double lvalue; 100 double uvalue; 101 if (dataset instanceof VectorXYDataset) { 102 VectorXYDataset vdataset = (VectorXYDataset) dataset; 103 for (int series = 0; series < seriesCount; series++) { 104 int itemCount = dataset.getItemCount(series); 105 for (int item = 0; item < itemCount; item++) { 106 double delta = vdataset.getVectorXValue(series, item); 107 if (delta < 0.0) { 108 uvalue = vdataset.getXValue(series, item); 109 lvalue = uvalue + delta; 110 } 111 else { 112 lvalue = vdataset.getXValue(series, item); 113 uvalue = lvalue + delta; 114 } 115 minimum = Math.min(minimum, lvalue); 116 maximum = Math.max(maximum, uvalue); 117 } 118 } 119 } 120 else { 121 for (int series = 0; series < seriesCount; series++) { 122 int itemCount = dataset.getItemCount(series); 123 for (int item = 0; item < itemCount; item++) { 124 lvalue = dataset.getXValue(series, item); 125 uvalue = lvalue; 126 minimum = Math.min(minimum, lvalue); 127 maximum = Math.max(maximum, uvalue); 128 } 129 } 130 } 131 if (minimum > maximum) { 132 return null; 133 } 134 else { 135 return new Range(minimum, maximum); 136 } 137 } 138 139 /** 140 * Returns the range of values the renderer requires to display all the 141 * items from the specified dataset. 142 * 143 * @param dataset the dataset (<code>null</code> permitted). 144 * 145 * @return The range (<code>null</code> if the dataset is <code>null</code> 146 * or empty). 147 */ 148 public Range findRangeBounds(XYDataset dataset) { 149 if (dataset == null) { 150 throw new IllegalArgumentException("Null 'dataset' argument."); 151 } 152 double minimum = Double.POSITIVE_INFINITY; 153 double maximum = Double.NEGATIVE_INFINITY; 154 int seriesCount = dataset.getSeriesCount(); 155 double lvalue; 156 double uvalue; 157 if (dataset instanceof VectorXYDataset) { 158 VectorXYDataset vdataset = (VectorXYDataset) dataset; 159 for (int series = 0; series < seriesCount; series++) { 160 int itemCount = dataset.getItemCount(series); 161 for (int item = 0; item < itemCount; item++) { 162 double delta = vdataset.getVectorYValue(series, item); 163 if (delta < 0.0) { 164 uvalue = vdataset.getYValue(series, item); 165 lvalue = uvalue + delta; 166 } 167 else { 168 lvalue = vdataset.getYValue(series, item); 169 uvalue = lvalue + delta; 170 } 171 minimum = Math.min(minimum, lvalue); 172 maximum = Math.max(maximum, uvalue); 173 } 174 } 175 } 176 else { 177 for (int series = 0; series < seriesCount; series++) { 178 int itemCount = dataset.getItemCount(series); 179 for (int item = 0; item < itemCount; item++) { 180 lvalue = dataset.getYValue(series, item); 181 uvalue = lvalue; 182 minimum = Math.min(minimum, lvalue); 183 maximum = Math.max(maximum, uvalue); 184 } 185 } 186 } 187 if (minimum > maximum) { 188 return null; 189 } 190 else { 191 return new Range(minimum, maximum); 192 } 193 } 194 195 /** 196 * Draws the block representing the specified item. 197 * 198 * @param g2 the graphics device. 199 * @param state the state. 200 * @param dataArea the data area. 201 * @param info the plot rendering info. 202 * @param plot the plot. 203 * @param domainAxis the x-axis. 204 * @param rangeAxis the y-axis. 205 * @param dataset the dataset. 206 * @param series the series index. 207 * @param item the item index. 208 * @param crosshairState the crosshair state. 209 * @param pass the pass index. 210 */ 211 public void drawItem(Graphics2D g2, XYItemRendererState state, 212 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 213 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 214 int series, int item, CrosshairState crosshairState, int pass) { 215 216 double x = dataset.getXValue(series, item); 217 double y = dataset.getYValue(series, item); 218 double dx = 0.0; 219 double dy = 0.0; 220 if (dataset instanceof VectorXYDataset) { 221 dx = ((VectorXYDataset) dataset).getVectorXValue(series, item); 222 dy = ((VectorXYDataset) dataset).getVectorYValue(series, item); 223 } 224 double xx0 = domainAxis.valueToJava2D(x, dataArea, 225 plot.getDomainAxisEdge()); 226 double yy0 = rangeAxis.valueToJava2D(y, dataArea, 227 plot.getRangeAxisEdge()); 228 double xx1 = domainAxis.valueToJava2D(x + dx, dataArea, 229 plot.getDomainAxisEdge()); 230 double yy1 = rangeAxis.valueToJava2D(y + dy, dataArea, 231 plot.getRangeAxisEdge()); 232 Line2D line; 233 PlotOrientation orientation = plot.getOrientation(); 234 if (orientation.equals(PlotOrientation.HORIZONTAL)) { 235 line = new Line2D.Double(yy0, xx0, yy1, xx1); 236 } 237 else { 238 line = new Line2D.Double(xx0, yy0, xx1, yy1); 239 } 240 g2.setPaint(getItemPaint(series, item)); 241 g2.setStroke(getItemStroke(series, item)); 242 g2.draw(line); 243 244 // calculate the arrow head and draw it... 245 double dxx = (xx1 - xx0); 246 double dyy = (yy1 - yy0); 247 double bx = xx0 + (1.0 - this.baseLength) * dxx; 248 double by = yy0 + (1.0 - this.baseLength) * dyy; 249 250 double cx = xx0 + (1.0 - this.headLength) * dxx; 251 double cy = yy0 + (1.0 - this.headLength) * dyy; 252 253 double angle = 0.0; 254 if (dxx != 0.0) { 255 angle = Math.PI / 2.0 - Math.atan(dyy / dxx); 256 } 257 double deltaX = 2.0 * Math.cos(angle); 258 double deltaY = 2.0 * Math.sin(angle); 259 260 double leftx = cx + deltaX; 261 double lefty = cy - deltaY; 262 double rightx = cx - deltaX; 263 double righty = cy + deltaY; 264 265 GeneralPath p = new GeneralPath(); 266 p.moveTo((float) xx1, (float) yy1); 267 p.lineTo((float) rightx, (float) righty); 268 p.lineTo((float) bx, (float) by); 269 p.lineTo((float) leftx, (float) lefty); 270 p.closePath(); 271 g2.draw(p); 272 273 274 } 275 276 /** 277 * Tests this <code>VectorRenderer</code> for equality with an arbitrary 278 * object. This method returns <code>true</code> if and only if: 279 * <ul> 280 * <li><code>obj</code> is an instance of <code>VectorRenderer</code> (not 281 * <code>null</code>);</li> 282 * <li><code>obj</code> has the same field values as this 283 * <code>VectorRenderer</code>;</li> 284 * </ul> 285 * 286 * @param obj the object (<code>null</code> permitted). 287 * 288 * @return A boolean. 289 */ 290 public boolean equals(Object obj) { 291 if (obj == this) { 292 return true; 293 } 294 if (!(obj instanceof VectorRenderer)) { 295 return false; 296 } 297 VectorRenderer that = (VectorRenderer) obj; 298 if (this.baseLength != that.baseLength) { 299 return false; 300 } 301 if (this.headLength != that.headLength) { 302 return false; 303 } 304 return super.equals(obj); 305 } 306 307 /** 308 * Returns a clone of this renderer. 309 * 310 * @return A clone of this renderer. 311 * 312 * @throws CloneNotSupportedException if there is a problem creating the 313 * clone. 314 */ 315 public Object clone() throws CloneNotSupportedException { 316 return super.clone(); 317 } 318 319 }