001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * MultiplePiePlot.java 029 * -------------------- 030 * (C) Copyright 2004, 2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: MultiplePiePlot.java,v 1.12.2.4 2005/11/28 12:06:35 mungady Exp $ 036 * 037 * Changes (from 21-Jun-2001) 038 * -------------------------- 039 * 29-Jan-2004 : Version 1 (DG); 040 * 31-Mar-2004 : Added setPieIndex() call during drawing (DG); 041 * 20-Apr-2005 : Small change for update to LegendItem constructors (DG); 042 * 05-May-2005 : Updated draw() method parameters (DG); 043 * 16-Jun-2005 : Added get/setDataset() and equals() methods (DG); 044 * 045 */ 046 047 package org.jfree.chart.plot; 048 049 import java.awt.Font; 050 import java.awt.Graphics2D; 051 import java.awt.Paint; 052 import java.awt.Rectangle; 053 import java.awt.Stroke; 054 import java.awt.geom.Point2D; 055 import java.awt.geom.Rectangle2D; 056 import java.io.Serializable; 057 import java.util.Iterator; 058 import java.util.List; 059 060 import org.jfree.chart.ChartRenderingInfo; 061 import org.jfree.chart.JFreeChart; 062 import org.jfree.chart.LegendItem; 063 import org.jfree.chart.LegendItemCollection; 064 import org.jfree.chart.event.PlotChangeEvent; 065 import org.jfree.chart.title.TextTitle; 066 import org.jfree.data.category.CategoryDataset; 067 import org.jfree.data.category.CategoryToPieDataset; 068 import org.jfree.data.general.DatasetChangeEvent; 069 import org.jfree.data.general.DatasetUtilities; 070 import org.jfree.data.general.PieDataset; 071 import org.jfree.ui.RectangleEdge; 072 import org.jfree.ui.RectangleInsets; 073 import org.jfree.util.ObjectUtilities; 074 import org.jfree.util.TableOrder; 075 076 /** 077 * A plot that displays multiple pie plots using data from a 078 * {@link CategoryDataset}. 079 */ 080 public class MultiplePiePlot extends Plot implements Cloneable, Serializable { 081 082 /** For serialization. */ 083 private static final long serialVersionUID = -355377800470807389L; 084 085 /** The chart object that draws the individual pie charts. */ 086 private JFreeChart pieChart; 087 088 /** The dataset. */ 089 private CategoryDataset dataset; 090 091 /** The data extract order (by row or by column). */ 092 private TableOrder dataExtractOrder; 093 094 /** The pie section limit percentage. */ 095 private double limit = 0.0; 096 097 /** 098 * Creates a new plot with no data. 099 */ 100 public MultiplePiePlot() { 101 this(null); 102 } 103 104 /** 105 * Creates a new plot. 106 * 107 * @param dataset the dataset (<code>null</code> permitted). 108 */ 109 public MultiplePiePlot(CategoryDataset dataset) { 110 super(); 111 this.dataset = dataset; 112 PiePlot piePlot = new PiePlot(null); 113 this.pieChart = new JFreeChart(piePlot); 114 this.pieChart.removeLegend(); 115 this.dataExtractOrder = TableOrder.BY_COLUMN; 116 this.pieChart.setBackgroundPaint(null); 117 TextTitle seriesTitle = new TextTitle( 118 "Series Title", new Font("SansSerif", Font.BOLD, 12) 119 ); 120 seriesTitle.setPosition(RectangleEdge.BOTTOM); 121 this.pieChart.setTitle(seriesTitle); 122 } 123 124 /** 125 * Returns the dataset used by the plot. 126 * 127 * @return The dataset (possibly <code>null</code>). 128 */ 129 public CategoryDataset getDataset() { 130 return this.dataset; 131 } 132 133 /** 134 * Sets the dataset used by the plot and sends a {@link PlotChangeEvent} 135 * to all registered listeners. 136 * 137 * @param dataset the dataset (<code>null</code> permitted). 138 */ 139 public void setDataset(CategoryDataset dataset) { 140 // if there is an existing dataset, remove the plot from the list of 141 // change listeners... 142 if (this.dataset != null) { 143 this.dataset.removeChangeListener(this); 144 } 145 146 // set the new dataset, and register the chart as a change listener... 147 this.dataset = dataset; 148 if (dataset != null) { 149 setDatasetGroup(dataset.getGroup()); 150 dataset.addChangeListener(this); 151 } 152 153 // send a dataset change event to self to trigger plot change event 154 datasetChanged(new DatasetChangeEvent(this, dataset)); 155 } 156 157 /** 158 * Returns the pie chart that is used to draw the individual pie plots. 159 * 160 * @return The pie chart. 161 */ 162 public JFreeChart getPieChart() { 163 return this.pieChart; 164 } 165 166 /** 167 * Sets the chart that is used to draw the individual pie plots. 168 * 169 * @param pieChart the pie chart. 170 */ 171 public void setPieChart(JFreeChart pieChart) { 172 this.pieChart = pieChart; 173 notifyListeners(new PlotChangeEvent(this)); 174 } 175 176 /** 177 * Returns the data extract order (by row or by column). 178 * 179 * @return The data extract order (never <code>null</code>). 180 */ 181 public TableOrder getDataExtractOrder() { 182 return this.dataExtractOrder; 183 } 184 185 /** 186 * Sets the data extract order (by row or by column) and sends a 187 * {@link PlotChangeEvent} to all registered listeners. 188 * 189 * @param order the order (<code>null</code> not permitted). 190 */ 191 public void setDataExtractOrder(TableOrder order) { 192 if (order == null) { 193 throw new IllegalArgumentException("Null 'order' argument"); 194 } 195 this.dataExtractOrder = order; 196 notifyListeners(new PlotChangeEvent(this)); 197 } 198 199 /** 200 * Returns the limit (as a percentage) below which small pie sections are 201 * aggregated. 202 * 203 * @return The limit percentage. 204 */ 205 public double getLimit() { 206 return this.limit; 207 } 208 209 /** 210 * Sets the limit below which pie sections are aggregated. 211 * Set this to 0.0 if you don't want any aggregation to occur. 212 * 213 * @param limit the limit percent. 214 */ 215 public void setLimit(double limit) { 216 this.limit = limit; 217 notifyListeners(new PlotChangeEvent(this)); 218 } 219 220 /** 221 * Returns a short string describing the type of plot. 222 * 223 * @return The plot type. 224 */ 225 public String getPlotType() { 226 return "Multiple Pie Plot"; 227 // TODO: need to fetch this from localised resources 228 } 229 230 /** 231 * Draws the plot on a Java 2D graphics device (such as the screen or a 232 * printer). 233 * 234 * @param g2 the graphics device. 235 * @param area the area within which the plot should be drawn. 236 * @param anchor the anchor point (<code>null</code> permitted). 237 * @param parentState the state from the parent plot, if there is one. 238 * @param info collects info about the drawing. 239 */ 240 public void draw(Graphics2D g2, 241 Rectangle2D area, 242 Point2D anchor, 243 PlotState parentState, 244 PlotRenderingInfo info) { 245 246 247 // adjust the drawing area for the plot insets (if any)... 248 RectangleInsets insets = getInsets(); 249 insets.trim(area); 250 drawBackground(g2, area); 251 drawOutline(g2, area); 252 253 // check that there is some data to display... 254 if (DatasetUtilities.isEmptyOrNull(this.dataset)) { 255 drawNoDataMessage(g2, area); 256 return; 257 } 258 259 int pieCount = 0; 260 if (this.dataExtractOrder == TableOrder.BY_ROW) { 261 pieCount = this.dataset.getRowCount(); 262 } 263 else { 264 pieCount = this.dataset.getColumnCount(); 265 } 266 267 // the columns variable is always >= rows 268 int displayCols = (int) Math.ceil(Math.sqrt(pieCount)); 269 int displayRows 270 = (int) Math.ceil((double) pieCount / (double) displayCols); 271 272 // swap rows and columns to match plotArea shape 273 if (displayCols > displayRows && area.getWidth() < area.getHeight()) { 274 int temp = displayCols; 275 displayCols = displayRows; 276 displayRows = temp; 277 } 278 279 // int fontHeight 280 // = g2.getFontMetrics(this.plotLabelFont).getHeight() * 2; 281 int x = (int) area.getX(); 282 int y = (int) area.getY(); 283 int width = ((int) area.getWidth()) / displayCols; 284 int height = ((int) area.getHeight()) / displayRows; 285 int row = 0; 286 int column = 0; 287 int diff = (displayRows * displayCols) - pieCount; 288 int xoffset = 0; 289 Rectangle rect = new Rectangle(); 290 291 for (int pieIndex = 0; pieIndex < pieCount; pieIndex++) { 292 rect.setBounds( 293 x + xoffset + (width * column), y + (height * row), 294 width, height 295 ); 296 297 String title = null; 298 if (this.dataExtractOrder == TableOrder.BY_ROW) { 299 title = this.dataset.getRowKey(pieIndex).toString(); 300 } 301 else { 302 title = this.dataset.getColumnKey(pieIndex).toString(); 303 } 304 this.pieChart.setTitle(title); 305 306 PieDataset piedataset = null; 307 PieDataset dd = new CategoryToPieDataset( 308 this.dataset, this.dataExtractOrder, pieIndex 309 ); 310 if (this.limit > 0.0) { 311 piedataset = DatasetUtilities.createConsolidatedPieDataset( 312 dd, "Other", this.limit 313 ); 314 } 315 else { 316 piedataset = dd; 317 } 318 PiePlot piePlot = (PiePlot) this.pieChart.getPlot(); 319 piePlot.setDataset(piedataset); 320 piePlot.setPieIndex(pieIndex); 321 ChartRenderingInfo subinfo = null; 322 if (info != null) { 323 subinfo = new ChartRenderingInfo(); 324 } 325 this.pieChart.draw(g2, rect, subinfo); 326 if (info != null) { 327 info.getOwner().getEntityCollection().addAll( 328 subinfo.getEntityCollection() 329 ); 330 info.addSubplotInfo(subinfo.getPlotInfo()); 331 } 332 333 ++column; 334 if (column == displayCols) { 335 column = 0; 336 ++row; 337 338 if (row == displayRows - 1 && diff != 0) { 339 xoffset = (diff * width) / 2; 340 } 341 } 342 } 343 344 } 345 346 /** 347 * Returns a collection of legend items for the pie chart. 348 * 349 * @return The legend items. 350 */ 351 public LegendItemCollection getLegendItems() { 352 353 LegendItemCollection result = new LegendItemCollection(); 354 355 if (this.dataset != null) { 356 List keys = null; 357 358 if (this.dataExtractOrder == TableOrder.BY_ROW) { 359 keys = this.dataset.getColumnKeys(); 360 } 361 else if (this.dataExtractOrder == TableOrder.BY_COLUMN) { 362 keys = this.dataset.getRowKeys(); 363 } 364 365 if (keys != null) { 366 int section = 0; 367 Iterator iterator = keys.iterator(); 368 while (iterator.hasNext()) { 369 String label = iterator.next().toString(); 370 String description = label; 371 PiePlot plot = (PiePlot) this.pieChart.getPlot(); 372 Paint paint = plot.getSectionPaint(section); 373 Paint outlinePaint = plot.getSectionOutlinePaint(section); 374 Stroke outlineStroke 375 = plot.getSectionOutlineStroke(section); 376 LegendItem item = new LegendItem(label, description, 377 null, null, Plot.DEFAULT_LEGEND_ITEM_CIRCLE, 378 paint, outlineStroke, outlinePaint); 379 380 result.add(item); 381 section++; 382 } 383 } 384 } 385 return result; 386 } 387 388 /** 389 * Tests this plot for equality with an arbitrary object. Note that the 390 * plot's dataset is not considered in the equality test. 391 * 392 * @param obj the object (<code>null</code> permitted). 393 */ 394 public boolean equals(Object obj) { 395 if (obj == this) { 396 return true; 397 } 398 if (!(obj instanceof MultiplePiePlot)) { 399 return false; 400 } 401 MultiplePiePlot that = (MultiplePiePlot) obj; 402 if (this.dataExtractOrder != that.dataExtractOrder) { 403 return false; 404 } 405 if (this.limit != that.limit) { 406 return false; 407 } 408 if (!ObjectUtilities.equal(this.pieChart, that.pieChart)) { 409 return false; 410 } 411 if (!super.equals(obj)) { 412 return false; 413 } 414 return true; 415 } 416 417 }