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