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