KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > chart2d > ChartArea


1 /**
2  * Chart2D, a java library for drawing two dimensional charts.
3  * Copyright (C) 2001 Jason J. Simas
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  *
18  * The author of this library may be contacted at:
19  * E-mail: jjsimas@users.sourceforge.net
20  * Street Address: J J Simas, 887 Tico Road, Ojai, CA 93023-3555 USA
21  */

22
23
24 package net.sourceforge.chart2d;
25
26
27 import java.awt.*;
28 import java.util.*;
29 import java.text.*;
30
31
32 /**
33  * A container containing information and components for all charts.
34  */

35 class ChartArea extends TitledArea {
36
37
38   /**
39    * Used by setLabelsPrecisionNum (int num).
40    * The number is 8.
41    * This makes the label with the largest absolute value, have
42    * as many zeroes as possible -- up to 8.
43    */

44   static final int MAX_INTEGER = 38;
45
46
47   /**
48    * Used by setLabelsPrecisionNum (int num).
49    * The number is -8.
50    * This makes the float labels have as many significant
51    * digits as possible -- 8.
52    */

53   static final int MAX_FLOAT = -38;
54
55
56   /**
57    * Used by setLabelsPrecisionNum (int num).
58    * The number is -2.
59    * This makes the float labels have two significant labels.
60    */

61   static final int DEFAULT_FLOAT = -2;
62
63   private Color[] datasetColors;
64   private boolean betweenChartAndLegendGapExistence;
65   private float betweenChartAndLegendGapToWidthRatio;
66   private int betweenChartAndLegendGapThicknessModel;
67   private LegendArea legend;
68   private boolean legendExistence;
69   private float legendToWidthRatio;
70   private float legendToHeightRatio;
71   private int labelsPrecisionNum;
72
73   private boolean autoSetLayoutRatios;
74   private boolean needsUpdate;
75
76
77   /**
78    * Creates a Chart Area with the default values.
79    */

80   ChartArea() {
81
82     needsUpdate = true;
83     legend = new LegendArea();
84
85     setAutoSizes (false, true);
86     setFontPointModel (16);
87     setLabelsPrecisionNum (MAX_INTEGER);
88     setTitleSqueeze (true);
89     setBetweenChartAndLegendGapExistence (true);
90     setBetweenChartAndLegendGapThicknessModel (5);
91     setLegendExistence (true);
92     setLegendToWidthRatio (.25f);
93     setLegendToHeightRatio (1f);
94     setAutoSetLayoutRatios (true);
95
96     setDatasetColors (new Color[0]);
97
98     resetChartAreaModel (true);
99   }
100
101
102   /**
103    * Specifies whether the gap between the chart and the legend exists. If the
104    * legend doesn't exist, this gap will automatically not exist.
105    * @param existence If true, the gap exists.
106    */

107   final void setBetweenChartAndLegendGapExistence (boolean existence) {
108     needsUpdate = true;
109     betweenChartAndLegendGapExistence = existence;
110   }
111
112
113   /**
114    * Specifies the thickness of the gap between the chart and the legend for the
115    * chart's model size.
116    * @param thickness The model thickness of the gap.
117    */

118   final void setBetweenChartAndLegendGapThicknessModel (int thickness) {
119     needsUpdate = true;
120     betweenChartAndLegendGapThicknessModel = thickness;
121   }
122
123
124   /**
125    * Indiates whether upon the next painting, the layout ratios distributing
126    * the max size of the space between the components, should be set to their
127    * ideal values based on particulars of the chart.
128    * @param auto If true, then the ratios will be adjusted.
129    */

130   final void setAutoSetLayoutRatios (boolean auto) {
131
132     needsUpdate = true;
133     autoSetLayoutRatios = auto;
134   }
135
136
137   /**
138    * Changes whether the chart title should be squeezed near to and on top of
139    * the chart graph and legend. The between title and chart gap will be
140    * respected.
141    * @param squeeze True if the title should be squeezed.
142    */

143   final void setTitleSqueeze (boolean squeeze) {
144
145     needsUpdate = true;
146     setTitleAutoLocate (!squeeze);
147   }
148
149
150   /**
151    * Passes in a chart colors object to specify the colors for this chart.
152    * @param colors The chart colors object.
153    */

154    final void setDatasetColors (Color[] colors) {
155
156     needsUpdate = true;
157     datasetColors = colors;
158   }
159
160
161   /**
162    * Enables or disables the legend from calculations and painting.
163    * @param existence The existence of the legend.
164    */

165   final void setLegendExistence (boolean existence) {
166
167     needsUpdate = true;
168     legendExistence = existence;
169   }
170
171
172   /**
173    * Specifies how much of the maximum width less borders and gaps to make
174    * availalble to the legend maximum size.
175    * @param ratio The ratio to the width, to make available to the legend.
176    */

177   final void setLegendToWidthRatio (float ratio) {
178
179     needsUpdate = true;
180     legendToWidthRatio = ratio;
181   }
182
183
184   /**
185    * Specifies how much of the maximum height less borders, gaps, and title to
186    * make availalble to the legend maximum size.
187    * @param ratio The ratio to the height, to make available to the legend.
188    */

189   final void setLegendToHeightRatio (float ratio) {
190
191     needsUpdate = true;
192     legendToHeightRatio = ratio;
193   }
194
195
196   /**
197    * Changes the number of decimal places to be displayed on the data values
198    * axis. A positive int specifies how many places to the right of the decimal
199    * place should be kept (Float Labels). A non positive int specifies how many
200    * zeros to the left of the decimal should occur (Integer Labels).
201    * Use MAX_ZEROES if you want integer labels with only one non-zero number.
202    * @param num The number of decimal places.
203    */

204   final void setLabelsPrecisionNum (int num) {
205
206     needsUpdate = true;
207     labelsPrecisionNum = num;
208   }
209
210
211   /**
212    * Returns whether upon the next painting, the layout ratios distributing
213    * the max size of the space between the components, should be set to their
214    * ideal values based on particulars of the chart.
215    * @return boolean If true, then the ratios should be adjusted.
216    */

217   final boolean getAutoSetLayoutRatios() {
218
219     return autoSetLayoutRatios;
220   }
221
222
223   /**
224    * Returns whether the gap between the chart and the legend exists. If the
225    * legend doesn't exist, this gap will automatically not exist.
226    * @return boolean If true, the gap exists.
227    */

228   final boolean getBetweenChartAndLegendGapExistence() {
229     return betweenChartAndLegendGapExistence;
230   }
231
232
233   /**
234    * Returns the thickness of the gap between the chart and the legend for the
235    * chart's model size.
236    * @return int The model thickness of the gap.
237    */

238   final int getBetweenChartAndLegendGapThicknessModel() {
239     return betweenChartAndLegendGapThicknessModel;
240   }
241
242
243   /**
244    * Indicates whether the title should be squeezed on top of, and near to the
245    * chart. The title gap spacing will be respected.
246    * @return True if the title will be squeezed.
247    */

248   final boolean getTitleSqueeze () {
249
250     return !getTitleAutoLocate();
251   }
252
253
254   /**
255    * Returns the total amount of data in this set.
256    * @param data set. The data set to sum.
257    * @return float The sum.
258    */

259   final static float getDatasetTotal (float[] dataset) {
260
261     float total = 0f;
262     for (int i = 0; i < dataset.length; ++i) total += dataset[i];
263     return total;
264   }
265
266
267   /**
268    * Returns the total amount of data in for each data category. Found by
269    * summing the data in each category, from each data set.
270    * @return float[] An array of the totals of each data category.
271    */

272   final static float[] getDataCategoryTotals (float[][] datasets) {
273
274     if (datasets.length > 0 && datasets[0].length > 0) {
275       float[] totals = new float[datasets[0].length];
276       for (int i = 0; i < datasets[0].length; ++i) {
277         totals[i] = 0;
278         for (int j = 0; j < datasets.length; ++j) {
279           totals[i] += datasets[j][i];
280         }
281       }
282       return totals;
283     }
284     else return new float[0];
285   }
286
287
288   /**
289    * Returns the colors the auto charting algorithm chose or your custom colors.
290    * @return The colors for the chart.
291    */

292   final Color[] getDatasetColors() {
293
294     return datasetColors;
295   }
296
297
298   /**
299    * Returns the colors the auto charting algorithm chose or your custom colors,
300    * from the beginning index (inclusive) and to the ending index (exclusive).
301    * @return The colors for the chart.
302    */

303   final Color[] getDatasetColors (int begin, int end) {
304
305     Color[] subColors = new Color[end - begin];
306     int j = 0;
307     for (int i = begin; i < end && i < datasetColors.length; ++i) {
308       subColors[j] = datasetColors[i];
309       ++j;
310     }
311     return subColors;
312   }
313
314
315   /**
316    * Returns the legend in order to allow customization of it.
317    * @return The legend of this chart.
318    */

319   final LegendArea getLegend() {
320
321     return legend;
322   }
323
324
325   /**
326    * Returns this property in order for subclasses to have access to it.
327    * @return The specified property.
328    */

329   final boolean getLegendExistence() {
330
331     return legendExistence;
332   }
333
334
335   /**
336    * Returns this property in order for subclasses to have access to it.
337    * @return The specified property.
338    */

339   final float getLegendToWidthRatio() {
340
341     return legendToWidthRatio;
342   }
343
344
345   /**
346    * Returns this property in order for subclasses to have access to it.
347    * @return The specified property.
348    */

349   final float getLegendToHeightRatio() {
350
351     return legendToHeightRatio;
352   }
353
354
355   /**
356    * Indicates how many decimal places should be in the labels of the value
357    * axis. For integer values, choose 0. For floats, a good choice is
358    * generally 2.
359    * @return The number of decimal places in the labels.
360    */

361   final int getLabelsPrecisionNum () {
362
363     return labelsPrecisionNum;
364   }
365
366
367   /**
368    * Provides a more sophisticated use of Math.ceil(double).
369    * For precision = 0, the return value is Math.ceil (value).
370    * For precision > 1, the return value is the value obtained by the following
371    * three operations: shift decimal left precision number of places, apply
372    * the resultant value in Math.ceil, shift decimal right precision number of
373    * places.
374    * For precision < 1, the return value is the value obtained by the following
375    * three operations: shift decimal right precision number of places, apply
376    * the resultant value in Math.ceil, shift decimal left precision number of
377    * places.
378    * @param value The value to ceil.
379    * @param precision The basically an indicator of what digit to apply ceil to.
380    * @return float The resultant value.
381    */

382   static final float getPrecisionCeil (float value, int precision) {
383
384     float sign = value < 0f ? -1f : 1f;
385     value = value == -0f ? 0f : sign * value;
386     if (precision > 0) {
387       DecimalFormat df = new DecimalFormat ("#0.0#");
388       String JavaDoc valueS = df.format (value);
389       int decIndex = valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
390       precision = precision < valueS.length() - (valueS.length() - decIndex) ?
391         precision : valueS.length() - (valueS.length() - decIndex) - 1;
392       String JavaDoc prefix = valueS.substring (0, decIndex - precision);
393       String JavaDoc postfix;
394       postfix = valueS.substring (decIndex - precision, decIndex) +
395           valueS.substring (decIndex + 1, valueS.length());
396       String JavaDoc toCeil = prefix + "." + postfix;
397       int ceiled = (int)Math.ceil (sign * Float.parseFloat(toCeil));
398       if (ceiled == 0f) return getPrecisionCeil (sign * value, --precision);
399       else {
400         String JavaDoc ceiledS = String.valueOf (ceiled);
401         int i = precision;
402         for (i = 0; i < precision; ++i) ceiledS += "0";
403         return Float.parseFloat (ceiledS);
404       }
405     }
406     else if (precision < 0) {
407       DecimalFormat df = new DecimalFormat ("#.00#");
408       String JavaDoc valueS = df.format (value);
409       precision = -precision;
410       int decIndex = valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
411       precision =
412         precision < valueS.length() - decIndex ? precision : valueS.length() - decIndex - 1;
413       String JavaDoc prefix =
414         valueS.substring (0, decIndex) + valueS.substring (decIndex + 1, decIndex + precision);
415       String JavaDoc postfix = valueS.substring (decIndex + precision, valueS.length());
416       String JavaDoc toCeil =
417         prefix.substring(0, prefix.length()) +
418         postfix.substring (0, 1)+ "." + postfix.substring (1, postfix.length());
419       int ceiled = (int)Math.ceil (sign * Float.parseFloat (toCeil));
420       String JavaDoc ceiledS = String.valueOf (ceiled);
421       decIndex = sign < 0f ? ++decIndex : decIndex;
422       prefix = ceiledS.substring (0, decIndex);
423       postfix = ceiledS.substring (decIndex, ceiledS.length());
424       valueS = prefix + "." + postfix;
425       return Float.parseFloat (valueS);
426     }
427     else return (float)Math.ceil (sign * value);
428   }
429
430
431   /**
432    * Provides a more sophisticated use of Math.floor(double).
433    * For precision = 0, the return value is Math.floor (value).
434    * For precision > 1, the return value is the value obtained by the following
435    * three operations: shift decimal left precision number of places, apply
436    * the resultant value in Math.floor, shift decimal right precision number of
437    * places.
438    * For precision < 1, the return value is the value obtained by the following
439    * three operations: shift decimal right precision number of places, apply
440    * the resultant value in Math.floor, shift decimal left precision number of
441    * places.
442    * @param value The value to floor.
443    * @param precision The basically an indicator of what digit to apply floor to.
444    * @return float The resultant value.
445    */

446   static final float getPrecisionFloor (float value, int precision) {
447
448     float sign = value < 0f ? -1f : 1f;
449     value = value == -0f ? 0f : sign * value;
450     if (precision > 0) {
451       DecimalFormat df =
452         new DecimalFormat ("#0.0#");
453       String JavaDoc valueS = df.format (value);
454       int decIndex =
455           valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
456       precision = precision < valueS.length() - (valueS.length() - decIndex) ?
457         precision : valueS.length() - (valueS.length() - decIndex) - 1;
458       String JavaDoc prefix = valueS.substring (0, decIndex - precision);
459       String JavaDoc postfix;
460       postfix = valueS.substring (decIndex - precision, decIndex) +
461           valueS.substring (decIndex + 1, valueS.length());
462       String JavaDoc toFloor = prefix + "." + postfix;
463       int floored = (int)Math.floor (sign * Float.parseFloat(toFloor));
464       if (floored == 0f) return getPrecisionFloor (sign * value, --precision);
465       else {
466         String JavaDoc flooredS = String.valueOf (floored);
467         int i = precision;
468         for (i = 0; i < precision; ++i) flooredS += "0";
469         return Float.parseFloat (flooredS);
470       }
471     }
472     else if (precision < 0) {
473       DecimalFormat df =
474         new DecimalFormat ("#.00#");
475       String JavaDoc valueS = df.format (value);
476       precision = -precision;
477       int decIndex =
478           valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
479       precision =
480         precision < valueS.length() - decIndex?
481         precision : valueS.length() - decIndex - 1;
482       String JavaDoc prefix = valueS.substring (0, decIndex) +
483         valueS.substring (decIndex + 1, decIndex + precision);
484       String JavaDoc postfix = valueS.substring (decIndex + precision, valueS.length());
485       String JavaDoc toFloor = prefix.substring(0, prefix.length()) +
486         postfix.substring (0, 1)+ "." + postfix.substring (1, postfix.length());
487       int floored = (int)Math.floor (sign * Float.parseFloat (toFloor));
488       String JavaDoc flooredS = String.valueOf (floored);
489       decIndex = sign < 0f ? ++decIndex : decIndex;
490       prefix = flooredS.substring (0, decIndex);
491       postfix = flooredS.substring (decIndex, flooredS.length());
492       valueS = prefix + "." + postfix;
493       return Float.parseFloat (valueS);
494     }
495     else return (float)Math.floor (sign * value);
496
497   }
498
499
500   /**
501    * Provides a more sophisticated use of Math.round(double).
502    * For precision = 0, the return value is Math.round (value).
503    * For precision > 1, the return value is the value obtained by the following
504    * three operations: shift decimal left precision number of places, apply
505    * the resultant value in Math.round, shift decimal right precision number of
506    * places.
507    * For precision < 1, the return value is the value obtained by the following
508    * three operations: shift decimal right precision number of places, apply
509    * the resultant value in Math.round, shift decimal left precision number of
510    * places.
511    * @param value The value to round.
512    * @param precision The basically an indicator of what digit to apply round to.
513    * @return float The resultant value.
514    */

515   static final float getPrecisionRound (float value, int precision) {
516
517     float sign = value < 0f ? -1f : 1f;
518     value = value == -0f ? 0f : sign * value;
519     if (precision > 0) {
520       DecimalFormat df =
521         new DecimalFormat ("#0.0#");
522       String JavaDoc valueS = df.format (value);
523       int decIndex =
524           valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
525       precision = precision < valueS.length() - (valueS.length() - decIndex) ?
526         precision : valueS.length() - (valueS.length() - decIndex) - 1;
527       String JavaDoc prefix = valueS.substring (0, decIndex - precision);
528       String JavaDoc postfix;
529       postfix = valueS.substring (decIndex - precision, decIndex) +
530           valueS.substring (decIndex + 1, valueS.length());
531       String JavaDoc toRound = prefix + "." + postfix;
532       int rounded = (int)Math.round (sign * Float.parseFloat (toRound));
533       if (rounded == 0f) return getPrecisionRound (sign * value, --precision);
534       else {
535         String JavaDoc roundedS = String.valueOf (rounded);
536         int i = precision;
537         for (i = 0; i < precision; ++i) roundedS += "0";
538         return Float.parseFloat (roundedS);
539       }
540     }
541     else if (precision < 0) {
542       DecimalFormat df =
543         new DecimalFormat ("#.00#");
544       String JavaDoc valueS = df.format (value);
545       precision = -precision;
546       int decIndex =
547           valueS.indexOf (df.getDecimalFormatSymbols().getDecimalSeparator());
548       precision =
549         precision < valueS.length() - decIndex?
550         precision : valueS.length() - decIndex - 1;
551       String JavaDoc prefix = valueS.substring (0, decIndex) +
552         valueS.substring (decIndex + 1, decIndex + precision);
553       String JavaDoc postfix = valueS.substring (decIndex + precision, valueS.length());
554       String JavaDoc toRound = prefix.substring (0, prefix.length()) +
555         postfix.substring (0, 1)+ "." + postfix.substring (1, postfix.length());
556       int rounded = (int)Math.round (sign * Float.parseFloat (toRound));
557       String JavaDoc roundedS = String.valueOf (rounded);
558       decIndex = sign < 0f ? ++decIndex : decIndex;
559       prefix = roundedS.substring (0, decIndex);
560       postfix = roundedS.substring (decIndex, roundedS.length());
561       valueS = prefix + "." + postfix;
562       return Float.parseFloat (valueS);
563     }
564     else return (float)Math.round (sign * value);
565   }
566
567
568   /**
569    * Returns a simple string representation of the float.
570    * If places >=0, then floats will have respresentation as integers.
571    * If places < 0, then floats will have floating point representation with
572    * the number of places to the right of the decimal equal to |places|.
573    * @param value The value to represent.
574    * @param places Roughly, the number of decimal places in the representation.
575    * @return The representation of the value.
576    */

577   static final String JavaDoc getFloatToString (float value, int places) {
578
579     String JavaDoc format;
580     value = value == -0f ? 0f : value;
581     if (places < 0) {
582       format = "0.0";
583       for (int i = 1; i < -places; ++i) format += "0";
584     }
585     else format = "#";
586     DecimalFormat df = new DecimalFormat (format);
587     return df.format (value);
588   }
589
590
591   /**
592    * Indicates whether some property of this class has changed.
593    * @return True if some property has changed.
594    */

595   final boolean getChartAreaNeedsUpdate() {
596
597     return (needsUpdate || getTitledAreaNeedsUpdate() || legend.getLegendAreaNeedsUpdate());
598   }
599
600
601   /**
602    * Resets the model for this class. The model is used for shrinking and
603    * growing of its components based on the maximum size of this class. If this
604    * method is called, then the next time the maximum size is set, this classes
605    * model maximum size will be made equal to the new maximum size. Effectively
606    * what this does is ensure that whenever this objects maximum size is equal
607    * to the one given, then all of the components will take on their default
608    * model sizes. Note: This is only useful when auto model max sizing is
609    * disabled.
610    * @param reset True causes the max model size to be set upon the next max
611    * sizing.
612    */

613   final void resetChartAreaModel (boolean reset) {
614
615     needsUpdate = true;
616     resetTitledAreaModel (reset);
617     legend.resetLegendAreaModel (reset);
618   }
619
620
621   /**
622    * Updates this parent's variables, and this' variables.
623    * @param g2D The graphics context to use for calculations.
624    */

625   final void updateChartArea (Graphics2D g2D) {
626
627     if (getChartAreaNeedsUpdate()) {
628       updateTitledArea (g2D);
629       update();
630       legend.updateLegendArea (g2D);
631     }
632     needsUpdate = false;
633   }
634
635
636   /**
637    * Updates this parent's variables, and this' variables.
638    * @param g2D The graphics context to use for calculations.
639    */

640   void paintComponent (Graphics2D g2D) {
641
642     updateChartArea (g2D);
643     super.paintComponent (g2D);
644     legend.paintComponent (g2D);
645   }
646
647
648   private void update() {
649     legend.setColors (datasetColors);
650   }
651 }
Popular Tags