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:
026: /**
027: * A titled pie chart with legend exactly like PieChart2D with one difference.
028: * This chart does not keep the title near to it, when auto min sizing is
029: * disabled. In this scenario, the title would be near the top of the max size
030: * and the chart would be centered within the left over space. This is not
031: * desireable. PieChart2D forces the title as near as possible to the chart
032: * under unless explicitly set not to. PieChart2D uses this class. It
033: * creates one of these charts with min sizing enabled. That produces a
034: * chart that is where the title is forced near the chart, and the chart
035: * is centered in the middle of the area. But really, if any settings of the
036: * chart need to be changed they are generally changed here, or in one of its
037: * components available through a get method.
038: */
039: final class PieChartArea extends ChartArea {
040:
041: private static final int LABEL = 0;
042: private static final int PIE = 1;
043:
044: private PieInfoArea pieLabels;
045: private float pieToWidthRatio;
046: private float pieToHeightRatio;
047: private float pieLabelsToWidthRatio;
048: private float pieLabelsToHeightRatio;
049: private float[] dataset;
050: private Dimension prefSize;
051: private boolean needsUpdate;
052:
053: private boolean linesExistence;
054: private GeneralPath[] lines;
055: private int linesThicknessModel;
056: private Color linesColor;
057: private BasicStroke linesStroke;
058:
059: private boolean lineDotsExistence;
060: private Ellipse2D.Float[][] lineDots;
061: private int lineDotsThicknessModel;
062: private Color lineDotsColor;
063:
064: private int tempX, tempY;
065:
066: /**
067: * Creates a Pie Chart Area with default values.
068: */
069: PieChartArea() {
070:
071: pieLabels = new PieInfoArea();
072: prefSize = new Dimension();
073: needsUpdate = true;
074:
075: setPieLabelsToWidthRatio(.50f);
076: setPieLabelsToHeightRatio(.25f);
077:
078: setLabelsLinesExistence(true);
079: setLabelsLinesThicknessModel(1);
080: setLabelsLinesColor(Color.black);
081: setLabelsLineDotsExistence(true);
082: setLabelsLineDotsThicknessModel(4);
083: setLabelsLineDotsColor(Color.black);
084: resetPieChartAreaModel(true);
085: }
086:
087: /**
088: * The dataset to plot.
089: * @param set The set of data.
090: */
091: final void setDataset(float[] d) {
092:
093: needsUpdate = true;
094: dataset = d;
095: }
096:
097: /**
098: * Sets the existence of the dots on the lines between the pie sector labels
099: * and the pie sectors.
100: * @param existence If true, the dots will be painted.
101: */
102: final void setLabelsLineDotsExistence(boolean existence) {
103:
104: lineDotsExistence = existence;
105: }
106:
107: /**
108: * Returns the existence of the dots on the lines between the pie sector
109: * labels and the pie sectors.
110: * @return If true, the dots will be painted.
111: */
112: final boolean getLabelsLineDotsExistence() {
113:
114: return lineDotsExistence;
115: }
116:
117: /**
118: * Sets the model thickness of the dots on the lines between the pie sector
119: * labels and the pie sectors.
120: * @param thickness The model thickness of the dots.
121: */
122: final void setLabelsLineDotsThicknessModel(int thickness) {
123:
124: needsUpdate = true;
125: lineDotsThicknessModel = thickness;
126: }
127:
128: /**
129: * Returns the model thickness of the dots on the lines between the pie sector
130: * labels and the pie sectors.
131: * @return The model thickness of the dots.
132: */
133: final int getLabelsLineDotsThicknessModel() {
134:
135: return lineDotsThicknessModel;
136: }
137:
138: /**
139: * Sets the color of the dots on the lines between the pie sector labels and
140: * the pie sectors.
141: * @param color The color of the dots.
142: */
143: final void setLabelsLineDotsColor(Color color) {
144:
145: needsUpdate = true;
146: lineDotsColor = color;
147: }
148:
149: /**
150: * Returns the color of the dots on the lines between the pie sector labels
151: * and the pie sectors.
152: * @return The color of the dots.
153: */
154: final Color getLabelsLineDotsColor() {
155:
156: return lineDotsColor;
157: }
158:
159: /**
160: * Sets the existence of the lines between the pie sector labels and the pie
161: * sectors.
162: * @param existence If true, the lines will be painted.
163: */
164: final void setLabelsLinesExistence(boolean existence) {
165:
166: linesExistence = existence;
167: }
168:
169: /**
170: * Sets the model thickness of the lines between the pie sector labels and the
171: * pie sectors.
172: * @param thickness The model thickness of the lines.
173: */
174: final void setLabelsLinesThicknessModel(int thickness) {
175:
176: needsUpdate = true;
177: linesThicknessModel = thickness;
178: }
179:
180: /**
181: * Return the model thickness of the lines between the pie sector labels and
182: * the pie sectors.
183: * @return The model thickness of the lines.
184: */
185: final int getLabelsLinesThicknessModel() {
186:
187: return linesThicknessModel;
188: }
189:
190: /**
191: * Sets the color of the lines between the pie sector labels and the pie
192: * sectors.
193: * @param color The color of the lines.
194: */
195: final void setLabelsLinesColor(Color color) {
196:
197: needsUpdate = true;
198: linesColor = color;
199: }
200:
201: /**
202: * Returns the color of the lines between the pie sector labels and the pie
203: * sectors.
204: * @return The color of the lines.
205: */
206: final Color getLabelsLinesColor() {
207:
208: return linesColor;
209: }
210:
211: /**
212: * Sets the width to be shared by all the labels, beyond the width of the
213: * pie. For instance, if there are labels on the left and the right of the
214: * pie, then their max widths will be equal and will each be half of the width
215: * indicated by applying the ratio to the max width of the chart.
216: * @param ratio The ratio for indicating the max widths of the labels.
217: */
218: final void setPieLabelsToWidthRatio(float ratio) {
219:
220: needsUpdate = true;
221: pieLabelsToWidthRatio = ratio;
222: }
223:
224: /**
225: * Sets the height to be shared by all the labels, beyond the height of the
226: * pie. For instance, if there are lables on the top and the bottom of the
227: * pie, then their max heights will be equal and will each be half of the
228: * height indicated by applying the ratio to the max width of the chart.
229: * @param ratio The ratio for indicating the max heights of the labels.
230: */
231: final void setPieLabelsToHeightRatio(float ratio) {
232:
233: needsUpdate = true;
234: pieLabelsToHeightRatio = ratio;
235: }
236:
237: /**
238: * Returns the minimum size that the chart would need if it was to be redrawn,
239: * the "preferred" size. The preferred size is the minimum size which would
240: * need to be set as the maxmodel size of the chart, if the chart was to be
241: * redrawn (assuming magnification is disabled).
242: * @param g2D The graphics context for calculations and painting.
243: * @return The size of the minimum maxmodel for a redraw.
244: */
245: final Dimension getPrefSize(Graphics2D g2D) {
246:
247: if (getPieChartAreaNeedsUpdate())
248: updatePieChartArea(g2D);
249: return prefSize;
250: }
251:
252: /**
253: * Returns the pie labels component of this chart in order to allow
254: * customization of it.
255: * @return The pie labels area.
256: */
257: final PieInfoArea getPieInfoArea() {
258: return pieLabels;
259: }
260:
261: /**
262: * Resets the model for this class. The model is used for shrinking and
263: * growing of its components based on the maximum size of this class. If this
264: * method is called, then the next time the maximum size is set, this classes
265: * model maximum size will be made equal to the new maximum size. Effectively
266: * what this does is ensure that whenever this objects maximum size is equal
267: * to the one given, then all of the components will take on their default
268: * model sizes. Note: This is only useful when auto model max sizing is
269: * disabled.
270: * @param reset True causes the max model to be reset upon next max sizing.
271: */
272: final void resetPieChartAreaModel(boolean reset) {
273:
274: resetChartAreaModel(reset);
275: pieLabels.resetPieInfoAreaModel(reset);
276: }
277:
278: /**
279: * Indicates whether some property of this class has changed.
280: * @return True if some property has changed.
281: */
282: final boolean getPieChartAreaNeedsUpdate() {
283:
284: if (needsUpdate || getChartAreaNeedsUpdate()
285: || pieLabels.getPieInfoAreaNeedsUpdate())
286: return true;
287: return false;
288: }
289:
290: /**
291: * Updates this parent's variables, and this' variables.
292: * @param g2D The graphics context to use for calculations.
293: */
294: final void updatePieChartArea(Graphics2D g2D) {
295:
296: if (getPieChartAreaNeedsUpdate()) {
297: updateChartArea(g2D);
298: update(g2D);
299: pieLabels.updatePieInfoArea(g2D);
300: }
301: needsUpdate = false;
302: }
303:
304: /**
305: * Paints pie chart area components.
306: * @param g2D The graphics context to use for calculations.
307: */
308: final void paintComponent(Graphics2D g2D) {
309:
310: updatePieChartArea(g2D);
311: super .paintComponent(g2D);
312: pieLabels.paintComponent(g2D);
313:
314: if (pieLabels.getPieLabelsExistence() && linesExistence) {
315: g2D.setColor(linesColor);
316: g2D.setStroke(linesStroke);
317: for (int i = 0; i < dataset.length; ++i) {
318: g2D.draw(lines[i]);
319: }
320: }
321:
322: if (pieLabels.getPieLabelsExistence() && lineDotsExistence) {
323: g2D.setColor(lineDotsColor);
324: for (int i = 0; i < dataset.length; ++i) {
325: g2D.fill(lineDots[i][LABEL]);
326: g2D.fill(lineDots[i][PIE]);
327: }
328: }
329: }
330:
331: private void update(Graphics2D g2D) {
332:
333: LegendArea legend = getLegend();
334:
335: float widthRatio = getRatio(WIDTH);
336: float heightRatio = getRatio(HEIGHT);
337: pieLabels.setCustomRatio(WIDTH, true, widthRatio);
338: pieLabels.setCustomRatio(HEIGHT, true, heightRatio);
339: legend.setCustomRatio(WIDTH, true, widthRatio);
340: legend.setCustomRatio(HEIGHT, true, heightRatio);
341:
342: pieLabels.setRawLabelsPrecision(getLabelsPrecisionNum());
343: pieLabels.setDatasetColors(getDatasetColors());
344: pieLabels.setDataset(dataset);
345:
346: Rectangle maxBounds = getMaxEntitledSpaceBounds(g2D);
347:
348: int betweenChartAndLegendGapThickness = 0;
349: int availableWidth = maxBounds.width;
350: if (getBetweenChartAndLegendGapExistence()
351: && getLegendExistence()) {
352:
353: betweenChartAndLegendGapThickness = applyRatio(
354: getBetweenChartAndLegendGapThicknessModel(),
355: getRatio(WIDTH));
356: betweenChartAndLegendGapThickness = betweenChartAndLegendGapThickness <= availableWidth ? betweenChartAndLegendGapThickness
357: : availableWidth;
358: availableWidth -= betweenChartAndLegendGapThickness;
359: }
360:
361: int legendWidth = 0, legendHeight = 0;
362: float legendToWidthRatio = getLegendToWidthRatio();
363: float legendToHeightRatio = getLegendToHeightRatio();
364: if (getLegendExistence()) {
365: legendWidth = (int) (legendToWidthRatio * availableWidth);
366: legendHeight = (int) (legendToHeightRatio * maxBounds.height);
367: }
368: legend.setSize(MAX, new Dimension(legendWidth, legendHeight));
369: legend.updateLegendArea(g2D);
370: legendWidth = legend.getSize(MIN).width;
371: legendHeight = legend.getSize(MIN).height;
372:
373: int pieLabelsWidth = 0, pieLabelsHeight = 0;
374: pieLabelsWidth = (int) (pieLabelsToWidthRatio * availableWidth);
375: pieLabelsHeight = (int) (pieLabelsToHeightRatio * maxBounds.height);
376: pieLabels.setSize(MAX, new Dimension(pieLabelsWidth,
377: pieLabelsHeight));
378: pieLabels.setCustomSize(false, new Dimension());
379: pieLabels.updatePieInfoArea(g2D);
380: pieLabelsWidth = pieLabels.getSize(MIN).width;
381: pieLabelsHeight = pieLabels.getSize(MIN).height;
382:
383: int width = 0, height = 0;
384: width = pieLabelsWidth + betweenChartAndLegendGapThickness
385: + legendWidth;
386: height = pieLabelsHeight > legendHeight ? pieLabelsHeight
387: : legendHeight;
388:
389: if (getAutoSetLayoutRatios()) {
390:
391: width -= betweenChartAndLegendGapThickness;
392:
393: pieLabelsToWidthRatio = width > 0 ? pieLabelsWidth
394: / (float) width : 0f;
395: pieLabelsToWidthRatio = pieLabelsToWidthRatio < 1f ? pieLabelsToWidthRatio
396: : 1f;
397: pieLabelsToHeightRatio = height > 0 ? pieLabelsHeight
398: / (float) height : 0f;
399: pieLabelsToHeightRatio = pieLabelsToHeightRatio < 1f ? pieLabelsToHeightRatio
400: : 1f;
401:
402: legendToWidthRatio = legendToWidthRatio != 0f ? 1f - pieLabelsToWidthRatio
403: : 0f;
404: legendToHeightRatio = legendToHeightRatio != 0f ? 1f : 0f;
405:
406: if (pieLabelsToWidthRatio <= 0f
407: || pieLabelsToHeightRatio <= 0f) {
408: pieLabelsToWidthRatio = pieLabelsToHeightRatio = 0f;
409: }
410:
411: if (legendToWidthRatio <= 0f || legendToHeightRatio <= 0f) {
412: legendToWidthRatio = legendToHeightRatio = 0f;
413: }
414:
415: setPieLabelsToWidthRatio(pieLabelsToWidthRatio);
416: setPieLabelsToHeightRatio(pieLabelsToHeightRatio);
417:
418: setLegendToWidthRatio(legendToWidthRatio);
419: setLegendToHeightRatio(legendToHeightRatio);
420:
421: setAutoSetLayoutRatios(false);
422:
423: width += betweenChartAndLegendGapThickness;
424: }
425:
426: Dimension titleSize = getTitleSize(MIN, g2D);
427: int titleGap = getBetweenTitleAndSpaceGapThickness(g2D);
428: int prefWidth1 = width + 2 * getOffsetThickness();
429: int prefWidth2 = titleSize.width + 2 * getOffsetThickness();
430: int prefWidth = prefWidth1 > prefWidth2 ? prefWidth1
431: : prefWidth2;
432: int prefHeight = height + 2 * getOffsetThickness()
433: + titleSize.height + titleGap;
434: prefSize = new Dimension((int) (1.3f * prefWidth),
435: (int) (1.3f * prefHeight));
436:
437: if (getAutoSize(MIN)) {
438:
439: int deficientWidth = maxBounds.width - width;
440: int deficientHeight = maxBounds.height - pieLabelsHeight;
441: int deficient = deficientWidth < deficientHeight ? deficientWidth
442: : deficientHeight;
443:
444: pieLabels.setCustomSize(true,
445: new Dimension(pieLabelsWidth + deficientWidth,
446: pieLabelsHeight + deficientHeight));
447:
448: pieLabels.updatePieInfoArea(g2D);
449:
450: pieLabelsWidth = pieLabels.getSize(MIN).width;
451: pieLabelsHeight = pieLabels.getSize(MIN).height;
452:
453: width = pieLabelsWidth + betweenChartAndLegendGapThickness
454: + legendWidth;
455: height = pieLabelsHeight > legendHeight ? pieLabelsHeight
456: : legendHeight;
457: }
458:
459: Rectangle minBounds = new Rectangle();
460: minBounds.setSize(width, height);
461:
462: if (!getAutoSize(MIN)) {
463:
464: int minWidth = titleSize.width > minBounds.width ? titleSize.width
465: : minBounds.width;
466: int minHeight;
467: if (titleSize.height > 0 && minBounds.height > 0) {
468: minHeight = titleSize.height + titleGap
469: + minBounds.height;
470: } else
471: minHeight = titleSize.height + minBounds.height;
472: setSpaceSize(MIN, new Dimension(minWidth, minHeight));
473: }
474:
475: int x = maxBounds.x + (maxBounds.width - minBounds.width) / 2;
476: int y = maxBounds.y + (maxBounds.height - minBounds.height) / 2;
477: minBounds.setLocation(x, y);
478:
479: if (getTitleSqueeze()) {
480: int titleX = maxBounds.x
481: + (maxBounds.width - titleSize.width) / 2;
482: int titleY = minBounds.y - titleSize.height - titleGap;
483: setTitleLocation(new Point(titleX, titleY));
484: }
485:
486: int legendX, legendY, pieLabelsX, pieLabelsY;
487: pieLabelsX = minBounds.x;
488: legendX = minBounds.x + minBounds.width - legendWidth
489: + legend.getOffsetThickness();
490:
491: if (pieLabelsHeight > legendHeight) {
492: pieLabelsY = minBounds.y;
493: legendY = pieLabelsY
494: + (pieLabelsHeight - legend.getSpaceSize(MIN).height)
495: / 2;
496: } else {
497: legendY = minBounds.y + legend.getOffsetThickness();
498: pieLabelsY = legendY
499: + (legendHeight - pieLabels.getSpaceSize(MIN).height)
500: / 2;
501: }
502:
503: pieLabels.setSpaceSizeLocation(MIN, new Point(pieLabelsX,
504: pieLabelsY));
505: pieLabels.updatePieInfoArea(g2D);
506:
507: legend.setSpaceSizeLocation(MIN, new Point(legendX, legendY));
508: legend.updateLegendArea(g2D);
509:
510: PieArea pie = pieLabels.getPieArea();
511:
512: Point[] linesLabel = pieLabels.getPointsNearLabels(g2D);
513: Point[] linesGap = pie.getPointsOutSectors();
514: Point[] linesSector = pie.getPointsInSectors();
515: lines = new GeneralPath[dataset.length];
516: for (int i = 0; i < dataset.length; ++i) {
517:
518: lines[i] = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 3);
519: lines[i].moveTo(linesSector[i].x, linesSector[i].y);
520: lines[i].lineTo(linesGap[i].x, linesGap[i].y);
521: lines[i].lineTo(linesLabel[i].x, linesLabel[i].y);
522: }
523: int tempLinesThicknessModel = linesThicknessModel > 2 * pieLabels
524: .getLabelsPointsGapThicknessModel() ? 2 * pieLabels
525: .getLabelsPointsGapThicknessModel()
526: : linesThicknessModel;
527: tempLinesThicknessModel = tempLinesThicknessModel > 2 * pie
528: .getGapThicknessModel() ? 2 * pie
529: .getGapThicknessModel() : tempLinesThicknessModel;
530: int linesThickness = applyRatio(tempLinesThicknessModel,
531: getRatio(LESSER));
532: linesThickness = linesThickness > pie.getGapThickness() ? pie
533: .getGapThickness() : linesThickness;
534: float[] style = { 10.0f, 0.0f };
535: linesStroke = new BasicStroke((float) linesThickness,
536: BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0f,
537: style, 0.0f);
538:
539: lineDots = new Ellipse2D.Float[dataset.length][2];
540: int tempLineDotsThicknessModel = lineDotsThicknessModel > 2 * pieLabels
541: .getLabelsPointsGapThicknessModel() ? 2 * pieLabels
542: .getLabelsPointsGapThicknessModel()
543: : lineDotsThicknessModel;
544: tempLineDotsThicknessModel = tempLineDotsThicknessModel > 2 * pie
545: .getGapThicknessModel() ? 2 * pie
546: .getGapThicknessModel() : tempLineDotsThicknessModel;
547: int lineDotsThickness = applyRatio(tempLineDotsThicknessModel,
548: getRatio(LESSER));
549: for (int i = 0; i < dataset.length; ++i) {
550:
551: lineDots[i][PIE] = new Ellipse2D.Float(linesSector[i].x
552: - lineDotsThickness / 2f, linesSector[i].y
553: - lineDotsThickness / 2f, lineDotsThickness,
554: lineDotsThickness);
555: lineDots[i][LABEL] = new Ellipse2D.Float(linesLabel[i].x
556: - lineDotsThickness / 2f, linesLabel[i].y
557: - lineDotsThickness / 2f, lineDotsThickness,
558: lineDotsThickness);
559: }
560: }
561: }
|