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.awt.geom.*;
025: import java.util.Date;
026:
027: /**
028: * A container for many variables and components relating to a pie area.
029: * A pie area is the area that contains only the pie of a pie chart. It doesn't
030: * have a legend.<br>
031: * <b>Features:</b><br>
032: * Allows for charting both floats and int values. Works fine even if all
033: * values are zero. Does not accept negative values. Outputs center points
034: * of sectors so that labels can be drawn into the sectors, touching these
035: * points. Outpouts how many sectors are in each quarter of the pie, so that
036: * appropriate placement of labels can be decided. The first datum in the
037: * data set will be the first sector beginning from the angle of 135 degrees and
038: * ending somewhere clockwise to it It will have the first color in the
039: * colors set. The remaining sectors are laid out similarly, clockwise.
040: */
041: final class PieArea extends Area {
042:
043: private float[] dataset;
044: private Color[] colors;
045: private Paint[] paints;
046:
047: private Arc2D.Float[] sectors;
048: private int numSectors;
049: private int[] numSectorsInQuarters;
050: private boolean outlineSectors;
051: private Color outlineSectorsColor;
052:
053: private float sectorPointDepthRatio;
054: private Point[] pointsInSectors;
055: private float sectorGapPointRatio;
056: private Point[] pointsOutSectors;
057:
058: private int piePrefSpaceSize;
059: private boolean customSizing;
060: private Dimension customSpaceSize;
061:
062: private int lightSource;
063:
064: private boolean needsUpdate;
065:
066: /**
067: * Creates a pie area with the default values.
068: */
069: PieArea() {
070:
071: setAutoSizes(false, false);
072: setAutoJustifys(false, false);
073: setBackgroundExistence(false);
074: setBorderExistence(false);
075: setGapExistence(true);
076: setGapThicknessModel(0);
077: setOutlineSectors(true);
078: setOutlineSectorsColor(Color.black);
079: setPiePrefSizeModel(100);
080: setSectorPointDepthRatio(.25f); //setSectorPointRatio
081: setSectorGapPointRatio(.25f);
082: setDataset(new float[0]);
083: setColors(new Color[0]);
084: setCustomSpaceSize(false, new Dimension());
085: setLightSource(TOP);
086: resetPieAreaModel(true);
087: needsUpdate = true;
088: numSectorsInQuarters = new int[4];
089: }
090:
091: /**
092: * Sets from which direction the light is coming for shading of the pie sectors.
093: * @param source The direction of the light.
094: */
095: final void setLightSource(int source) {
096:
097: needsUpdate = true;
098: lightSource = source;
099: }
100:
101: /**
102: * Indicates whether the sectors should have a thin outline.
103: * @param outline If true, then the outline exists.
104: */
105: final void setOutlineSectors(boolean outline) {
106:
107: needsUpdate = true;
108: outlineSectors = outline;
109: }
110:
111: /**
112: * Indicates the color of the sectors outline if it exists.
113: * @param color The color for the outline.
114: */
115: final void setOutlineSectorsColor(Color color) {
116:
117: needsUpdate = true;
118: outlineSectorsColor = color;
119: }
120:
121: /**
122: * Determines how far into the sector the depth of the points from the
123: * getPointsInSectors() method are.
124: * @param ratio The ratio on the radius of the circle.
125: */
126: final void setSectorPointDepthRatio(float ratio) {
127: needsUpdate = true;
128: sectorPointDepthRatio = ratio;
129: }
130:
131: /**
132: * Determines how far out of the sector the points from the
133: * getPointsOutSectors() method are.
134: * @param ratio The ratio on the radius of the circle.
135: */
136: final void setSectorGapPointRatio(float ratio) {
137: needsUpdate = true;
138: sectorGapPointRatio = ratio;
139: }
140:
141: /**
142: * For input of the raw numbers to represent by the pie. Array element i
143: * is sector i, clockwise from degree 135.
144: * @param values The raw numbers to represent by the pie.
145: */
146: final void setDataset(float[] values) {
147:
148: needsUpdate = true;
149: dataset = values;
150: }
151:
152: /**
153: * For input of the color of each sector that represents a datum of the data
154: * set. Array element i is sector i, clockise from degree 135.
155: * @param colors The colors of the sectors.
156: */
157: final void setColors(Color[] colors) {
158:
159: needsUpdate = true;
160: this .colors = colors;
161: }
162:
163: /**
164: * Sets the model size of the pie. When initially drawn, the pie will be
165: * at least this size, if this size is less than the max space size of this
166: * area. After that and if magnificying, the pie size will be applied to the
167: * size ratios.
168: * @param size The model size of the pie.
169: */
170: final void setPiePrefSizeModel(int size) {
171:
172: needsUpdate = true;
173: piePrefSpaceSize = size;
174: }
175:
176: /**
177: * Sets the "minimum" size of the pie directly. If the minimum is too
178: * large, then the size will be the pie's maximum size.
179: * @param size The number to multiply to get the minimum size.
180: */
181: final void setCustomSpaceSize(boolean customize, Dimension size) {
182:
183: needsUpdate = true;
184: customSizing = customize;
185: customSpaceSize = size;
186: }
187:
188: /**
189: * Gets from which direction the light is coming for shading of the pie sectors.
190: * @return The direction of the light.
191: */
192: final int getLightSource() {
193: return lightSource;
194: }
195:
196: /**
197: * Returns whether the sectors should have a thin outline.
198: * @return outline If true, then the outline exists.
199: */
200: final boolean getOutlineSectors() {
201:
202: return outlineSectors;
203: }
204:
205: /**
206: * Returns the color of the sectors outline if it exists.
207: * @return color The color for the outline.
208: */
209: final Color getOutlineSectorsColor() {
210:
211: return outlineSectorsColor;
212: }
213:
214: /**
215: * Returns the raw numbers to represent by the pie.
216: * @return The raw numbers to represent by the pie.
217: */
218: final float[] getDataset() {
219:
220: return dataset;
221: }
222:
223: /**
224: * Returns this property.
225: * @return The colors of the lines.
226: */
227: final Color[] getColors() {
228:
229: return colors;
230: }
231:
232: /**
233: * Returns this property.
234: * @return The number of sectors in this pie.
235: */
236: final int getNumSectors() {
237:
238: updatePieArea();
239: return numSectors;
240: }
241:
242: /**
243: * Returns this property. This property allows figuring out where to place
244: * labels if one wants to label this pie, more than is provided by the legend
245: * in PieChartArea. One can use a
246: * HorizontalTextListArea for the TOP and BOTTOM labels; and a
247: * VerticalTextListArea for the LEFT and RIGHT labels.
248: * This information helps them figure out how many and which labels belong in
249: * each text list.
250: * @return The number of sectors in the quarters. Actually, the number of
251: * center points of sectors in this quarter. Quarters are defined as follows:
252: * TOP = 135 to 45, LEFT = 45 to 315, BOTTOM = 315 to 225, RIGHT = 225 to 135.
253: */
254: final int[] getNumSectorsInQuarters() {
255:
256: updatePieArea();
257: return numSectorsInQuarters;
258: }
259:
260: /**
261: * Returns how far into the sector the depth of the points from the
262: * getPointsInSectors() method are.
263: * @return The ratio on the radius of the circle.
264: */
265: final float getSectorPointDepthRatio() {
266:
267: return sectorPointDepthRatio;
268: }
269:
270: /**
271: * Returns a point in each sector at the depth specified by
272: * setSectorsPointDepthRatio(float).
273: * @return Point[] The points inside the sectors.
274: */
275: final Point[] getPointsInSectors() {
276:
277: updatePieArea();
278: return pointsInSectors;
279: }
280:
281: /**
282: * Returns a point in out of each sector at the location indicated by
283: * setSectorsPointExternalRatio(float).
284: * @return Point[] The points outside the sectors.
285: */
286: final Point[] getPointsOutSectors() {
287: updatePieArea();
288: return pointsOutSectors;
289: }
290:
291: /**
292: * Resets the model for this class. The model is used for shrinking and
293: * growing of its components based on the maximum size of this class. If this
294: * method is called, then the next time the maximum size is set, this classes
295: * model maximum size will be made equal to the new maximum size. Effectively
296: * what this does is ensure that whenever this objects maximum size is equal
297: * to the one given, then all of the components will take on their default
298: * model sizes. Note: This is only useful when auto model max sizing is
299: * disabled.
300: * @param reset True causes the max model size to be set upon the next max
301: * sizing.
302: */
303: final void resetPieAreaModel(boolean reset) {
304:
305: needsUpdate = true;
306: resetAreaModel(reset);
307: }
308:
309: /**
310: * Indicates whether some property of this class has changed.
311: * @return True if some property has changed.
312: */
313: final boolean getPieAreaNeedsUpdate() {
314:
315: return (needsUpdate || getAreaNeedsUpdate());
316: }
317:
318: /**
319: * Updates this parent's variables, and this' variables.
320: */
321: final void updatePieArea() {
322:
323: if (getPieAreaNeedsUpdate()) {
324: updateArea();
325: update();
326: }
327: needsUpdate = false;
328: }
329:
330: /**
331: * Paints all the components of this class. First all variables are updated.
332: * @param g2D The graphics context for calculations and painting.
333: */
334: final void paintComponent(Graphics2D g2D) {
335:
336: updatePieArea();
337: super .paintComponent(g2D);
338:
339: for (int i = 0; i < numSectors; ++i) {
340: g2D.setPaint(paints[i]);
341: g2D.fill(sectors[i]);
342: }
343:
344: if (outlineSectors) {
345: for (int i = 0; i < numSectors; ++i) {
346: g2D.setColor(outlineSectorsColor);
347: g2D.draw(sectors[i]);
348: }
349: }
350: }
351:
352: private void update() {
353:
354: if (!getAutoSize(MIN) && !customSizing) {
355: piePrefSpaceSize = applyRatio(piePrefSpaceSize,
356: getRatio(LESSER));
357: int tempPrefSize = piePrefSpaceSize < getSpaceSize(MAX).width ? piePrefSpaceSize
358: : getSpaceSize(MAX).width;
359: tempPrefSize = piePrefSpaceSize < getSpaceSize(MAX).height ? piePrefSpaceSize
360: : getSpaceSize(MAX).height;
361: setSpaceSize(MIN, new Dimension(tempPrefSize, tempPrefSize));
362: } else if (!getAutoSize(MIN) && customSizing) {
363: setSpaceSize(MIN, customSpaceSize);
364: }
365:
366: int width = getSpaceSize(MIN).width;
367: int height = getSpaceSize(MIN).height;
368: int minSpaceSizeSide = width < height ? width : height;
369:
370: Point location = getSpaceSizeLocation(MIN);
371: int locationX = location.x
372: + (int) ((width - minSpaceSizeSide) / 2f);
373: int locationY = location.y
374: + (int) ((height - minSpaceSizeSide) / 2f);
375:
376: numSectors = dataset.length;
377: sectors = new Arc2D.Float[numSectors];
378:
379: float total = 0f;
380: for (int i = 0; i < numSectors; ++i) {
381: total = total + dataset[i];
382: }
383:
384: if (total != 0f) {
385: float end = 135f;
386: float begin = 135f;
387: float extent = 0f;
388: for (int i = 0; i < (numSectors - 1); ++i) {
389: extent = (dataset[i] / total) * 360f;
390: begin = end - extent;
391: sectors[i] = new Arc2D.Float(locationX, locationY,
392: minSpaceSizeSide, minSpaceSizeSide, begin,
393: extent, Arc2D.PIE);
394: end = begin;
395: }
396: extent = (dataset[numSectors - 1] / total) * 360f;
397: begin = 135f;
398: sectors[numSectors - 1] = new Arc2D.Float(locationX,
399: locationY, minSpaceSizeSide, minSpaceSizeSide,
400: begin, extent, Arc2D.PIE);
401: } else if (numSectors != 0) {
402: float end = 135f;
403: float begin = 135f;
404: float extent = (1f / numSectors) * 360f;
405: for (int i = 0; i < (numSectors - 1); ++i) {
406: begin = end - extent;
407: sectors[i] = new Arc2D.Float(locationX, locationY,
408: minSpaceSizeSide, minSpaceSizeSide, begin,
409: extent, Arc2D.PIE);
410: end = begin;
411: }
412: begin = 135f;
413: sectors[numSectors - 1] = new Arc2D.Float(locationX,
414: locationY, minSpaceSizeSide, minSpaceSizeSide,
415: begin, extent, Arc2D.PIE);
416: }
417:
418: pointsInSectors = new Point[numSectors];
419: float radius = minSpaceSizeSide / 2f;
420: float centerX = locationX + radius;
421: float centerY = locationY + radius;
422: for (int i = 0; i < numSectors; ++i) {
423: float begin = sectors[i].start;
424: float extent = sectors[i].extent;
425: float theta = begin + extent / 2f;
426: float offsetX = (float) ((1f - sectorPointDepthRatio)
427: * radius * Math.cos(Math.toRadians(theta)));
428: float offsetY = (float) ((1f - sectorPointDepthRatio)
429: * radius * Math.sin(Math.toRadians(theta)));
430: float x = centerX + offsetX;
431: float y = centerY - offsetY;
432: pointsInSectors[i] = new Point(Math.round(x), Math.round(y));
433: }
434:
435: int gapThickness = getGapThickness();
436: pointsOutSectors = new Point[numSectors];
437: float outOffset = sectorGapPointRatio * gapThickness;
438: for (int i = 0; i < numSectors; ++i) {
439:
440: float begin = sectors[i].start;
441: float extent = sectors[i].extent;
442: float theta = begin + extent / 2f;
443: float offsetX = (float) ((radius + outOffset) * Math
444: .cos(Math.toRadians(theta)));
445: float offsetY = (float) ((radius + outOffset) * Math
446: .sin(Math.toRadians(theta)));
447: float x = centerX + offsetX;
448: float y = centerY - offsetY;
449: pointsOutSectors[i] = new Point(Math.round(x), Math
450: .round(y));
451: }
452:
453: numSectorsInQuarters[TOP] = 0;
454: numSectorsInQuarters[RIGHT] = 0;
455: numSectorsInQuarters[BOTTOM] = 0;
456: numSectorsInQuarters[LEFT] = 0;
457:
458: boolean topDone = false;
459:
460: for (int i = 0; i < numSectors; ++i) {
461:
462: float begin = sectors[i].start;
463: float extent = sectors[i].extent;
464: begin = (begin + 720) % 360;
465: extent = (extent + 720) % 360;
466: float angle = begin + (extent / 2);
467:
468: if (angle <= 135 && angle > 45) {
469: if (!topDone)
470: ++numSectorsInQuarters[TOP];
471: else
472: ++numSectorsInQuarters[LEFT];
473: } else if (angle > 135 && angle <= 225) {
474: ++numSectorsInQuarters[LEFT];
475: topDone = true;
476: } else if (angle > 225 && angle <= 315) {
477: ++numSectorsInQuarters[BOTTOM];
478: topDone = true;
479: } else {
480: ++numSectorsInQuarters[RIGHT];
481: topDone = true;
482: }
483: }
484:
485: paints = new Paint[colors.length];
486: for (int i = 0; i < paints.length; ++i) {
487: if (lightSource == TOP) {
488: paints[i] = new GradientPaint(locationX, locationY,
489: colors[i].brighter(), locationX, locationY
490: + height, colors[i]);
491: } else if (lightSource == BOTTOM) {
492: paints[i] = new GradientPaint(locationX, locationY,
493: colors[i], locationX, locationY + height,
494: colors[i].brighter());
495: } else if (lightSource == LEFT) {
496: paints[i] = new GradientPaint(locationX, locationY,
497: colors[i].brighter(), locationX + width,
498: locationY, colors[i]);
499: } else if (lightSource == RIGHT) {
500: paints[i] = new GradientPaint(locationX, locationY,
501: colors[i], locationX + width, locationY,
502: colors[i].brighter());
503: } else {
504: paints[i] = colors[i];
505: }
506: }
507: }
508: }
|