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