001package org.javasimon.utils; 002 003import java.text.DecimalFormat; 004import java.text.DecimalFormatSymbols; 005import java.util.LinkedList; 006import java.util.List; 007import java.util.Locale; 008 009import org.javasimon.StopwatchSample; 010import org.javasimon.clock.SimonUnit; 011 012/** 013 * Produces URLs for Google Chart Image API - column type. 014 * http://code.google.com/apis/chart/image/ 015 * 016 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 017 * @noinspection UnusedDeclaration 018 */ 019public final class GoogleChartImageGenerator { 020 021 private static final int FIXED_WIDTH = 100; 022 private static final int BAR_WIDTH = 80; 023 private static final int BAR_SPACING = 40; 024 private static final int BAR_SPACING_MAX_MIN = 25; 025 private static final int IMAGE_HEIGHT = 320; 026 027 private static final String URL_START = "http://chart.apis.google.com/chart?chs="; 028 private static final String TYPE_BAR1 = "&cht=bvg&chbh=a,3,"; 029 private static final String TYPE_BAR2 = "&chco=2d69f9,a6c9fd,d0eeff&chxt=y,x,x"; 030 private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.US)); 031 032 private static final List<Replacer> REPLACERS = new LinkedList<>(); 033 034 private static final String DEFAULT_TITLE = "Java Simon chart"; 035 036 private static final int TEN_BASE = 10; 037 private StopwatchSample[] samples; 038 private String title; 039 private final SimonUnit unit; 040 private boolean showMaxMin; 041 042 private double max = 0; 043 private boolean first = true; 044 045 static { 046 REPLACERS.add(new Replacer("\\+", "%2b")); 047 REPLACERS.add(new Replacer(" ", "+")); 048 REPLACERS.add(new Replacer("&", "%26")); 049 } 050 051 private GoogleChartImageGenerator(StopwatchSample[] samples, String title, SimonUnit unit, boolean showMaxMin) { 052 this.samples = samples; 053 this.title = title; 054 this.unit = unit; 055 this.showMaxMin = showMaxMin; 056 } 057 058 /** 059 * Generates Google bar chart URL for the provided samples. 060 * 061 * @param title chart title 062 * @param unit unit requested for displaying results 063 * @param showMaxMin true if additional datasets for max and min values should be shown 064 * @param samples stopwatch samples 065 * @return URL generating the bar chart 066 */ 067 public static String barChart(String title, SimonUnit unit, boolean showMaxMin, StopwatchSample... samples) { 068 return new GoogleChartImageGenerator(samples, title, unit, showMaxMin).process(); 069 } 070 071 /** 072 * Generates Google bar chart URL for the provided samples showing only mean values. 073 * 074 * @param title chart title 075 * @param unit unit requested for displaying results 076 * @param samples stopwatch samples 077 * @return URL generating the bar chart 078 */ 079 public static String barChart(String title, SimonUnit unit, StopwatchSample... samples) { 080 return new GoogleChartImageGenerator(samples, title, unit, false).process(); 081 } 082 083 /** 084 * Generates Google bar chart URL for the provided samples showing mean values in milliseconds. 085 * 086 * @param title chart title 087 * @param samples stopwatch samples 088 * @return URL generating the bar chart 089 */ 090 public static String barChart(String title, StopwatchSample... samples) { 091 return new GoogleChartImageGenerator(samples, title, SimonUnit.MILLISECOND, false).process(); 092 } 093 094 /** 095 * Generates Google bar chart URL for the provided samples showing mean values in milliseconds with default title. 096 * 097 * @param samples stopwatch samples 098 * @return URL generating the bar chart 099 */ 100 public static String barChart(StopwatchSample... samples) { 101 return new GoogleChartImageGenerator(samples, DEFAULT_TITLE, SimonUnit.MILLISECOND, false).process(); 102 } 103 104 private String process() { 105 final StringBuilder result = new StringBuilder(URL_START); 106 int setSpacing = BAR_SPACING; 107 if (showMaxMin) { 108 setSpacing = BAR_SPACING_MAX_MIN; 109 } 110 result.append(FIXED_WIDTH + BAR_WIDTH * samples.length).append('x').append(IMAGE_HEIGHT).append(TYPE_BAR1) 111 .append(setSpacing).append(TYPE_BAR2); 112 if (showMaxMin) { 113 result.append(",x,x"); 114 } 115 result.append("&chtt=").append(encode(title)); 116 // 0: is Y axis calculated later 117 final StringBuilder simonNamesAxis = new StringBuilder("|1:"); 118 final StringBuilder meanValuesAxis = new StringBuilder("|2:"); 119 // following axis values are optional 120 final StringBuilder maxValuesAxis = new StringBuilder("|3:"); 121 final StringBuilder minValuesAxis = new StringBuilder("|4:"); 122 final StringBuilder meanData = new StringBuilder("&chd=t:"); 123 final StringBuilder maxData = new StringBuilder("|"); 124 final StringBuilder minData = new StringBuilder("|"); 125 for (StopwatchSample sample : samples) { 126 if (first) { 127 first = false; 128 } else { 129 meanData.append(','); 130 maxData.append(','); 131 minData.append(','); 132 } 133 simonNamesAxis.append('|').append(encode(sample.getName())); 134 meanData.append(addValueToAxis(meanValuesAxis, sample.getMean())); 135 if (showMaxMin) { 136 maxData.append(addValueToAxis(maxValuesAxis, sample.getMax())); 137 minData.append(addValueToAxis(minValuesAxis, sample.getMin())); 138 } 139 } 140 double division = Math.pow(TEN_BASE, Math.floor(Math.log10(max))); 141 StringBuilder yAxis = new StringBuilder("&chxl=0:"); 142 double x = 0; 143 for (; x < max + division; x += division) { 144 yAxis.append('|').append(Double.valueOf(x).longValue()); 145 } 146 result.append("&chxr=2,0,").append(Double.valueOf(x - division).longValue()); 147 result.append("&chds=0,").append(Double.valueOf(x - division).longValue()); 148 result.append(yAxis).append(simonNamesAxis).append(meanValuesAxis); 149 if (showMaxMin) { 150 result.append(maxValuesAxis).append(minValuesAxis); 151 } 152 result.append(meanData); 153 if (showMaxMin) { 154 result.append(maxData).append(minData); 155 } 156 result.append("&chdl=avg").append(showMaxMin ? "|max|min" : "").append("&.png"); 157 return result.toString(); 158 } 159 160 private String addValueToAxis(StringBuilder axis, double value) { 161 value = value / unit.getDivisor(); 162 if (value > max) { 163 max = value; 164 } 165 166 String formattedValue = NUMBER_FORMAT.format(value); 167 axis.append('|').append(formattedValue).append("+").append(unit.getSymbol()); 168 return formattedValue; 169 } 170 171 private static String encode(String s) { 172 for (final Replacer replacer : REPLACERS) { 173 s = replacer.process(s); 174 } 175 return s; 176 } 177}