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