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 * XYBubbleRenderer.java
029 * ---------------------
030 * (C) Copyright 2003-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Christian W. Zuckschwerdt;
034 *
035 */
036
037package org.jfree.chart.renderer.xy;
038
039import java.awt.Graphics2D;
040import java.awt.Paint;
041import java.awt.Shape;
042import java.awt.Stroke;
043import java.awt.geom.Ellipse2D;
044import java.awt.geom.Rectangle2D;
045
046import org.jfree.chart.LegendItem;
047import org.jfree.chart.axis.ValueAxis;
048import org.jfree.chart.entity.EntityCollection;
049import org.jfree.chart.plot.CrosshairState;
050import org.jfree.chart.plot.PlotOrientation;
051import org.jfree.chart.plot.PlotRenderingInfo;
052import org.jfree.chart.plot.XYPlot;
053import org.jfree.chart.ui.RectangleEdge;
054import org.jfree.chart.util.PublicCloneable;
055import org.jfree.data.xy.XYDataset;
056import org.jfree.data.xy.XYZDataset;
057
058/**
059 * A renderer that draws a circle at each data point with a diameter that is
060 * determined by the z-value in the dataset (the renderer requires the dataset
061 * to be an instance of {@link XYZDataset}.  The example shown here
062 * is generated by the {@code XYBubbleChartDemo1.java} program
063 * included in the JFreeChart demo collection:
064 * <br><br>
065 * <img src="doc-files/XYBubbleRendererSample.png"
066 * alt="XYBubbleRendererSample.png">
067 */
068public class XYBubbleRenderer extends AbstractXYItemRenderer
069        implements XYItemRenderer, PublicCloneable {
070
071    /** For serialization. */
072    public static final long serialVersionUID = -5221991598674249125L;
073
074    /**
075     * A constant to specify that the bubbles drawn by this renderer should be
076     * scaled on both axes (see {@link #XYBubbleRenderer(int)}).
077     */
078    public static final int SCALE_ON_BOTH_AXES = 0;
079
080    /**
081     * A constant to specify that the bubbles drawn by this renderer should be
082     * scaled on the domain axis (see {@link #XYBubbleRenderer(int)}).
083     */
084    public static final int SCALE_ON_DOMAIN_AXIS = 1;
085
086    /**
087     * A constant to specify that the bubbles drawn by this renderer should be
088     * scaled on the range axis (see {@link #XYBubbleRenderer(int)}).
089     */
090    public static final int SCALE_ON_RANGE_AXIS = 2;
091
092    /** Controls how the width and height of the bubble are scaled. */
093    private int scaleType;
094
095    /**
096     * Constructs a new renderer.
097     */
098    public XYBubbleRenderer() {
099        this(SCALE_ON_BOTH_AXES);
100    }
101
102    /**
103     * Constructs a new renderer with the specified type of scaling.
104     *
105     * @param scaleType  the type of scaling (must be one of:
106     *        {@link #SCALE_ON_BOTH_AXES}, {@link #SCALE_ON_DOMAIN_AXIS},
107     *        {@link #SCALE_ON_RANGE_AXIS}).
108     */
109    public XYBubbleRenderer(int scaleType) {
110        super();
111        if (scaleType < 0 || scaleType > 2) {
112            throw new IllegalArgumentException("Invalid 'scaleType'.");
113        }
114        this.scaleType = scaleType;
115        setDefaultLegendShape(new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0));
116    }
117
118    /**
119     * Returns the scale type that was set when the renderer was constructed.
120     *
121     * @return The scale type (one of: {@link #SCALE_ON_BOTH_AXES},
122     *         {@link #SCALE_ON_DOMAIN_AXIS}, {@link #SCALE_ON_RANGE_AXIS}).
123     */
124    public int getScaleType() {
125        return this.scaleType;
126    }
127
128    /**
129     * Draws the visual representation of a single data item.
130     *
131     * @param g2  the graphics device.
132     * @param state  the renderer state.
133     * @param dataArea  the area within which the data is being drawn.
134     * @param info  collects information about the drawing.
135     * @param plot  the plot (can be used to obtain standard color
136     *              information etc).
137     * @param domainAxis  the domain (horizontal) axis.
138     * @param rangeAxis  the range (vertical) axis.
139     * @param dataset  the dataset (an {@link XYZDataset} is expected).
140     * @param series  the series index (zero-based).
141     * @param item  the item index (zero-based).
142     * @param crosshairState  crosshair information for the plot
143     *                        ({@code null} permitted).
144     * @param pass  the pass index.
145     */
146    @Override
147    public void drawItem(Graphics2D g2, XYItemRendererState state,
148            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
149            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
150            int series, int item, CrosshairState crosshairState, int pass) {
151
152        // return straight away if the item is not visible
153        if (!getItemVisible(series, item)) {
154            return;
155        }
156
157        PlotOrientation orientation = plot.getOrientation();
158
159        // get the data point...
160        double x = dataset.getXValue(series, item);
161        double y = dataset.getYValue(series, item);
162        double z = Double.NaN;
163        if (dataset instanceof XYZDataset) {
164            XYZDataset xyzData = (XYZDataset) dataset;
165            z = xyzData.getZValue(series, item);
166        }
167        if (!Double.isNaN(z)) {
168            RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
169            RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
170            double transX = domainAxis.valueToJava2D(x, dataArea,
171                    domainAxisLocation);
172            double transY = rangeAxis.valueToJava2D(y, dataArea,
173                    rangeAxisLocation);
174
175            double transDomain;
176            double transRange;
177            double zero;
178
179            switch(getScaleType()) {
180                case SCALE_ON_DOMAIN_AXIS:
181                    zero = domainAxis.valueToJava2D(0.0, dataArea,
182                            domainAxisLocation);
183                    transDomain = domainAxis.valueToJava2D(z, dataArea,
184                            domainAxisLocation) - zero;
185                    transRange = transDomain;
186                    break;
187                case SCALE_ON_RANGE_AXIS:
188                    zero = rangeAxis.valueToJava2D(0.0, dataArea,
189                            rangeAxisLocation);
190                    transRange = zero - rangeAxis.valueToJava2D(z, dataArea,
191                            rangeAxisLocation);
192                    transDomain = transRange;
193                    break;
194                default:
195                    double zero1 = domainAxis.valueToJava2D(0.0, dataArea,
196                            domainAxisLocation);
197                    double zero2 = rangeAxis.valueToJava2D(0.0, dataArea,
198                            rangeAxisLocation);
199                    transDomain = domainAxis.valueToJava2D(z, dataArea,
200                            domainAxisLocation) - zero1;
201                    transRange = zero2 - rangeAxis.valueToJava2D(z, dataArea,
202                            rangeAxisLocation);
203            }
204            transDomain = Math.abs(transDomain);
205            transRange = Math.abs(transRange);
206            Ellipse2D circle = null;
207            if (orientation == PlotOrientation.VERTICAL) {
208                circle = new Ellipse2D.Double(transX - transDomain / 2.0,
209                        transY - transRange / 2.0, transDomain, transRange);
210            }
211            else if (orientation == PlotOrientation.HORIZONTAL) {
212                circle = new Ellipse2D.Double(transY - transRange / 2.0,
213                        transX - transDomain / 2.0, transRange, transDomain);
214            } else {
215                throw new IllegalStateException();
216            }
217            g2.setPaint(getItemPaint(series, item));
218            g2.fill(circle);
219            g2.setStroke(getItemOutlineStroke(series, item));
220            g2.setPaint(getItemOutlinePaint(series, item));
221            g2.draw(circle);
222
223            if (isItemLabelVisible(series, item)) {
224                if (orientation == PlotOrientation.VERTICAL) {
225                    drawItemLabel(g2, orientation, dataset, series, item,
226                            transX, transY, false);
227                }
228                else if (orientation == PlotOrientation.HORIZONTAL) {
229                    drawItemLabel(g2, orientation, dataset, series, item,
230                            transY, transX, false);
231                }
232            }
233
234            // add an entity if this info is being collected
235            if (info != null) {
236                EntityCollection entities 
237                        = info.getOwner().getEntityCollection();
238                if (entities != null && circle.intersects(dataArea)) {
239                    addEntity(entities, circle, dataset, series, item,
240                            circle.getCenterX(), circle.getCenterY());
241                }
242            }
243
244            int datasetIndex = plot.indexOf(dataset);
245            updateCrosshairValues(crosshairState, x, y, datasetIndex,
246                    transX, transY, orientation);
247        }
248
249    }
250
251    /**
252     * Returns a legend item for the specified series.  The default method
253     * is overridden so that the legend displays circles for all series.
254     *
255     * @param datasetIndex  the dataset index (zero-based).
256     * @param series  the series index (zero-based).
257     *
258     * @return A legend item for the series.
259     */
260    @Override
261    public LegendItem getLegendItem(int datasetIndex, int series) {
262        LegendItem result = null;
263        XYPlot plot = getPlot();
264        if (plot == null) {
265            return null;
266        }
267
268        XYDataset dataset = plot.getDataset(datasetIndex);
269        if (dataset != null) {
270            if (getItemVisible(series, 0)) {
271                String label = getLegendItemLabelGenerator().generateLabel(
272                        dataset, series);
273                String description = label;
274                String toolTipText = null;
275                if (getLegendItemToolTipGenerator() != null) {
276                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
277                            dataset, series);
278                }
279                String urlText = null;
280                if (getLegendItemURLGenerator() != null) {
281                    urlText = getLegendItemURLGenerator().generateLabel(
282                            dataset, series);
283                }
284                Shape shape = lookupLegendShape(series);
285                Paint paint = lookupSeriesPaint(series);
286                Paint outlinePaint = lookupSeriesOutlinePaint(series);
287                Stroke outlineStroke = lookupSeriesOutlineStroke(series);
288                result = new LegendItem(label, description, toolTipText,
289                        urlText, shape, paint, outlineStroke, outlinePaint);
290                result.setLabelFont(lookupLegendTextFont(series));
291                Paint labelPaint = lookupLegendTextPaint(series);
292                if (labelPaint != null) {
293                    result.setLabelPaint(labelPaint);
294                }
295                result.setDataset(dataset);
296                result.setDatasetIndex(datasetIndex);
297                result.setSeriesKey(dataset.getSeriesKey(series));
298                result.setSeriesIndex(series);
299            }
300        }
301        return result;
302    }
303
304    /**
305     * Tests this renderer for equality with an arbitrary object.
306     *
307     * @param obj  the object ({@code null} permitted).
308     *
309     * @return A boolean.
310     */
311    @Override
312    public boolean equals(Object obj) {
313        if (obj == this) {
314            return true;
315        }
316        if (!(obj instanceof XYBubbleRenderer)) {
317            return false;
318        }
319        XYBubbleRenderer that = (XYBubbleRenderer) obj;
320        if (this.scaleType != that.scaleType) {
321            return false;
322        }
323        return super.equals(obj);
324    }
325
326    /**
327     * Returns a clone of the renderer.
328     *
329     * @return A clone.
330     *
331     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
332     */
333    @Override
334    public Object clone() throws CloneNotSupportedException {
335        return super.clone();
336    }
337
338}