001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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     * XYBubbleRenderer.java
029     * ---------------------
030     * (C) Copyright 2003-2006, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Christian W. Zuckschwerdt;
034     *
035     * $Id: XYBubbleRenderer.java,v 1.8.2.5 2006/01/26 14:57:48 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 28-Jan-2003 : Version 1 (DG);
040     * 25-Mar-2003 : Implemented Serializable (DG);
041     * 01-May-2003 : Modified drawItem() method signature (DG);
042     * 30-Jul-2003 : Modified entity constructor (CZ);
043     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
044     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
045     * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
046     *               overriding easier (DG);
047     * 15-Jul-2004 : Switched getZ() and getZValue() methods (DG);
048     * 19-Jan-2005 : Now accesses only primitives from dataset (DG);
049     * 28-Feb-2005 : Modify renderer to use circles in legend (DG);
050     * 17-Mar-2005 : Fixed bug in bubble bounds calculation (DG);
051     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
052     * ------------- JFREECHART 1.0.0 ---------------------------------------------
053     * 13-Dec-2005 : Added support for item labels (bug 1373371) (DG);
054     * 20-Jan-2006 : Check flag for drawing item labels (DG);
055     * 
056     */
057    
058    package org.jfree.chart.renderer.xy;
059    
060    import java.awt.BasicStroke;
061    import java.awt.Color;
062    import java.awt.Graphics2D;
063    import java.awt.Paint;
064    import java.awt.Shape;
065    import java.awt.Stroke;
066    import java.awt.geom.Ellipse2D;
067    import java.awt.geom.Rectangle2D;
068    import java.io.Serializable;
069    
070    import org.jfree.chart.LegendItem;
071    import org.jfree.chart.axis.ValueAxis;
072    import org.jfree.chart.entity.EntityCollection;
073    import org.jfree.chart.entity.XYItemEntity;
074    import org.jfree.chart.labels.XYToolTipGenerator;
075    import org.jfree.chart.plot.CrosshairState;
076    import org.jfree.chart.plot.PlotOrientation;
077    import org.jfree.chart.plot.PlotRenderingInfo;
078    import org.jfree.chart.plot.XYPlot;
079    import org.jfree.data.xy.XYDataset;
080    import org.jfree.data.xy.XYZDataset;
081    import org.jfree.ui.RectangleEdge;
082    import org.jfree.util.PublicCloneable;
083    
084    /**
085     * A renderer that draws a circle at each data point with a diameter that is
086     * determined by the z-value in the dataset (the renderer requires the dataset 
087     * to be an instance of {@link XYZDataset}.
088     */
089    public class XYBubbleRenderer extends AbstractXYItemRenderer 
090                                  implements XYItemRenderer, 
091                                             Cloneable,
092                                             PublicCloneable,
093                                             Serializable {
094    
095        /** For serialization. */
096        public static final long serialVersionUID = -5221991598674249125L;
097        
098        /** A useful constant. */
099        public static final int SCALE_ON_BOTH_AXES = 0;
100    
101        /** A useful constant. */
102        public static final int SCALE_ON_DOMAIN_AXIS = 1;
103    
104        /** A useful constant. */
105        public static final int SCALE_ON_RANGE_AXIS = 2;
106    
107        /** Controls how the width and height of the bubble are scaled. */
108        private int scaleType;
109    
110        /**
111         * Constructs a new renderer.
112         */
113        public XYBubbleRenderer() {
114            this(SCALE_ON_BOTH_AXES); 
115        }
116    
117        /**
118         * Constructs a new renderer with the specified type of scaling. 
119         *
120         * @param scaleType  the type of scaling (must be one of: 
121         *        {@link #SCALE_ON_BOTH_AXES}, {@link #SCALE_ON_DOMAIN_AXIS}, 
122         *        {@link #SCALE_ON_RANGE_AXIS}).
123         */
124        public XYBubbleRenderer(int scaleType) {
125            super();
126            if (scaleType < 0 || scaleType > 2) {
127                throw new IllegalArgumentException("Invalid 'scaleType'.");
128            }
129            this.scaleType = scaleType;
130        }
131    
132        /**
133         * Returns the scale type that was set when the renderer was constructed.
134         *
135         * @return The scale type (one of: {@link #SCALE_ON_BOTH_AXES}, 
136         *         {@link #SCALE_ON_DOMAIN_AXIS}, {@link #SCALE_ON_RANGE_AXIS}).
137         */
138        public int getScaleType() {
139            return this.scaleType;
140        }
141    
142        /**
143         * Draws the visual representation of a single data item.
144         *
145         * @param g2  the graphics device.
146         * @param state  the renderer state.
147         * @param dataArea  the area within which the data is being drawn.
148         * @param info  collects information about the drawing.
149         * @param plot  the plot (can be used to obtain standard color 
150         *              information etc).
151         * @param domainAxis  the domain (horizontal) axis.
152         * @param rangeAxis  the range (vertical) axis.
153         * @param dataset  the dataset.
154         * @param series  the series index (zero-based).
155         * @param item  the item index (zero-based).
156         * @param crosshairState  crosshair information for the plot 
157         *                        (<code>null</code> permitted).
158         * @param pass  the pass index.
159         */
160        public void drawItem(Graphics2D g2, XYItemRendererState state,
161                Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
162                ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 
163                int series, int item, CrosshairState crosshairState, int pass) {
164    
165            PlotOrientation orientation = plot.getOrientation();
166            
167            // get the data point...
168            double x = dataset.getXValue(series, item);
169            double y = dataset.getYValue(series, item);
170            double z = Double.NaN;
171            if (dataset instanceof XYZDataset) {
172                XYZDataset xyzData = (XYZDataset) dataset;
173                z = xyzData.getZValue(series, item);
174            }
175            if (!Double.isNaN(z)) {
176                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
177                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
178                double transX = domainAxis.valueToJava2D(x, dataArea, 
179                        domainAxisLocation);
180                double transY = rangeAxis.valueToJava2D(y, dataArea, 
181                        rangeAxisLocation);
182    
183                double transDomain = 0.0;
184                double transRange = 0.0;
185                double zero;
186    
187                switch(getScaleType()) {
188                    case SCALE_ON_DOMAIN_AXIS:
189                        zero = domainAxis.valueToJava2D(0.0, dataArea, 
190                                domainAxisLocation);
191                        transDomain = domainAxis.valueToJava2D(z, dataArea, 
192                                domainAxisLocation) - zero;
193                        transRange = transDomain;
194                        break;
195                    case SCALE_ON_RANGE_AXIS:
196                        zero = rangeAxis.valueToJava2D(0.0, dataArea, 
197                                rangeAxisLocation);
198                        transRange = zero - rangeAxis.valueToJava2D(z, dataArea, 
199                                rangeAxisLocation);
200                        transDomain = transRange;
201                        break;
202                    default:
203                        double zero1 = domainAxis.valueToJava2D(0.0, dataArea, 
204                                domainAxisLocation);
205                        double zero2 = rangeAxis.valueToJava2D(0.0, dataArea, 
206                                rangeAxisLocation);
207                        transDomain = domainAxis.valueToJava2D(z, dataArea, 
208                                domainAxisLocation) - zero1;
209                        transRange = zero2 - rangeAxis.valueToJava2D(z, dataArea, 
210                                rangeAxisLocation);
211                }
212                transDomain = Math.abs(transDomain);
213                transRange = Math.abs(transRange);
214                Ellipse2D circle = null;
215                if (orientation == PlotOrientation.VERTICAL) {
216                    circle = new Ellipse2D.Double(transX - transDomain / 2.0, 
217                            transY - transRange / 2.0, transDomain, transRange);
218                }
219                else if (orientation == PlotOrientation.HORIZONTAL) {
220                    circle = new Ellipse2D.Double(transY - transRange / 2.0, 
221                            transX - transDomain / 2.0, transRange, transDomain);
222                }
223                g2.setPaint(getItemPaint(series, item));
224                g2.fill(circle);
225                g2.setStroke(new BasicStroke(1.0f));
226                g2.setPaint(Color.lightGray);
227                g2.draw(circle);
228    
229                if (isItemLabelVisible(series, item)) {
230                    if (orientation == PlotOrientation.VERTICAL) {
231                        drawItemLabel(g2, orientation, dataset, series, item, 
232                                transX, transY, false);
233                    }
234                    else if (orientation == PlotOrientation.HORIZONTAL) {
235                        drawItemLabel(g2, orientation, dataset, series, item, 
236                                transY, transX, false);                
237                    }
238                }
239                
240                // setup for collecting optional entity info...
241                EntityCollection entities = null;
242                if (info != null) {
243                    entities = info.getOwner().getEntityCollection();
244                }
245    
246                // add an entity for the item...
247                if (entities != null) {
248                    String tip = null;
249                    XYToolTipGenerator generator 
250                        = getToolTipGenerator(series, item);
251                    if (generator != null) {
252                        tip = generator.generateToolTip(dataset, series, item);
253                    }
254                    String url = null;
255                    if (getURLGenerator() != null) {
256                        url = getURLGenerator().generateURL(dataset, series, item);
257                    }
258                    XYItemEntity entity = new XYItemEntity(circle, dataset, series,
259                            item, tip, url);
260                    entities.add(entity);
261                }
262    
263                updateCrosshairValues(crosshairState, x, y, transX, transY, 
264                        orientation);
265            }
266    
267        }
268    
269        /**
270         * Returns a legend item for the specified series.  The default method
271         * is overridden so that the legend displays circles for all series.
272         *
273         * @param datasetIndex  the dataset index (zero-based).
274         * @param series  the series index (zero-based).
275         *
276         * @return A legend item for the series.
277         */
278        public LegendItem getLegendItem(int datasetIndex, int series) {
279            LegendItem result = null;
280            XYPlot xyplot = getPlot();
281            if (xyplot != null) {
282                XYDataset dataset = xyplot.getDataset(datasetIndex);
283                if (dataset != null) {
284                    if (getItemVisible(series, 0)) {
285                        String label = getLegendItemLabelGenerator().generateLabel(
286                            dataset, series);
287                        String description = label;
288                        String toolTipText = null;
289                        if (getLegendItemToolTipGenerator() != null) {
290                            toolTipText 
291                                = getLegendItemToolTipGenerator().generateLabel(
292                                    dataset, series);
293                        }
294                        String urlText = null;
295                        if (getLegendItemURLGenerator() != null) {
296                            urlText = getLegendItemURLGenerator().generateLabel(
297                                dataset, series);
298                        }
299                        Shape shape = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0);
300                        Paint paint = getSeriesPaint(series);
301                        Paint outlinePaint = getSeriesOutlinePaint(series);
302                        Stroke outlineStroke = getSeriesOutlineStroke(series);
303                        result = new LegendItem(label, description, 
304                                toolTipText, urlText, shape, paint, 
305                                outlineStroke, outlinePaint);
306                    }
307                }
308    
309            }
310            return result;
311        }
312        
313        /**
314         * Returns a clone of the renderer.
315         * 
316         * @return A clone.
317         * 
318         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
319         */
320        public Object clone() throws CloneNotSupportedException {
321            return super.clone();
322        }
323    
324    }