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