Wed, 24 Jun 2009 05:22:24 +0100
Initial commit
0 | 1 | |
2 | module(..., package.seeall); | |
3 | ||
4 | local chart = {}; | |
5 | chart.__index = chart; | |
6 | ||
7 | local writers = {}; -- Table of functions which build the URL | |
8 | ||
9 | -- Defaults | |
10 | chart.base_url = "http://chart.apis.google.com/chart"; | |
11 | chart.width, chart.height = 320, 200; | |
12 | chart.marker_color = "4D89F9"; | |
13 | ||
14 | -- Helpers | |
15 | local function urlencode(s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end | |
16 | local typemap = { line = "lc", sparkline = "ls", plot = "lxy", bar = "bhs" }; | |
17 | ||
18 | function new_chart(type) | |
19 | local chart_obj = { | |
20 | type = typemap[type] or type or "lc"; | |
21 | series = {}; | |
22 | axes = {}; | |
23 | markers = {}; | |
24 | }; | |
25 | ||
26 | return setmetatable(chart_obj, chart); | |
27 | end | |
28 | ||
29 | -- Library methods -- | |
30 | ||
31 | function set_base_url(url) | |
32 | chart.base_url = url; | |
33 | end | |
34 | ||
35 | function set_default_size(width, height) | |
36 | chart.width, chart.height = width or 320, height or 200; | |
37 | end | |
38 | ||
39 | ----- Chart methods ----- | |
40 | ||
41 | ---- Base URL ---- | |
42 | function chart:set_base_url(url) | |
43 | self.base_url = url; | |
44 | end | |
45 | ||
46 | -- No writer for base URL | |
47 | ||
48 | ---- Chart type ---- | |
49 | function chart:set_type(type) | |
50 | self.type = typemap[type] or type; | |
51 | end | |
52 | ||
53 | -- No writer for type | |
54 | ||
55 | ---- Data series ---- | |
56 | function chart:add_series(data) | |
57 | table.insert(self.series, data); | |
58 | end | |
59 | ||
60 | local ee_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."; | |
61 | local ee_len = #ee_string; | |
62 | local function to_extended_encoding(value) | |
63 | value = tonumber(value); | |
64 | if not value or value < 0 then return "__"; end | |
65 | local div, rem = math.floor(value/ee_len)+1, math.floor(value % ee_len)+1; | |
66 | return ee_string:sub(div, div)..ee_string:sub(rem, rem); | |
67 | end | |
68 | ||
69 | function writers:data() | |
70 | local data = {}; | |
71 | for n, series in ipairs(self.series) do | |
72 | local encoded = {}; | |
73 | for _, value in ipairs(series) do | |
74 | if self.scale and value > 0 then | |
75 | --value = value - (self.scale.min or 0); | |
76 | --print(string.format("4096/(%d-%d)/(%d-%d) = %f", self.scale.max, self.scale.min, value, self.scale.min), value); | |
77 | value = 4096/((self.scale.max-self.scale.min)/(value-self.scale.min)); | |
78 | end | |
79 | table.insert(encoded, to_extended_encoding(value)); | |
80 | end | |
81 | table.insert(data, table.concat(encoded)); | |
82 | end | |
83 | return "chd=e:"..table.concat(data, ","); | |
84 | end | |
85 | ||
86 | ---- Scale ---- | |
87 | function chart:set_scale(min, max) | |
88 | self.scale = { min = min, max = max }; | |
89 | end | |
90 | ||
91 | ---- Size ---- | |
92 | function chart:set_size(width, height) | |
93 | self.width, self.height = width, height; | |
94 | end | |
95 | ||
96 | function writers:size() | |
97 | return "chs="..tostring(self.width).."x"..tostring(self.height); | |
98 | end | |
99 | ||
100 | ---- Title ---- | |
101 | function chart:set_title(title) | |
102 | self.title = title; | |
103 | end | |
104 | ||
105 | function writers:title() | |
106 | if self.title then | |
107 | return "chtt="..urlencode(tostring(self.title):gsub("\n", "|")); | |
108 | end | |
109 | end | |
110 | ||
111 | ---- Axes display ---- | |
112 | local axismap = { bottom = "x", left = "y", top = "t", right = "r" }; | |
113 | function chart:add_axis(which, options) | |
114 | table.insert(self.axes, { type = axismap[which], options = options }); | |
115 | end | |
116 | ||
117 | function writers:axes() | |
118 | local axes, ranges = {}, {}; | |
119 | local labels, positions = {}, {}; | |
120 | local styles = {}; | |
121 | ||
122 | for index, axis in ipairs(self.axes) do | |
123 | index = index - 1; | |
124 | table.insert(axes, axis.type); | |
125 | if axis.options.range then | |
126 | local range = axis.options.range; | |
127 | table.insert(ranges, index..","..(range.min or 0)..","..(range.max or 100)..(range.interval and (","..range.interval) or "")); | |
128 | end | |
129 | if axis.options.labels then | |
130 | if axis.options.labels[1] then -- A list of strings | |
131 | table.insert(labels, index..":|"..table.concat(axis.options.labels, "|")); | |
132 | else -- Specifying positions too | |
133 | local label_list, position_list = {}, {}; | |
134 | for label, position in pairs(axis.options.labels) do | |
135 | table.insert(label_list, label); | |
136 | table.insert(position_list, position); | |
137 | end | |
138 | table.insert(labels, index..":|"..table.concat(label_list, "|")); | |
139 | table.insert(positions, index..","..table.concat(positions, ",")); | |
140 | end | |
141 | end | |
142 | if axis.options.style then | |
143 | table.insert(styles, index..","..axis.options.style); | |
144 | end | |
145 | if axis.options.ticklength then | |
146 | table.insert(ticklengths, index..","..axis.options.ticklength); | |
147 | end | |
148 | end | |
149 | ||
150 | local result = {}; | |
151 | ||
152 | if next(axes) then | |
153 | table.insert(result, "chxt="..urlencode(table.concat(axes, ","))); | |
154 | end | |
155 | if next(ranges) then | |
156 | table.insert(result, "chxr="..urlencode(table.concat(ranges, ","))); | |
157 | end | |
158 | if next(labels) then | |
159 | table.insert(result, "chxl="..urlencode(table.concat(labels, "|"))); | |
160 | end | |
161 | if next(positions) then | |
162 | table.insert(result, "chxp="..urlencode(table.concat(positions, ","))); | |
163 | end | |
164 | if next(styles) then | |
165 | table.insert(result, "chxs="..urlencode(table.concat(styles, "|"))); | |
166 | end | |
167 | if next(ticklengths) | |
168 | table.insert(result, "chxtc="..urlencode(table.concat(ticklengths, "|"))); | |
169 | end | |
170 | ||
171 | return table.concat(result, "&"); | |
172 | end | |
173 | ||
174 | ---- Data points ---- | |
175 | function chart:add_marker(marker) | |
176 | table.insert(self.markers, marker); | |
177 | end | |
178 | ||
179 | local marker_type_map = { flag = "f", text = "t", number = "N" }; | |
180 | function writers:markers() | |
181 | local result = { }; | |
182 | for _, marker in ipairs(self.markers) do | |
183 | table.insert(result, urlencode( | |
184 | (marker_type_map[marker.type] or "f") | |
185 | ..(marker.label or "Label").."," | |
186 | ..(marker.color or self.marker_color).."," | |
187 | ..(marker.series or 0).."," | |
188 | ..(marker.index or 0).."," | |
189 | ..(marker.size or 11).."," | |
190 | ..(marker.priority or 0))); | |
191 | end | |
192 | if next(result) then | |
193 | return "chm="..table.concat(result, "%7c"); | |
194 | end | |
195 | end | |
196 | ||
197 | ---- Colours and fill ---- | |
198 | function chart:set_color(color) | |
199 | self.color = color; | |
200 | end | |
201 | ||
202 | function chart:set_fill(fill_color) | |
203 | self.fill = fill_color; | |
204 | end | |
205 | ||
206 | function writers:color() | |
207 | if self.color then | |
208 | return "chco="..self.color; | |
209 | end | |
210 | end | |
211 | ||
212 | function chart:url() | |
213 | local url = self.base_url.."?cht="..self.type.."&"; | |
214 | ||
215 | local params = {}; | |
216 | for name, writer in pairs(writers) do | |
217 | local ret = writer(self); | |
218 | if ret then | |
219 | table.insert(params, tostring(ret)); | |
220 | end | |
221 | end | |
222 | ||
223 | return url..table.concat(params, "&"); | |
224 | end | |
225 |