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 }