001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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, 2008, 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 * 18-Feb-2008 : Fixed bug 1880114, arrows for horizontal plot 041 * orientation (DG); 042 * 22-Apr-2008 : Implemented PublicCloneable (DG); 043 * 044 */ 045 046 package org.jfree.chart.renderer.xy; 047 048 import java.awt.Graphics2D; 049 import java.awt.geom.GeneralPath; 050 import java.awt.geom.Line2D; 051 import java.awt.geom.Rectangle2D; 052 import java.io.Serializable; 053 054 import org.jfree.chart.axis.ValueAxis; 055 import org.jfree.chart.plot.CrosshairState; 056 import org.jfree.chart.plot.PlotOrientation; 057 import org.jfree.chart.plot.PlotRenderingInfo; 058 import org.jfree.chart.plot.XYPlot; 059 import org.jfree.data.Range; 060 import org.jfree.data.xy.VectorXYDataset; 061 import org.jfree.data.xy.XYDataset; 062 import org.jfree.util.PublicCloneable; 063 064 /** 065 * A renderer that represents data from an {@link VectorXYDataset} by drawing a 066 * line with an arrow at each (x, y) point. 067 * 068 * @since 1.0.6 069 */ 070 public class VectorRenderer extends AbstractXYItemRenderer 071 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 072 073 /** The length of the base. */ 074 private double baseLength = 0.10; 075 076 /** The length of the head. */ 077 private double headLength = 0.14; 078 079 /** 080 * Creates a new <code>XYBlockRenderer</code> instance with default 081 * attributes. 082 */ 083 public VectorRenderer() { 084 } 085 086 /** 087 * Returns the lower and upper bounds (range) of the x-values in the 088 * specified dataset. 089 * 090 * @param dataset the dataset (<code>null</code> permitted). 091 * 092 * @return The range (<code>null</code> if the dataset is <code>null</code> 093 * or empty). 094 */ 095 public Range findDomainBounds(XYDataset dataset) { 096 if (dataset == null) { 097 throw new IllegalArgumentException("Null 'dataset' argument."); 098 } 099 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 }