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