001: /* Chart.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Mon Jul 10 16:57:48 2006, Created by henrichen
010: }}IS_NOTE
011:
012: Copyright (C) 2006 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul;
020:
021: import org.zkoss.zk.ui.Component;
022: import org.zkoss.zk.ui.Executions;
023: import org.zkoss.zk.ui.UiException;
024: import org.zkoss.zk.ui.event.Event;
025: import org.zkoss.zk.ui.event.Events;
026: import org.zkoss.zk.ui.event.EventListener;
027: import org.zkoss.zul.impl.ChartEngine;
028: import org.zkoss.zul.event.ChartDataEvent;
029: import org.zkoss.zul.event.ChartDataListener;
030: import org.zkoss.zul.event.ChartAreaListener;
031: import org.zkoss.image.AImage;
032: import org.zkoss.lang.Classes;
033: import org.zkoss.lang.Objects;
034: import org.zkoss.lang.Strings;
035:
036: import java.util.Date;
037: import java.util.TimeZone;
038: import java.io.ByteArrayOutputStream;
039: import java.awt.image.BufferedImage;
040: import java.awt.Paint;
041:
042: /**
043: * The generic chart component. Developers set proper chart type, data model,
044: * and the threeD (3D) attribute to draw proper chart. The model and type must
045: * match to each other; or the result is unpredictable. The 3D chart is not supported
046: * on all chart type.
047: *
048: * <table>
049: * <tr><th>type</th><th>model</th><th>3D</th></tr>
050: * <tr><td>pie</td><td>{@link PieModel}</td><td>o</td></tr>
051: * <tr><td>ring</td><td>{@link PieModel}</td><td>x</td></tr>
052: * <tr><td>bar</td><td>{@link CategoryModel}</td><td>o</td></tr>
053: * <tr><td>line</td><td>{@link CategoryModel} or {@link XYModel}</td><td>o</td></tr>
054: * <tr><td>area</td><td>{@link CategoryModel} or {@link XYModel}</td><td>x</td></tr>
055: * <tr><td>stacked_bar</td><td>{@link CategoryModel}</td><td>o</td></tr>
056: * <tr><td>stacked_area</td><td>{@link CategoryModel} or {@link XYModel}</td><td>x</td></tr>
057: * <tr><td>waterfall</td><td>{@link CategoryModel}</td><td>x</td></tr>
058: * <tr><td>polar</td><td>{@link XYModel}</td><td>x</td></tr>
059: * <tr><td>scatter</td><td>{@link XYModel}</td><td>x</td></tr>
060: * <tr><td>time_series</td><td>{@link XYModel}</td><td>x</td></tr>
061: * <tr><td>polar</td><td>{@link XYModel}</td><td>x</td></tr>
062: * <tr><td>step_area</td><td>{@link XYModel}</td><td>x</td></tr>
063: * <tr><td>step</td><td>{@link XYModel}</td><td>x</td></tr>
064: * <tr><td>histogram</td><td>{@link XYModel}</td><td>x</td></tr>
065: * <tr><td>candlestick</td><td>{@link HiLoModel}</td><td>x</td></tr>
066: * <tr><td>hilow</td><td>{@link HiLoModel}</td><td>x</td></tr>
067: * </table>
068: *
069: * @see ChartEngine
070: * @see ChartModel
071: * @author henrichen
072: */
073: public class Chart extends Imagemap {
074: //chart type
075: public static final String PIE = "pie";
076: public static final String RING = "ring";
077: public static final String BAR = "bar";
078: public static final String LINE = "line";
079: public static final String AREA = "area";
080: public static final String STACKED_BAR = "stacked_bar";
081: public static final String STACKED_AREA = "stacked_area";
082: public static final String WATERFALL = "waterfall";
083: public static final String POLAR = "polar";
084: public static final String SCATTER = "scatter";
085: public static final String TIME_SERIES = "time_series";
086: public static final String STEP = "step";
087: public static final String STEP_AREA = "step_area";
088: public static final String HISTOGRAM = "histogram";
089: public static final String CANDLESTICK = "candlestick";
090: public static final String HIGHLOW = "highlow";
091:
092: //Time Series Chart Period
093: public static final String YEAR = "year";
094: public static final String QUARTER = "quarter";
095: public static final String MONTH = "month";
096: public static final String WEEK = "week";
097: public static final String DAY = "day";
098: public static final String HOUR = "hour";
099: public static final String MINUTE = "minute";
100: public static final String SECOND = "second";
101: public static final String MILLISECOND = "millisecond";
102:
103: //control variable
104: private boolean _smartDrawChart; //whether post the smartDraw event already?
105: private transient ChartDataListener _dataListener;
106: private transient EventListener _smartDrawChartListener; //the smartDrawListner
107:
108: private String _type = PIE; //chart type (pie, ring, bar, line, xy, etc)
109: private boolean _threeD; //whether a 3D chart
110:
111: //chart related attributes
112: private String _title; //chart title
113: private int _intWidth = 400; //default to 400
114: private int _intHeight = 200; //default to 200
115: private String _xAxis;
116: private String _yAxis;
117: private boolean _showLegend = true; // wether show legend
118: private boolean _showTooltiptext = true; //wether show tooltiptext
119: private String _orient = "vertical"; //orient
120: private ChartAreaListener _areaListener; //callback function when chart area changed
121: private String _paneColor; // pane's color
122: private int[] _paneRGB; //pane red, green, blue (0 ~ 255, 0 ~ 255, 0 ~ 255)
123: private int _paneAlpha = 255; //pane alpha transparency (0 ~ 255, default to 255)
124:
125: //plot related attributes
126: private int _fgAlpha = 255; //foreground alpha transparency (0 ~ 255, default to 255)
127: private String _bgColor;
128: private int[] _bgRGB; //background red, green, blue (0 ~ 255, 0 ~ 255, 0 ~ 255)
129: private int _bgAlpha = 255; //background alpha transparency (0 ~ 255, default to 255)
130:
131: //Time Series Chart related attributes
132: private TimeZone _tzone;
133: private String _period;
134:
135: //chart data model
136: private ChartModel _model; //chart data model
137:
138: //chart engine
139: private ChartEngine _engine; //chart engine. model and engine is related
140:
141: public Chart() {
142: setWidth("500px");
143: setHeight("250px");
144: }
145:
146: /**
147: * Set the chart's type (Chart.PIE, Chart.BAR, Chart.LINE, etc.).
148: *
149: * <p>Default: pie.
150:
151: */
152: public void setType(String type) {
153: if (Objects.equals(_type, type)) {
154: return;
155: }
156: _type = type;
157: smartDrawChart();
158: }
159:
160: /**
161: * Get the chart's type.
162: */
163: public String getType() {
164: return _type;
165: }
166:
167: /**
168: * Set true to show three dimensional graph (If a type of chart got no 3d peer, this is ignored).
169: */
170: public void setThreeD(boolean b) {
171: if (_threeD == b) {
172: return;
173: }
174: _threeD = b;
175: smartDrawChart();
176: }
177:
178: /**
179: * Whether a 3d chart.
180: */
181: public boolean isThreeD() {
182: return _threeD;
183: }
184:
185: /**
186: * Set the chart's title.
187: * @param title the chart's title.
188: *
189: */
190: public void setTitle(String title) {
191: if (Objects.equals(_title, title)) {
192: return;
193: }
194: _title = title;
195: smartDrawChart();
196: }
197:
198: /**
199: * Get the chart's title.
200: */
201: public String getTitle() {
202: return _title;
203: }
204:
205: /**
206: * Override super class to prepare the int width.
207: */
208: public void setWidth(String w) {
209: if (Objects.equals(w, getWidth())) {
210: return;
211: }
212: _intWidth = stringToInt(w);
213: super .setWidth(w);
214: smartDrawChart();
215: }
216:
217: /**
218: * Get the chart int width in pixel; to be used by the derived subclass.
219: */
220: public int getIntWidth() {
221: return _intWidth;
222: }
223:
224: /**
225: * Override super class to prepare the int height.
226: */
227: public void setHeight(String h) {
228: if (Objects.equals(h, getHeight())) {
229: return;
230: }
231: _intHeight = stringToInt(h);
232: super .setHeight(h);
233: smartDrawChart();
234: }
235:
236: /**
237: * Get the chart int width in pixel; to be used by the derived subclass.
238: */
239: public int getIntHeight() {
240: return _intHeight;
241: }
242:
243: /**
244: * Set the label in xAxis.
245: * @param label label in xAxis.
246: */
247: public void setXAxis(String label) {
248: if (Objects.equals(_xAxis, label)) {
249: return;
250: }
251: _xAxis = label;
252: smartDrawChart();
253: }
254:
255: /**
256: * Get the label in xAxis.
257: */
258: public String getXAxis() {
259: return _xAxis;
260: }
261:
262: /**
263: * Set the label in yAxis.
264: * @param label label in yAxis.
265: */
266: public void setYAxis(String label) {
267: if (Objects.equals(_yAxis, label)) {
268: return;
269: }
270: _yAxis = label;
271: smartDrawChart();
272: }
273:
274: /**
275: * Get the label in yAxis.
276: */
277: public String getYAxis() {
278: return _yAxis;
279: }
280:
281: /**
282: * whether show the chart's legend.
283: * @param showLegend true if want to show the legend (default to true).
284: */
285: public void setShowLegend(boolean showLegend) {
286: if (_showLegend == showLegend) {
287: return;
288: }
289: _showLegend = showLegend;
290: smartDrawChart();
291: }
292:
293: /**
294: * Check whether show the legend of the chart.
295: */
296: public boolean isShowLegend() {
297: return _showLegend;
298: }
299:
300: /**
301: * whether show the chart's tooltip.
302: * @param showTooltiptext true if want to pop the tooltiptext (default to true).
303: */
304: public void setShowTooltiptext(boolean showTooltiptext) {
305: if (_showTooltiptext == showTooltiptext) {
306: return;
307: }
308: _showTooltiptext = showTooltiptext;
309: smartDrawChart();
310: }
311:
312: /**
313: * Check whether show the tooltiptext.
314: */
315: public boolean isShowTooltiptext() {
316: return _showTooltiptext;
317: }
318:
319: /**
320: * Set the pane alpha (transparency, 0 ~ 255).
321: * @param alpha the transparency of pane color (0 ~ 255, default to 255 opaque).
322: */
323: public void setPaneAlpha(int alpha) {
324: if (alpha == _paneAlpha) {
325: return;
326: }
327: if (alpha > 255 || alpha < 0) {
328: alpha = 255;
329: }
330: _paneAlpha = alpha;
331: smartDrawChart();
332: }
333:
334: /**
335: * Get the pane alpha (transparency, 0 ~ 255, opacue).
336: */
337: public int getPaneAlpha() {
338: return _paneAlpha;
339: }
340:
341: /**
342: * Set the pane color of the chart.
343: * @param color in #RRGGBB format (hexdecimal).
344: */
345: public void setPaneColor(String color) {
346: if (Objects.equals(color, _paneColor)) {
347: return;
348: }
349: _paneColor = color;
350: if (_paneColor == null) {
351: _paneRGB = null;
352: } else {
353: _paneRGB = new int[3];
354: decode(_paneColor, _paneRGB);
355: }
356: smartDrawChart();
357: }
358:
359: /**
360: * Get the pane color of the chart (in string as #RRGGBB).
361: * null means default.
362: */
363: public String getPaneColor() {
364: return _paneColor;
365: }
366:
367: /**
368: * Get the pane color in int array (0: red, 1: green, 2:blue).
369: * null means default.
370: */
371: public int[] getPaneRGB() {
372: return _paneRGB;
373: }
374:
375: /**
376: * Set the foreground alpha (transparency, 0 ~ 255).
377: * @param alpha the transparency of foreground color (0 ~ 255, default to 255 opaque).
378: */
379: public void setFgAlpha(int alpha) {
380: if (alpha == _fgAlpha) {
381: return;
382: }
383:
384: if (alpha > 255 || alpha < 0) {
385: alpha = 255;
386: }
387: _fgAlpha = alpha;
388: smartDrawChart();
389: }
390:
391: /**
392: * Get the foreground alpha (transparency, 0 ~ 255, opacue).
393: */
394: public int getFgAlpha() {
395: return _fgAlpha;
396: }
397:
398: /**
399: * Set the background alpha (transparency, 0 ~ 255).
400: * @param alpha the transparency of background color (0 ~ 255, default to 255 opaque).
401: */
402: public void setBgAlpha(int alpha) {
403: if (alpha == _bgAlpha) {
404: return;
405: }
406: if (alpha > 255 || alpha < 0) {
407: alpha = 255;
408: }
409: _bgAlpha = alpha;
410: smartDrawChart();
411: }
412:
413: /**
414: * Get the background alpha (transparency, 0 ~ 255, opacue).
415: */
416: public int getBgAlpha() {
417: return _bgAlpha;
418: }
419:
420: /**
421: * Set the background color of the chart.
422: * @param color in #RRGGBB format (hexdecimal).
423: */
424: public void setBgColor(String color) {
425: if (Objects.equals(color, _bgColor)) {
426: return;
427: }
428: _bgColor = color;
429: if (_bgColor == null) {
430: _bgRGB = null;
431: } else {
432: _bgRGB = new int[3];
433: decode(_bgColor, _bgRGB);
434: }
435: smartDrawChart();
436: }
437:
438: /**
439: * Get the background color of the chart (in string as #RRGGBB).
440: * null means default.
441: */
442: public String getBgColor() {
443: return _bgColor;
444: }
445:
446: /**
447: * Get the background color in int array (0: red, 1: green, 2:blue).
448: * null means default.
449: */
450: public int[] getBgRGB() {
451: return _bgRGB;
452: }
453:
454: /**
455: * Set the chart orientation.
456: * @param orient vertical or horizontal (default to vertical)
457: */
458: public void setOrient(String orient) {
459: if (Objects.equals(orient, _orient)) {
460: return;
461: }
462: _orient = orient;
463: smartDrawChart();
464: }
465:
466: /**
467: * Get the chart orientation (vertical or horizontal)
468: */
469: public String getOrient() {
470: return _orient;
471: }
472:
473: /** Returns the time zone that this Time Series Chart belongs to, or null if
474: * the default time zone is used.
475: * <p>The default time zone is determined by {@link org.zkoss.util.TimeZones#getCurrent}.
476: */
477: public TimeZone getTimeZone() {
478: return _tzone;
479: }
480:
481: /** Sets the time zone that this Time Series Chart belongs to, or null if
482: * the default time zone is used.
483: * <p>The default time zone is determined by {@link org.zkoss.util.TimeZones#getCurrent}.
484: */
485: public void setTimeZone(TimeZone tzone) {
486: if (Objects.equals(tzone, _tzone)) {
487: return;
488: }
489: _tzone = tzone;
490: smartDrawChart();
491: }
492:
493: /** Returns the period used in Time Series Chart. The value can be
494: * "millisecond", "second", "minute", "hour", "day", "week", "month", "quarter", and "year".
495: * default is "millisecond" if not specified.
496: */
497: public String getPeriod() {
498: return _period;
499: }
500:
501: /** Sets the period used in Time Series Chart. The value can be
502: * "millisecond", "second", "minute", "hour", "day", "week", "month", "quarter", and "year".
503: */
504: public void setPeriod(String period) {
505: if (Objects.equals(period, _period)) {
506: return;
507: }
508: _period = period;
509: smartDrawChart();
510: }
511:
512: /** Returns the chart model associated with this chart, or null
513: * if this chart is not associated with any chart data model.
514: */
515: public ChartModel getModel() {
516: return _model;
517: }
518:
519: /** Sets the chart model associated with this chart.
520: * If a non-null model is assigned, no matter whether it is the same as
521: * the previous, it will always cause re-render.
522: *
523: * @param model the chart model to associate, or null to dis-associate
524: * any previous model.
525: * @exception UiException if failed to initialize with the model
526: */
527: public void setModel(ChartModel model) {
528: if (_model != model) {
529: initDataListener();
530: if (_model != null) {
531: _model.removeChartDataListener(_dataListener);
532: }
533: if (model != null) {
534: model.addChartDataListener(_dataListener);
535: }
536: _model = model;
537: }
538:
539: //Always redraw
540: smartDrawChart();
541: }
542:
543: /** Sets the model by use of a class name.
544: * It creates an instance automatically.
545: */
546: public void setModel(String clsnm) throws ClassNotFoundException,
547: NoSuchMethodException, IllegalAccessException,
548: InstantiationException,
549: java.lang.reflect.InvocationTargetException {
550: if (clsnm != null) {
551: setModel((ChartModel) Classes.newInstanceByThread(clsnm));
552: }
553: }
554:
555: /** Returns the implemetation chart engine.
556: * @exception UiException if failed to load the engine.
557: */
558: public ChartEngine getEngine() throws UiException {
559: if (_engine == null)
560: _engine = newChartEngine();
561: return _engine;
562: }
563:
564: /** Instantiates the default chart engine.
565: * It is called, if {@link #setEngine} is not called with non-null
566: * engine.
567: *
568: * <p>By default, it looks up the component attribute called
569: * chart-engine. If found, the value is assumed to be the class
570: * or the class name of the default engine (it must implement
571: * {@link ChartEngine}).
572: * If not found, {@link UiException} is thrown.
573: *
574: * <p>Derived class might override this method to provide your
575: * own default class.
576: *
577: * @exception UiException if failed to instantiate the engine
578: * @since 3.0.0
579: */
580: protected ChartEngine newChartEngine() throws UiException {
581: Object v = getAttribute("chart-engine");
582: if (v == null)
583: v = "org.zkoss.zkex.zul.impl.JFreeChartEngine";
584:
585: try {
586: final Class cls;
587: if (v instanceof String) {
588: cls = Classes.forNameByThread((String) v);
589: } else if (v instanceof Class) {
590: cls = (Class) v;
591: } else {
592: throw new UiException(
593: v != null ? "Unknown chart-engine, " + v
594: : "The chart-engine attribute is not defined");
595: }
596:
597: v = cls.newInstance();
598: } catch (Exception ex) {
599: throw UiException.Aide.wrap(ex);
600: }
601: if (!(v instanceof ChartEngine))
602: throw new UiException(ChartEngine.class
603: + " must be implemented by " + v);
604: return (ChartEngine) v;
605: }
606:
607: /** Sets the chart engine.
608: */
609: public void setEngine(ChartEngine engine) {
610: if (_engine != engine) {
611: _engine = engine;
612: }
613:
614: //Always redraw
615: smartDrawChart();
616: }
617:
618: /** Sets the chart engine by use of a class name.
619: * It creates an instance automatically.
620: */
621: public void setEngine(String clsnm) throws ClassNotFoundException,
622: NoSuchMethodException, IllegalAccessException,
623: InstantiationException,
624: java.lang.reflect.InvocationTargetException {
625: if (clsnm != null) {
626: setEngine((ChartEngine) Classes.newInstanceByThread(clsnm));
627: }
628: }
629:
630: private void initDataListener() {
631: if (_dataListener == null) {
632: _dataListener = new ChartDataListener() {
633: public void onChange(ChartDataEvent event) {
634: smartDrawChart();
635: }
636: };
637: }
638: }
639:
640: /** Returns the renderer to render each area, or null if the default
641: * renderer is used.
642: */
643: public ChartAreaListener getAreaListener() {
644: return _areaListener;
645: }
646:
647: /** Sets the renderer which is used to render each area.
648: *
649: * <p>Note: changing a render will not cause the chart to re-render.
650: * If you want it to re-render, you could call smartDraw.
651: *
652: * @param listener the area listener, or null to ignore it.
653: * @exception UiException if failed to initialize.
654: */
655: public void setAreaListener(ChartAreaListener listener) {
656: if (_areaListener != listener) {
657: _areaListener = listener;
658: }
659: }
660:
661: /** Sets the renderer by use of a class name.
662: * It creates an instance automatically.
663: */
664: public void setAreaListener(String clsnm)
665: throws ClassNotFoundException, NoSuchMethodException,
666: IllegalAccessException, InstantiationException,
667: java.lang.reflect.InvocationTargetException {
668: if (clsnm != null) {
669: setAreaListener((ChartAreaListener) Classes
670: .newInstanceByThread(clsnm));
671: }
672: }
673:
674: /**
675: * mark a draw flag to inform that this Chart needs update.
676: */
677: protected void smartDrawChart() {
678: if (_smartDrawChart) { //already mark smart draw
679: return;
680: }
681: _smartDrawChart = true;
682: if (_smartDrawChartListener == null) {
683: _smartDrawChartListener = new EventListener() {
684: public void onEvent(Event event) {
685: if (Strings.isBlank(getType()))
686: throw new UiException(
687: "chart must specify type (pie, bar, line, ...)");
688:
689: if (_model == null)
690: throw new UiException(
691: "chart must specify a data model");
692:
693: if (Strings.isBlank(getWidth()))
694: throw new UiException(
695: "chart must specify width");
696:
697: if (Strings.isBlank(getHeight()))
698: throw new UiException(
699: "chart must specify height");
700:
701: try {
702: final String title = getTitle();
703: final AImage image = new AImage("chart"
704: + new Date().getTime(), getEngine()
705: .drawChart(Chart.this ));
706: setContent(image);
707: } catch (java.io.IOException ex) {
708: throw UiException.Aide.wrap(ex);
709: } finally {
710: _smartDrawChart = false;
711: }
712: }
713: };
714: addEventListener("onSmartDrawChart",
715: _smartDrawChartListener);
716: }
717: Events.postEvent("onSmartDrawChart", this , null);
718: }
719:
720: //-- utilities --//
721: /*package*/static void decode(String color, int[] rgb) {
722: if (color == null) {
723: return;
724: }
725: if (color.length() != 7 || !color.startsWith("#")) {
726: throw new UiException("Incorrect color format (#RRGGBB) : "
727: + color);
728: }
729: rgb[0] = Integer.parseInt(color.substring(1, 3), 16);
730: rgb[1] = Integer.parseInt(color.substring(3, 5), 16);
731: rgb[2] = Integer.parseInt(color.substring(5, 7), 16);
732: }
733:
734: /*package*/static int stringToInt(String str) {
735: int j = str.lastIndexOf("px");
736: if (j > 0) {
737: final String num = str.substring(0, j);
738: return Integer.parseInt(num);
739: }
740:
741: j = str.lastIndexOf("pt");
742: if (j > 0) {
743: final String num = str.substring(0, j);
744: return (int) (Integer.parseInt(num) * 1.3333);
745: }
746:
747: j = str.lastIndexOf("em");
748: if (j > 0) {
749: final String num = str.substring(0, j);
750: return (int) (Integer.parseInt(num) * 13.3333);
751: }
752: return Integer.parseInt(str);
753: }
754: }
|