|
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 |