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