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}