Wed, 24 Jun 2009 14:46:26 +0100
Fix traceback when writing axes
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(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 ---- Legend ---- function chart:set_legend(entries) self.legend = entries; end function writers:legend() if self.legend then return "chdl="..table.concat(self.legend, "|"); end end ---- Legend position ---- local position_map = { vertical = { bottom = "b", top = "t", left = "l", right = "r" }; horizontal = { bottom = "b", top = "t" }; }; function chart:set_legend_position(position, layout) self.legend_position = positionmap[layout or "vertical"][position or "right"] or position; end function writers:legend_position() if self.legend_position then return "chdlp="..self.legend_position; 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, ticklengths = {}, {}; 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) then 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