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