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 * ChartEntity.java 029 * ---------------- 030 * (C) Copyright 2002-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Richard Atkinson; 034 * Xavier Poinsard; 035 * Robert Fuller; 036 * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 037 */ 038 039package org.jfree.chart.entity; 040 041import java.awt.Shape; 042import java.awt.geom.PathIterator; 043import java.awt.geom.Rectangle2D; 044import java.io.IOException; 045import java.io.ObjectInputStream; 046import java.io.ObjectOutputStream; 047import java.io.Serializable; 048import java.util.Objects; 049 050import org.jfree.chart.HashUtils; 051import org.jfree.chart.imagemap.ToolTipTagFragmentGenerator; 052import org.jfree.chart.imagemap.URLTagFragmentGenerator; 053import org.jfree.chart.util.Args; 054import org.jfree.chart.util.PublicCloneable; 055import org.jfree.chart.util.SerialUtils; 056 057/** 058 * A class that captures information about some component of a chart (a bar, 059 * line etc). 060 */ 061public class ChartEntity implements Cloneable, PublicCloneable, Serializable { 062 063 /** For serialization. */ 064 private static final long serialVersionUID = -4445994133561919083L; 065 066 /** The area occupied by the entity (in Java 2D space). */ 067 private transient Shape area; 068 069 /** The tool tip text for the entity. */ 070 private String toolTipText; 071 072 /** The URL text for the entity. */ 073 private String urlText; 074 075 /** 076 * Creates a new chart entity. 077 * 078 * @param area the area ({@code null} not permitted). 079 */ 080 public ChartEntity(Shape area) { 081 // defer argument checks... 082 this(area, null); 083 } 084 085 /** 086 * Creates a new chart entity. 087 * 088 * @param area the area ({@code null} not permitted). 089 * @param toolTipText the tool tip text ({@code null} permitted). 090 */ 091 public ChartEntity(Shape area, String toolTipText) { 092 // defer argument checks... 093 this(area, toolTipText, null); 094 } 095 096 /** 097 * Creates a new entity. 098 * 099 * @param area the area ({@code null} not permitted). 100 * @param toolTipText the tool tip text ({@code null} permitted). 101 * @param urlText the URL text for HTML image maps ({@code null} 102 * permitted). 103 */ 104 public ChartEntity(Shape area, String toolTipText, String urlText) { 105 Args.nullNotPermitted(area, "area"); 106 this.area = area; 107 this.toolTipText = toolTipText; 108 this.urlText = urlText; 109 } 110 111 /** 112 * Returns the area occupied by the entity (in Java 2D space). 113 * 114 * @return The area (never {@code null}). 115 */ 116 public Shape getArea() { 117 return this.area; 118 } 119 120 /** 121 * Sets the area for the entity. 122 * <P> 123 * This class conveys information about chart entities back to a client. 124 * Setting this area doesn't change the entity (which has already been 125 * drawn). 126 * 127 * @param area the area ({@code null} not permitted). 128 */ 129 public void setArea(Shape area) { 130 Args.nullNotPermitted(area, "area"); 131 this.area = area; 132 } 133 134 /** 135 * Returns the tool tip text for the entity. Be aware that this text 136 * may have been generated from user supplied data, so for security 137 * reasons some form of filtering should be applied before incorporating 138 * this text into any HTML output. 139 * 140 * @return The tool tip text (possibly {@code null}). 141 */ 142 public String getToolTipText() { 143 return this.toolTipText; 144 } 145 146 /** 147 * Sets the tool tip text. 148 * 149 * @param text the text ({@code null} permitted). 150 */ 151 public void setToolTipText(String text) { 152 this.toolTipText = text; 153 } 154 155 /** 156 * Returns the URL text for the entity. Be aware that this text 157 * may have been generated from user supplied data, so some form of 158 * filtering should be applied before this "URL" is used in any output. 159 * 160 * @return The URL text (possibly {@code null}). 161 */ 162 public String getURLText() { 163 return this.urlText; 164 } 165 166 /** 167 * Sets the URL text. 168 * 169 * @param text the text ({@code null} permitted). 170 */ 171 public void setURLText(String text) { 172 this.urlText = text; 173 } 174 175 /** 176 * Returns a string describing the entity area. This string is intended 177 * for use in an AREA tag when generating an image map. 178 * 179 * @return The shape type (never {@code null}). 180 */ 181 public String getShapeType() { 182 if (this.area instanceof Rectangle2D) { 183 return "rect"; 184 } 185 else { 186 return "poly"; 187 } 188 } 189 190 /** 191 * Returns the shape coordinates as a string. 192 * 193 * @return The shape coordinates (never {@code null}). 194 */ 195 public String getShapeCoords() { 196 if (this.area instanceof Rectangle2D) { 197 return getRectCoords((Rectangle2D) this.area); 198 } 199 else { 200 return getPolyCoords(this.area); 201 } 202 } 203 204 /** 205 * Returns a string containing the coordinates (x1, y1, x2, y2) for a given 206 * rectangle. This string is intended for use in an image map. 207 * 208 * @param rectangle the rectangle ({@code null} not permitted). 209 * 210 * @return Upper left and lower right corner of a rectangle. 211 */ 212 private String getRectCoords(Rectangle2D rectangle) { 213 Args.nullNotPermitted(rectangle, "rectangle"); 214 int x1 = (int) rectangle.getX(); 215 int y1 = (int) rectangle.getY(); 216 int x2 = x1 + (int) rectangle.getWidth(); 217 int y2 = y1 + (int) rectangle.getHeight(); 218 // fix by rfuller 219 if (x2 == x1) { 220 x2++; 221 } 222 if (y2 == y1) { 223 y2++; 224 } 225 // end fix by rfuller 226 return x1 + "," + y1 + "," + x2 + "," + y2; 227 } 228 229 /** 230 * Returns a string containing the coordinates for a given shape. This 231 * string is intended for use in an image map. 232 * 233 * @param shape the shape ({@code null} not permitted). 234 * 235 * @return The coordinates for a given shape as string. 236 */ 237 private String getPolyCoords(Shape shape) { 238 Args.nullNotPermitted(shape, "shape"); 239 StringBuilder result = new StringBuilder(); 240 boolean first = true; 241 float[] coords = new float[6]; 242 PathIterator pi = shape.getPathIterator(null, 1.0); 243 while (!pi.isDone()) { 244 pi.currentSegment(coords); 245 if (first) { 246 first = false; 247 result.append((int) coords[0]); 248 result.append(",").append((int) coords[1]); 249 } 250 else { 251 result.append(","); 252 result.append((int) coords[0]); 253 result.append(","); 254 result.append((int) coords[1]); 255 } 256 pi.next(); 257 } 258 return result.toString(); 259 } 260 261 /** 262 * Returns an HTML image map tag for this entity. The returned fragment 263 * should be {@code XHTML 1.0} compliant. 264 * 265 * @param toolTipTagFragmentGenerator a generator for the HTML fragment 266 * that will contain the tooltip text ({@code null} not permitted 267 * if this entity contains tooltip information). 268 * @param urlTagFragmentGenerator a generator for the HTML fragment that 269 * will contain the URL reference ({@code null} not permitted if 270 * this entity has a URL). 271 * 272 * @return The HTML tag. 273 */ 274 public String getImageMapAreaTag( 275 ToolTipTagFragmentGenerator toolTipTagFragmentGenerator, 276 URLTagFragmentGenerator urlTagFragmentGenerator) { 277 278 StringBuilder tag = new StringBuilder(); 279 boolean hasURL = (this.urlText == null ? false 280 : !this.urlText.equals("")); 281 boolean hasToolTip = (this.toolTipText == null ? false 282 : !this.toolTipText.equals("")); 283 if (hasURL || hasToolTip) { 284 tag.append("<area shape=\"").append(getShapeType()).append("\"") 285 .append(" coords=\"").append(getShapeCoords()).append("\""); 286 if (hasToolTip) { 287 tag.append(toolTipTagFragmentGenerator.generateToolTipFragment( 288 this.toolTipText)); 289 } 290 if (hasURL) { 291 tag.append(urlTagFragmentGenerator.generateURLFragment( 292 this.urlText)); 293 } 294 else { 295 tag.append(" nohref=\"nohref\""); 296 } 297 // if there is a tool tip, we expect it to generate the title and 298 // alt values, so we only add an empty alt if there is no tooltip 299 if (!hasToolTip) { 300 tag.append(" alt=\"\""); 301 } 302 tag.append("/>"); 303 } 304 return tag.toString(); 305 } 306 307 /** 308 * Returns a string representation of the chart entity, useful for 309 * debugging. 310 * 311 * @return A string. 312 */ 313 @Override 314 public String toString() { 315 return "ChartEntity: tooltip = " + this.toolTipText; 316 } 317 318 /** 319 * Tests the entity for equality with an arbitrary object. 320 * 321 * @param obj the object to test against ({@code null} permitted). 322 * 323 * @return A boolean. 324 */ 325 @Override 326 public boolean equals(Object obj) { 327 if (obj == this) { 328 return true; 329 } 330 if (!(obj instanceof ChartEntity)) { 331 return false; 332 } 333 ChartEntity that = (ChartEntity) obj; 334 if (!Objects.equals(this.area, that.area)) { 335 return false; 336 } 337 if (!Objects.equals(this.toolTipText, that.toolTipText)) { 338 return false; 339 } 340 if (!Objects.equals(this.urlText, that.urlText)) { 341 return false; 342 } 343 344 // fix the "equals not symmetric" problem 345 if (!that.canEqual(this)) { 346 return false; 347 } 348 return true; 349 } 350 351 /** 352 * Ensures symmetry between super/subclass implementations of equals. For 353 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 354 * 355 * @param other Object 356 * 357 * @return true ONLY if the parameter is THIS class type 358 */ 359 public boolean canEqual(Object other) { 360 // fix the "equals not symmetric" problem 361 return (other instanceof ChartEntity); 362 } 363 364 /** 365 * Returns a hash code for this instance. 366 * 367 * @return A hash code. 368 */ 369 @Override 370 public int hashCode() { 371 int result = 37; 372 result = HashUtils.hashCode(result, this.toolTipText); 373 result = HashUtils.hashCode(result, this.urlText); 374 result = HashUtils.hashCode(result, this.area); 375 return result; 376 } 377 378 /** 379 * Returns a clone of the entity. 380 * 381 * @return A clone. 382 * 383 * @throws CloneNotSupportedException if there is a problem cloning the 384 * entity. 385 */ 386 @Override 387 public Object clone() throws CloneNotSupportedException { 388 return super.clone(); 389 } 390 391 /** 392 * Provides serialization support. 393 * 394 * @param stream the output stream. 395 * 396 * @throws IOException if there is an I/O error. 397 */ 398 private void writeObject(ObjectOutputStream stream) throws IOException { 399 stream.defaultWriteObject(); 400 SerialUtils.writeShape(this.area, stream); 401 } 402 403 /** 404 * Provides serialization support. 405 * 406 * @param stream the input stream. 407 * 408 * @throws IOException if there is an I/O error. 409 * @throws ClassNotFoundException if there is a classpath problem. 410 */ 411 private void readObject(ObjectInputStream stream) 412 throws IOException, ClassNotFoundException { 413 stream.defaultReadObject(); 414 this.area = SerialUtils.readShape(stream); 415 } 416 417}