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}