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