# HG changeset patch # User Matthew Wild # Date 1245817344 -3600 # Node ID 757c17d808a8d6d200866f8115bf0ede35fdcf0a Initial commit diff -r 000000000000 -r 757c17d808a8 gchart.lua --- /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 +