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