Initial commit

Wed, 24 Jun 2009 05:22:24 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Wed, 24 Jun 2009 05:22:24 +0100
changeset 0
757c17d808a8
child 1
f930ba6a8923

Initial commit

gchart.lua file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gchart.lua	Wed Jun 24 05:22:24 2009 +0100
@@ -0,0 +1,225 @@
+
+module(..., package.seeall);
+
+local chart = {};
+chart.__index = chart;
+
+local writers = {}; -- Table of functions which build the URL
+
+-- Defaults
+chart.base_url = "http://chart.apis.google.com/chart";
+chart.width, chart.height = 320, 200;
+chart.marker_color = "4D89F9";
+
+-- Helpers
+local function urlencode(s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end
+local typemap = { line = "lc", sparkline = "ls", plot = "lxy", bar = "bhs" };
+
+function new_chart(type)
+	local chart_obj = {
+			type = typemap[type] or type or "lc";
+			series = {};
+			axes = {};
+			markers = {};
+		};
+	
+	return setmetatable(chart_obj, chart);
+end
+
+-- Library methods --
+
+function set_base_url(url)
+	chart.base_url = url;
+end
+
+function set_default_size(width, height)
+	chart.width, chart.height = width or 320, height or 200;
+end
+
+----- Chart methods -----
+
+---- Base URL ----
+function chart:set_base_url(url)
+	self.base_url = url;
+end
+
+-- No writer for base URL
+
+---- Chart type ----
+function chart:set_type(type)
+	self.type = typemap[type] or type;
+end
+
+-- No writer for type
+
+---- Data series ----
+function chart:add_series(data)
+	table.insert(self.series, data);
+end
+
+local ee_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";
+local ee_len = #ee_string;
+local function to_extended_encoding(value)
+	value = tonumber(value);
+	if not value or value < 0 then return "__"; end
+	local div, rem = math.floor(value/ee_len)+1, math.floor(value % ee_len)+1;
+	return ee_string:sub(div, div)..ee_string:sub(rem, rem);
+end
+
+function writers:data()
+	local data = {};
+	for n, series in ipairs(self.series) do
+		local encoded = {};
+		for _, value in ipairs(series) do
+			if self.scale and value > 0 then
+				--value = value - (self.scale.min or 0);
+				--print(string.format("4096/(%d-%d)/(%d-%d) = %f", self.scale.max, self.scale.min, value, self.scale.min), value);
+				value = 4096/((self.scale.max-self.scale.min)/(value-self.scale.min));
+			end
+			table.insert(encoded, to_extended_encoding(value));
+		end
+		table.insert(data, table.concat(encoded));
+	end
+	return "chd=e:"..table.concat(data, ",");
+end
+
+---- Scale ----
+function chart:set_scale(min, max)
+	self.scale = { min = min, max = max };
+end
+
+---- Size ----
+function chart:set_size(width, height)
+	self.width, self.height = width, height;
+end
+
+function writers:size()
+	return "chs="..tostring(self.width).."x"..tostring(self.height);
+end
+
+---- Title ----
+function chart:set_title(title)
+	self.title = title;
+end
+
+function writers:title()
+	if self.title then
+		return "chtt="..urlencode(tostring(self.title):gsub("\n", "|"));
+	end
+end
+
+---- Axes display ----
+local axismap = { bottom = "x", left = "y", top = "t", right = "r" };
+function chart:add_axis(which, options)
+	table.insert(self.axes, { type = axismap[which], options = options });
+end
+
+function writers:axes()
+	local axes, ranges = {}, {};
+	local labels, positions = {}, {};
+	local styles = {};
+	
+	for index, axis in ipairs(self.axes) do
+		index = index - 1;
+		table.insert(axes, axis.type);
+		if axis.options.range then
+			local range = axis.options.range;
+			table.insert(ranges, index..","..(range.min or 0)..","..(range.max or 100)..(range.interval and (","..range.interval) or ""));
+		end
+		if axis.options.labels then
+			if axis.options.labels[1] then -- A list of strings
+				table.insert(labels, index..":|"..table.concat(axis.options.labels, "|"));
+			else -- Specifying positions too
+				local label_list, position_list = {}, {};
+				for label, position in pairs(axis.options.labels) do
+					table.insert(label_list, label);
+					table.insert(position_list, position);
+				end
+				table.insert(labels, index..":|"..table.concat(label_list, "|"));
+				table.insert(positions, index..","..table.concat(positions, ","));
+			end
+		end
+		if axis.options.style then
+			table.insert(styles, index..","..axis.options.style);
+		end
+		if axis.options.ticklength then
+			table.insert(ticklengths, index..","..axis.options.ticklength);
+		end
+	end
+	
+	local result = {};
+	
+	if next(axes) then
+		table.insert(result, "chxt="..urlencode(table.concat(axes, ",")));
+	end
+	if next(ranges) then
+		table.insert(result, "chxr="..urlencode(table.concat(ranges, ",")));
+	end
+	if next(labels) then
+		table.insert(result, "chxl="..urlencode(table.concat(labels, "|")));
+	end
+	if next(positions) then
+		table.insert(result, "chxp="..urlencode(table.concat(positions, ",")));
+	end
+	if next(styles) then
+		table.insert(result, "chxs="..urlencode(table.concat(styles, "|")));
+	end
+	if next(ticklengths)
+		table.insert(result, "chxtc="..urlencode(table.concat(ticklengths, "|")));
+	end
+	
+	return table.concat(result, "&");
+end
+
+---- Data points ----
+function chart:add_marker(marker)
+	table.insert(self.markers, marker);
+end
+
+local marker_type_map = { flag = "f", text = "t", number = "N" };
+function writers:markers()
+	local result = { };
+	for _, marker in ipairs(self.markers) do
+		table.insert(result, urlencode(
+			(marker_type_map[marker.type] or "f")
+			..(marker.label or "Label")..","
+			..(marker.color or self.marker_color)..","
+			..(marker.series or 0)..","
+			..(marker.index or 0)..","
+			..(marker.size or 11)..","
+			..(marker.priority or 0)));
+	end
+	if next(result) then
+		return "chm="..table.concat(result, "%7c");
+	end
+end
+
+---- Colours and fill ----
+function chart:set_color(color)
+	self.color = color;
+end
+
+function chart:set_fill(fill_color)
+	self.fill = fill_color;
+end
+
+function writers:color()
+	if self.color then
+		return "chco="..self.color;
+	end
+end
+
+function chart:url()
+	local url = self.base_url.."?cht="..self.type.."&";
+	
+	local params = {};
+	for name, writer in pairs(writers) do
+		local ret = writer(self);
+		if ret then
+			table.insert(params, tostring(ret));
+		end
+	end
+
+	return url..table.concat(params, "&");
+end
+

mercurial