001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, 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: * DefaultIntervalCategoryDataset.java
029: * -----------------------------------
030: * (C) Copyright 2002-2007, by Jeremy Bowman and Contributors.
031: *
032: * Original Author: Jeremy Bowman;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: DefaultIntervalCategoryDataset.java,v 1.9.2.5 2007/03/09 15:50:23 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG);
040: * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG);
041: * ------------- JFREECHART 1.0.x ---------------------------------------------
042: * 08-Mar-2007 : Added equals() and clone() overrides (DG);
043: *
044: */
045:
046: package org.jfree.data.category;
047:
048: import java.util.ArrayList;
049: import java.util.Arrays;
050: import java.util.Collections;
051: import java.util.List;
052: import java.util.ResourceBundle;
053:
054: import org.jfree.data.DataUtilities;
055: import org.jfree.data.UnknownKeyException;
056: import org.jfree.data.general.AbstractSeriesDataset;
057:
058: /**
059: * A convenience class that provides a default implementation of the
060: * {@link IntervalCategoryDataset} interface.
061: * <p>
062: * The standard constructor accepts data in a two dimensional array where the
063: * first dimension is the series, and the second dimension is the category.
064: */
065: public class DefaultIntervalCategoryDataset extends
066: AbstractSeriesDataset implements IntervalCategoryDataset {
067:
068: /** The series keys. */
069: private Comparable[] seriesKeys;
070:
071: /** The category keys. */
072: private Comparable[] categoryKeys;
073:
074: /** Storage for the start value data. */
075: private Number[][] startData;
076:
077: /** Storage for the end value data. */
078: private Number[][] endData;
079:
080: /**
081: * Creates a new dataset.
082: *
083: * @param starts the starting values for the intervals.
084: * @param ends the ending values for the intervals.
085: */
086: public DefaultIntervalCategoryDataset(double[][] starts,
087: double[][] ends) {
088: this (DataUtilities.createNumberArray2D(starts), DataUtilities
089: .createNumberArray2D(ends));
090: }
091:
092: /**
093: * Constructs a dataset and populates it with data from the array.
094: * <p>
095: * The arrays are indexed as data[series][category]. Series and category
096: * names are automatically generated - you can change them using the
097: * {@link #setSeriesKeys(Comparable[])} and
098: * {@link #setCategoryKeys(Comparable[])} methods.
099: *
100: * @param starts the start values data.
101: * @param ends the end values data.
102: */
103: public DefaultIntervalCategoryDataset(Number[][] starts,
104: Number[][] ends) {
105: this (null, null, starts, ends);
106: }
107:
108: /**
109: * Constructs a DefaultIntervalCategoryDataset, populates it with data
110: * from the arrays, and uses the supplied names for the series.
111: * <p>
112: * Category names are generated automatically ("Category 1", "Category 2",
113: * etc).
114: *
115: * @param seriesNames the series names.
116: * @param starts the start values data, indexed as data[series][category].
117: * @param ends the end values data, indexed as data[series][category].
118: */
119: public DefaultIntervalCategoryDataset(String[] seriesNames,
120: Number[][] starts, Number[][] ends) {
121:
122: this (seriesNames, null, starts, ends);
123:
124: }
125:
126: /**
127: * Constructs a DefaultIntervalCategoryDataset, populates it with data
128: * from the arrays, and uses the supplied names for the series and the
129: * supplied objects for the categories.
130: *
131: * @param seriesKeys the series keys.
132: * @param categoryKeys the categories.
133: * @param starts the start values data, indexed as data[series][category].
134: * @param ends the end values data, indexed as data[series][category].
135: */
136: public DefaultIntervalCategoryDataset(Comparable[] seriesKeys,
137: Comparable[] categoryKeys, Number[][] starts,
138: Number[][] ends) {
139:
140: this .startData = starts;
141: this .endData = ends;
142:
143: if (starts != null && ends != null) {
144:
145: String baseName = "org.jfree.data.resources.DataPackageResources";
146: ResourceBundle resources = ResourceBundle
147: .getBundle(baseName);
148:
149: int seriesCount = starts.length;
150: if (seriesCount != ends.length) {
151: String errMsg = "DefaultIntervalCategoryDataset: the number "
152: + "of series in the start value dataset does "
153: + "not match the number of series in the end "
154: + "value dataset.";
155: throw new IllegalArgumentException(errMsg);
156: }
157: if (seriesCount > 0) {
158:
159: // set up the series names...
160: if (seriesKeys != null) {
161:
162: if (seriesKeys.length != seriesCount) {
163: throw new IllegalArgumentException(
164: "The number of series keys does not "
165: + "match the number of series in the data.");
166: }
167:
168: this .seriesKeys = seriesKeys;
169: } else {
170: String prefix = resources
171: .getString("series.default-prefix")
172: + " ";
173: this .seriesKeys = generateKeys(seriesCount, prefix);
174: }
175:
176: // set up the category names...
177: int categoryCount = starts[0].length;
178: if (categoryCount != ends[0].length) {
179: String errMsg = "DefaultIntervalCategoryDataset: the "
180: + "number of categories in the start value "
181: + "dataset does not match the number of "
182: + "categories in the end value dataset.";
183: throw new IllegalArgumentException(errMsg);
184: }
185: if (categoryKeys != null) {
186: if (categoryKeys.length != categoryCount) {
187: throw new IllegalArgumentException(
188: "The number of category keys does not match "
189: + "the number of categories in the data.");
190: }
191: this .categoryKeys = categoryKeys;
192: } else {
193: String prefix = resources
194: .getString("categories.default-prefix")
195: + " ";
196: this .categoryKeys = generateKeys(categoryCount,
197: prefix);
198: }
199:
200: } else {
201: this .seriesKeys = null;
202: this .categoryKeys = null;
203: }
204: }
205:
206: }
207:
208: /**
209: * Returns the number of series in the dataset (possibly zero).
210: *
211: * @return The number of series in the dataset.
212: *
213: * @see #getRowCount()
214: * @see #getCategoryCount()
215: */
216: public int getSeriesCount() {
217: int result = 0;
218: if (this .startData != null) {
219: result = this .startData.length;
220: }
221: return result;
222: }
223:
224: /**
225: * Returns a series index.
226: *
227: * @param seriesKey the series key.
228: *
229: * @return The series index.
230: *
231: * @see #getRowIndex(Comparable)
232: * @see #getSeriesKey(int)
233: */
234: public int getSeriesIndex(Comparable seriesKey) {
235: int result = -1;
236: for (int i = 0; i < this .seriesKeys.length; i++) {
237: if (seriesKey.equals(this .seriesKeys[i])) {
238: result = i;
239: break;
240: }
241: }
242: return result;
243: }
244:
245: /**
246: * Returns the name of the specified series.
247: *
248: * @param series the index of the required series (zero-based).
249: *
250: * @return The name of the specified series.
251: *
252: * @see #getSeriesIndex(Comparable)
253: */
254: public Comparable getSeriesKey(int series) {
255: if ((series >= getSeriesCount()) || (series < 0)) {
256: throw new IllegalArgumentException("No such series : "
257: + series);
258: }
259: return this .seriesKeys[series];
260: }
261:
262: /**
263: * Sets the names of the series in the dataset.
264: *
265: * @param seriesKeys the new keys (<code>null</code> not permitted, the
266: * length of the array must match the number of series in the
267: * dataset).
268: *
269: * @see #setCategoryKeys(Comparable[])
270: */
271: public void setSeriesKeys(Comparable[] seriesKeys) {
272: if (seriesKeys == null) {
273: throw new IllegalArgumentException(
274: "Null 'seriesKeys' argument.");
275: }
276: if (seriesKeys.length != getSeriesCount()) {
277: throw new IllegalArgumentException(
278: "The number of series keys does not match the data.");
279: }
280: this .seriesKeys = seriesKeys;
281: fireDatasetChanged();
282: }
283:
284: /**
285: * Returns the number of categories in the dataset.
286: *
287: * @return The number of categories in the dataset.
288: *
289: * @see #getColumnCount()
290: */
291: public int getCategoryCount() {
292: int result = 0;
293: if (this .startData != null) {
294: if (getSeriesCount() > 0) {
295: result = this .startData[0].length;
296: }
297: }
298: return result;
299: }
300:
301: /**
302: * Returns a list of the categories in the dataset. This method supports
303: * the {@link CategoryDataset} interface.
304: *
305: * @return A list of the categories in the dataset.
306: *
307: * @see #getRowKeys()
308: */
309: public List getColumnKeys() {
310: // the CategoryDataset interface expects a list of categories, but
311: // we've stored them in an array...
312: if (this .categoryKeys == null) {
313: return new ArrayList();
314: } else {
315: return Collections.unmodifiableList(Arrays
316: .asList(this .categoryKeys));
317: }
318: }
319:
320: /**
321: * Sets the categories for the dataset.
322: *
323: * @param categoryKeys an array of objects representing the categories in
324: * the dataset.
325: *
326: * @see #getRowKeys()
327: * @see #setSeriesKeys(Comparable[])
328: */
329: public void setCategoryKeys(Comparable[] categoryKeys) {
330: if (categoryKeys == null) {
331: throw new IllegalArgumentException(
332: "Null 'categoryKeys' argument.");
333: }
334: if (categoryKeys.length != this .startData[0].length) {
335: throw new IllegalArgumentException(
336: "The number of categories does not match the data.");
337: }
338: for (int i = 0; i < categoryKeys.length; i++) {
339: if (categoryKeys[i] == null) {
340: throw new IllegalArgumentException(
341: "DefaultIntervalCategoryDataset.setCategoryKeys(): "
342: + "null category not permitted.");
343: }
344: }
345: this .categoryKeys = categoryKeys;
346: fireDatasetChanged();
347: }
348:
349: /**
350: * Returns the data value for one category in a series.
351: * <P>
352: * This method is part of the CategoryDataset interface. Not particularly
353: * meaningful for this class...returns the end value.
354: *
355: * @param series The required series (zero based index).
356: * @param category The required category.
357: *
358: * @return The data value for one category in a series (null possible).
359: *
360: * @see #getEndValue(Comparable, Comparable)
361: */
362: public Number getValue(Comparable series, Comparable category) {
363: int seriesIndex = getSeriesIndex(series);
364: if (seriesIndex < 0) {
365: throw new UnknownKeyException("Unknown 'series' key.");
366: }
367: int itemIndex = getColumnIndex(category);
368: if (itemIndex < 0) {
369: throw new UnknownKeyException("Unknown 'category' key.");
370: }
371: return getValue(seriesIndex, itemIndex);
372: }
373:
374: /**
375: * Returns the data value for one category in a series.
376: * <P>
377: * This method is part of the CategoryDataset interface. Not particularly
378: * meaningful for this class...returns the end value.
379: *
380: * @param series the required series (zero based index).
381: * @param category the required category.
382: *
383: * @return The data value for one category in a series (null possible).
384: *
385: * @see #getEndValue(int, int)
386: */
387: public Number getValue(int series, int category) {
388: return getEndValue(series, category);
389: }
390:
391: /**
392: * Returns the start data value for one category in a series.
393: *
394: * @param series the required series.
395: * @param category the required category.
396: *
397: * @return The start data value for one category in a series
398: * (possibly <code>null</code>).
399: *
400: * @see #getStartValue(int, int)
401: */
402: public Number getStartValue(Comparable series, Comparable category) {
403: int seriesIndex = getSeriesIndex(series);
404: if (seriesIndex < 0) {
405: throw new UnknownKeyException("Unknown 'series' key.");
406: }
407: int itemIndex = getColumnIndex(category);
408: if (itemIndex < 0) {
409: throw new UnknownKeyException("Unknown 'category' key.");
410: }
411: return getStartValue(seriesIndex, itemIndex);
412: }
413:
414: /**
415: * Returns the start data value for one category in a series.
416: *
417: * @param series the required series (zero based index).
418: * @param category the required category.
419: *
420: * @return The start data value for one category in a series
421: * (possibly <code>null</code>).
422: *
423: * @see #getStartValue(Comparable, Comparable)
424: */
425: public Number getStartValue(int series, int category) {
426:
427: // check arguments...
428: if ((series < 0) || (series >= getSeriesCount())) {
429: throw new IllegalArgumentException(
430: "DefaultIntervalCategoryDataset.getValue(): "
431: + "series index out of range.");
432: }
433:
434: if ((category < 0) || (category >= getCategoryCount())) {
435: throw new IllegalArgumentException(
436: "DefaultIntervalCategoryDataset.getValue(): "
437: + "category index out of range.");
438: }
439:
440: // fetch the value...
441: return this .startData[series][category];
442:
443: }
444:
445: /**
446: * Returns the end data value for one category in a series.
447: *
448: * @param series the required series.
449: * @param category the required category.
450: *
451: * @return The end data value for one category in a series (null possible).
452: *
453: * @see #getEndValue(int, int)
454: */
455: public Number getEndValue(Comparable series, Comparable category) {
456: int seriesIndex = getSeriesIndex(series);
457: if (seriesIndex < 0) {
458: throw new UnknownKeyException("Unknown 'series' key.");
459: }
460: int itemIndex = getColumnIndex(category);
461: if (itemIndex < 0) {
462: throw new UnknownKeyException("Unknown 'category' key.");
463: }
464: return getEndValue(seriesIndex, itemIndex);
465: }
466:
467: /**
468: * Returns the end data value for one category in a series.
469: *
470: * @param series the required series (zero based index).
471: * @param category the required category.
472: *
473: * @return The end data value for one category in a series (null possible).
474: *
475: * @see #getEndValue(Comparable, Comparable)
476: */
477: public Number getEndValue(int series, int category) {
478: if ((series < 0) || (series >= getSeriesCount())) {
479: throw new IllegalArgumentException(
480: "DefaultIntervalCategoryDataset.getValue(): "
481: + "series index out of range.");
482: }
483:
484: if ((category < 0) || (category >= getCategoryCount())) {
485: throw new IllegalArgumentException(
486: "DefaultIntervalCategoryDataset.getValue(): "
487: + "category index out of range.");
488: }
489:
490: return this .endData[series][category];
491: }
492:
493: /**
494: * Sets the start data value for one category in a series.
495: *
496: * @param series the series (zero-based index).
497: * @param category the category.
498: *
499: * @param value The value.
500: *
501: * @see #setEndValue(int, Comparable, Number)
502: */
503: public void setStartValue(int series, Comparable category,
504: Number value) {
505:
506: // does the series exist?
507: if ((series < 0) || (series > getSeriesCount() - 1)) {
508: throw new IllegalArgumentException(
509: "DefaultIntervalCategoryDataset.setValue: "
510: + "series outside valid range.");
511: }
512:
513: // is the category valid?
514: int categoryIndex = getCategoryIndex(category);
515: if (categoryIndex < 0) {
516: throw new IllegalArgumentException(
517: "DefaultIntervalCategoryDataset.setValue: "
518: + "unrecognised category.");
519: }
520:
521: // update the data...
522: this .startData[series][categoryIndex] = value;
523: fireDatasetChanged();
524:
525: }
526:
527: /**
528: * Sets the end data value for one category in a series.
529: *
530: * @param series the series (zero-based index).
531: * @param category the category.
532: *
533: * @param value the value.
534: *
535: * @see #setStartValue(int, Comparable, Number)
536: */
537: public void setEndValue(int series, Comparable category,
538: Number value) {
539:
540: // does the series exist?
541: if ((series < 0) || (series > getSeriesCount() - 1)) {
542: throw new IllegalArgumentException(
543: "DefaultIntervalCategoryDataset.setValue: "
544: + "series outside valid range.");
545: }
546:
547: // is the category valid?
548: int categoryIndex = getCategoryIndex(category);
549: if (categoryIndex < 0) {
550: throw new IllegalArgumentException(
551: "DefaultIntervalCategoryDataset.setValue: "
552: + "unrecognised category.");
553: }
554:
555: // update the data...
556: this .endData[series][categoryIndex] = value;
557: fireDatasetChanged();
558:
559: }
560:
561: /**
562: * Returns the index for the given category.
563: *
564: * @param category the category (<code>null</code> not permitted).
565: *
566: * @return The index.
567: *
568: * @see #getColumnIndex(Comparable)
569: */
570: public int getCategoryIndex(Comparable category) {
571: int result = -1;
572: for (int i = 0; i < this .categoryKeys.length; i++) {
573: if (category.equals(this .categoryKeys[i])) {
574: result = i;
575: break;
576: }
577: }
578: return result;
579: }
580:
581: /**
582: * Generates an array of keys, by appending a space plus an integer
583: * (starting with 1) to the supplied prefix string.
584: *
585: * @param count the number of keys required.
586: * @param prefix the name prefix.
587: *
588: * @return An array of <i>prefixN</i> with N = { 1 .. count}.
589: */
590: private Comparable[] generateKeys(int count, String prefix) {
591: Comparable[] result = new Comparable[count];
592: String name;
593: for (int i = 0; i < count; i++) {
594: name = prefix + (i + 1);
595: result[i] = name;
596: }
597: return result;
598: }
599:
600: /**
601: * Returns a column key.
602: *
603: * @param column the column index.
604: *
605: * @return The column key.
606: *
607: * @see #getRowKey(int)
608: */
609: public Comparable getColumnKey(int column) {
610: return this .categoryKeys[column];
611: }
612:
613: /**
614: * Returns a column index.
615: *
616: * @param columnKey the column key (<code>null</code> not permitted).
617: *
618: * @return The column index.
619: *
620: * @see #getCategoryIndex(Comparable)
621: */
622: public int getColumnIndex(Comparable columnKey) {
623: if (columnKey == null) {
624: throw new IllegalArgumentException(
625: "Null 'columnKey' argument.");
626: }
627: return getCategoryIndex(columnKey);
628: }
629:
630: /**
631: * Returns a row index.
632: *
633: * @param rowKey the row key.
634: *
635: * @return The row index.
636: *
637: * @see #getSeriesIndex(Comparable)
638: */
639: public int getRowIndex(Comparable rowKey) {
640: return getSeriesIndex(rowKey);
641: }
642:
643: /**
644: * Returns a list of the series in the dataset. This method supports the
645: * {@link CategoryDataset} interface.
646: *
647: * @return A list of the series in the dataset.
648: *
649: * @see #getColumnKeys()
650: */
651: public List getRowKeys() {
652: // the CategoryDataset interface expects a list of series, but
653: // we've stored them in an array...
654: if (this .seriesKeys == null) {
655: return new java.util.ArrayList();
656: } else {
657: return Collections.unmodifiableList(Arrays
658: .asList(this .seriesKeys));
659: }
660: }
661:
662: /**
663: * Returns the name of the specified series.
664: *
665: * @param row the index of the required row/series (zero-based).
666: *
667: * @return The name of the specified series.
668: *
669: * @see #getColumnKey(int)
670: */
671: public Comparable getRowKey(int row) {
672: if ((row >= getRowCount()) || (row < 0)) {
673: throw new IllegalArgumentException(
674: "The 'row' argument is out of bounds.");
675: }
676: return this .seriesKeys[row];
677: }
678:
679: /**
680: * Returns the number of categories in the dataset. This method is part of
681: * the {@link CategoryDataset} interface.
682: *
683: * @return The number of categories in the dataset.
684: *
685: * @see #getCategoryCount()
686: * @see #getRowCount()
687: */
688: public int getColumnCount() {
689: return this .categoryKeys.length;
690: }
691:
692: /**
693: * Returns the number of series in the dataset (possibly zero).
694: *
695: * @return The number of series in the dataset.
696: *
697: * @see #getSeriesCount()
698: * @see #getColumnCount()
699: */
700: public int getRowCount() {
701: return this .seriesKeys.length;
702: }
703:
704: /**
705: * Tests this dataset for equality with an arbitrary object.
706: *
707: * @param obj the object (<code>null</code> permitted).
708: *
709: * @return A boolean.
710: */
711: public boolean equals(Object obj) {
712: if (obj == this ) {
713: return true;
714: }
715: if (!(obj instanceof DefaultIntervalCategoryDataset)) {
716: return false;
717: }
718: DefaultIntervalCategoryDataset that = (DefaultIntervalCategoryDataset) obj;
719: if (!Arrays.equals(this .seriesKeys, that.seriesKeys)) {
720: return false;
721: }
722: if (!Arrays.equals(this .categoryKeys, that.categoryKeys)) {
723: return false;
724: }
725: if (!equal(this .startData, that.startData)) {
726: return false;
727: }
728: if (!equal(this .endData, that.endData)) {
729: return false;
730: }
731: // seem to be the same...
732: return true;
733: }
734:
735: /**
736: * Returns a clone of this dataset.
737: *
738: * @return A clone.
739: *
740: * @throws CloneNotSupportedException if there is a problem cloning the
741: * dataset.
742: */
743: public Object clone() throws CloneNotSupportedException {
744: DefaultIntervalCategoryDataset clone = (DefaultIntervalCategoryDataset) super
745: .clone();
746: clone.categoryKeys = (Comparable[]) this .categoryKeys.clone();
747: clone.seriesKeys = (Comparable[]) this .seriesKeys.clone();
748: clone.startData = clone(this .startData);
749: clone.endData = clone(this .endData);
750: return clone;
751: }
752:
753: /**
754: * Tests two double[][] arrays for equality.
755: *
756: * @param array1 the first array (<code>null</code> permitted).
757: * @param array2 the second arrray (<code>null</code> permitted).
758: *
759: * @return A boolean.
760: */
761: private static boolean equal(Number[][] array1, Number[][] array2) {
762: if (array1 == null) {
763: return (array2 == null);
764: }
765: if (array2 == null) {
766: return false;
767: }
768: if (array1.length != array2.length) {
769: return false;
770: }
771: for (int i = 0; i < array1.length; i++) {
772: if (!Arrays.equals(array1[i], array2[i])) {
773: return false;
774: }
775: }
776: return true;
777: }
778:
779: /**
780: * Clones a two dimensional array of <code>Number</code> objects.
781: *
782: * @param array the array (<code>null</code> not permitted).
783: *
784: * @return A clone of the array.
785: */
786: private static Number[][] clone(Number[][] array) {
787: if (array == null) {
788: throw new IllegalArgumentException("Null 'array' argument.");
789: }
790: Number[][] result = new Number[array.length][];
791: for (int i = 0; i < array.length; i++) {
792: Number[] child = array[i];
793: Number[] copychild = new Number[child.length];
794: System.arraycopy(child, 0, copychild, 0, child.length);
795: result[i] = copychild;
796: }
797: return result;
798: }
799:
800: /**
801: * Returns a list of the series in the dataset.
802: *
803: * @return A list of the series in the dataset.
804: *
805: * @deprecated Use {@link #getRowKeys()} instead.
806: */
807: public List getSeries() {
808: if (this .seriesKeys == null) {
809: return new java.util.ArrayList();
810: } else {
811: return Collections.unmodifiableList(Arrays
812: .asList(this .seriesKeys));
813: }
814: }
815:
816: /**
817: * Returns a list of the categories in the dataset.
818: *
819: * @return A list of the categories in the dataset.
820: *
821: * @deprecated Use {@link #getColumnKeys()} instead.
822: */
823: public List getCategories() {
824: return getColumnKeys();
825: }
826:
827: /**
828: * Returns the item count.
829: *
830: * @return The item count.
831: *
832: * @deprecated Use {@link #getCategoryCount()} instead.
833: */
834: public int getItemCount() {
835: return this.categoryKeys.length;
836: }
837:
838: }
|