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