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