001: /**
002: * Chart2D, a java library for drawing two dimensional charts.
003: * Copyright (C) 2001 Jason J. Simas
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * The author of this library may be contacted at:
019: * E-mail: jjsimas@users.sourceforge.net
020: * Street Address: J J Simas, 887 Tico Road, Ojai, CA 93023-3555 USA
021: */package net.sourceforge.chart2d;
022:
023: import java.awt.*;
024: import java.util.*;
025: import java.text.*;
026:
027: /**
028: * A container containing information and components for all charts.
029: */
030: class ChartArea extends TitledArea {
031:
032: /**
033: * Used by setLabelsPrecisionNum (int num).
034: * The number is 8.
035: * This makes the label with the largest absolute value, have
036: * as many zeroes as possible -- up to 8.
037: */
038: static final int MAX_INTEGER = 38;
039:
040: /**
041: * Used by setLabelsPrecisionNum (int num).
042: * The number is -8.
043: * This makes the float labels have as many significant
044: * digits as possible -- 8.
045: */
046: static final int MAX_FLOAT = -38;
047:
048: /**
049: * Used by setLabelsPrecisionNum (int num).
050: * The number is -2.
051: * This makes the float labels have two significant labels.
052: */
053: static final int DEFAULT_FLOAT = -2;
054:
055: private Color[] datasetColors;
056: private boolean betweenChartAndLegendGapExistence;
057: private float betweenChartAndLegendGapToWidthRatio;
058: private int betweenChartAndLegendGapThicknessModel;
059: private LegendArea legend;
060: private boolean legendExistence;
061: private float legendToWidthRatio;
062: private float legendToHeightRatio;
063: private int labelsPrecisionNum;
064:
065: private boolean autoSetLayoutRatios;
066: private boolean needsUpdate;
067:
068: /**
069: * Creates a Chart Area with the default values.
070: */
071: ChartArea() {
072:
073: needsUpdate = true;
074: legend = new LegendArea();
075:
076: setAutoSizes(false, true);
077: setFontPointModel(16);
078: setLabelsPrecisionNum(MAX_INTEGER);
079: setTitleSqueeze(true);
080: setBetweenChartAndLegendGapExistence(true);
081: setBetweenChartAndLegendGapThicknessModel(5);
082: setLegendExistence(true);
083: setLegendToWidthRatio(.25f);
084: setLegendToHeightRatio(1f);
085: setAutoSetLayoutRatios(true);
086:
087: setDatasetColors(new Color[0]);
088:
089: resetChartAreaModel(true);
090: }
091:
092: /**
093: * Specifies whether the gap between the chart and the legend exists. If the
094: * legend doesn't exist, this gap will automatically not exist.
095: * @param existence If true, the gap exists.
096: */
097: final void setBetweenChartAndLegendGapExistence(boolean existence) {
098: needsUpdate = true;
099: betweenChartAndLegendGapExistence = existence;
100: }
101:
102: /**
103: * Specifies the thickness of the gap between the chart and the legend for the
104: * chart's model size.
105: * @param thickness The model thickness of the gap.
106: */
107: final void setBetweenChartAndLegendGapThicknessModel(int thickness) {
108: needsUpdate = true;
109: betweenChartAndLegendGapThicknessModel = thickness;
110: }
111:
112: /**
113: * Indiates whether upon the next painting, the layout ratios distributing
114: * the max size of the space between the components, should be set to their
115: * ideal values based on particulars of the chart.
116: * @param auto If true, then the ratios will be adjusted.
117: */
118: final void setAutoSetLayoutRatios(boolean auto) {
119:
120: needsUpdate = true;
121: autoSetLayoutRatios = auto;
122: }
123:
124: /**
125: * Changes whether the chart title should be squeezed near to and on top of
126: * the chart graph and legend. The between title and chart gap will be
127: * respected.
128: * @param squeeze True if the title should be squeezed.
129: */
130: final void setTitleSqueeze(boolean squeeze) {
131:
132: needsUpdate = true;
133: setTitleAutoLocate(!squeeze);
134: }
135:
136: /**
137: * Passes in a chart colors object to specify the colors for this chart.
138: * @param colors The chart colors object.
139: */
140: final void setDatasetColors(Color[] colors) {
141:
142: needsUpdate = true;
143: datasetColors = colors;
144: }
145:
146: /**
147: * Enables or disables the legend from calculations and painting.
148: * @param existence The existence of the legend.
149: */
150: final void setLegendExistence(boolean existence) {
151:
152: needsUpdate = true;
153: legendExistence = existence;
154: }
155:
156: /**
157: * Specifies how much of the maximum width less borders and gaps to make
158: * availalble to the legend maximum size.
159: * @param ratio The ratio to the width, to make available to the legend.
160: */
161: final void setLegendToWidthRatio(float ratio) {
162:
163: needsUpdate = true;
164: legendToWidthRatio = ratio;
165: }
166:
167: /**
168: * Specifies how much of the maximum height less borders, gaps, and title to
169: * make availalble to the legend maximum size.
170: * @param ratio The ratio to the height, to make available to the legend.
171: */
172: final void setLegendToHeightRatio(float ratio) {
173:
174: needsUpdate = true;
175: legendToHeightRatio = ratio;
176: }
177:
178: /**
179: * Changes the number of decimal places to be displayed on the data values
180: * axis. A positive int specifies how many places to the right of the decimal
181: * place should be kept (Float Labels). A non positive int specifies how many
182: * zeros to the left of the decimal should occur (Integer Labels).
183: * Use MAX_ZEROES if you want integer labels with only one non-zero number.
184: * @param num The number of decimal places.
185: */
186: final void setLabelsPrecisionNum(int num) {
187:
188: needsUpdate = true;
189: labelsPrecisionNum = num;
190: }
191:
192: /**
193: * Returns whether upon the next painting, the layout ratios distributing
194: * the max size of the space between the components, should be set to their
195: * ideal values based on particulars of the chart.
196: * @return boolean If true, then the ratios should be adjusted.
197: */
198: final boolean getAutoSetLayoutRatios() {
199:
200: return autoSetLayoutRatios;
201: }
202:
203: /**
204: * Returns whether the gap between the chart and the legend exists. If the
205: * legend doesn't exist, this gap will automatically not exist.
206: * @return boolean If true, the gap exists.
207: */
208: final boolean getBetweenChartAndLegendGapExistence() {
209: return betweenChartAndLegendGapExistence;
210: }
211:
212: /**
213: * Returns the thickness of the gap between the chart and the legend for the
214: * chart's model size.
215: * @return int The model thickness of the gap.
216: */
217: final int getBetweenChartAndLegendGapThicknessModel() {
218: return betweenChartAndLegendGapThicknessModel;
219: }
220:
221: /**
222: * Indicates whether the title should be squeezed on top of, and near to the
223: * chart. The title gap spacing will be respected.
224: * @return True if the title will be squeezed.
225: */
226: final boolean getTitleSqueeze() {
227:
228: return !getTitleAutoLocate();
229: }
230:
231: /**
232: * Returns the total amount of data in this set.
233: * @param data set. The data set to sum.
234: * @return float The sum.
235: */
236: final static float getDatasetTotal(float[] dataset) {
237:
238: float total = 0f;
239: for (int i = 0; i < dataset.length; ++i)
240: total += dataset[i];
241: return total;
242: }
243:
244: /**
245: * Returns the total amount of data in for each data category. Found by
246: * summing the data in each category, from each data set.
247: * @return float[] An array of the totals of each data category.
248: */
249: final static float[] getDataCategoryTotals(float[][] datasets) {
250:
251: if (datasets.length > 0 && datasets[0].length > 0) {
252: float[] totals = new float[datasets[0].length];
253: for (int i = 0; i < datasets[0].length; ++i) {
254: totals[i] = 0;
255: for (int j = 0; j < datasets.length; ++j) {
256: totals[i] += datasets[j][i];
257: }
258: }
259: return totals;
260: } else
261: return new float[0];
262: }
263:
264: /**
265: * Returns the colors the auto charting algorithm chose or your custom colors.
266: * @return The colors for the chart.
267: */
268: final Color[] getDatasetColors() {
269:
270: return datasetColors;
271: }
272:
273: /**
274: * Returns the colors the auto charting algorithm chose or your custom colors,
275: * from the beginning index (inclusive) and to the ending index (exclusive).
276: * @return The colors for the chart.
277: */
278: final Color[] getDatasetColors(int begin, int end) {
279:
280: Color[] subColors = new Color[end - begin];
281: int j = 0;
282: for (int i = begin; i < end && i < datasetColors.length; ++i) {
283: subColors[j] = datasetColors[i];
284: ++j;
285: }
286: return subColors;
287: }
288:
289: /**
290: * Returns the legend in order to allow customization of it.
291: * @return The legend of this chart.
292: */
293: final LegendArea getLegend() {
294:
295: return legend;
296: }
297:
298: /**
299: * Returns this property in order for subclasses to have access to it.
300: * @return The specified property.
301: */
302: final boolean getLegendExistence() {
303:
304: return legendExistence;
305: }
306:
307: /**
308: * Returns this property in order for subclasses to have access to it.
309: * @return The specified property.
310: */
311: final float getLegendToWidthRatio() {
312:
313: return legendToWidthRatio;
314: }
315:
316: /**
317: * Returns this property in order for subclasses to have access to it.
318: * @return The specified property.
319: */
320: final float getLegendToHeightRatio() {
321:
322: return legendToHeightRatio;
323: }
324:
325: /**
326: * Indicates how many decimal places should be in the labels of the value
327: * axis. For integer values, choose 0. For floats, a good choice is
328: * generally 2.
329: * @return The number of decimal places in the labels.
330: */
331: final int getLabelsPrecisionNum() {
332:
333: return labelsPrecisionNum;
334: }
335:
336: /**
337: * Provides a more sophisticated use of Math.ceil(double).
338: * For precision = 0, the return value is Math.ceil (value).
339: * For precision > 1, the return value is the value obtained by the following
340: * three operations: shift decimal left precision number of places, apply
341: * the resultant value in Math.ceil, shift decimal right precision number of
342: * places.
343: * For precision < 1, the return value is the value obtained by the following
344: * three operations: shift decimal right precision number of places, apply
345: * the resultant value in Math.ceil, shift decimal left precision number of
346: * places.
347: * @param value The value to ceil.
348: * @param precision The basically an indicator of what digit to apply ceil to.
349: * @return float The resultant value.
350: */
351: static final float getPrecisionCeil(float value, int precision) {
352:
353: float sign = value < 0f ? -1f : 1f;
354: value = value == -0f ? 0f : sign * value;
355: if (precision > 0) {
356: DecimalFormat df = new DecimalFormat("#0.0#");
357: String valueS = df.format(value);
358: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
359: .getDecimalSeparator());
360: precision = precision < valueS.length()
361: - (valueS.length() - decIndex) ? precision : valueS
362: .length()
363: - (valueS.length() - decIndex) - 1;
364: String prefix = valueS.substring(0, decIndex - precision);
365: String postfix;
366: postfix = valueS.substring(decIndex - precision, decIndex)
367: + valueS.substring(decIndex + 1, valueS.length());
368: String toCeil = prefix + "." + postfix;
369: int ceiled = (int) Math.ceil(sign
370: * Float.parseFloat(toCeil));
371: if (ceiled == 0f)
372: return getPrecisionCeil(sign * value, --precision);
373: else {
374: String ceiledS = String.valueOf(ceiled);
375: int i = precision;
376: for (i = 0; i < precision; ++i)
377: ceiledS += "0";
378: return Float.parseFloat(ceiledS);
379: }
380: } else if (precision < 0) {
381: DecimalFormat df = new DecimalFormat("#.00#");
382: String valueS = df.format(value);
383: precision = -precision;
384: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
385: .getDecimalSeparator());
386: precision = precision < valueS.length() - decIndex ? precision
387: : valueS.length() - decIndex - 1;
388: String prefix = valueS.substring(0, decIndex)
389: + valueS.substring(decIndex + 1, decIndex
390: + precision);
391: String postfix = valueS.substring(decIndex + precision,
392: valueS.length());
393: String toCeil = prefix.substring(0, prefix.length())
394: + postfix.substring(0, 1) + "."
395: + postfix.substring(1, postfix.length());
396: int ceiled = (int) Math.ceil(sign
397: * Float.parseFloat(toCeil));
398: String ceiledS = String.valueOf(ceiled);
399: decIndex = sign < 0f ? ++decIndex : decIndex;
400: prefix = ceiledS.substring(0, decIndex);
401: postfix = ceiledS.substring(decIndex, ceiledS.length());
402: valueS = prefix + "." + postfix;
403: return Float.parseFloat(valueS);
404: } else
405: return (float) Math.ceil(sign * value);
406: }
407:
408: /**
409: * Provides a more sophisticated use of Math.floor(double).
410: * For precision = 0, the return value is Math.floor (value).
411: * For precision > 1, the return value is the value obtained by the following
412: * three operations: shift decimal left precision number of places, apply
413: * the resultant value in Math.floor, shift decimal right precision number of
414: * places.
415: * For precision < 1, the return value is the value obtained by the following
416: * three operations: shift decimal right precision number of places, apply
417: * the resultant value in Math.floor, shift decimal left precision number of
418: * places.
419: * @param value The value to floor.
420: * @param precision The basically an indicator of what digit to apply floor to.
421: * @return float The resultant value.
422: */
423: static final float getPrecisionFloor(float value, int precision) {
424:
425: float sign = value < 0f ? -1f : 1f;
426: value = value == -0f ? 0f : sign * value;
427: if (precision > 0) {
428: DecimalFormat df = new DecimalFormat("#0.0#");
429: String valueS = df.format(value);
430: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
431: .getDecimalSeparator());
432: precision = precision < valueS.length()
433: - (valueS.length() - decIndex) ? precision : valueS
434: .length()
435: - (valueS.length() - decIndex) - 1;
436: String prefix = valueS.substring(0, decIndex - precision);
437: String postfix;
438: postfix = valueS.substring(decIndex - precision, decIndex)
439: + valueS.substring(decIndex + 1, valueS.length());
440: String toFloor = prefix + "." + postfix;
441: int floored = (int) Math.floor(sign
442: * Float.parseFloat(toFloor));
443: if (floored == 0f)
444: return getPrecisionFloor(sign * value, --precision);
445: else {
446: String flooredS = String.valueOf(floored);
447: int i = precision;
448: for (i = 0; i < precision; ++i)
449: flooredS += "0";
450: return Float.parseFloat(flooredS);
451: }
452: } else if (precision < 0) {
453: DecimalFormat df = new DecimalFormat("#.00#");
454: String valueS = df.format(value);
455: precision = -precision;
456: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
457: .getDecimalSeparator());
458: precision = precision < valueS.length() - decIndex ? precision
459: : valueS.length() - decIndex - 1;
460: String prefix = valueS.substring(0, decIndex)
461: + valueS.substring(decIndex + 1, decIndex
462: + precision);
463: String postfix = valueS.substring(decIndex + precision,
464: valueS.length());
465: String toFloor = prefix.substring(0, prefix.length())
466: + postfix.substring(0, 1) + "."
467: + postfix.substring(1, postfix.length());
468: int floored = (int) Math.floor(sign
469: * Float.parseFloat(toFloor));
470: String flooredS = String.valueOf(floored);
471: decIndex = sign < 0f ? ++decIndex : decIndex;
472: prefix = flooredS.substring(0, decIndex);
473: postfix = flooredS.substring(decIndex, flooredS.length());
474: valueS = prefix + "." + postfix;
475: return Float.parseFloat(valueS);
476: } else
477: return (float) Math.floor(sign * value);
478:
479: }
480:
481: /**
482: * Provides a more sophisticated use of Math.round(double).
483: * For precision = 0, the return value is Math.round (value).
484: * For precision > 1, the return value is the value obtained by the following
485: * three operations: shift decimal left precision number of places, apply
486: * the resultant value in Math.round, shift decimal right precision number of
487: * places.
488: * For precision < 1, the return value is the value obtained by the following
489: * three operations: shift decimal right precision number of places, apply
490: * the resultant value in Math.round, shift decimal left precision number of
491: * places.
492: * @param value The value to round.
493: * @param precision The basically an indicator of what digit to apply round to.
494: * @return float The resultant value.
495: */
496: static final float getPrecisionRound(float value, int precision) {
497:
498: float sign = value < 0f ? -1f : 1f;
499: value = value == -0f ? 0f : sign * value;
500: if (precision > 0) {
501: DecimalFormat df = new DecimalFormat("#0.0#");
502: String valueS = df.format(value);
503: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
504: .getDecimalSeparator());
505: precision = precision < valueS.length()
506: - (valueS.length() - decIndex) ? precision : valueS
507: .length()
508: - (valueS.length() - decIndex) - 1;
509: String prefix = valueS.substring(0, decIndex - precision);
510: String postfix;
511: postfix = valueS.substring(decIndex - precision, decIndex)
512: + valueS.substring(decIndex + 1, valueS.length());
513: String toRound = prefix + "." + postfix;
514: int rounded = (int) Math.round(sign
515: * Float.parseFloat(toRound));
516: if (rounded == 0f)
517: return getPrecisionRound(sign * value, --precision);
518: else {
519: String roundedS = String.valueOf(rounded);
520: int i = precision;
521: for (i = 0; i < precision; ++i)
522: roundedS += "0";
523: return Float.parseFloat(roundedS);
524: }
525: } else if (precision < 0) {
526: DecimalFormat df = new DecimalFormat("#.00#");
527: String valueS = df.format(value);
528: precision = -precision;
529: int decIndex = valueS.indexOf(df.getDecimalFormatSymbols()
530: .getDecimalSeparator());
531: precision = precision < valueS.length() - decIndex ? precision
532: : valueS.length() - decIndex - 1;
533: String prefix = valueS.substring(0, decIndex)
534: + valueS.substring(decIndex + 1, decIndex
535: + precision);
536: String postfix = valueS.substring(decIndex + precision,
537: valueS.length());
538: String toRound = prefix.substring(0, prefix.length())
539: + postfix.substring(0, 1) + "."
540: + postfix.substring(1, postfix.length());
541: int rounded = (int) Math.round(sign
542: * Float.parseFloat(toRound));
543: String roundedS = String.valueOf(rounded);
544: decIndex = sign < 0f ? ++decIndex : decIndex;
545: prefix = roundedS.substring(0, decIndex);
546: postfix = roundedS.substring(decIndex, roundedS.length());
547: valueS = prefix + "." + postfix;
548: return Float.parseFloat(valueS);
549: } else
550: return (float) Math.round(sign * value);
551: }
552:
553: /**
554: * Returns a simple string representation of the float.
555: * If places >=0, then floats will have respresentation as integers.
556: * If places < 0, then floats will have floating point representation with
557: * the number of places to the right of the decimal equal to |places|.
558: * @param value The value to represent.
559: * @param places Roughly, the number of decimal places in the representation.
560: * @return The representation of the value.
561: */
562: static final String getFloatToString(float value, int places) {
563:
564: String format;
565: value = value == -0f ? 0f : value;
566: if (places < 0) {
567: format = "0.0";
568: for (int i = 1; i < -places; ++i)
569: format += "0";
570: } else
571: format = "#";
572: DecimalFormat df = new DecimalFormat(format);
573: return df.format(value);
574: }
575:
576: /**
577: * Indicates whether some property of this class has changed.
578: * @return True if some property has changed.
579: */
580: final boolean getChartAreaNeedsUpdate() {
581:
582: return (needsUpdate || getTitledAreaNeedsUpdate() || legend
583: .getLegendAreaNeedsUpdate());
584: }
585:
586: /**
587: * Resets the model for this class. The model is used for shrinking and
588: * growing of its components based on the maximum size of this class. If this
589: * method is called, then the next time the maximum size is set, this classes
590: * model maximum size will be made equal to the new maximum size. Effectively
591: * what this does is ensure that whenever this objects maximum size is equal
592: * to the one given, then all of the components will take on their default
593: * model sizes. Note: This is only useful when auto model max sizing is
594: * disabled.
595: * @param reset True causes the max model size to be set upon the next max
596: * sizing.
597: */
598: final void resetChartAreaModel(boolean reset) {
599:
600: needsUpdate = true;
601: resetTitledAreaModel(reset);
602: legend.resetLegendAreaModel(reset);
603: }
604:
605: /**
606: * Updates this parent's variables, and this' variables.
607: * @param g2D The graphics context to use for calculations.
608: */
609: final void updateChartArea(Graphics2D g2D) {
610:
611: if (getChartAreaNeedsUpdate()) {
612: updateTitledArea(g2D);
613: update();
614: legend.updateLegendArea(g2D);
615: }
616: needsUpdate = false;
617: }
618:
619: /**
620: * Updates this parent's variables, and this' variables.
621: * @param g2D The graphics context to use for calculations.
622: */
623: void paintComponent(Graphics2D g2D) {
624:
625: updateChartArea(g2D);
626: super .paintComponent(g2D);
627: legend.paintComponent(g2D);
628: }
629:
630: private void update() {
631: legend.setColors(datasetColors);
632: }
633: }
|