Tue, 01 Dec 2009 13:53:46 +0000
Use correct seperator between ranges
0 | 1 | |
2 | module(..., package.seeall); | |
3 | ||
4 | local chart = {}; | |
5 | chart.__index = chart; | |
6 | ||
7
a6977f303c2c
Export base chart to allow configuration of defaults
Matthew Wild <mwild1@gmail.com>
parents:
6
diff
changeset
|
7 | default = chart; -- Export defaults as the base chart object |
a6977f303c2c
Export base chart to allow configuration of defaults
Matthew Wild <mwild1@gmail.com>
parents:
6
diff
changeset
|
8 | |
0 | 9 | local writers = {}; -- Table of functions which build the URL |
10 | ||
11 | -- Defaults | |
12 | chart.base_url = "http://chart.apis.google.com/chart"; | |
13 | chart.width, chart.height = 320, 200; | |
14 | chart.marker_color = "4D89F9"; | |
5
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
15 | chart.auto_scale_factor = 0.25; |
0 | 16 | |
17 | -- Helpers | |
18 | local function urlencode(s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end | |
4
e17867506327
Fix "bar" type to be vertical by default
Matthew Wild <mwild1@gmail.com>
parents:
3
diff
changeset
|
19 | local typemap = { line = "lc", sparkline = "ls", plot = "lxy", bar = "bvs" }; |
0 | 20 | |
2
107b9d00e4d4
gchart.new_chart() => gchart.new()
Matthew Wild <mwild1@gmail.com>
parents:
1
diff
changeset
|
21 | function new(type) |
0 | 22 | local chart_obj = { |
23 | type = typemap[type] or type or "lc"; | |
24 | series = {}; | |
25 | axes = {}; | |
26 | markers = {}; | |
5
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
27 | scale = { min = true, max = true }; |
0 | 28 | }; |
29 | ||
30 | return setmetatable(chart_obj, chart); | |
31 | end | |
32 | ||
33 | -- Library methods -- | |
34 | ||
35 | function set_base_url(url) | |
36 | chart.base_url = url; | |
37 | end | |
38 | ||
39 | function set_default_size(width, height) | |
40 | chart.width, chart.height = width or 320, height or 200; | |
41 | end | |
42 | ||
43 | ----- Chart methods ----- | |
44 | ||
45 | ---- Base URL ---- | |
46 | function chart:set_base_url(url) | |
47 | self.base_url = url; | |
48 | end | |
49 | ||
50 | -- No writer for base URL | |
51 | ||
52 | ---- Chart type ---- | |
53 | function chart:set_type(type) | |
54 | self.type = typemap[type] or type; | |
55 | end | |
56 | ||
57 | -- No writer for type | |
58 | ||
59 | ---- Data series ---- | |
60 | function chart:add_series(data) | |
61 | table.insert(self.series, data); | |
62 | end | |
63 | ||
64 | local ee_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."; | |
65 | local ee_len = #ee_string; | |
66 | local function to_extended_encoding(value) | |
67 | value = tonumber(value); | |
68 | if not value or value < 0 then return "__"; end | |
69 | local div, rem = math.floor(value/ee_len)+1, math.floor(value % ee_len)+1; | |
70 | return ee_string:sub(div, div)..ee_string:sub(rem, rem); | |
71 | end | |
72 | ||
73 | function writers:data() | |
5
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
74 | |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
75 | if self.scale then |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
76 | local autoscale_min, autoscale_max = (self.scale.min == true), (self.scale.max == true); |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
77 | if autoscale_min or autoscale_max then |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
78 | local min, max; |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
79 | for n, series in ipairs(self.series) do |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
80 | min, max = min or series[1], max or series[1]; |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
81 | for _, value in ipairs(series) do |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
82 | if autoscale_min and value < min then min = value; end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
83 | if autoscale_max and value > max then max = value; end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
84 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
85 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
86 | if autoscale_min then |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
87 | self.scale.min = min*(1-self.auto_scale_factor); |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
88 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
89 | if autoscale_max then |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
90 | self.scale.max = max*(1+self.auto_scale_factor); |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
91 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
92 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
93 | end |
b4983e638117
Support for auto-scaling of data
Matthew Wild <mwild1@gmail.com>
parents:
4
diff
changeset
|
94 | |
0 | 95 | local data = {}; |
96 | for n, series in ipairs(self.series) do | |
97 | local encoded = {}; | |
98 | for _, value in ipairs(series) do | |
99 | if self.scale and value > 0 then | |
100 | --value = value - (self.scale.min or 0); | |
101 | --print(string.format("4096/(%d-%d)/(%d-%d) = %f", self.scale.max, self.scale.min, value, self.scale.min), value); | |
6
8d4be5429414
Maximum value in extended encoding is 4095, not 4096
Matthew Wild <mwild1@gmail.com>
parents:
5
diff
changeset
|
102 | value = 4095/((self.scale.max-self.scale.min)/(value-self.scale.min)); |
0 | 103 | end |
104 | table.insert(encoded, to_extended_encoding(value)); | |
105 | end | |
106 | table.insert(data, table.concat(encoded)); | |
107 | end | |
108 | return "chd=e:"..table.concat(data, ","); | |
109 | end | |
110 | ||
111 | ---- Scale ---- | |
112 | function chart:set_scale(min, max) | |
113 | self.scale = { min = min, max = max }; | |
114 | end | |
115 | ||
116 | ---- Size ---- | |
117 | function chart:set_size(width, height) | |
118 | self.width, self.height = width, height; | |
119 | end | |
120 | ||
121 | function writers:size() | |
122 | return "chs="..tostring(self.width).."x"..tostring(self.height); | |
123 | end | |
124 | ||
125 | ---- Title ---- | |
126 | function chart:set_title(title) | |
127 | self.title = title; | |
128 | end | |
129 | ||
130 | function writers:title() | |
131 | if self.title then | |
132 | return "chtt="..urlencode(tostring(self.title):gsub("\n", "|")); | |
133 | end | |
134 | end | |
135 | ||
1
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
136 | ---- Legend ---- |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
137 | function chart:set_legend(entries) |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
138 | self.legend = entries; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
139 | end |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
140 | |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
141 | function writers:legend() |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
142 | if self.legend then |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
143 | return "chdl="..table.concat(self.legend, "|"); |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
144 | end |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
145 | end |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
146 | |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
147 | ---- Legend position ---- |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
148 | local position_map = { |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
149 | vertical = { bottom = "b", top = "t", left = "l", right = "r" }; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
150 | horizontal = { bottom = "b", top = "t" }; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
151 | }; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
152 | |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
153 | function chart:set_legend_position(position, layout) |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
154 | self.legend_position = positionmap[layout or "vertical"][position or "right"] or position; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
155 | end |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
156 | |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
157 | function writers:legend_position() |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
158 | if self.legend_position then |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
159 | return "chdlp="..self.legend_position; |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
160 | end |
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
161 | end |
0 | 162 | ---- Axes display ---- |
163 | local axismap = { bottom = "x", left = "y", top = "t", right = "r" }; | |
164 | function chart:add_axis(which, options) | |
165 | table.insert(self.axes, { type = axismap[which], options = options }); | |
166 | end | |
167 | ||
168 | function writers:axes() | |
169 | local axes, ranges = {}, {}; | |
170 | local labels, positions = {}, {}; | |
3
49a62f0f4a96
Fix traceback when writing axes
Matthew Wild <mwild1@gmail.com>
parents:
2
diff
changeset
|
171 | local styles, ticklengths = {}, {}; |
0 | 172 | |
173 | for index, axis in ipairs(self.axes) do | |
174 | index = index - 1; | |
175 | table.insert(axes, axis.type); | |
176 | if axis.options.range then | |
177 | local range = axis.options.range; | |
178 | table.insert(ranges, index..","..(range.min or 0)..","..(range.max or 100)..(range.interval and (","..range.interval) or "")); | |
179 | end | |
180 | if axis.options.labels then | |
181 | if axis.options.labels[1] then -- A list of strings | |
182 | table.insert(labels, index..":|"..table.concat(axis.options.labels, "|")); | |
183 | else -- Specifying positions too | |
184 | local label_list, position_list = {}, {}; | |
185 | for label, position in pairs(axis.options.labels) do | |
186 | table.insert(label_list, label); | |
187 | table.insert(position_list, position); | |
188 | end | |
189 | table.insert(labels, index..":|"..table.concat(label_list, "|")); | |
190 | table.insert(positions, index..","..table.concat(positions, ",")); | |
191 | end | |
192 | end | |
193 | if axis.options.style then | |
194 | table.insert(styles, index..","..axis.options.style); | |
195 | end | |
196 | if axis.options.ticklength then | |
197 | table.insert(ticklengths, index..","..axis.options.ticklength); | |
198 | end | |
199 | end | |
200 | ||
201 | local result = {}; | |
202 | ||
203 | if next(axes) then | |
204 | table.insert(result, "chxt="..urlencode(table.concat(axes, ","))); | |
205 | end | |
206 | if next(ranges) then | |
10
8deebb43b0de
Use correct seperator between ranges
Matthew Wild <mwild1@gmail.com>
parents:
7
diff
changeset
|
207 | table.insert(result, "chxr="..urlencode(table.concat(ranges, "|"))); |
0 | 208 | end |
209 | if next(labels) then | |
210 | table.insert(result, "chxl="..urlencode(table.concat(labels, "|"))); | |
211 | end | |
212 | if next(positions) then | |
213 | table.insert(result, "chxp="..urlencode(table.concat(positions, ","))); | |
214 | end | |
215 | if next(styles) then | |
216 | table.insert(result, "chxs="..urlencode(table.concat(styles, "|"))); | |
217 | end | |
1
f930ba6a8923
Support for legends, and legend positioning
Matthew Wild <mwild1@gmail.com>
parents:
0
diff
changeset
|
218 | if next(ticklengths) then |
0 | 219 | table.insert(result, "chxtc="..urlencode(table.concat(ticklengths, "|"))); |
220 | end | |
221 | ||
222 | return table.concat(result, "&"); | |
223 | end | |
224 | ||
225 | ---- Data points ---- | |
226 | function chart:add_marker(marker) | |
227 | table.insert(self.markers, marker); | |
228 | end | |
229 | ||
230 | local marker_type_map = { flag = "f", text = "t", number = "N" }; | |
231 | function writers:markers() | |
232 | local result = { }; | |
233 | for _, marker in ipairs(self.markers) do | |
234 | table.insert(result, urlencode( | |
235 | (marker_type_map[marker.type] or "f") | |
236 | ..(marker.label or "Label").."," | |
237 | ..(marker.color or self.marker_color).."," | |
238 | ..(marker.series or 0).."," | |
239 | ..(marker.index or 0).."," | |
240 | ..(marker.size or 11).."," | |
241 | ..(marker.priority or 0))); | |
242 | end | |
243 | if next(result) then | |
244 | return "chm="..table.concat(result, "%7c"); | |
245 | end | |
246 | end | |
247 | ||
248 | ---- Colours and fill ---- | |
249 | function chart:set_color(color) | |
250 | self.color = color; | |
251 | end | |
252 | ||
253 | function chart:set_fill(fill_color) | |
254 | self.fill = fill_color; | |
255 | end | |
256 | ||
257 | function writers:color() | |
258 | if self.color then | |
259 | return "chco="..self.color; | |
260 | end | |
261 | end | |
262 | ||
263 | function chart:url() | |
264 | local url = self.base_url.."?cht="..self.type.."&"; | |
265 | ||
266 | local params = {}; | |
267 | for name, writer in pairs(writers) do | |
268 | local ret = writer(self); | |
269 | if ret then | |
270 | table.insert(params, tostring(ret)); | |
271 | end | |
272 | end | |
273 | ||
274 | return url..table.concat(params, "&"); | |
275 | end | |
276 |