001    package org.hackystat.projectbrowser.googlechart;
002    
003    import java.awt.Color;
004    import java.io.Serializable;
005    import java.text.DecimalFormat;
006    import java.text.NumberFormat;
007    import java.util.ArrayList;
008    import java.util.Arrays;
009    import java.util.List;
010    import java.util.Random;
011    
012    /**
013     * Chart class that represent a chart image that generated from Google Chart API.
014     * @author Shaoxuan Zhang
015     */
016    public class GoogleChart implements Serializable {
017      /** Support serialization. */
018      private static final long serialVersionUID = 1L;
019      /** host of google chart service. */
020      public static final String GOOGLECHART_API_URL = "http://chart.apis.google.com/chart?";
021      /** character that separate parameter. */
022      public static final String PARAMETER_SEPARATOR = "&";
023      /** character that separate data sets. */
024      public static final String DATASET_SEPARATOR = "|";
025      /** character that separate data items in a data set. */
026      public static final String DATAITEM_SEPARATOR = ",";
027      /** The maximum size of a google chart. */
028      public static final int MAX_SIZE = 300000;
029      /** chart type of this chart. */
030      private ChartType chartType;
031      /** width in pixel. */
032      private int width;
033      /** height in pixel. */
034      private int height;
035      /** the background color. */
036      private String backgroundColor;
037      /** data to display. */
038      private List<List<Double>> chartData = new ArrayList<List<Double>>();
039      /** scale of the data. */
040      private List<List<Double>> chartDataScale = new ArrayList<List<Double>>();
041      /** colors of the chart. */
042      private List<String> colors = new ArrayList<String>();
043      /** title. */
044      private String title = null;
045      /** Axis types. */
046      private List<String> axisTypes = new ArrayList<String>();
047      /** Axis labels. */
048      private List<List<String>> axisLabels = new ArrayList<List<String>>();
049      /** Axis max ranges. */
050      private List<List<Double>> axisMaxRange = new ArrayList<List<Double>>();
051      /** Axis colors. */
052      private List<String> axisColor = new ArrayList<String>();
053      /** the markers. */
054      private List<String> chartMarker = new ArrayList<String>();
055      /** colors of the markers. */
056      private List<String> markerColors = new ArrayList<String>();
057      /** the legends. */
058      private List<String> chartLegend = new ArrayList<String>();
059      /** the line styles.*/
060      private List<List<Double>> chartLineStyle = new ArrayList<List<Double>>();
061      /** the bar chart size. */
062      private List<Integer> barChartSize = new ArrayList<Integer>();
063      /** collection of markers.*/
064      private static final List<String> markers = Arrays.asList(new String[]{"o", "d", "c", "x", "s"});
065      /** index of the current available marker. */
066      private static int markersIndex = 0;
067      
068      private static final List<String> jetColors = Arrays.asList(new String[] { "00007F", "0000DC",
069          "0039FF", "0096FF", "00F3FF", "50FFAD", "ADFF50", "FFF300", "FF9600", "FF3900", "DC0000",
070          "7F0000" });
071      private static short currentJetColor = 0;
072    
073      
074      /**
075       * initialize the chart with indispensable parameter.
076       * @param chartType type of this chart.
077       * @param width width.
078       * @param height height.
079       */
080      public GoogleChart(ChartType chartType, int width, int height) {
081        this.chartType = chartType;
082        this.width = width;
083        this.height = height;
084      }
085      
086      /**
087       * Return the next available marker.
088       * @return a String that represents the marker
089       */
090      public static String getNextMarker() {
091        if (markersIndex >= markers.size()) {
092          markersIndex = 0;
093        }
094        return markers.get(markersIndex++);
095      }
096    
097      /**
098       * Return the URL that represent this chart from Google Chart API.
099       * @return the URL of this chart.
100       */
101      public String getUrl() {
102        String url = GOOGLECHART_API_URL;
103        url += "chs=" + this.width + "x" + this.height;
104        //add data
105        url += PARAMETER_SEPARATOR + "chd=t:" + getDataTableAsString(this.chartData);
106        //add data scale
107        if (chartDataScale != null && !chartDataScale.isEmpty()) {
108          String scaleData = getDataTableAsString(this.chartDataScale);
109          scaleData = scaleData.replace('|', ',');
110          url += PARAMETER_SEPARATOR + "chds=" + scaleData;
111        }
112        //add chart type
113        url += PARAMETER_SEPARATOR + "cht=" + chartType.abbrev();
114        //add bar chart size
115        if (!this.barChartSize.isEmpty()) {
116          url += PARAMETER_SEPARATOR + "chbh=" + getDataListAsString(barChartSize);
117        }
118        //add title
119        if (this.title != null) {
120          url += PARAMETER_SEPARATOR + "chtt=" + this.title;
121        }
122        //add stream color
123        if (colors != null && !colors.isEmpty()) {
124          url += PARAMETER_SEPARATOR + "chco=" + getDataListAsString(this.colors);
125        }
126        if (!this.chartLineStyle.isEmpty()) {
127          url += PARAMETER_SEPARATOR + "chls=" + getDataTableAsString(chartLineStyle);
128        }
129        //add axis type
130        if (!this.axisTypes.isEmpty()) {
131          url += PARAMETER_SEPARATOR + "chxt=" + getDataListAsString(this.axisTypes);
132        }
133        //add axis label
134        if (!this.axisLabels.isEmpty()) {
135          StringBuffer stringBuffer = new StringBuffer();
136          for (int i = 0; i < this.axisLabels.size(); ++i) {
137            if (this.axisLabels.get(i).size() > 0) {
138              stringBuffer.append((stringBuffer.length() > 0) ? '|' : "");
139              stringBuffer.append(i);
140              stringBuffer.append(':');
141              for (String label : this.axisLabels.get(i)) {
142                stringBuffer.append('|');
143                stringBuffer.append(label);
144              }
145            }
146          }
147          url += PARAMETER_SEPARATOR + "chxl=" + stringBuffer.toString();
148        }
149        //add axis range
150        if (!this.axisMaxRange.isEmpty()) {
151          List<List<String>> axisRange = new ArrayList<List<String>>();
152          for (int i = 0; i < this.axisMaxRange.size(); ++i) {
153            if (!this.axisMaxRange.get(i).isEmpty()) {
154              List<String> range = new ArrayList<String>();
155              range.add(String.valueOf(i));
156              for (Double value : this.axisMaxRange.get(i)) {
157                range.add(value.toString());
158              }
159              axisRange.add(range);
160            }
161          }
162          url += PARAMETER_SEPARATOR + "chxr=" + this.getDataTableAsString(axisRange);
163        }
164        //add axis color
165        if (!this.axisColor.isEmpty()) {
166          List<List<String>> axisStyle = new ArrayList<List<String>>();
167          for (int i = 0; i < this.axisColor.size(); ++i) {
168            if (this.axisColor.get(i).length() > 0) {
169              axisStyle.add(Arrays.asList(new String[]{String.valueOf(i), this.axisColor.get(i)}));
170            }
171          }
172          url += PARAMETER_SEPARATOR + "chxs=" + this.getDataTableAsString(axisStyle);
173        }
174        //add chart mark
175        if (!this.chartMarker.isEmpty()) {
176          List<List<String>> markerList = new ArrayList<List<String>>();
177          for (int i = 0; i < this.chartMarker.size(); ++i) {
178              List<String> marker = new ArrayList<String>();
179              marker.add(this.chartMarker.get(i));
180              marker.add(this.getMarkerColors().get(i));
181              marker.add(i + "");
182              marker.add("-1.0");
183              marker.add("10.0");
184              markerList.add(marker);
185          }
186          url += PARAMETER_SEPARATOR + "chm=" + this.getDataTableAsString(markerList);
187        }
188        // add legend
189        if (!this.chartLegend.isEmpty()) {
190          StringBuffer stringBuffer = new StringBuffer();
191          for (String dataItem : this.chartLegend) {
192            /*
193            int index = dataItem.indexOf('<');
194            String data = dataItem.substring(0, index);
195            */
196            stringBuffer.append(dataItem + DATASET_SEPARATOR);
197          }
198          String dataString = stringBuffer.toString();
199          if (dataString.endsWith(DATASET_SEPARATOR)) {
200            dataString = dataString.substring(0, dataString.lastIndexOf(DATASET_SEPARATOR));
201          }
202          url += PARAMETER_SEPARATOR + "chdl=" + dataString;
203        }
204        if (backgroundColor != null && backgroundColor.length() > 0) {
205          url += PARAMETER_SEPARATOR + "chf=bg,s," + backgroundColor;
206        }
207        return url;
208      }
209      
210      /**
211       * convert a Color object to a String, with format of RRGGBB.
212       * @param color the Color to be convert.
213       * @return a string that represent the color.
214       */
215      public static String colorToString(Color color) {
216        String colorString = "";
217        String red = "000" + Integer.toHexString(color.getRed());
218        String green = "00" + Integer.toHexString(color.getGreen());
219        String blue = "000" + Integer.toHexString(color.getBlue());
220        String alpha = "00" + Integer.toHexString(color.getAlpha());
221        colorString += red.substring(red.length() - 2);
222        colorString += green.substring(green.length() - 2);
223        colorString += blue.substring(blue.length() - 2);
224        colorString += alpha.substring(alpha.length() - 2);
225        return colorString;
226      }
227      
228      /**
229       * return the text encoding data with scaling.
230       * @param list the list of data to be encoded.
231       * @return String of the encoded data.
232       */
233      private String getDataTableAsString(List<? extends List<? extends Object>> list) {
234        StringBuffer buffer = new StringBuffer();
235        for (List<? extends Object> dataList : list) {
236          buffer.append(getDataListAsString(dataList) + DATASET_SEPARATOR);
237        }
238        String dataString = buffer.toString();
239        if (dataString.endsWith(DATASET_SEPARATOR)) {
240          dataString = dataString.substring(0, dataString.lastIndexOf(DATASET_SEPARATOR));
241        }
242        return dataString;
243      }
244    
245      /**
246       * convert the data list to encoded data string.
247       * @param dataList the data list to be converted.
248       * @return the string of result.
249       */
250      private String getDataListAsString(List<? extends Object> dataList) {
251        StringBuffer buffer = new StringBuffer();
252        for (Object dataItem : dataList) {
253          if (dataItem instanceof Double) {
254            dataItem = trimDouble((Double)dataItem);
255          }
256          buffer.append(dataItem + DATAITEM_SEPARATOR);
257        }
258        String dataString = buffer.toString();
259        if (dataString.endsWith(DATAITEM_SEPARATOR)) {
260          dataString = dataString.substring(0, dataString.lastIndexOf(DATAITEM_SEPARATOR));
261        }
262        return dataString;
263      }
264      
265      /**
266       * Trim the double number. Keep only one number after decimal point.
267       * 
268       * @param dataItem the Double number.
269       * @return a string of result.
270       */
271      private String trimDouble(Double dataItem) {
272        NumberFormat format = new DecimalFormat("0.##");
273        return format.format(dataItem);
274        // String number = dataItem.toString();
275        // int index = number.indexOf('.');
276        // if (index > 0 && index + 4 < number.length()) {
277        // number = number.substring(0, index + 4);
278        // }
279        // return number;
280      }
281    
282      /**
283       * @param width the width to set
284       */
285      public void setWidth(int width) {
286        this.width = width;
287      }
288    
289      /**
290       * @return the width
291       */
292      public int getWidth() {
293        return width;
294      }
295    
296      /**
297       * @param height the height to set
298       */
299      public void setHeight(int height) {
300        this.height = height;
301      }
302    
303      /**
304       * @return the height
305       */
306      public int getHeight() {
307        return height;
308      }
309    
310      /**
311       * @param chartType the chartType to set
312       */
313      public void setChartType(ChartType chartType) {
314        this.chartType = chartType;
315      }
316    
317      /**
318       * @return the chartType
319       */
320      public ChartType getChartType() {
321        return chartType;
322      }
323    
324      /**
325       * @param chartData the chartData to set
326       */
327      public void setChartData(List<List<Double>> chartData) {
328        this.chartData = chartData;
329      }
330    
331      /**
332       * @return the chartData
333       */
334      public List<List<Double>> getChartData() {
335        return chartData;
336      }
337    
338      /**
339       * @param chartDataScale the chartDataScale to set
340       */
341      public void setChartDataScale(List<List<Double>> chartDataScale) {
342        this.chartDataScale = chartDataScale;
343      }
344    
345      /**
346       * @return the chartDataScale
347       */
348      public List<List<Double>> getChartDataScale() {
349        return chartDataScale;
350      }
351    
352      /**
353       * @return the color list.
354       */
355      public List<String> getColors() {
356        return this.colors;
357      }
358      
359      /**
360       * add the color to color list.
361       * @param color String that represent the color in format RRGGBB.
362       */
363      public void addColor(String color) {
364        this.colors.add(color);
365      }
366    
367      /**
368       * add the color to color list.
369       * @param color the Color to add.
370       */
371      public void addColor(Color color) {
372        this.colors.add(colorToString(color));
373      }
374      
375      /**
376       * @param title the title to set
377       */
378      public void setTitle(String title) {
379        this.title = title;
380      }
381    
382      /**
383       * @return the title
384       */
385      public String getTitle() {
386        return title;
387      }
388      
389      /**
390       * add a label with default label values.
391       * @param type axis label type, either x, t, y, or r.
392       */
393      public void addAxisLabel(String type) {
394        addAxisLabel(type, new ArrayList<String>(), "");
395      }
396    
397      /**
398       * add a label with given label values.
399       * @param type axis label type, either x, t, y, or r.
400       * @param labels the given label values.
401       * @param color the axis color.
402       */
403      public void addAxisLabel(String type, List<String> labels, String color) {
404        this.axisTypes.add(type);
405        this.axisLabels.add(labels);
406        this.axisMaxRange.add(new ArrayList<Double>());
407        this.axisColor.add(color);
408      }
409    
410      /**
411       * add a label with given label range.
412       * @param type axis label type, either x, t, y, or r.
413       * @param range the given range.
414       * @param color the axis color.
415       */
416      public void addAxisRange(String type, List<Double> range, String color) {
417        this.axisTypes.add(type);
418        this.axisLabels.add(new ArrayList<String>());
419        this.axisMaxRange.add(range);
420        this.axisColor.add(color);
421      }
422      
423      /**
424       * add a line style with the given parameters.
425       * @param thickness thickness of the line.
426       * @param lineLength length of the line segment.
427       * @param blankLength length of the blank segment.
428       */
429      public void addLineStyle(double thickness, double lineLength, double blankLength) {
430        this.chartLineStyle.add(Arrays.asList(new Double[]{thickness, lineLength, blankLength}));
431      }
432      /**
433       * @return true if no Y axis in this chart yet.
434       */
435      public boolean isYAxisEmpty() {
436        for (String axisType : this.axisTypes) {
437          if ("y".equals(axisType) || "y".equals(axisType)) {
438            return false;
439          }
440        }
441        return true;
442      }
443      
444      /**
445       * @return the chartMarker
446       */
447      public List<String> getChartMarker() {
448        return chartMarker;
449      }
450    
451      /**
452       * @return the chartLegend
453       */
454      public List<String> getChartLegend() {
455        return chartLegend;
456      }
457    
458      /**
459       * @return a random color in format of RRGGBB.
460       */
461      public static String getRandomColor() {
462        Long longValue = Math.round(Math.random() * 0xFFFFFF);
463        String randomColor = "000000" + Long.toHexString(longValue);
464        randomColor = randomColor.substring(randomColor.length() - 6);
465        return randomColor;
466      }
467      
468      /**
469       * @return a random marker.
470       */
471      public static String getRandomMarker() {
472        Random random = new Random();
473        return markers.get(random.nextInt(markers.size()));
474      }
475      /**
476       * @param markerColors the markerColors to set
477       */
478      public void setMarkerColors(List<String> markerColors) {
479        this.markerColors = markerColors;
480      }
481    
482      /**
483       * @return the markerColors
484       */
485      public List<String> getMarkerColors() {
486        return markerColors;
487      }
488      /**
489       * Set the size of the bar chart.
490       * @param barHeight the height or width of the bar.
491       * @param groupSpace the space between groups.
492       * @param barSpace the space between bars in a group.
493       */
494      public void setBarChartSize(int barHeight, int groupSpace, int barSpace) {
495        this.barChartSize.clear();
496        barChartSize.add(barHeight);
497        barChartSize.add(groupSpace);
498        barChartSize.add(barSpace);
499      }
500    
501      /**
502       * @param backgroundColor the backgroundColor to set
503       */
504      public void setBackgroundColor(String backgroundColor) {
505        this.backgroundColor = backgroundColor;
506      }
507    
508      /**
509       * @return the backgroundColor
510       */
511      public String getBackgroundColor() {
512        return backgroundColor;
513      }
514    
515      /**
516       * @return a random color in format of RRGGBB.
517       */
518      public static String getNextJetColor() {
519        if ((jetColors.size() % 2) > 0) {
520          currentJetColor += 2;
521        }
522        else {
523          currentJetColor += 3;
524        }
525        if (currentJetColor >= jetColors.size()) {
526          currentJetColor = 0;
527        }
528        return jetColors.get(currentJetColor);
529      }
530      
531    }