gchart.lua

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