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 * XYAreaRenderer2.java 029 * -------------------- 030 * (C) Copyright 2004-present, by Hari and Contributors. 031 * 032 * Original Author: Hari (ourhari@hotmail.com); 033 * Contributor(s): David Gilbert; 034 * Richard Atkinson; 035 * Christian W. Zuckschwerdt; 036 * Martin Krauskopf; 037 * Ulrich Voigt (patch #312); 038 */ 039 040package org.jfree.chart.renderer.xy; 041 042import java.awt.Graphics2D; 043import java.awt.Paint; 044import java.awt.Shape; 045import java.awt.Stroke; 046import java.awt.geom.Area; 047import java.awt.geom.GeneralPath; 048import java.awt.geom.Rectangle2D; 049import java.io.IOException; 050import java.io.ObjectInputStream; 051import java.io.ObjectOutputStream; 052 053import org.jfree.chart.LegendItem; 054import org.jfree.chart.axis.ValueAxis; 055import org.jfree.chart.entity.EntityCollection; 056import org.jfree.chart.entity.XYItemEntity; 057import org.jfree.chart.event.RendererChangeEvent; 058import org.jfree.chart.labels.XYSeriesLabelGenerator; 059import org.jfree.chart.labels.XYToolTipGenerator; 060import org.jfree.chart.plot.CrosshairState; 061import org.jfree.chart.plot.PlotOrientation; 062import org.jfree.chart.plot.PlotRenderingInfo; 063import org.jfree.chart.plot.XYPlot; 064import org.jfree.chart.urls.XYURLGenerator; 065import org.jfree.chart.util.Args; 066import org.jfree.chart.util.PublicCloneable; 067import org.jfree.chart.util.SerialUtils; 068import org.jfree.chart.util.ShapeUtils; 069import org.jfree.data.xy.XYDataset; 070 071/** 072 * Area item renderer for an {@link XYPlot}. The example shown here is 073 * generated by the {@code XYAreaRenderer2Demo1.java} program included in 074 * the JFreeChart demo collection: 075 * <br><br> 076 * <img src="doc-files/XYAreaRenderer2Sample.png" 077 * alt="XYAreaRenderer2Sample.png"> 078 */ 079public class XYAreaRenderer2 extends AbstractXYItemRenderer 080 implements XYItemRenderer, PublicCloneable { 081 082 /** For serialization. */ 083 private static final long serialVersionUID = -7378069681579984133L; 084 085 /** A flag that controls whether or not the outline is shown. */ 086 private boolean showOutline; 087 088 /** 089 * The shape used to represent an area in each legend item (this should 090 * never be {@code null}). 091 */ 092 private transient Shape legendArea; 093 094 /** 095 * Constructs a new renderer. 096 */ 097 public XYAreaRenderer2() { 098 this(null, null); 099 } 100 101 /** 102 * Constructs a new renderer. 103 * 104 * @param labelGenerator the tool tip generator to use ({@code null} 105 * permitted). 106 * @param urlGenerator the URL generator ({@code null} permitted). 107 */ 108 public XYAreaRenderer2(XYToolTipGenerator labelGenerator, 109 XYURLGenerator urlGenerator) { 110 super(); 111 this.showOutline = false; 112 setDefaultToolTipGenerator(labelGenerator); 113 setURLGenerator(urlGenerator); 114 GeneralPath area = new GeneralPath(); 115 area.moveTo(0.0f, -4.0f); 116 area.lineTo(3.0f, -2.0f); 117 area.lineTo(4.0f, 4.0f); 118 area.lineTo(-4.0f, 4.0f); 119 area.lineTo(-3.0f, -2.0f); 120 area.closePath(); 121 this.legendArea = area; 122 } 123 124 /** 125 * Returns a flag that controls whether or not outlines of the areas are 126 * drawn. 127 * 128 * @return The flag. 129 * 130 * @see #setOutline(boolean) 131 */ 132 public boolean isOutline() { 133 return this.showOutline; 134 } 135 136 /** 137 * Sets a flag that controls whether or not outlines of the areas are 138 * drawn, and sends a {@link RendererChangeEvent} to all registered 139 * listeners. 140 * 141 * @param show the flag. 142 * 143 * @see #isOutline() 144 */ 145 public void setOutline(boolean show) { 146 this.showOutline = show; 147 fireChangeEvent(); 148 } 149 150 /** 151 * Returns the shape used to represent an area in the legend. 152 * 153 * @return The legend area (never {@code null}). 154 * 155 * @see #setLegendArea(Shape) 156 */ 157 public Shape getLegendArea() { 158 return this.legendArea; 159 } 160 161 /** 162 * Sets the shape used as an area in each legend item and sends a 163 * {@link RendererChangeEvent} to all registered listeners. 164 * 165 * @param area the area ({@code null} not permitted). 166 * 167 * @see #getLegendArea() 168 */ 169 public void setLegendArea(Shape area) { 170 Args.nullNotPermitted(area, "area"); 171 this.legendArea = area; 172 fireChangeEvent(); 173 } 174 175 /** 176 * Returns a default legend item for the specified series. Subclasses 177 * should override this method to generate customised items. 178 * 179 * @param datasetIndex the dataset index (zero-based). 180 * @param series the series index (zero-based). 181 * 182 * @return A legend item for the series. 183 */ 184 @Override 185 public LegendItem getLegendItem(int datasetIndex, int series) { 186 LegendItem result = null; 187 XYPlot xyplot = getPlot(); 188 if (xyplot != null) { 189 XYDataset dataset = xyplot.getDataset(datasetIndex); 190 if (dataset != null) { 191 XYSeriesLabelGenerator lg = getLegendItemLabelGenerator(); 192 String label = lg.generateLabel(dataset, series); 193 String description = label; 194 String toolTipText = null; 195 if (getLegendItemToolTipGenerator() != null) { 196 toolTipText = getLegendItemToolTipGenerator().generateLabel( 197 dataset, series); 198 } 199 String urlText = null; 200 if (getLegendItemURLGenerator() != null) { 201 urlText = getLegendItemURLGenerator().generateLabel( 202 dataset, series); 203 } 204 Paint paint = lookupSeriesPaint(series); 205 result = new LegendItem(label, description, toolTipText, 206 urlText, this.legendArea, paint); 207 result.setLabelFont(lookupLegendTextFont(series)); 208 Paint labelPaint = lookupLegendTextPaint(series); 209 if (labelPaint != null) { 210 result.setLabelPaint(labelPaint); 211 } 212 result.setDataset(dataset); 213 result.setDatasetIndex(datasetIndex); 214 result.setSeriesKey(dataset.getSeriesKey(series)); 215 result.setSeriesIndex(series); 216 } 217 } 218 return result; 219 } 220 221 /** 222 * Draws the visual representation of a single data item. 223 * 224 * @param g2 the graphics device. 225 * @param state the renderer state. 226 * @param dataArea the area within which the data is being drawn. 227 * @param info collects information about the drawing. 228 * @param plot the plot (can be used to obtain standard color 229 * information etc). 230 * @param domainAxis the domain axis. 231 * @param rangeAxis the range axis. 232 * @param dataset the dataset. 233 * @param series the series index (zero-based). 234 * @param item the item index (zero-based). 235 * @param crosshairState crosshair information for the plot 236 * ({@code null} permitted). 237 * @param pass the pass index. 238 */ 239 @Override 240 public void drawItem(Graphics2D g2, XYItemRendererState state, 241 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 242 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 243 int series, int item, CrosshairState crosshairState, int pass) { 244 245 if (!getItemVisible(series, item)) { 246 return; 247 } 248 // get the data point... 249 double x1 = dataset.getXValue(series, item); 250 double y1 = dataset.getYValue(series, item); 251 if (Double.isNaN(y1)) { 252 y1 = 0.0; 253 } 254 255 double transX1 = domainAxis.valueToJava2D(x1, dataArea, 256 plot.getDomainAxisEdge()); 257 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 258 plot.getRangeAxisEdge()); 259 260 // get the previous point and the next point so we can calculate a 261 // "hot spot" for the area (used by the chart entity)... 262 double x0 = dataset.getXValue(series, Math.max(item - 1, 0)); 263 double y0 = dataset.getYValue(series, Math.max(item - 1, 0)); 264 if (Double.isNaN(y0)) { 265 y0 = 0.0; 266 } 267 double transX0 = domainAxis.valueToJava2D(x0, dataArea, 268 plot.getDomainAxisEdge()); 269 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 270 plot.getRangeAxisEdge()); 271 272 int itemCount = dataset.getItemCount(series); 273 double x2 = dataset.getXValue(series, Math.min(item + 1, 274 itemCount - 1)); 275 double y2 = dataset.getYValue(series, Math.min(item + 1, 276 itemCount - 1)); 277 if (Double.isNaN(y2)) { 278 y2 = 0.0; 279 } 280 double transX2 = domainAxis.valueToJava2D(x2, dataArea, 281 plot.getDomainAxisEdge()); 282 double transY2 = rangeAxis.valueToJava2D(y2, dataArea, 283 plot.getRangeAxisEdge()); 284 285 double transZero = rangeAxis.valueToJava2D(0.0, dataArea, 286 plot.getRangeAxisEdge()); 287 GeneralPath hotspot = new GeneralPath(); 288 if (plot.getOrientation() == PlotOrientation.HORIZONTAL) { 289 moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0)); 290 lineTo(hotspot, ((transY0 + transY1) / 2.0), 291 ((transX0 + transX1) / 2.0)); 292 lineTo(hotspot, transY1, transX1); 293 lineTo(hotspot, ((transY1 + transY2) / 2.0), 294 ((transX1 + transX2) / 2.0)); 295 lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0)); 296 } 297 else { // vertical orientation 298 moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero); 299 lineTo(hotspot, ((transX0 + transX1) / 2.0), 300 ((transY0 + transY1) / 2.0)); 301 lineTo(hotspot, transX1, transY1); 302 lineTo(hotspot, ((transX1 + transX2) / 2.0), 303 ((transY1 + transY2) / 2.0)); 304 lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero); 305 } 306 hotspot.closePath(); 307 308 PlotOrientation orientation = plot.getOrientation(); 309 Paint paint = getItemPaint(series, item); 310 Stroke stroke = getItemStroke(series, item); 311 g2.setPaint(paint); 312 g2.setStroke(stroke); 313 314 // Check if the item is the last item for the series. 315 // and number of items > 0. We can't draw an area for a single point. 316 g2.fill(hotspot); 317 318 // draw an outline around the Area. 319 if (isOutline()) { 320 g2.setStroke(lookupSeriesOutlineStroke(series)); 321 g2.setPaint(lookupSeriesOutlinePaint(series)); 322 g2.draw(hotspot); 323 } 324 int datasetIndex = plot.indexOf(dataset); 325 updateCrosshairValues(crosshairState, x1, y1, datasetIndex, 326 transX1, transY1, orientation); 327 328 // collect entity and tool tip information... 329 if (state.getInfo() != null) { 330 EntityCollection entities = state.getEntityCollection(); 331 if (entities != null) { 332 // limit the entity hotspot area to the data area 333 Area dataAreaHotspot = new Area(hotspot); 334 dataAreaHotspot.intersect(new Area(dataArea)); 335 if (!dataAreaHotspot.isEmpty()) { 336 String tip = null; 337 XYToolTipGenerator generator = getToolTipGenerator(series, 338 item); 339 if (generator != null) { 340 tip = generator.generateToolTip(dataset, series, item); 341 } 342 String url = null; 343 if (getURLGenerator() != null) { 344 url = getURLGenerator().generateURL(dataset, series, 345 item); 346 } 347 XYItemEntity entity = new XYItemEntity(dataAreaHotspot, 348 dataset, series, item, tip, url); 349 entities.add(entity); 350 } 351 } 352 } 353 354 } 355 356 /** 357 * Tests this renderer for equality with an arbitrary object. 358 * 359 * @param obj the object ({@code null} not permitted). 360 * 361 * @return A boolean. 362 */ 363 @Override 364 public boolean equals(Object obj) { 365 if (obj == this) { 366 return true; 367 } 368 if (!(obj instanceof XYAreaRenderer2)) { 369 return false; 370 } 371 XYAreaRenderer2 that = (XYAreaRenderer2) obj; 372 if (this.showOutline != that.showOutline) { 373 return false; 374 } 375 if (!ShapeUtils.equal(this.legendArea, that.legendArea)) { 376 return false; 377 } 378 return super.equals(obj); 379 } 380 381 /** 382 * Returns a clone of the renderer. 383 * 384 * @return A clone. 385 * 386 * @throws CloneNotSupportedException if the renderer cannot be cloned. 387 */ 388 @Override 389 public Object clone() throws CloneNotSupportedException { 390 XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone(); 391 clone.legendArea = ShapeUtils.clone(this.legendArea); 392 return clone; 393 } 394 395 /** 396 * Provides serialization support. 397 * 398 * @param stream the input stream. 399 * 400 * @throws IOException if there is an I/O error. 401 * @throws ClassNotFoundException if there is a classpath problem. 402 */ 403 private void readObject(ObjectInputStream stream) 404 throws IOException, ClassNotFoundException { 405 stream.defaultReadObject(); 406 this.legendArea = SerialUtils.readShape(stream); 407 } 408 409 /** 410 * Provides serialization support. 411 * 412 * @param stream the output stream. 413 * 414 * @throws IOException if there is an I/O error. 415 */ 416 private void writeObject(ObjectOutputStream stream) throws IOException { 417 stream.defaultWriteObject(); 418 SerialUtils.writeShape(this.legendArea, stream); 419 } 420 421} 422