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}