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