001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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 * CategoryToPieDataset.java 029 * ------------------------- 030 * (C) Copyright 2003-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Christian W. Zuckschwerdt; 034 * 035 */ 036 037package org.jfree.data.category; 038 039import java.util.Collections; 040import java.util.List; 041import org.jfree.chart.internal.Args; 042import org.jfree.chart.api.TableOrder; 043 044import org.jfree.data.general.AbstractDataset; 045import org.jfree.data.general.DatasetChangeEvent; 046import org.jfree.data.general.DatasetChangeListener; 047import org.jfree.data.general.PieDataset; 048 049/** 050 * A {@link PieDataset} implementation that obtains its data from one row or 051 * column of a {@link CategoryDataset}. 052 */ 053public class CategoryToPieDataset extends AbstractDataset 054 implements PieDataset, DatasetChangeListener { 055 056 /** For serialization. */ 057 static final long serialVersionUID = 5516396319762189617L; 058 059 /** The source. */ 060 private CategoryDataset source; 061 062 /** The extract type. */ 063 private TableOrder extract; 064 065 /** The row or column index. */ 066 private int index; 067 068 /** 069 * An adaptor class that converts any {@link CategoryDataset} into a 070 * {@link PieDataset}, by taking the values from a single row or column. 071 * <p> 072 * If {@code source} is {@code null}, the created dataset will 073 * be empty. 074 * 075 * @param source the source dataset ({@code null} permitted). 076 * @param extract extract data from rows or columns? ({@code null} 077 * not permitted). 078 * @param index the row or column index. 079 */ 080 public CategoryToPieDataset(CategoryDataset source, TableOrder extract, 081 int index) { 082 Args.nullNotPermitted(extract, "extract"); 083 this.source = source; 084 if (this.source != null) { 085 this.source.addChangeListener(this); 086 } 087 this.extract = extract; 088 this.index = index; 089 } 090 091 /** 092 * Returns the underlying dataset. 093 * 094 * @return The underlying dataset (possibly {@code null}). 095 * 096 * @since 1.0.2 097 */ 098 public CategoryDataset getUnderlyingDataset() { 099 return this.source; 100 } 101 102 /** 103 * Returns the extract type, which determines whether data is read from 104 * one row or one column of the underlying dataset. 105 * 106 * @return The extract type. 107 * 108 * @since 1.0.2 109 */ 110 public TableOrder getExtractType() { 111 return this.extract; 112 } 113 114 /** 115 * Returns the index of the row or column from which to extract the data. 116 * 117 * @return The extract index. 118 * 119 * @since 1.0.2 120 */ 121 public int getExtractIndex() { 122 return this.index; 123 } 124 125 /** 126 * Returns the number of items (values) in the collection. If the 127 * underlying dataset is {@code null}, this method returns zero. 128 * 129 * @return The item count. 130 */ 131 @Override 132 public int getItemCount() { 133 int result = 0; 134 if (this.source != null) { 135 if (this.extract == TableOrder.BY_ROW) { 136 result = this.source.getColumnCount(); 137 } 138 else if (this.extract == TableOrder.BY_COLUMN) { 139 result = this.source.getRowCount(); 140 } 141 } 142 return result; 143 } 144 145 /** 146 * Returns a value from the dataset. 147 * 148 * @param item the item index (zero-based). 149 * 150 * @return The value (possibly {@code null}). 151 * 152 * @throws IndexOutOfBoundsException if {@code item} is not in the 153 * range {@code 0} to {@code getItemCount() -1}. 154 */ 155 @Override 156 public Number getValue(int item) { 157 Number result = null; 158 if (item < 0 || item >= getItemCount()) { 159 // this will include the case where the underlying dataset is null 160 throw new IndexOutOfBoundsException( 161 "The 'item' index is out of bounds."); 162 } 163 if (this.extract == TableOrder.BY_ROW) { 164 result = this.source.getValue(this.index, item); 165 } 166 else if (this.extract == TableOrder.BY_COLUMN) { 167 result = this.source.getValue(item, this.index); 168 } 169 return result; 170 } 171 172 /** 173 * Returns the key at the specified index. 174 * 175 * @param index the item index (in the range {@code 0} to 176 * {@code getItemCount() -1}). 177 * 178 * @return The key. 179 * 180 * @throws IndexOutOfBoundsException if {@code index} is not in the 181 * specified range. 182 */ 183 @Override 184 public Comparable getKey(int index) { 185 Comparable result = null; 186 if (index < 0 || index >= getItemCount()) { 187 // this includes the case where the underlying dataset is null 188 throw new IndexOutOfBoundsException("Invalid 'index': " + index); 189 } 190 if (this.extract == TableOrder.BY_ROW) { 191 result = this.source.getColumnKey(index); 192 } 193 else if (this.extract == TableOrder.BY_COLUMN) { 194 result = this.source.getRowKey(index); 195 } 196 return result; 197 } 198 199 /** 200 * Returns the index for a given key, or {@code -1} if there is no 201 * such key. 202 * 203 * @param key the key. 204 * 205 * @return The index for the key, or {@code -1}. 206 */ 207 @Override 208 public int getIndex(Comparable key) { 209 int result = -1; 210 if (this.source != null) { 211 if (this.extract == TableOrder.BY_ROW) { 212 result = this.source.getColumnIndex(key); 213 } 214 else if (this.extract == TableOrder.BY_COLUMN) { 215 result = this.source.getRowIndex(key); 216 } 217 } 218 return result; 219 } 220 221 /** 222 * Returns the keys for the dataset. 223 * <p> 224 * If the underlying dataset is {@code null}, this method returns an 225 * empty list. 226 * 227 * @return The keys. 228 */ 229 @Override 230 public List getKeys() { 231 List result = Collections.EMPTY_LIST; 232 if (this.source != null) { 233 if (this.extract == TableOrder.BY_ROW) { 234 result = this.source.getColumnKeys(); 235 } 236 else if (this.extract == TableOrder.BY_COLUMN) { 237 result = this.source.getRowKeys(); 238 } 239 } 240 return result; 241 } 242 243 /** 244 * Returns the value for a given key. If the key is not recognised, the 245 * method should return {@code null} (but note that {@code null} 246 * can be associated with a valid key also). 247 * 248 * @param key the key. 249 * 250 * @return The value (possibly {@code null}). 251 */ 252 @Override 253 public Number getValue(Comparable key) { 254 Number result = null; 255 int keyIndex = getIndex(key); 256 if (keyIndex != -1) { 257 if (this.extract == TableOrder.BY_ROW) { 258 result = this.source.getValue(this.index, keyIndex); 259 } 260 else if (this.extract == TableOrder.BY_COLUMN) { 261 result = this.source.getValue(keyIndex, this.index); 262 } 263 } 264 return result; 265 } 266 267 /** 268 * Sends a {@link DatasetChangeEvent} to all registered listeners, with 269 * this (not the underlying) dataset as the source. 270 * 271 * @param event the event (ignored, a new event with this dataset as the 272 * source is sent to the listeners). 273 */ 274 @Override 275 public void datasetChanged(DatasetChangeEvent event) { 276 fireDatasetChanged(); 277 } 278 279 /** 280 * Tests this dataset for equality with an arbitrary object, returning 281 * {@code true} if {@code obj} is a dataset containing the same 282 * keys and values in the same order as this dataset. 283 * 284 * @param obj the object to test ({@code null} permitted). 285 * 286 * @return A boolean. 287 */ 288 @Override 289 public boolean equals(Object obj) { 290 if (obj == this) { 291 return true; 292 } 293 if (!(obj instanceof PieDataset)) { 294 return false; 295 } 296 PieDataset that = (PieDataset) obj; 297 int count = getItemCount(); 298 if (that.getItemCount() != count) { 299 return false; 300 } 301 for (int i = 0; i < count; i++) { 302 Comparable k1 = getKey(i); 303 Comparable k2 = that.getKey(i); 304 if (!k1.equals(k2)) { 305 return false; 306 } 307 308 Number v1 = getValue(i); 309 Number v2 = that.getValue(i); 310 if (v1 == null) { 311 if (v2 != null) { 312 return false; 313 } 314 } 315 else { 316 if (!v1.equals(v2)) { 317 return false; 318 } 319 } 320 } 321 return true; 322 } 323 324}