Blame docs/server-status/server-status.lua

Packit 90a5c9
--[[
Packit 90a5c9
Licensed to the Apache Software Foundation (ASF) under one
Packit 90a5c9
or more contributor license agreements.  See the NOTICE file
Packit 90a5c9
distributed with this work for additional information
Packit 90a5c9
regarding copyright ownership.  The ASF licenses this file
Packit 90a5c9
to you under the Apache License, Version 2.0 (the
Packit 90a5c9
"License"); you may not use this file except in compliance
Packit 90a5c9
with the License.  You may obtain a copy of the License at
Packit 90a5c9
Packit 90a5c9
    http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
Packit 90a5c9
Unless required by applicable law or agreed to in writing,
Packit 90a5c9
software distributed under the License is distributed on an
Packit 90a5c9
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Packit 90a5c9
KIND, either express or implied.  See the License for the
Packit 90a5c9
specific language governing permissions and limitations
Packit 90a5c9
under the License.
Packit 90a5c9
]]
Packit 90a5c9
Packit 90a5c9
--[[ mod_lua implementation of the server-status page ]]
Packit 90a5c9
local ssversion = "0.11" -- verion of this script
Packit 90a5c9
local redact_ips = true -- whether to replace the last two bits of every IP with 'x.x'
Packit 90a5c9
local warning_banner = [[
Packit 90a5c9
    
Packit 90a5c9
        

Don't be alarmed - this page is here for a reason!

Packit 90a5c9
        

This is an example server status page for the Apache HTTP Server. Nothing on this server is secret, no URL tokens, no sensitive passwords. Everything served from here is static data.

Packit 90a5c9
    
Packit 90a5c9
]]
Packit 90a5c9
local show_warning = true -- whether to display the above warning/notice on the page
Packit 90a5c9
local show_modules = false -- Whether to list loaded modules or not
Packit 90a5c9
local show_threads = true -- whether to list thread information or not
Packit 90a5c9
Packit 90a5c9
-- pre-declare some variables defined at the bottom of this script:
Packit 90a5c9
local status_js, status_css, quokka_js
Packit 90a5c9
Packit 90a5c9
-- quick and dirty JSON conversion
Packit 90a5c9
local function quickJSON(input)
Packit 90a5c9
    if type(input) == "table" then
Packit 90a5c9
        local t = 'array'
Packit 90a5c9
        for k, v in pairs(input) do
Packit 90a5c9
            if type(k) ~= "number" then
Packit 90a5c9
                t = 'hash'
Packit 90a5c9
                break
Packit 90a5c9
            end
Packit 90a5c9
        end
Packit 90a5c9
        
Packit 90a5c9
        if t == 'hash' then
Packit 90a5c9
            local out = ""
Packit 90a5c9
            local tbl = {}
Packit 90a5c9
            for k, v in pairs(input) do
Packit 90a5c9
                local kv = ([["%s": %s]]):format(k, quickJSON(v))
Packit 90a5c9
                table.insert(tbl, kv)
Packit 90a5c9
            end
Packit 90a5c9
            return "{" .. table.concat(tbl, ", ") .. "}"
Packit 90a5c9
        else
Packit 90a5c9
            local tbl = {}
Packit 90a5c9
            for k, v in pairs(input) do
Packit 90a5c9
                table.insert(tbl, quickJSON(v))
Packit 90a5c9
            end
Packit 90a5c9
            return "[" .. table.concat(tbl, ", ") .. "]"
Packit 90a5c9
        end
Packit 90a5c9
    elseif type(input) == "string" then
Packit 90a5c9
        return ([["%s"]]):format(input:gsub('"', '\\"'):gsub("[\r\n\t]", " "))
Packit 90a5c9
    elseif type(input) == "number" then
Packit 90a5c9
        return tostring(input)
Packit 90a5c9
    elseif type(input) == "boolean" then
Packit 90a5c9
        return (input and "true" or "false")
Packit 90a5c9
    else
Packit 90a5c9
        return "null"
Packit 90a5c9
    end
Packit 90a5c9
end
Packit 90a5c9
Packit 90a5c9
-- Module information callback
Packit 90a5c9
local function modInfo(r, modname)
Packit 90a5c9
    if modname then
Packit 90a5c9
            r:puts [[
Packit 90a5c9
    
Packit 90a5c9
    <html>
Packit 90a5c9
      <head>
Packit 90a5c9
        <meta charset="utf-8">
Packit 90a5c9
        <style>
Packit 90a5c9
        ]]
Packit 90a5c9
        r:puts (status_css)
Packit 90a5c9
        r:puts [[
Packit 90a5c9
        </style>
Packit 90a5c9
        <title>Module information</title>
Packit 90a5c9
      </head>
Packit 90a5c9
    
Packit 90a5c9
      <body>
Packit 90a5c9
    ]]
Packit 90a5c9
        r:puts( ("

Details for module %s

\n"):format(r:escape_html(modname)) )
Packit 90a5c9
        -- Queries the server for information about a module
Packit 90a5c9
        local mod = r.module_info(modname)
Packit 90a5c9
        if mod then
Packit 90a5c9
            for k, v in pairs(mod.commands) do
Packit 90a5c9
                -- print out all directives accepted by this module
Packit 90a5c9
                r:puts( ("%s: %s
\n"):format(r:escape_html(k), v))
Packit 90a5c9
            end
Packit 90a5c9
        end
Packit 90a5c9
        -- HTML tail
Packit 90a5c9
        r:puts[[
Packit 90a5c9
      </body>
Packit 90a5c9
    </html>
Packit 90a5c9
    ]]
Packit 90a5c9
    end
Packit 90a5c9
end
Packit 90a5c9
Packit 90a5c9
-- Function for generating server stats
Packit 90a5c9
function getServerState(r, verbose)
Packit 90a5c9
    local state = {}
Packit 90a5c9
    
Packit 90a5c9
    state.mpm = {
Packit 90a5c9
        type = "prefork", -- default to prefork until told otherwise
Packit 90a5c9
        threadsPerChild = 1,
Packit 90a5c9
        threaded = false,
Packit 90a5c9
        maxServers = r.mpm_query(12),
Packit 90a5c9
        activeServers = 0
Packit 90a5c9
    }
Packit 90a5c9
    if r.mpm_query(14) == 1 then
Packit 90a5c9
        state.mpm.type = "event" -- this is event mpm
Packit 90a5c9
    elseif r.mpm_query(3) >= 1 then
Packit 90a5c9
        state.mpm.type = "worker" -- it's not event, but it's threaded, we'll assume worker mpm (could be motorz??)
Packit 90a5c9
    elseif r.mpm_query(2) == 1 then
Packit 90a5c9
        state.mpm.type = "winnt" -- it's threaded, but not worker nor event, so it's probably winnt
Packit 90a5c9
    end
Packit 90a5c9
    if state.mpm.type ~= "prefork" then
Packit 90a5c9
        state.mpm.threaded = true -- it's threaded
Packit 90a5c9
        state.mpm.threadsPerChild = r.mpm_query(6) -- get threads per child proc
Packit 90a5c9
    end
Packit 90a5c9
    
Packit 90a5c9
    state.processes = {} -- list of child procs
Packit 90a5c9
    state.connections = { -- overall connection info
Packit 90a5c9
        idle = 0,
Packit 90a5c9
        active = 0
Packit 90a5c9
    }
Packit 90a5c9
    -- overall server stats
Packit 90a5c9
    state.server = {
Packit 90a5c9
        connections = 0,
Packit 90a5c9
        bytes = 0,
Packit 90a5c9
        built = r.server_built,
Packit 90a5c9
        localtime = os.time(),
Packit 90a5c9
        uptime = os.time() - r.started,
Packit 90a5c9
        version = r.banner,
Packit 90a5c9
        host = r.server_name,
Packit 90a5c9
        modules = nil,
Packit 90a5c9
        extended = show_threads, -- whether extended status is available or not
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    -- if show_modules is true, add list of modules to the JSON
Packit 90a5c9
    if show_modules then
Packit 90a5c9
        state.server.modules = {}
Packit 90a5c9
        for k, module in pairs(r:loaded_modules()) do
Packit 90a5c9
            table.insert(state.server.modules, module)
Packit 90a5c9
        end
Packit 90a5c9
    end
Packit 90a5c9
    
Packit 90a5c9
    -- Fetch process/thread data
Packit 90a5c9
    for i=0,state.mpm.maxServers-1,1 do
Packit 90a5c9
        local server = r.scoreboard_process(r, i);
Packit 90a5c9
        if server then
Packit 90a5c9
            local s = {
Packit 90a5c9
                active = false,
Packit 90a5c9
                pid = nil,
Packit 90a5c9
                bytes = 0,
Packit 90a5c9
                stime = 0,
Packit 90a5c9
                utime = 0,
Packit 90a5c9
                connections = 0,
Packit 90a5c9
            }
Packit 90a5c9
            local tstates = {}
Packit 90a5c9
            if server.pid then
Packit 90a5c9
                state.connections.idle = state.connections.idle + (server.keepalive or 0)
Packit 90a5c9
                s.connections = 0
Packit 90a5c9
                if server.pid > 0 then
Packit 90a5c9
                    state.mpm.activeServers = state.mpm.activeServers + 1
Packit 90a5c9
                    s.active = true
Packit 90a5c9
                    s.pid = server.pid
Packit 90a5c9
                end
Packit 90a5c9
                for j = 0, state.mpm.threadsPerChild-1, 1 do
Packit 90a5c9
                    local worker = r.scoreboard_worker(r, i, j)
Packit 90a5c9
                    if worker then
Packit 90a5c9
                        s.stime = s.stime + (worker.stimes or 0);
Packit 90a5c9
                        s.utime = s.utime + (worker.utimes or 0);
Packit 90a5c9
                        if verbose and show_threads then
Packit 90a5c9
                            s.threads = s.threads or {}
Packit 90a5c9
                            table.insert(s.threads, {
Packit 90a5c9
                                bytes = worker.bytes_served,
Packit 90a5c9
                                thread = ("0x%x"):format(worker.tid),
Packit 90a5c9
                                client = redact_ips and (worker.client or "???"):gsub("[a-f0-9]+[.:]+[a-f0-9]+$", "x.x") or worker.client or "???",
Packit 90a5c9
                                cost = ((worker.utimes or 0) + (worker.stimes or 0)),
Packit 90a5c9
                                count = worker.access_count,
Packit 90a5c9
                                vhost = worker.vhost:gsub(":%d+", ""),
Packit 90a5c9
                                request = worker.request,
Packit 90a5c9
                                last_used = math.floor(worker.last_used/1000000)
Packit 90a5c9
                            })
Packit 90a5c9
                        end
Packit 90a5c9
                        state.server.connections = state.server.connections + worker.access_count
Packit 90a5c9
                        s.bytes = s.bytes + worker.bytes_served
Packit 90a5c9
                        s.connections = s.connections + worker.access_count
Packit 90a5c9
                        if server.pid > 0 then
Packit 90a5c9
                            tstates[worker.status] = (tstates[worker.status] or 0) + 1
Packit 90a5c9
                        end
Packit 90a5c9
                    end
Packit 90a5c9
                end
Packit 90a5c9
            end
Packit 90a5c9
            
Packit 90a5c9
            s.workerStates = {
Packit 90a5c9
                keepalive = (server.keepalive > 0) and server.keepalive or tstates[5] or 0,
Packit 90a5c9
                closing = tstates[8] or 0,
Packit 90a5c9
                idle = tstates[2] or 0,
Packit 90a5c9
                writing = tstates[4] or 0,
Packit 90a5c9
                reading = tstates[3] or 0,
Packit 90a5c9
                graceful = tstates[9] or 0
Packit 90a5c9
            }
Packit 90a5c9
            table.insert(state.processes, s)
Packit 90a5c9
            state.server.bytes = state.server.bytes + s.bytes
Packit 90a5c9
            state.connections.active = state.connections.active + (tstates[8] or 0) + (tstates[4] or 0) + (tstates[3] or 0)
Packit 90a5c9
        end
Packit 90a5c9
    end
Packit 90a5c9
    return state
Packit 90a5c9
end
Packit 90a5c9
Packit 90a5c9
-- Handler function
Packit 90a5c9
function handle(r)
Packit 90a5c9
    
Packit 90a5c9
    -- Parse GET data, if any, and set content type
Packit 90a5c9
    local GET = r:parseargs()
Packit 90a5c9
    
Packit 90a5c9
    if GET['module'] then
Packit 90a5c9
        modInfo(r, GET['module'])
Packit 90a5c9
        return apache2.OK
Packit 90a5c9
    end
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    -- If we only need the stats feed, compact it and hand it over
Packit 90a5c9
    if GET['view'] and GET['view'] == "json" then
Packit 90a5c9
        local state = getServerState(r, GET['extended'] == 'true')
Packit 90a5c9
        r.content_type = "application/json"
Packit 90a5c9
        r:puts(quickJSON(state))
Packit 90a5c9
        return apache2.OK
Packit 90a5c9
    end
Packit 90a5c9
    
Packit 90a5c9
    if not GET['resource'] then
Packit 90a5c9
    
Packit 90a5c9
        local state = getServerState(r, show_threads)
Packit 90a5c9
        
Packit 90a5c9
        -- Print out the HTML for the front page
Packit 90a5c9
        r.content_type = "text/html"
Packit 90a5c9
        r:puts ( ([=[
Packit 90a5c9
    
Packit 90a5c9
    <html>
Packit 90a5c9
      <head>
Packit 90a5c9
        <meta charset="utf-8">
Packit 90a5c9
        
Packit 90a5c9
        <link href="?resource=css" rel="stylesheet">
Packit 90a5c9
        
Packit 90a5c9
        
Packit 90a5c9
        <script type="text/javascript" src="?resource=js"></script>
Packit 90a5c9
        
Packit 90a5c9
        <title>Server status for %s</title>
Packit 90a5c9
      </head>
Packit 90a5c9
    
Packit 90a5c9
      <body onload="refreshCharts(false);">
Packit 90a5c9
        
Packit 90a5c9
            
Packit 90a5c9
                
Packit 90a5c9
                Apache HTTPd
Packit 90a5c9
            
Packit 90a5c9
            
Packit 90a5c9
            
Packit 90a5c9
            
Packit 90a5c9
                
    Packit 90a5c9
                        
  • Packit 90a5c9
                            Dashboard
    Packit 90a5c9
                        
    Packit 90a5c9
                        
  • Packit 90a5c9
                            Server Info
    Packit 90a5c9
                        
    Packit 90a5c9
                        
  • Packit 90a5c9
                            Show thread information
    Packit 90a5c9
                        
    Packit 90a5c9
                        
  • Packit 90a5c9
                            Show loaded modules
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                    
    Packit 90a5c9
                     %s 
    Packit 90a5c9
                    
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                    
    Packit 90a5c9
                        
    Quick Stats
    Packit 90a5c9
                        
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                    
    Packit 90a5c9
                        
    Charts
    Packit 90a5c9
                        
    Packit 90a5c9
                            
    Packit 90a5c9
                            <canvas id="actions_div" width="1400" height="400" class="canvas_wide"></canvas>
    Packit 90a5c9
                            <canvas id="status_div" width=580" height="400" class="canvas_narrow"></canvas>
    Packit 90a5c9
                            <canvas id="traffic_div" width="1400" height="400" class="canvas_wide"></canvas>
    Packit 90a5c9
                            <canvas id="idle_div" width="580" height="400" class="canvas_narrow"></canvas>
    Packit 90a5c9
                            <canvas id="connection_div" width="1400" height="400" class="canvas_wide"></canvas>
    Packit 90a5c9
                            <canvas id="cpu_div" width="580" height="400" class="canvas_narrow"></canvas>
    Packit 90a5c9
                            
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                    
    Packit 90a5c9
                        
    General server information
    Packit 90a5c9
                        
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                    
    Packit 90a5c9
                        
    Thread breakdown
    Packit 90a5c9
                        
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                    
    Packit 90a5c9
                        
    Modules loaded
    Packit 90a5c9
                        
    Packit 90a5c9
                        blabla
    Packit 90a5c9
                        
    Packit 90a5c9
                    
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
            
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        ]=]):format(
    Packit 90a5c9
            r.server_name,
    Packit 90a5c9
            r.banner,
    Packit 90a5c9
            r.server_name,
    Packit 90a5c9
            show_warning and warning_banner or ""
    Packit 90a5c9
            ) );
    Packit 90a5c9
            -- HTML tail
    Packit 90a5c9
            r:puts[[
    Packit 90a5c9
            </body>
    Packit 90a5c9
          </html>
    Packit 90a5c9
          ]]
    Packit 90a5c9
        else
    Packit 90a5c9
            -- Resource documents (CSS, JS, PNG)
    Packit 90a5c9
            if GET['resource'] == 'js' then
    Packit 90a5c9
                r.content_type = "application/javascript"
    Packit 90a5c9
                r:puts(quokka_js)
    Packit 90a5c9
                r:puts(status_js)
    Packit 90a5c9
            elseif GET['resource'] == 'css' then
    Packit 90a5c9
                r.content_type = "text/css"
    Packit 90a5c9
                r:puts(status_css)
    Packit 90a5c9
            elseif GET['resource'] == 'feather' then
    Packit 90a5c9
                r.content_type = "image/png"
    Packit 90a5c9
                r:write(r:base64_decode('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'))
    Packit 90a5c9
            end
    Packit 90a5c9
        end
    Packit 90a5c9
        return apache2.OK;
    Packit 90a5c9
    end
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    ------------------------------------
    Packit 90a5c9
    -- JavaScript and CSS definitions --
    Packit 90a5c9
    ------------------------------------
    Packit 90a5c9
    Packit 90a5c9
    -- Set up some JavaScripts:
    Packit 90a5c9
    status_js = [==[
    Packit 90a5c9
    Number.prototype.pad = function(size) {
    Packit 90a5c9
        var str = String(this);
    Packit 90a5c9
        while (str.length < size) {
    Packit 90a5c9
            str = "0" + str;
    Packit 90a5c9
        }
    Packit 90a5c9
        return str;
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    function getAsync(theUrl, xstate, callback) {
    Packit 90a5c9
        var xmlHttp = null;
    Packit 90a5c9
        if (window.XMLHttpRequest) {
    Packit 90a5c9
    	xmlHttp = new XMLHttpRequest();
    Packit 90a5c9
        } else {
    Packit 90a5c9
    	xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    Packit 90a5c9
        }
    Packit 90a5c9
        xmlHttp.open("GET", theUrl, true);
    Packit 90a5c9
        xmlHttp.send(null);
    Packit 90a5c9
        xmlHttp.onreadystatechange = function(state) {
    Packit 90a5c9
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
    Packit 90a5c9
                if (callback) {
    Packit 90a5c9
                    callback(JSON.parse(xmlHttp.responseText));
    Packit 90a5c9
                }
    Packit 90a5c9
                
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    var actionCache = [];
    Packit 90a5c9
    var connectionCache = [];
    Packit 90a5c9
    var trafficCache = [];
    Packit 90a5c9
    var processes = {};
    Packit 90a5c9
    var lastBytes = 0;
    Packit 90a5c9
    var lastConnections = 0;
    Packit 90a5c9
    var negativeBytes = 0; // cache for proc reloads, which skews traffic
    Packit 90a5c9
    var updateSpeed = 5; // How fast do charts update?
    Packit 90a5c9
    var maxRecords = 24; // How many records to show per chart
    Packit 90a5c9
    var cpumax = 1000000; // random cpu max(?)
    Packit 90a5c9
    Packit 90a5c9
    function refreshCharts(json, state) {
    Packit 90a5c9
        if (json && json.processes) {
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
             // general server info box
    Packit 90a5c9
            var gs = document.getElementById('server_breakdown');
    Packit 90a5c9
            gs.innerHTML = "";
    Packit 90a5c9
            gs.innerHTML += "Server version: " + json.server.version + "
    ";
    Packit 90a5c9
            gs.innerHTML += "Server built: " + json.server.built + "
    ";
    Packit 90a5c9
            gs.innerHTML += "Server MPM: " + json.mpm.type + " 
    ";
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // Get a timestamp
    Packit 90a5c9
            var now = new Date();
    Packit 90a5c9
            var ts = now.getHours().pad(2) + ":" + now.getMinutes().pad(2) + ":" + now.getSeconds().pad(2);
    Packit 90a5c9
            
    Packit 90a5c9
            var utime = 0;
    Packit 90a5c9
            var stime = 0;
    Packit 90a5c9
            
    Packit 90a5c9
            // Construct state based on proc details
    Packit 90a5c9
            var state = {
    Packit 90a5c9
                timestamp: ts,
    Packit 90a5c9
                closing: 0,
    Packit 90a5c9
                idle: 0,
    Packit 90a5c9
                writing: 0,
    Packit 90a5c9
                reading: 0,
    Packit 90a5c9
                keepalive: 0,
    Packit 90a5c9
                graceful: 0
    Packit 90a5c9
            }
    Packit 90a5c9
            for (var i in json.processes) {
    Packit 90a5c9
                var proc = json.processes[i];
    Packit 90a5c9
                if (proc.pid) {
    Packit 90a5c9
                    state.closing += proc.workerStates.closing||0;
    Packit 90a5c9
                    state.idle += proc.workerStates.idle||0;
    Packit 90a5c9
                    state.writing += proc.workerStates.writing||0;
    Packit 90a5c9
                    state.reading += proc.workerStates.reading||0;
    Packit 90a5c9
                    state.keepalive += proc.workerStates.keepalive||0;
    Packit 90a5c9
                    state.graceful += proc.workerStates.graceful||0;
    Packit 90a5c9
                    utime += proc.utime;
    Packit 90a5c9
                    stime += proc.stime;
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // Push action state entry into action cache with timestamp
    Packit 90a5c9
            // Shift if more than 10 entries in cache
    Packit 90a5c9
            actionCache.push(state);
    Packit 90a5c9
            if (actionCache.length > maxRecords) {
    Packit 90a5c9
                actionCache.shift();
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // construct array for QuokkaLines
    Packit 90a5c9
            var arr = [];
    Packit 90a5c9
            for (var i in actionCache) {
    Packit 90a5c9
                var el = actionCache[i];
    Packit 90a5c9
                if (json.mpm.type == 'event') {
    Packit 90a5c9
                arr.push([el.timestamp, el.closing, el.idle, el.writing, el.reading, el.graceful]);
    Packit 90a5c9
                } else {
    Packit 90a5c9
                    arr.push([el.timestamp, el.keepalive, el.closing, el.idle, el.writing, el.reading, el.graceful]);
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            var states = ['Keepalive', 'Closing', 'Idle', 'Writing', 'Reading', 'Graceful']
    Packit 90a5c9
            if (json.mpm.type == 'event') {
    Packit 90a5c9
                states.shift();
    Packit 90a5c9
                if (document.getElementById('mpminfo')) {
    Packit 90a5c9
                    document.getElementById('mpminfo').innerHTML = "(" + fn(parseInt(json.connections.idle)) + " connections in idle keepalive)";
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            // Draw action chart
    Packit 90a5c9
            quokkaLines("actions_div", states, arr, { lastsum: true, hires: true, nosum: true, stack: true, curve: true, title: "Thread states" } );
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // Get traffic, figure out how much it was this time (0 if just started!)
    Packit 90a5c9
            var bytesThisTurn = 0;
    Packit 90a5c9
            var connectionsThisTurn = 0;
    Packit 90a5c9
            for (var i in json.processes) {
    Packit 90a5c9
                var proc = json.processes[i];
    Packit 90a5c9
                var pid = proc.pid
    Packit 90a5c9
                // if we haven't seen this proc before, ignore its bytes first time
    Packit 90a5c9
                if (!processes[pid]) {
    Packit 90a5c9
                    processes[pid] = {
    Packit 90a5c9
                        bytes: proc.bytes,
    Packit 90a5c9
                        connections: proc.connections,
    Packit 90a5c9
                    }
    Packit 90a5c9
                } else {
    Packit 90a5c9
                    bytesThisTurn += proc.bytes - processes[pid].bytes;
    Packit 90a5c9
                    if (pid) {
    Packit 90a5c9
                        x = proc.connections - processes[pid].connections;
    Packit 90a5c9
                        connectionsThisTurn += (x > 0) ? x : 0;
    Packit 90a5c9
                    }
    Packit 90a5c9
                    processes[pid].bytes = proc.bytes;
    Packit 90a5c9
                    processes[pid].connections = proc.connections;
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            if (lastBytes == 0 ) {
    Packit 90a5c9
                bytesThisTurn = 0;
    Packit 90a5c9
            }
    Packit 90a5c9
            lastBytes = 1;
    Packit 90a5c9
    Packit 90a5c9
            // Push a new element into cache, prune cache
    Packit 90a5c9
            var el = {
    Packit 90a5c9
                timestamp: ts,
    Packit 90a5c9
                bytes: bytesThisTurn/updateSpeed
    Packit 90a5c9
            };
    Packit 90a5c9
            trafficCache.push(el);
    Packit 90a5c9
            if (trafficCache.length > maxRecords) {
    Packit 90a5c9
                trafficCache.shift();
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // construct array for QuokkaLines
    Packit 90a5c9
            arr = [];
    Packit 90a5c9
            for (var i in trafficCache) {
    Packit 90a5c9
                var el = trafficCache[i];
    Packit 90a5c9
                arr.push([el.timestamp, el.bytes]);
    Packit 90a5c9
            }
    Packit 90a5c9
            // Draw action chart
    Packit 90a5c9
            quokkaLines("traffic_div", ['Traffic'], arr, { traffic: true, hires: true, nosum: true, stack: true, curve: true, title: "Traffic per second" } );
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // Get connections per second
    Packit 90a5c9
            // Push a new element into cache, prune cache
    Packit 90a5c9
            var el = {
    Packit 90a5c9
                timestamp: ts,
    Packit 90a5c9
                connections: (connectionsThisTurn+1)/updateSpeed
    Packit 90a5c9
            };
    Packit 90a5c9
            connectionCache.push(el);
    Packit 90a5c9
            if (connectionCache.length > maxRecords) {
    Packit 90a5c9
                connectionCache.shift();
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // construct array for QuokkaLines
    Packit 90a5c9
            arr = [];
    Packit 90a5c9
            for (var i in connectionCache) {
    Packit 90a5c9
                var el = connectionCache[i];
    Packit 90a5c9
                arr.push([el.timestamp, el.connections]);
    Packit 90a5c9
            }
    Packit 90a5c9
            // Draw connection chart
    Packit 90a5c9
            quokkaLines("connection_div", ['Connections/sec'], arr, { traffic: false, hires: true, nosum: true, stack: true, curve: true, title: "Connections per second" } );
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // Thread info
    Packit 90a5c9
            quokkaCircle("status_div", [
    Packit 90a5c9
            { title: 'Active', value: (json.mpm.threadsPerChild*json.mpm.activeServers)},
    Packit 90a5c9
            { title: 'Reserve', value: (json.mpm.threadsPerChild*(json.mpm.activeServers-json.mpm.maxServers))}
    Packit 90a5c9
            ],
    Packit 90a5c9
                { title: "Worker pool", hires: true});
    Packit 90a5c9
            
    Packit 90a5c9
            // Idle vs active connections
    Packit 90a5c9
            var idlecons = json.connections.idle;
    Packit 90a5c9
            var activecons = json.connections.active;
    Packit 90a5c9
            quokkaCircle("idle_div", [
    Packit 90a5c9
                { title: 'Idle', value: idlecons},
    Packit 90a5c9
                { title: 'Active', value: activecons},
    Packit 90a5c9
                ],
    Packit 90a5c9
                { hires: true, title: "Idle vs active connections"});
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // CPU info
    Packit 90a5c9
            while ( (stime+utime) > cpumax ) {
    Packit 90a5c9
                cpumax = cpumax * 2;
    Packit 90a5c9
            }
    Packit 90a5c9
    Packit 90a5c9
            quokkaCircle("cpu_div", [
    Packit 90a5c9
                { title: 'Idle', value: (cpumax - stime - utime) / (cpumax/100)},
    Packit 90a5c9
                { title: 'System', value: stime/(cpumax/100)},
    Packit 90a5c9
                { title: 'User', value: utime/(cpumax/100)}
    Packit 90a5c9
                ],
    Packit 90a5c9
                { hires: true, title: "CPU usage", pct: true});
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            // General stats infobox
    Packit 90a5c9
            var gstats = document.getElementById('general_stats');
    Packit 90a5c9
            gstats.innerHTML = ''; // wipe the box
    Packit 90a5c9
            
    Packit 90a5c9
                // Days since restart
    Packit 90a5c9
                var u_f = Math.floor(json.server.uptime/8640.0) / 10;
    Packit 90a5c9
                var u_d = Math.floor(json.server.uptime/86400);
    Packit 90a5c9
                var u_h = Math.floor((json.server.uptime%86400)/3600);
    Packit 90a5c9
                var u_m = Math.floor((json.server.uptime%3600)/60);
    Packit 90a5c9
                var u_s = Math.floor(json.server.uptime %60);
    Packit 90a5c9
                var str =  u_d + " day" + (u_d != 1 ? "s, " : ", ") + u_h + " hour" + (u_h != 1 ? "s, " : ", ") + u_m + " minute" + (u_m != 1 ? "s" : "");
    Packit 90a5c9
                var ubox = document.createElement('div');
    Packit 90a5c9
                ubox.setAttribute("class", "statsbox");
    Packit 90a5c9
                ubox.innerHTML = "" + u_f + " days
    since last (re)start.
    <small>" + str;
    Packit 90a5c9
                gstats.appendChild(ubox);
    Packit 90a5c9
                
    Packit 90a5c9
                
    Packit 90a5c9
                // Bytes transferred in total
    Packit 90a5c9
                var MB = fnmb(json.server.bytes);
    Packit 90a5c9
                var KB = (json.server.bytes > 0) ? fnmb(json.server.bytes/json.server.connections) : 0;
    Packit 90a5c9
                var KBs = fnmb(json.server.bytes/json.server.uptime);
    Packit 90a5c9
                var mbbox = document.createElement('div');
    Packit 90a5c9
                mbbox.setAttribute("class", "statsbox");
    Packit 90a5c9
                mbbox.innerHTML = "" + MB + "
    transferred in total.
    <small>" + KBs + "/sec, " + KB + "/request";
    Packit 90a5c9
                gstats.appendChild(mbbox);
    Packit 90a5c9
                
    Packit 90a5c9
                // connections in total
    Packit 90a5c9
                var cons = fn(json.server.connections);
    Packit 90a5c9
                var cps = Math.floor(json.server.connections/json.server.uptime*100)/100;
    Packit 90a5c9
                var conbox = document.createElement('div');
    Packit 90a5c9
                conbox.setAttribute("class", "statsbox");
    Packit 90a5c9
                conbox.innerHTML = "" + cons + " conns
    since server started.
    <small>" + cps + " requests per second";
    Packit 90a5c9
                gstats.appendChild(conbox);
    Packit 90a5c9
                
    Packit 90a5c9
                // threads working
    Packit 90a5c9
                var tpc = json.mpm.threadsPerChild;
    Packit 90a5c9
                var activeThreads = fn(json.mpm.activeServers * json.mpm.threadsPerChild);
    Packit 90a5c9
                var maxThreads = json.mpm.maxServers * json.mpm.threadsPerChild;
    Packit 90a5c9
                var tbox = document.createElement('div');
    Packit 90a5c9
                tbox.setAttribute("class", "statsbox");
    Packit 90a5c9
                tbox.innerHTML = "" + activeThreads + " threads
    currently at work (" + json.mpm.activeServers + "x" + tpc+" threads).
    <small>" + maxThreads + " (" + json.mpm.maxServers + "x"+tpc+") threads allowed.";
    Packit 90a5c9
                gstats.appendChild(tbox);
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            window.setTimeout(waitTwo, updateSpeed*1000);
    Packit 90a5c9
            
    Packit 90a5c9
            // resize pane
    Packit 90a5c9
            document.getElementById('leftpane').style.height = document.getElementById('wrapper').getBoundingClientRect().height + "px";
    Packit 90a5c9
            
    Packit 90a5c9
            // Do we have extended info and module lists??
    Packit 90a5c9
            if (json.server.extended) document.getElementById('threads_button').style.display = 'block';
    Packit 90a5c9
            if (json.server.modules && json.server.modules.length > 0) {
    Packit 90a5c9
                var panel = document.getElementById('modules_breakdown');
    Packit 90a5c9
                var list = "
      ";
    Packit 90a5c9
                for (var i in json.server.modules) {
    Packit 90a5c9
                    var mod = json.server.modules[i];
    Packit 90a5c9
                    list += "
  • " + mod + "
  • ";
    Packit 90a5c9
                }
    Packit 90a5c9
                list += "";
    Packit 90a5c9
                panel.innerHTML = list;
    Packit 90a5c9
                
    Packit 90a5c9
                document.getElementById('modules_button').style.display = 'block';
    Packit 90a5c9
            }
    Packit 90a5c9
           
    Packit 90a5c9
            
    Packit 90a5c9
        } else if (json === false) {
    Packit 90a5c9
            waitTwo();
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    function refreshThreads(json, state) {
    Packit 90a5c9
        var box = document.getElementById('threads_breakdown');
    Packit 90a5c9
        box.innerHTML = "";
    Packit 90a5c9
        for (var i in json.processes) {
    Packit 90a5c9
            var proc = json.processes[i];
    Packit 90a5c9
            var phtml = '
    ';
    Packit 90a5c9
            if (!proc.active) phtml = '
    ';
    Packit 90a5c9
            phtml += "

    Process " + i + ":

    ";
    Packit 90a5c9
            phtml += "PID: " + (proc.pid||"None (not active)") + "
    ";
    Packit 90a5c9
            if (proc.threads && proc.active) {
    Packit 90a5c9
                phtml += "";
    Thread IDAccess countBytes servedLast UsedLast clientLast request
    Packit 90a5c9
                for (var j in proc.threads) {
    Packit 90a5c9
                    var thread = proc.threads[j];
    Packit 90a5c9
                    thread.request = (thread.request||"(Unknown)").replace(/[<>]+/g, "");
    Packit 90a5c9
                    phtml += ""+thread.thread+""+thread.count+""+thread.bytes+""+thread.last_used+""+thread.client+""+thread.request+"";
    Packit 90a5c9
                }
    Packit 90a5c9
                phtml += "";
    Packit 90a5c9
            } else {
    Packit 90a5c9
                phtml += "

    No thread information avaialable

    ";
    Packit 90a5c9
            }
    Packit 90a5c9
            phtml += "";
    Packit 90a5c9
            box.innerHTML += phtml;
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    function waitTwo() {
    Packit 90a5c9
        getAsync(location.href + "?view=json&rnd=" + Math.random(), null, refreshCharts)
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
        function showPanel(what) {
    Packit 90a5c9
            var items = ['dashboard','misc','threads','modules'];
    Packit 90a5c9
            for (var i in items) {
    Packit 90a5c9
                var item = items[i];
    Packit 90a5c9
                var btn = document.getElementById(item+'_button');
    Packit 90a5c9
                var panel = document.getElementById(item+'_panel');
    Packit 90a5c9
                if (item == what) {
    Packit 90a5c9
                    btn.setAttribute("class", "btn active");
    Packit 90a5c9
                    panel.style.display = 'block';
    Packit 90a5c9
                } else {
    Packit 90a5c9
                    btn.setAttribute("class", "btn");
    Packit 90a5c9
                    panel.style.display = 'none';
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // special constructors
    Packit 90a5c9
            if (what == 'threads') {
    Packit 90a5c9
                getAsync(location.href + "?view=json&extended=true&rnd=" + Math.random(), null, refreshThreads)
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        function fn(num) {
    Packit 90a5c9
            num = num + "";
    Packit 90a5c9
            num = num.replace(/(\d)(\d{9})$/, '$1,$2');
    Packit 90a5c9
            num = num.replace(/(\d)(\d{6})$/, '$1,$2');
    Packit 90a5c9
            num = num.replace(/(\d)(\d{3})$/, '$1,$2');
    Packit 90a5c9
            return num;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        function fnmb(num) {
    Packit 90a5c9
            var add = "bytes";
    Packit 90a5c9
            var dec = "";
    Packit 90a5c9
            var mul = 1;
    Packit 90a5c9
            if (num > 1024) { add = "KB"; mul= 1024; }
    Packit 90a5c9
            if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
    Packit 90a5c9
            if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
    Packit 90a5c9
            if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
    Packit 90a5c9
            num = num / mul;
    Packit 90a5c9
            if (add != "bytes") {
    Packit 90a5c9
                dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
    Packit 90a5c9
            }
    Packit 90a5c9
            return ( fn(Math.floor(num)) + dec + " " + add );
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        function sort(a,b){
    Packit 90a5c9
            last_col = -1;
    Packit 90a5c9
            var sort_reverse = false;
    Packit 90a5c9
            var sortWay = a.getAttribute("sort_" + b);
    Packit 90a5c9
            if (sortWay && sortWay == "forward") {
    Packit 90a5c9
                a.setAttribute("sort_" + b, "reverse");
    Packit 90a5c9
                sort_reverse = true;
    Packit 90a5c9
            }
    Packit 90a5c9
            else {
    Packit 90a5c9
                a.setAttribute("sort_" + b, "forward");
    Packit 90a5c9
            }
    Packit 90a5c9
            var c,d,e,f,g,h,i;
    Packit 90a5c9
            c=a.rows.length;
    Packit 90a5c9
            if(c<1){ return; }
    Packit 90a5c9
            d=a.rows[1].cells.length;
    Packit 90a5c9
            e=1;
    Packit 90a5c9
            var j=new Array(c);
    Packit 90a5c9
            f=0;
    Packit 90a5c9
            for(h=e;h
    Packit 90a5c9
                var k=new Array(d);
    Packit 90a5c9
                for(i=0;i
    Packit 90a5c9
                    cell_text="";
    Packit 90a5c9
                    cell_text=a.rows[h].cells[i].textContent;
    Packit 90a5c9
                    if(cell_text===undefined){cell_text=a.rows[h].cells[i].innerText;}
    Packit 90a5c9
                    k[i]=cell_text;
    Packit 90a5c9
                }
    Packit 90a5c9
                j[f++]=k;
    Packit 90a5c9
            }
    Packit 90a5c9
            var l=false;
    Packit 90a5c9
            var m,n;
    Packit 90a5c9
            if(b!=lastcol) lastseq="A";
    Packit 90a5c9
            else{
    Packit 90a5c9
                if(lastseq=="A") lastseq="D";
    Packit 90a5c9
                lastseq="A";
    Packit 90a5c9
            }
    Packit 90a5c9
    Packit 90a5c9
            g=c-1;
    Packit 90a5c9
    Packit 90a5c9
            for(h=0;h
    Packit 90a5c9
                l=false;
    Packit 90a5c9
                for(i=0;i
    Packit 90a5c9
                    m=j[i];
    Packit 90a5c9
                    n=j[i+1];
    Packit 90a5c9
                    if(lastseq=="A"){
    Packit 90a5c9
                        var gt = (m[b]>n[b]) ? true : false;
    Packit 90a5c9
                        var lt = (m[b]
    Packit 90a5c9
                        if (n[b].match(/^(\d+)$/)) { gt = parseInt(m[b], 10) > parseInt(n[b], 10) ? true : false; lt = parseInt(m[b], 10) < parseInt(n[b], 10) ? true : false; }
    Packit 90a5c9
                        if (sort_reverse) {gt = (!gt); lt = (!lt);}
    Packit 90a5c9
                        if(gt){
    Packit 90a5c9
                            j[i+1]=m;
    Packit 90a5c9
                            j[i]=n;
    Packit 90a5c9
                            l=true;
    Packit 90a5c9
                        }
    Packit 90a5c9
                    }
    Packit 90a5c9
                    else{
    Packit 90a5c9
                        if(lt){
    Packit 90a5c9
                            j[i+1]=m;
    Packit 90a5c9
                            j[i]=n;
    Packit 90a5c9
                            l=true;
    Packit 90a5c9
                        }
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
                if(l===false){
    Packit 90a5c9
                    break;
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            f=e;
    Packit 90a5c9
            for(h=0;h
    Packit 90a5c9
                m=j[h];
    Packit 90a5c9
                for(i=0;i
    Packit 90a5c9
                    if(a.rows[f].cells[i].innerText!==undefined){
    Packit 90a5c9
                        a.rows[f].cells[i].innerText=m[i];
    Packit 90a5c9
                    }
    Packit 90a5c9
                    else{
    Packit 90a5c9
                        a.rows[f].cells[i].textContent=m[i];
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
                f++;
    Packit 90a5c9
            }
    Packit 90a5c9
            lastcol=b;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        
    Packit 90a5c9
        var CPUmax =            1000000;
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        var showing = false;
    Packit 90a5c9
        function showDetails() {
    Packit 90a5c9
            for (i=1; i < 1000; i++) {
    Packit 90a5c9
                var obj = document.getElementById("srv_" + i);
    Packit 90a5c9
                if (obj) {
    Packit 90a5c9
                    if (showing) { obj.style.display = "none"; }
    Packit 90a5c9
                    else { obj.style.display = "block"; }
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            var link = document.getElementById("show_link");
    Packit 90a5c9
            showing = (!showing);
    Packit 90a5c9
            if (showing) { link.innerHTML = "Hide thread information"; }
    Packit 90a5c9
            else { link.innerHTML = "Show thread information"; }
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        var showing_modules = false;
    Packit 90a5c9
        function show_modules() {
    Packit 90a5c9
    Packit 90a5c9
            var obj = document.getElementById("modules");
    Packit 90a5c9
            if (obj) {
    Packit 90a5c9
                if (showing_modules) { obj.style.display = "none"; }
    Packit 90a5c9
                else { obj.style.display = "block"; }
    Packit 90a5c9
            }
    Packit 90a5c9
            var link = document.getElementById("show_modules_link");
    Packit 90a5c9
            showing_modules = (!showing_modules);
    Packit 90a5c9
            if (showing_modules) { link.innerHTML = "Hide loaded modules"; }
    Packit 90a5c9
            else { link.innerHTML = "Show loaded modules"; }
    Packit 90a5c9
        }
    Packit 90a5c9
    ]==]
    Packit 90a5c9
    Packit 90a5c9
    quokka_js = [==[
    Packit 90a5c9
    /*
    Packit 90a5c9
     * 
    Packit 90a5c9
     * Licensed under the Apache License, Version 2.0 (the "License");
    Packit 90a5c9
     * you may not use this file except in compliance with the License.
    Packit 90a5c9
     * You may obtain a copy of the License at
    Packit 90a5c9
     * 
    Packit 90a5c9
     *     http://www.apache.org/licenses/LICENSE-2.0
    Packit 90a5c9
     * 
    Packit 90a5c9
     * Unless required by applicable law or agreed to in writing, software
    Packit 90a5c9
     * distributed under the License is distributed on an "AS IS" BASIS,
    Packit 90a5c9
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    Packit 90a5c9
     * See the License for the specific language governing permissions and
    Packit 90a5c9
     * limitations under the License.
    Packit 90a5c9
     */
    Packit 90a5c9
    Packit 90a5c9
    // Traffic shaper
    Packit 90a5c9
    function quokka_fnmb(num) {
    Packit 90a5c9
        var add = "b";
    Packit 90a5c9
        var dec = "";
    Packit 90a5c9
        var mul = 1;
    Packit 90a5c9
        if (num > 1024) { add = "KB"; mul= 1024; }
    Packit 90a5c9
        if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
    Packit 90a5c9
        if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
    Packit 90a5c9
        if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
    Packit 90a5c9
        num = num / mul;
    Packit 90a5c9
        if (add != "b" && num < 10) {
    Packit 90a5c9
            dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
    Packit 90a5c9
        }
    Packit 90a5c9
        return ( Math.floor(num) + dec + " " + add );
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    // Hue, Saturation and Lightness to Red, Green and Blue:
    Packit 90a5c9
    function quokka_internal_hsl2rgb (h,s,l)
    Packit 90a5c9
    {
    Packit 90a5c9
        var min, sv, switcher, fract, vsf;
    Packit 90a5c9
        h = h % 1;
    Packit 90a5c9
        if (s > 1) s = 1;
    Packit 90a5c9
        if (l > 1) l = 1;
    Packit 90a5c9
        var v = (l <= 0.5) ? (l * (1 + s)) : (l + s - l * s);
    Packit 90a5c9
        if (v === 0)
    Packit 90a5c9
            return { r: 0, g: 0, b: 0 };
    Packit 90a5c9
    Packit 90a5c9
        min = 2 * l - v;
    Packit 90a5c9
        sv = (v - min) / v;
    Packit 90a5c9
        var sh = (6 * h) % 6;
    Packit 90a5c9
        switcher = Math.floor(sh);
    Packit 90a5c9
        fract = sh - switcher;
    Packit 90a5c9
        vsf = v * sv * fract;
    Packit 90a5c9
    Packit 90a5c9
        switch (switcher)
    Packit 90a5c9
        {
    Packit 90a5c9
            case 0: return { r: v, g: min + vsf, b: min };
    Packit 90a5c9
            case 1: return { r: v - vsf, g: v, b: min };
    Packit 90a5c9
            case 2: return { r: min, g: v, b: min + vsf };
    Packit 90a5c9
            case 3: return { r: min, g: v - vsf, b: v };
    Packit 90a5c9
            case 4: return { r: min + vsf, g: min, b: v };
    Packit 90a5c9
            case 5: return { r: v, g: min, b: v - vsf };
    Packit 90a5c9
        }
    Packit 90a5c9
        return {r:0, g:0, b: 0};
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    // RGB to Hex conversion
    Packit 90a5c9
    function quokka_internal_rgb2hex(r, g, b) {
    Packit 90a5c9
        return "#" + ((1 << 24) + (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b)).toString(16).slice(1);
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    // Generate color list used for charts
    Packit 90a5c9
    var colors = [];
    Packit 90a5c9
    var rgbs = []
    Packit 90a5c9
    var numColorRows = 6;
    Packit 90a5c9
    var numColorColumns = 20;
    Packit 90a5c9
    for (var x=0;x
    Packit 90a5c9
        for (var y=0;y
    Packit 90a5c9
            var rnd = [[148, 221, 119], [0, 203, 171], [51, 167, 215] , [35, 160, 253], [218, 54, 188], [16, 171, 246], [110, 68, 206], [21, 49, 248], [142, 104, 210]][y]
    Packit 90a5c9
            var color = quokka_internal_hsl2rgb(y > 8 ? (Math.random()) : (rnd[0]/255), y > 8 ? (0.75+(y*0.05)) : (rnd[1]/255), y > 8 ? (0.42 + (y*0.05*(x/numColorRows))) : (0.1 + rnd[2]/512));
    Packit 90a5c9
            
    Packit 90a5c9
            // Light (primary) color:
    Packit 90a5c9
            var hex = quokka_internal_rgb2hex(color.r*255, color.g*255, color.b*255);
    Packit 90a5c9
            
    Packit 90a5c9
            // Darker variant for gradients:
    Packit 90a5c9
            var dhex = quokka_internal_rgb2hex(color.r*131, color.g*131, color.b*131);
    Packit 90a5c9
            
    Packit 90a5c9
            // Medium variant for legends:
    Packit 90a5c9
            var mhex = quokka_internal_rgb2hex(color.r*200, color.g*200, color.b*200);
    Packit 90a5c9
            
    Packit 90a5c9
            colors.push([hex, dhex, color, mhex]);
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    /* Function for drawing pie diagrams
    Packit 90a5c9
     * Example usage:
    Packit 90a5c9
     * quokkaCircle("canvasName", [ { title: 'ups', value: 30}, { title: 'downs', value: 70} ] );
    Packit 90a5c9
     */
    Packit 90a5c9
    Packit 90a5c9
    function quokkaCircle(id, tags, opts) {
    Packit 90a5c9
        // Get Canvas object and context
    Packit 90a5c9
        var canvas = document.getElementById(id);
    Packit 90a5c9
        var ctx=canvas.getContext("2d");
    Packit 90a5c9
        
    Packit 90a5c9
        // Calculate the total value of the pie
    Packit 90a5c9
        var total = 0;
    Packit 90a5c9
        var k;
    Packit 90a5c9
        for (k in tags) {
    Packit 90a5c9
            tags[k].value = Math.abs(tags[k].value);
    Packit 90a5c9
            total += tags[k].value;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw the empty pie
    Packit 90a5c9
        var begin = 0;
    Packit 90a5c9
        var stop = 0;
    Packit 90a5c9
        var radius = (canvas.height*0.75)/2;
    Packit 90a5c9
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    Packit 90a5c9
        ctx.beginPath();
    Packit 90a5c9
        ctx.shadowBlur = 6;
    Packit 90a5c9
        ctx.shadowOffsetX = 6;
    Packit 90a5c9
        ctx.shadowOffsetY = 6;
    Packit 90a5c9
        ctx.shadowColor = "#555";
    Packit 90a5c9
        ctx.lineWidth = (opts && opts.hires) ? 6 : 2;
    Packit 90a5c9
        ctx.strokeStyle = "#222";
    Packit 90a5c9
        ctx.arc((canvas.width-140)/2,canvas.height/2,radius, 0, Math.PI * 2);
    Packit 90a5c9
        ctx.closePath();
    Packit 90a5c9
        ctx.stroke();
    Packit 90a5c9
        ctx.fill();
    Packit 90a5c9
        ctx.shadowBlur = 0;
    Packit 90a5c9
        ctx.shadowOffsetY = 0;
    Packit 90a5c9
        ctx.shadowOffsetX = 0;
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw a title if set:
    Packit 90a5c9
        if (opts && opts.title) {
    Packit 90a5c9
            ctx.font= (opts && opts.hires) ? "28px Sans-Serif" : "15px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#000000";
    Packit 90a5c9
            ctx.textAlign = "center";
    Packit 90a5c9
            ctx.fillText(opts.title,(canvas.width-140)/2, (opts && opts.hires) ? 30:15);
    Packit 90a5c9
            ctx.textAlign = "left";
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        ctx.beginPath();
    Packit 90a5c9
        var posY = 50;
    Packit 90a5c9
        var left = 120 + ((canvas.width-140)/2) + ((opts && opts.hires) ? 40 : 25)
    Packit 90a5c9
        for (k in tags) {
    Packit 90a5c9
            var val = tags[k].value;
    Packit 90a5c9
            stop = stop + (2 * Math.PI * (val / total));
    Packit 90a5c9
            
    Packit 90a5c9
            // Make a pizza slice
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            ctx.lineCap = 'round';
    Packit 90a5c9
            ctx.arc((canvas.width-140)/2,canvas.height/2,radius,begin,stop);
    Packit 90a5c9
            ctx.lineTo((canvas.width-140)/2,canvas.height/2);
    Packit 90a5c9
            ctx.closePath();
    Packit 90a5c9
            ctx.lineWidth = 0;
    Packit 90a5c9
            ctx.stroke();
    Packit 90a5c9
            
    Packit 90a5c9
            // Add color gradient
    Packit 90a5c9
            var grd=ctx.createLinearGradient(0,canvas.height*0.2,0,canvas.height);
    Packit 90a5c9
            grd.addColorStop(0,colors[k % colors.length][1]);
    Packit 90a5c9
            grd.addColorStop(1,colors[k % colors.length][0]);
    Packit 90a5c9
            ctx.fillStyle = grd;
    Packit 90a5c9
            ctx.fill();
    Packit 90a5c9
            begin = stop;
    Packit 90a5c9
            
    Packit 90a5c9
            // Make color legend
    Packit 90a5c9
            ctx.fillRect(left, posY-((opts && opts.hires) ? 15 : 10), (opts && opts.hires) ? 14 : 7, (opts && opts.hires) ? 14 : 7);
    Packit 90a5c9
            
    Packit 90a5c9
            // Add legend text
    Packit 90a5c9
            ctx.shadowColor = "rgba(0,0,0,0)"
    Packit 90a5c9
            ctx.font= (opts && opts.hires) ? "22px Sans-Serif" : "12px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#000";
    Packit 90a5c9
            ctx.fillText(tags[k].title + " (" + Math.floor(val) + (opts && opts.pct ? "%" : "") + ")",left+20,posY);
    Packit 90a5c9
            
    Packit 90a5c9
            posY += (opts && opts.hires) ? 28 : 14;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    /* Function for drawing line charts
    Packit 90a5c9
     * Example usage:
    Packit 90a5c9
     * quokkaLines("myCanvas", ['Line a', 'Line b', 'Line c'], [ [x1,a1,b1,c1], [x2,a2,b2,c2], [x3,a3,b3,c3] ], { stacked: true, curve: false, title: "Some title" } );
    Packit 90a5c9
     */
    Packit 90a5c9
    function quokkaLines(id, titles, values, options, sums) {
    Packit 90a5c9
        var canvas = document.getElementById(id);
    Packit 90a5c9
        var ctx=canvas.getContext("2d");
    Packit 90a5c9
        // clear the canvas first
    Packit 90a5c9
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
        ctx.lineWidth = 0.25;
    Packit 90a5c9
        ctx.strokeStyle = "#000000";
    Packit 90a5c9
        
    Packit 90a5c9
        var lwidth = 300;
    Packit 90a5c9
        var lheight = 75;
    Packit 90a5c9
        wspace = (options && options.hires) ? 110 : 55;
    Packit 90a5c9
        var rectwidth = canvas.width - lwidth - wspace;
    Packit 90a5c9
        var stack = options ? options.stack : false;
    Packit 90a5c9
        var curve = options ? options.curve : false;
    Packit 90a5c9
        var title = options ? options.title : null;
    Packit 90a5c9
        var spots = options ? options.points : false;
    Packit 90a5c9
        var noX = options ? options.nox : false;
    Packit 90a5c9
        var verts = options ? options.verts : true;
    Packit 90a5c9
        if (noX) {
    Packit 90a5c9
            lheight = 0;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // calc rectwidth if titles are large
    Packit 90a5c9
        var nlwidth = 0
    Packit 90a5c9
        for (var k in titles) {
    Packit 90a5c9
            ctx.font= (options && options.hires) ? "24px Sans-Serif" : "12px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#00000";
    Packit 90a5c9
            var x = parseInt(k)
    Packit 90a5c9
            if (!noX) {
    Packit 90a5c9
                x = x + 1;
    Packit 90a5c9
            }
    Packit 90a5c9
            var sum = 0
    Packit 90a5c9
            for (var y in values) {
    Packit 90a5c9
                sum += values[y][x]
    Packit 90a5c9
            }
    Packit 90a5c9
            var t = titles[k] + (!options.nosum ? " (" + ((sums && sums[k]) ? sums[k] : sum.toFixed(0)) + ")" : "");
    Packit 90a5c9
            var w = ctx.measureText(t).width + 48;
    Packit 90a5c9
            if (w > lwidth && w > nlwidth) {
    Packit 90a5c9
                nlwidth = w
    Packit 90a5c9
            }
    Packit 90a5c9
            if (nlwidth > 0) {
    Packit 90a5c9
                rectwidth -= nlwidth - lwidth
    Packit 90a5c9
                lwidth = nlwidth
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw a border
    Packit 90a5c9
        ctx.lineWidth = 0.5;
    Packit 90a5c9
        ctx.strokeRect((wspace*0.75), 30, rectwidth, canvas.height - lheight - 40);
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw a title if set:
    Packit 90a5c9
        if (title != null) {
    Packit 90a5c9
            ctx.font= (options && options.hires) ? "24px Sans-Serif" : "15px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#00000";
    Packit 90a5c9
            ctx.textAlign = "center";
    Packit 90a5c9
            ctx.fillText(title,rectwidth/2, 20);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw legend
    Packit 90a5c9
        ctx.textAlign = "left";
    Packit 90a5c9
        var posY = 50;
    Packit 90a5c9
        for (var k in titles) {
    Packit 90a5c9
            var x = parseInt(k)
    Packit 90a5c9
            if (!noX) {
    Packit 90a5c9
                x = x + 1;
    Packit 90a5c9
            }
    Packit 90a5c9
            var sum = 0
    Packit 90a5c9
            for (var y in values) {
    Packit 90a5c9
                sum += values[y][x]
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            var title = titles[k] + (!options.nosum ? (" (" + ((sums && sums[k]) ? sums[k] : sum.toFixed(0)) + ")") : "");
    Packit 90a5c9
            if (options && options.lastsum) {
    Packit 90a5c9
                title = titles[k] + " (" + values[values.length-1][x].toFixed(0) + ")";
    Packit 90a5c9
            }
    Packit 90a5c9
            ctx.fillStyle = colors[k % colors.length][3];
    Packit 90a5c9
            ctx.fillRect(wspace + rectwidth + 75 , posY-((options && options.hires) ? 18:9), (options && options.hires) ? 20:10, (options && options.hires) ?20:10);
    Packit 90a5c9
            
    Packit 90a5c9
            // Add legend text
    Packit 90a5c9
            ctx.font= (options && options.hires) ? "24px Sans-Serif" : "14px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#00000";
    Packit 90a5c9
            ctx.fillText(title,canvas.width - lwidth + ((options && options.hires) ? 100:60), posY);
    Packit 90a5c9
            
    Packit 90a5c9
            posY += (options && options.hires) ? 30:15;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Find max and min
    Packit 90a5c9
        var max = null;
    Packit 90a5c9
        var min = 0;
    Packit 90a5c9
        var stacked = null;
    Packit 90a5c9
        for (x in values) {
    Packit 90a5c9
            var s = 0;
    Packit 90a5c9
            for (y in values[x]) {
    Packit 90a5c9
                if (y > 0 || noX) {
    Packit 90a5c9
                    s += values[x][y];
    Packit 90a5c9
                    if (max === null || max < values[x][y]) {
    Packit 90a5c9
                        max = values[x][y];
    Packit 90a5c9
                    }
    Packit 90a5c9
                    if (min === null || min > values[x][y]) {
    Packit 90a5c9
                        min = values[x][y];
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            if (stacked === null || stacked < s) {
    Packit 90a5c9
                stacked = s;
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
        if (min == max) max++;
    Packit 90a5c9
        if (stack) {
    Packit 90a5c9
            min = 0;
    Packit 90a5c9
            max = stacked;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Set number of lines to draw and each step
    Packit 90a5c9
        var numLines = 5;
    Packit 90a5c9
        var step = (max-min) / (numLines+1);
    Packit 90a5c9
        
    Packit 90a5c9
        // Prettify the max value so steps aren't ugly numbers
    Packit 90a5c9
        if (step %1 != 0) {
    Packit 90a5c9
            step = (Math.round(step+0.5));
    Packit 90a5c9
            max = step * (numLines+1);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw horizontal lines
    Packit 90a5c9
        
    Packit 90a5c9
        for (x = -1; x <= numLines; x++) {
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            var y = 30 + (((canvas.height-40-lheight) / (numLines+1)) * (x+1));
    Packit 90a5c9
            ctx.moveTo(wspace*0.75, y);
    Packit 90a5c9
            ctx.lineTo(wspace*0.75 + rectwidth, y);
    Packit 90a5c9
            ctx.lineWidth = 0.25;
    Packit 90a5c9
            ctx.stroke();
    Packit 90a5c9
            
    Packit 90a5c9
            // Add values
    Packit 90a5c9
            ctx.font= (options && options.hires) ? "20px Sans-Serif" : "12px Sans-Serif";
    Packit 90a5c9
            ctx.fillStyle = "#000000";
    Packit 90a5c9
            
    Packit 90a5c9
            var val = Math.round( ((max-min) - (step*(x+1))) );
    Packit 90a5c9
            if (options && options.traffic) {
    Packit 90a5c9
                val = quokka_fnmb(val);
    Packit 90a5c9
            }
    Packit 90a5c9
            ctx.textAlign = "left";
    Packit 90a5c9
            ctx.fillText( val,canvas.width - lwidth - 20, y+8);
    Packit 90a5c9
            ctx.textAlign = "right";
    Packit 90a5c9
            ctx.fillText( val,wspace-32, y+8);
    Packit 90a5c9
            ctx.closePath();
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw vertical lines
    Packit 90a5c9
        var sx = 1
    Packit 90a5c9
        var numLines = values.length-1;
    Packit 90a5c9
        var step = (canvas.width - lwidth - wspace*0.75) / values.length;
    Packit 90a5c9
        while (step < 24) {
    Packit 90a5c9
            step *= 2
    Packit 90a5c9
            sx *= 2
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        if (verts) {
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            for (var x = 1; x < values.length; x++) {
    Packit 90a5c9
                if (x % sx == 0) {
    Packit 90a5c9
                    var y = (wspace*0.75) + (step * (x/sx));
    Packit 90a5c9
                    ctx.moveTo(y, 30);
    Packit 90a5c9
                    ctx.lineTo(y, canvas.height - 10 - lheight);
    Packit 90a5c9
                    ctx.lineWidth = 0.25;
    Packit 90a5c9
                    ctx.stroke();
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            ctx.closePath();
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Some pre-calculations of steps
    Packit 90a5c9
        var step = (rectwidth) / (values.length > 1 ? values.length-1:1);
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw X values if noX isn't set:
    Packit 90a5c9
        if (noX != true) {
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            for (var i = 0; i < values.length; i++) {
    Packit 90a5c9
                zz = 1
    Packit 90a5c9
                var x = (wspace*0.75) + ((step) * i);
    Packit 90a5c9
                var y = canvas.height - lheight + 5;
    Packit 90a5c9
                if (i % sx == 0) {
    Packit 90a5c9
                    ctx.translate(x, y);
    Packit 90a5c9
                    ctx.moveTo(0,0);
    Packit 90a5c9
                    ctx.lineTo(0,-15);
    Packit 90a5c9
                    ctx.stroke();
    Packit 90a5c9
                    ctx.rotate(45*Math.PI/180);
    Packit 90a5c9
                    ctx.textAlign = "left";
    Packit 90a5c9
                    var val = values[i][0];
    Packit 90a5c9
                    if (val.constructor.toString().match("Date()")) {
    Packit 90a5c9
                        val = val.toDateString();
    Packit 90a5c9
                    }
    Packit 90a5c9
                    ctx.fillText(val.toString(), 0, 0);
    Packit 90a5c9
                    ctx.rotate(-45*Math.PI/180);
    Packit 90a5c9
                    ctx.translate(-x,-y);
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            ctx.closePath();
    Packit 90a5c9
            
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw each line
    Packit 90a5c9
        var stacks = [];
    Packit 90a5c9
        var pstacks = [];
    Packit 90a5c9
        for (k in values) { if (k > 0) { stacks[k] = 0; pstacks[k] = canvas.height - 40 - lheight; }}
    Packit 90a5c9
        
    Packit 90a5c9
        for (k in titles) {
    Packit 90a5c9
            var maxY = 0, minY = 99999;
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            var color = colors[k % colors.length][0];
    Packit 90a5c9
            var f = parseInt(k) + 1;
    Packit 90a5c9
            if (noX) {
    Packit 90a5c9
                f = parseInt(k);
    Packit 90a5c9
            }
    Packit 90a5c9
            var value = values[0][f];
    Packit 90a5c9
            var step = rectwidth / numLines;
    Packit 90a5c9
            var x = (wspace*0.75);
    Packit 90a5c9
            var y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
    Packit 90a5c9
            var py = y;
    Packit 90a5c9
            if (stack) {
    Packit 90a5c9
                stacks[0] = stacks[0] ? stacks[0] : 0
    Packit 90a5c9
                y -= stacks[0];
    Packit 90a5c9
                pstacks[0] = stacks[0];
    Packit 90a5c9
                stacks[0] += (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            // Draw line
    Packit 90a5c9
            ctx.moveTo(x, y);
    Packit 90a5c9
            var pvalY = y;
    Packit 90a5c9
            var pvalX = x;
    Packit 90a5c9
            for (var i in values) {
    Packit 90a5c9
                if (i > 0) {
    Packit 90a5c9
                    x = (wspace*0.75) + (step*i);
    Packit 90a5c9
                    var f = parseInt(k) + 1;
    Packit 90a5c9
                    if (noX == true) {
    Packit 90a5c9
                        f = parseInt(k);
    Packit 90a5c9
                    }
    Packit 90a5c9
                    value = values[i][f];
    Packit 90a5c9
                    y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
    Packit 90a5c9
                    if (stack) {
    Packit 90a5c9
                        y -= stacks[i];
    Packit 90a5c9
                        pstacks[i] = stacks[i];
    Packit 90a5c9
                        stacks[i] += (((value-min) / (max-min)) * (canvas.height - 40- lheight));
    Packit 90a5c9
                    }
    Packit 90a5c9
                    if (y > maxY) maxY = y;
    Packit 90a5c9
                    if (y < minY) minY = y;
    Packit 90a5c9
                    // Draw curved lines??
    Packit 90a5c9
                    /* We'll do: (x1,y1)-----(x1.5,y1)
    Packit 90a5c9
                     *                          |
    Packit 90a5c9
                     *                       (x1.5,y2)-----(x2,y2)
    Packit 90a5c9
                     * with a quadratic beizer thingy
    Packit 90a5c9
                    */
    Packit 90a5c9
                    if (curve) {
    Packit 90a5c9
                        ctx.bezierCurveTo((pvalX + x) / 2, pvalY, (pvalX + x) / 2, y, x, y);
    Packit 90a5c9
                        pvalX = x;
    Packit 90a5c9
                        pvalY = y;
    Packit 90a5c9
                    }
    Packit 90a5c9
                    // Nope, just draw straight lines
    Packit 90a5c9
                    else {
    Packit 90a5c9
                        ctx.lineTo(x, y);
    Packit 90a5c9
                    }
    Packit 90a5c9
                    if (spots) {
    Packit 90a5c9
                        ctx.fillStyle = color;
    Packit 90a5c9
                        ctx.translate(x-2, y-2);
    Packit 90a5c9
                        ctx.rotate(-45*Math.PI/180);
    Packit 90a5c9
                        ctx.fillRect(-2,1,4,4);
    Packit 90a5c9
                        ctx.rotate(45*Math.PI/180);
    Packit 90a5c9
                        ctx.translate(-x+2, -y+2);
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
            ctx.lineWidth = 4;
    Packit 90a5c9
            ctx.strokeStyle = color;
    Packit 90a5c9
            ctx.stroke();
    Packit 90a5c9
            
    Packit 90a5c9
            
    Packit 90a5c9
            if (minY == maxY) maxY++;
    Packit 90a5c9
            
    Packit 90a5c9
            // Draw stack area
    Packit 90a5c9
            if (stack) {
    Packit 90a5c9
                ctx.globalAlpha = 0.65;
    Packit 90a5c9
                for (i in values) {
    Packit 90a5c9
                    if (i > 0) {
    Packit 90a5c9
                        var f = parseInt(k) + 1;
    Packit 90a5c9
                        if (noX == true) {
    Packit 90a5c9
                            f = parseInt(k);
    Packit 90a5c9
                        }
    Packit 90a5c9
                        x = (wspace*0.75) + (step*i);
    Packit 90a5c9
                        value = values[i][f];
    Packit 90a5c9
                        y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
    Packit 90a5c9
                        y -= stacks[i];
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
                var pvalY = y;
    Packit 90a5c9
                var pvalX = x;
    Packit 90a5c9
                if (y > maxY) maxY = y;
    Packit 90a5c9
                if (y < minY) minY = y;
    Packit 90a5c9
                for (i in values) {
    Packit 90a5c9
                    var l = values.length - i - 1;
    Packit 90a5c9
                    x = (wspace*0.75) + (step*l);
    Packit 90a5c9
                    y = canvas.height - 10 - lheight - pstacks[l];
    Packit 90a5c9
                    if (y > maxY) maxY = y;
    Packit 90a5c9
                    if (y < minY) minY = y;
    Packit 90a5c9
                    if (curve) {
    Packit 90a5c9
                        ctx.bezierCurveTo((pvalX + x) / 2, pvalY, (pvalX + x) / 2, y, x, y);
    Packit 90a5c9
                        pvalX = x;
    Packit 90a5c9
                        pvalY = y;
    Packit 90a5c9
                    }
    Packit 90a5c9
                    else {
    Packit 90a5c9
                        ctx.lineTo(x, y);
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
                ctx.lineTo((wspace*0.75), py - pstacks[0]);
    Packit 90a5c9
                ctx.lineWidth = 0;
    Packit 90a5c9
                var grad = ctx.createLinearGradient(0, minY, 0, maxY);
    Packit 90a5c9
                grad.addColorStop(0.25, colors[k % colors.length][0])
    Packit 90a5c9
                grad.addColorStop(1, colors[k % colors.length][1])
    Packit 90a5c9
                ctx.strokeStyle = colors[k % colors.length][0];
    Packit 90a5c9
                ctx.fillStyle = grad;
    Packit 90a5c9
                ctx.fill();
    Packit 90a5c9
                ctx.fillStyle = "#000"
    Packit 90a5c9
                ctx.strokeStyle = "#000"
    Packit 90a5c9
                ctx.globalAlpha = 1;
    Packit 90a5c9
            }
    Packit 90a5c9
            ctx.closePath();
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // draw feather
    Packit 90a5c9
        base_image = new Image();
    Packit 90a5c9
        base_image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAEACAYAAAB7+X6nAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACJQAAAiUBweyXgQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7Z13vBXF2ce/z+65hUuxYUNEuFyaF0EEwRaDPRobKsYSW0w0auwKWBKPFUEs0ddujJpEDWo0aiyxYBcEgqKXJsJFEQuitFvP2XneP07b0+utnN/nA/fs7MyzszO/eZ5nnpndFYrocNC7q7s1SMNvUY40qsNQa3NVvkGZbYw+02Ndl6fEW9OciSxp6coWUVg03Fd5hqreoipboaAKqAT/Bv6pUmtUzt78kqX/TSevSIAOgrX39Nmi1PI8BhwORHe4ESJkCP9Vo/xlM2m6QC5Z2ZBMbpEAHQAbHqiqttU8h1IVTozq8BgCmMh5VX3H+Dlyy0nL1iWSXSRAO0f9fVV7gvkPsEXUibgRL7EawEUGmec4Zr9EJLBa/haKyBUN9/ffD8yrxHY+BIauxCcRmyyA6AjLtp7TO6vKYsUUCdBOUX9f5TGq+jLQPVkeCf4nCciQAGPXNzI5oYwi2hc23tv/dEv0IcBOmTGdGTBucyCoomI4dLNJS18NiShqgHaGjfdWnizwF9J1fhrEaYfAUBcVHvj2lmFdQ/mKBGhHqLuv6ghB/kqm/eLq3IT2Pwoaytun3DScG0otEqCdYMO9VWNRnQ6UQHBqlwHE/SN6tCeH6oV6/8gSKBKgXaDu7srdbfR5oDw/SRr5mUo7CDv8tOang6BIgDZH4wOVA7HkRU3h7adEgtGeoMND08FwolgyHsCT00WLKAjqH6zq7fj1TWCbvARJ5I9ClCJInl+OUA34iEW0AfTu6m71dsO7qOyaKp9Iut5Eg9M+SRcWJmaqaDlmUNEEtAHUi1VvNf49XednAAM0EWUIogkTmgYmChb5LWt0kQBtgLptK6cBR+UpRoF1QHki7z+pahd1O4cjiwRoZdTdV3WmIBfnK0dgAa41grDjl+lUMJC3d17RpiKyw8b7+h0kKk+Q1exLEnhqMhMYQjBmkKxcXIqGJQKgsLZIgFbChrsHDLEsXgEqsi0bQ4D/AdsCPaMzEWP+JcEvV0rAEviLJqAV8NXF1Vs2fCc3omyep6gvAr4+fRKedTt8ZBQW7lkkQAtDx2OXlvr+bpoY529gXk4yAmHhDcBHKCOT5YvfB0BSfyCYZIoEaGF8XzngJoVDARpX2yZHMUZV7wCOy7xITPwgRjsE4RQJ0IL4dsKgIxW5PHSshpH+OvkoWzmCTBVLTiWl00e2u4RAij5Ai+H7y4cMQPQxYtq+cY10RclGEzxv0EqUnTLJnGQfQLK8RQK0BL66uHcXx3L+CWwWe06NVDdvsD7IUNRSFZkpcHymy8MxVwv8SU6GjcXFoBZAaVnFvaqMSHa+6UfZobQHPlKr9AYR/RPKw4WqlwAqkRmjwrdFDVBgfDNx4HmqnJYmW7+mn6z3U+ZQuUBVLiHbPQJZ7BISdFWRAAXENxMGjsZwa2gFLhWa18lglPqEJ1X/ArodMCo6ubC7hASWFQlQIHx1cfWWOPJPVMrCT+WEiJCYDNs1rrFnJkhfatk8gvDHwtQsyS4hARUWFwlQAKgXy8b/hCp9CTyJE/6nMU/quOGrY7ga1rqSmsSyfm2M9X9Aac4VSrVLSDHGx0qnQf6njnxWdAILgFU/DbpchIPB1ccanIoFvS4NnolSz8pWjT9Yb3TZxhwAICJXqjFHAMPzrZOq1pkm62vjkzW+Rmk2zZSpw1ao9AF6A1tt+1P3T4sEyBOrLhg8UpXrFA2stiXYmiGQlAj+ehmjDt+JzceIvI/qe6mupyqBXUKKUSOrjY/VpknWOc3i8zdLifrpoYbtCSwWDUwuiP/JA3N9RQLkgdUTBnVvbuBJoJTgKg1uIgT/JiSCashT79bwnfV2t16+PxjDSwoGP98ZR9aroc7x0ag+mo0fNX6rBIduGLbUwIpg6F/WUJgJxU2heaGpjruBKgTX6I8QgVBSIiKE1uRV8TXx8PoVpaersiMB259zx2YOKRIgH6z8/aDjUTkldBzu3PDoT6DyQ0QI/hVAVR7H0oVqeALJw/HLDurxBOIQxV3BOaD2t4P62R7rY6AHouH4uzvyEk4LnwsSITo6s9o2zbuY0pLHgf1jzrUkZm83ZcloKD4YkjXUO9YjlvUPlB6RbdYSfhoX11O5UVNAE5wWmqi0c9Qu/SVG9o87l3Y3eO4QlRdCv4smIEt8tfLba0Vkz7AaT2D7wwM4pe3nSUv97xiPvTC0QcftF0TJLrRGMPw79LNoArLAl2cO2VfhTZHgo9spVX6QErFqPRCF+8H2+apNecktoKfGnHPldz3KVTgi1Gw3ZcnQ0EFRA2SIFSftsgXG+RuCHRn9kQ6KHuWBBA33pkb1naLnaknJEFU9JaoMLgdRcbFBw4985esnKPKY+7hIgAyhHv9DwShaJC2kuENE0IDzD9FEQII+QsBhfLrBcp6vwPMJRiQmJoC7WEA2uPdxJYwoZg7HUedxd0KRABlgxak7/1rhmLCDFmX7XUQIdZuLCODuI/mh2Wf/obycKxQGRc6F/YIASaJkE/6RNrScHi/2nrp0pTuhSIA0+PKEQb0clT8HuyicHj/vj6h8NxHc837gDyW200MME4MZo2ICRJXOMLQcqz1SEEGE/4tNK04D08DxlDyIYcvA8m5gyodrmTdutc8QnBZKJC3Qc//a8f6F0xHuVZXyqDLBpeOoqWSS6WX8NRNMLxNOIfXzbW5e8kZsalEDpMCyk4aeqY4eFvtoVqa2XyVIAtF14jfnf332kFNQDkjgF7hkB+XkEVpONIVUlTslATWKBEiCz08e1hu/My3c0GEbTEa2P5oz8scusKHBYUpI50ap/BS2P9vQcpgIrjqJxXoP8mii+yyagARQEPGZh1Rlc1yqPCq6F2UGQipfoiOBgc0hc/tsWHBPo1hXAduF1Xisyk6k8mOvqUQiirGmJ86MROpjVB7ZeuriDYnutRgISoBlx1afp5YEHCZ3IAZwv2cneQAomA+MKntZJfZqcBYglCUNDrnKh66TSHZ0XYCgvLj4QCSvT7EG9Lp14YpE91rUADFYNH5YP4PcHLWfL2oUSnAUxqYTM/oF1Lq7398WzBI101DKwg6ikfjRG6dZXBrBJTu5ExivPYJ5H03W+VDUAFFQL9ayT3aZocK+uEaqJBiFMSM9ZvQD8C0lJUPE+EYg+mZCbREsn2j0Jh79SfImkhk4dhxjDdnxzoWfJ7vnogZwYen/drnIKPtqjA2NtsHEjNrEtl/g0r51Azag3B7vF7j/ScbTy4RaIcUU0ihPpOp8KBIgjIXjhg8EbojqgBARTAoihJd+XURQ3t3pHzVP1JbV/FaV4THnwrLjzUAMEQzJiZDejKhHrSnp7rtoAggo+88P3+U1hODuXBI5U8EfaZxAMFiMwSn73CppXIIE3wHoluM2G7FyotI0gRlIqfLdPfp077sXjU9370UNACw5bNiZqBwQpfKTRt9cTmCiEarc3//Jz+aIp/FPqmyTcJSaaI2Q0AlMFFHMfArpWOr8KZN73+Q1wLIjh27r91kLVQJv3IqM/tROYJRGIJz+k1PaPNAypZvZUINometcQs0SNdLTTi8zm0KqcF+fexeek8n9b/IawNcsd6qyRba2X+Nsv6CqVw96YskPluEOVcqS2f5oJzBz25/hFLLOOFyX6f1v0hpg4cHDDxf0hWQ2GGJHv8anRf5+WlUyeMQXLPi5qLyRUE6M7U8+vYy3/YmnkPHaQy25bqf7F1yTaRtsshrgk4OHdRXlzsjolTS2n8QbO0P/0MuofkoxckfCMG0C258wtBxl++Onl6m0h6h+X+FzpmXTDpvsYlCpXyar0A8hauUNIOGCS7CcuP4Prbyp8sqgFz7771IZdiroLgQe3QosDCnuDT1BrohrYUkj6UQGd7g8rtc6hssHMsZuSjFY12/9cE3CmH8ybJImoGb/4aMt+ACwUzteyVW+K81xjI4wXc3i0iZrIRaV0bJinMmkJsZlHpKo/DRTyJrVJV1GjHpgri+bttjkTMCMsWM9YuR+NWIXYuVNDH8d8tKnn5Y22ucClfEqO6LKU5mYhCo/lRmJrreqI+dn2/mwCWqA+fsMv8Rjy61A4kBK6G8iRy04ml1pdeq3BzqbN68vabC+QNgmxapc6uml67oSrFPi8sSMfgWRv/V9rObUrBoiiE1KA8wbMmaAs8bzezU057fyFk6bNvi1eatK6q0JqGzjPpdwNTHV9DJquhejEZJoj6ATuB5jJubaJpuUBvjfoFH/QTjM09W8a1eYnwE5234RvtPyxgG2r6Lc4HyB0D2jhzoKZPtD5dXi/Mq/18Rt9swUm4wGmDdo1FEoh6Hg32hXYmiOC6RkYfvV0asGP794g1FzDUr3qBBtFrafbG1/dJ0/XbFy6/vyaZdNQgN80HvPLmVdfJ8BlaGRY3c179gVum/mQZeotfgF3zpbDN++9McdgUUIpfna/iSh5VTaw6/oXv3/WTM7m7aIxSahAcrKnYkYqXSPUqfe7o+hKdGCS9xsIOQnRHbdXLLfW2/5Ra0bUSmN8gtytP2JQsvJwseqYGBKvp0Pm4AGmFM5sg9YC4GKWBttdeGdkm7OvuG0sFftzke07UfeHPL2vAMWHTB0mGLPQ7ASzhRcMt2ysggtJ9Ueqiy0u2/crd8jtY05NksYm0AkUP6MBr7SoUqkcxBMA4O1gkaR4Ns4lfine4IIljXG5hIANfYUJKBBw3KVcG9qWCBR28lDUgP9GqhMomcDwzIUJPohUZ+onlaIzodObgJm9xt1ECpHRyW61a1hG3+dPSvTlTdR/jl0xrxPFu434ueq8otMNnbGOZMZTC9TTSFVrYv6/yt/1R9CpyVATXV1qSB3Jc0QbFxTL0Mw1CfzwEO2H8Xx++3rANTRaxP4BcmJkGhWkXBzRxrbjz424F/z7ylkO3VaAjQ0VFyiwSdwU0GVbXz19uw4JzB+182jwz6cu6hm32H7q/LzNDtyooiQTWg5afgYPqSx8axCt1OnJMAHvffcQVWugkgbp4JTJ0NVpS7ugc5Ix/gcy3NjILN1TWynZf1AZ6plZU1EBPm4udEcPuDlpU0FbSg6KQFKPM4toN3caamIIMJWzkZrjtv2R+/ZkweHvTd72YI9RhyoKvtmYvs1B9ufZAo5w24sGVv9as2PBW8oOiEBZvcbNRrRE5KdT0YEf6PsooaNsdE3VWk0+CcDGMEbZ/tTESFL2x+3KQXu8Xd1ftH/9bnrQvWcM3Jk3FdI8kGnmwYq1rTIjCq58g+dCU+9hS2dBustu4sZG7WBQ/Tu4bPmr/x0zPCD1bB39HQtzZO9ESGuKWDM4+DBcjGbUlYb9JwhL81/JpRv3phdB9iOfS0W04HnMmmLTNCpCDBrp9HjBH4WSYnMn5PBfcY0yTC7jA1qBWL7ImykyZkayGh5w2VCnea6TrjTg3P6QEwhpiYKad4LYFD5u+W3Lx381twfAOaPGLGnipwjPk5E1CNSkvd3h93oNJHAGWPHerqtqJ9P4Ju6KZDaJbQ9+pbd1YwFUJEbdp0394+f7L7bL0R5uUUf6kBfN7ZMqH79k3mfDt1tmPFYR6DmFIRBLjkLh//vfzunvr/s0Gk0QLcV9b9XGJKe0am1guOXEZZjrRVLpcRXejuAGLzhEkF17o4URkY/rtEfE1EMnovVHoq87zRb9/nX4UHk3I+HjjzEEXbERGyKBDWGIs9n0BRZoVMQ4L1Be3fXZt/VEN2tqcmQlAibOY3yttWF16prPvxx/q6jDlNjxsS9JiZEBI2M9HjbjzHIahH9AcNGNdKEQdWRElW6Gb90x5FhCH+LeAhuc+GWK1hiwm/4LBQ6BQFKm5qvQCTu9epxHZIQ8UQwfrbv2lR2ZyDVuQYVV1w+qvA6g3yLQx1Kszrix6FUjXQ1PtkKpSewLcK2hEyC22SE/mow3h8e6a6aRYiwYpd58xJ9YygvdHgCfNB7zx3AuTBVnqyJoHrv4MXvb5g7ePQh6jd+Vd5WQB3K8Ut3VXpi6Engw5DJp2Xi/hMazpp8uzggxBMBQAyPSgJ1lS86PAFKPM5NChWZ9HKGRPhOms0DAKZJrzNNMjpl7lQnQ+Y/yu5HrexlRAQU9VtEveK1UOjQBJjVb/RwVX4dlZgnEUSYOmrV3Pq5fUcfYmB0ukGXGakIB4qiVH7wRBQRQs5iWK4AvD7qszlfpLtELujQBBDVW0ASRzNzI8IPXbpUPADgCFdJ7NksAkvpMkY6PYYIIRmuWYNY+ud0YnNFhyXAzH5jDlbVgyAzNZwJEUCmVNe8tXF23z3GgvlZfM7MA0s5ESGREwhLd1049+V04nJFhyWAqIYfgc6o0dNn+qFrRZf7AFT0qhynkFlcLjpjQidQQUTvFLL63HxW6JCLQTP7jjkKGBObnmyhJ5NMqtxSXfPWxjl99xgDemBGsqLCf1ldLmnGmO3ia+wuvoczKZ4rOhwBFCyBa9PkyZwIgYw/dOtacQ+AEXNV1rJagggGMHrb8Pnz6zIplis6HAFm991jPDC8kI2uhjuqa97aOLPfmGHA4bnLyrxOGdRrnaX+gm7/SoQORYDpjLcVvSY6Ne9GX18qzXcDWKoTY1Zrk8pKjfR1SidLVG8fUfvx2iSnC4YORYC+fb86haSrfUIuWkGEu0bUfrx2dp/dK1GOT5gpAzmFrBPwrc9j355WfAHQYQgwZ+TIEoP+MbNYaMaNXm/bvj8DqCWXITGzooIRIas6IXDhHktnrc9IbJ7oMARw1ti/ASohGzuautEFfXC3pfNWz+w3Zlvg9KQZsyBC3uZB5eGRy2dPTyumQOgQBJjRd2y5IlclOpdHo/t8UnJ7UMhFCl1ynUIWsE6LSxsaL0h/hcKhQxCgXBp/B+yYKk8Ojf7E3ss/WDGzakwPQcMvVcxhClmoOjVg9MTh37XstC8W7Z4AL1UdWoZhQhrnPIwMG10t29wCII6eTYIl3YxVemGI4IjKSbuvmDMv3eUKjXZPgC38P52B0BtAkXSztDBSN7q+uPsXcz77vOrQMlG5KD9ZmWdKkqVZ0ZNG1X5UsJ2+2aBdE2A6421VuTS24/MlgmUCr1Ff4//xJIVeeUzXcsrkylKPyLjRrej0xaJdE6D3Tit/pSJVoeOCEEF5b/cvZ74fWGeRS+JzthoRalV1792XzXoJYO5Oewz5qGr36nRiC412SwAFUZHLAr9Td3w2RMDWKQAf9t3zEESHJs/YYkQwIPc0l5YMG107++N3++yzxUd9x0x2LPN47dK+i9LWv8BotwR4r3KfXyoyIpuOz4AIC0cv++glAEvNpa260BPItERh7Ojls86z6kzJrH6jryq3m79AdJJgXXM8Tzlpq1NgtFsCiOqk0O9sOz4ZEUT0ZgHzUd/dd0U4MJK/hYmgrAS90Gp2RliOMbP7jn7U43G+FrhBYQvg1d2Xzyz4nv9M0C43hLxXuc8+atg7dOz6nHJOx8G0r+wtnSdYDkatSxL1pYbzpkLmm0GA5cBTIroQtQ4wpXI1sHVMxnpVMvq4Q0ugXRLAGLks+smbvDo+9PPWUXPn+mZWjemNn6RPDwfKh8qmQnoiANtCIIYhkiSfyDVjamctT3mpFkS7MwHv77hXf+DwkBrPV/UH035s6NrlLwD4uECRkkzqUoAVv4pUsgRm1C7v0yqrfsnQ7gjgWPaFitjutFyJEH7sUuTe/Wre2vjeoL27I/wuUZlUaIkVP+A7UefktnD83GhXBJhZNaaHipwG8Z3oTsv0OJjms23nPgCryZxpsDbPN5ZQAIexCdHjR62Y+01GF25BtCsCNDhdzlKkR74d7yaPoI/vsXTWSgVLkfMzlZEOeRBBRfS3o5fPfifji7Ug2g0BpjPeRvmDO61AGuAOgA/77nkkUJmt+UiHLImgilw8evnsv2ckvBXQbmYBW/ddfQSwUyIPPudZgPDGPsvf/ziYdlFQI2QkI1laMmQwc3CA8/aonXV/WmGtiHZDAEXOgdQdkPWxkdsB3u23z3BV/Xk2MlLlSX0fobJRaFDllD1WfPRMXIE2RrsgwBs77t8f9EDIrAMyPF7ysxXvvhxMuDBbGZnmSQYXEZYgevwetR99krZQG6Bd+ADi0XMVsZLM4bM+DqbdLmDeqfrZ1oqcmKOMjMokgQKP1HftMnLM8kDnv99/r23SFWpttDkBPui9Zxej1umh4wJN/35saOjyNwD8nAOU5+hA5kqEj0X053vUzjpjG1Y3z+o3evzMnca8ZjkmZQSyLdDmBGjwdDkB2LIQHR8+Vu455Lv/1tVUV5cq8vs8tEi29ahR5AzHtg9RlR1m9h3z8Ia6bl+qynSE/R3HfjrnhmohtLkPYLDOgoI6f82C3gPwff3Wx1uY7bORkeN1GxV5B8MqLM63HPMX4gfX2/t89f6qLJqmVdCmBHi98oCBatgDCur8Pb7vine/ARDVC1rC+UtwXA4c7O7yuDwiT2TcMK2INiWAMdaZod+F0gAqgcDPm33221vR3fNZQcy1HgnSfFaz+VeGzdKqaDMfYMbYsR5Fwu/3KZAP8Pb+y2d8AiAWF+YiI8frppShIv8d8/VHa9K1SVugzQjQtLzsEKBXQTtAuQtgRtXY3qqMa0XnL52Mf2bYLK2ONiOAIqcXuAO+7tFz/fMAxmedTfBBz9bUAElIuVFLpU32/GeCNiHAK70P2VJEj4DCdQDKvaPmzvXNGTmyBOE3Be3EDI+T5Hlyn8Xvb8ikXdoCbeIEmhLrWFEtg4I5YU1qyUMAa9dsfizQK1dHLpcyqeqO8JdM26Ut0DYmQCN78gox8gzWkwcuf+O74MnzcpHRQhpg0T617xf8/b6FRKtrgP/0PWw70J8XcuRZlrkb4M0++1U7IvvkIiOXMmllKA9m3jJtg7bQAMera89fAUbezAOXvTEbwG95Mt7xU0jnL4mMZstj2s3Gj2RoCwL8CgrXAUatuwBeqjq0B8rJbaH6E8pQeX7vLz74PrMmaTu0KgGe63/UjorsWcAO+N6UWM8A2H7ndBXploOMXK6bVoZlt9z7fQuJVvUBbMc5kdDmuAL4AAbr3sOWvhz6mOJZuchoER9AmbPPsvfey7BZ2hSt7QQeV8AO8NmO8wDAy31+cYCi1TnIyIuAyequIrdm0SZtilYzAc/sdMz2iowKHeerggX918FfvRZYXrU4NxcZ7uOCOX/Kyu5bbWh3e/+SodUIUGo1Hw6Bj6QVogMM1r0A/93xoF7AEQX24LM6jkm7Y9Tcub7MWqXt0WomQFWOUOK3ZUNOanvhobUvvwPgs0vOErSk4HP4LI5daRsc234oq4ZpY7SKBpjee3wXRQ6Awow8I9a9Ajqd8baonpmLjEKo/gQyHjpo2evh7/x2BLSKBvCU+g9QIxVQkJFXj8PfASr61f9SVXrnIKMgzl+MjAb8dBjnL4TWMQEm8gr2fDtAVR4/4ssXfwqeODsXGe7jXMokkmFh7tp/5YyvM2qPdoRWMQGCHlwoFSyq90EgqAQc0pbOnythY6OnfFpmrdG+0OIa4Nkdj+6vSL8CqeDZh3/5n7kAtt/8TkXstnT+wjKEaYctfXl1lk3TLtDiGsDYVviRr3w1AHAvBPYTIvqbTMq0pAYIpq3xeUra9C0f+aDlfQDlQJXkT+UmSktyvFaa+SfAxtruhyvskEGZFtUAwYObD1v6cqu8278l0KIawIvXAsZCQTTAI0eseqE+kB6I+2dQpqU1QG1XX93dmbVG+0SLaoChlZ/upkZ6QgFGnglsrnih8og+xsjBOckoRD1cxyJ64V4rP2zIrlXaF1pUA6iRg8K/8xt5M47+8t8LABy1zgLiNpRke5yP1ggev3rw8tfa5OWOhUTLEgD5eYGcv/sh6PwpZxSiE3Mp4zpuciy7Vb/s0VJoMQIE7f8eoeM8OmBNqaf5OYB1yzc7QpFeBRi9+WqAa3+57KUlmbZFe0aLEWDnyppqYLN8O0BEHwlt+lCR32VSJpPjPGTMKu/beEs2bdGe0XImQEm49Ss6S/oG9zklDwM8U3VMb0WSRhSzPc5V9VvGnLnfW2/5s2qLdowWI4BRa0/IswOU947/8qkFADicTgGcPzeylqFy5S++fLUmi2Zo92hJJ3CvfEeesawHA+kIyukFst+5lRH5z2ErXuqwEb9kaBECPLrDqVsBAyCvkbeuvKnpaYBndxq3nyL9c5ARd5wjeb5sbi49TcAVAuwcaJFAkFNij1JEII/Ai/CPUOTPiHVmTjISHOdQpkksPfaYr59tl8/354sW0QAbSroO90nJEshdA4jRhwCe7Xv05oqMy0VGATSAqsjvfrnspTnZtkFHQYsQwKg1dL3VY10ezt+c41Y8Mw+gWUtPBrpkLSPJcTZlVOSaI5e/8LfcW6L9o4WcQKn2iWf3ZilZmEsHYEUeqhTR3xSi47OVIehfj1r+/PU53X4HQsEJ4MVrKQwBWG9vVh9Kz6ID6prt0icBpvcdv6siu7nlt4oGEHnmm622P5tNAAUnQEXlj/1BuijgxxrZQOnCbDpAkX/+euk/1gMYtaJ2/LaSBpj+3Zbbnnj23Ac6zN7+fFBwAnjEHqqEPGlhg92jAbLoAA286eOlqkPLgBMK5fxlIkPQJzfvu/bkTaXzoQUIYCzCnz9VwBF7tyarbEGGnbTwpBVPfAiw3t9jHEJP9/mW1ACC3vFx7YiTO1OYNxO0RByg2tWoAKyT7mZr1hCbnuD4gZAQRU4N/c5jDh/V4Uny+BDOG7f8uQeh3b7Mq8VQcA2gyNCg9g+PMCPW0Ear/DNXnkQjsdnnK/kbwBM7ntBLiez6KYQGSJLnR1H9xTHLn233r3JpKaQlwE0DLx2cqbDgHoABGhpbLiKss7qlVsGqz5/29WNrANQjJwN2Th585qr/Axtn5DErnn0z0/vrjEhLABV7+8kDL5t+U9UVW6fL22Pguu0VyoI75ggRQQVUrKFNVtknkLST/ho+VjklRw8+6bErzQGm/LjVlmPHHim3iwAADrJJREFU1T5Xm+g+pg28tGe6e+0sSEuAqxZPnQGyTizfZzcNvPToVHn94ukb+q2EVk4CakCBdXaP0iSd9G2vfqv+C/BY5SmjgF3c5wvo/C0T1X3H1z49KZmnf0fVhed6fH470bnOiIx8gKZm3+UgfsF6dvLAy+/39vWWJ8pnjO4UaezQvD5CBIMMabRKP4ntJIP1SMj7th3ntEKq/iB8wJ10Zfj4FU9/kKju0xlv3z7ggvvBdL9o+Z+/y6RdOgMyIoC39o61oH8I+uJnlZXWv3dz5cQ+sflE2Cn02x0LiBzDBqtHWSRPoJM8+B8FmF49vtRgFfolkm+L0V1/VTv9wuNrntqY6P7u6XPOFl9Xbf8sah2y1r+hQ7zcqVDIeBZwxZJpz6L6dKAjdaR6zKzJgy8d6c5jwm//jiCWCI7I4AYpm+fqpA9Oqn1iEUDzxtJfIvQshAYAPlWVY06sfXJseFdRAtxeddGejaWlcxU5QtVM8tY+0phpm3QGZDUN9JTK+cBaAIXt1Fgzbh5w6b6RHLp1oK8TjdwIEdZ5ulcAqggqEefPEfv0Ajh/i4xYp1m1ZsSJK558Ntm93Fl1ftltVRfeqPAuSD9g5sVf3NluX+veUsiKABNqbvlWkRtcjd7diPXSzQMn7gOAWNuFbX5MLCCEwHlrUL1dPg9oMGo9DfBw1RlbC3poIE/WGkBV5A1VOdZT6x968vLHH0v2VW4FubXqkvE+7BpFrlSwFZow1lkSclc2IWQdCfSVVNxV4qs/B+gf7ICuDubZG/pfspsqW4MggIbaUggeh5o3UGqjdOtRrs1Pn7HikbUAtt85SZGSQJHMoniKrEZ50hJzz0nLA2YkGbxjvZ5uq9YefquKF3Q4GiXHe+my2z/Nti06A7ImgLfG23z9oAmTRHnK1T09xfY8AbodRLz+kPsXGlbhTWIKjlC1xurqfr3baRl2/Ocq8jzw77LlTR8kG+khTBl04SBL7TP4ev2pirV9uHbBSxiVt3daurLT7PPPFpI+SzwU5MZBEz9CdVQiIRKjSaMNQPj428Ze3Xb0vuX1P9rv1GGq8klMeZ8iqxX53of9sVjyjlViXj1z8cNJP73m7eWt6NplY6URxqjoPhbsDTog8c0qoKtK/LrbpjTti0VOi0ECer3oZEGeAVCXanf/iozeULkonfCo9y2vH+Anu/vhJWre9Vmech92D0c8WzpYWwvaC+gF7AqcLoqZPPDyVaDuDqsX2AplB9i4mXGRLKkmCvzfgOpxm3LnQ44aAAJa4IZBkz4GHSauxGgFHrpIvEYQYw2dtHRKjXes1+P5puFLlO1jNETS8pE8GnPsTkpUj3AGn2CNu/zzaf9JfoebBnJeDRRQFW4HV7QvagoYQQIPfu6kpVNqAOxVTQcrbB8qG+ruVOUj10wQcQwnJaoHKOJDOKXY+QHktRy8ub/uiaCdBiKdoCEiSHRnhuf1oq6dtvprd9kwESQXIkg8EaIJWSeiR09YctsmN99PhrwIcMHSu5oUHgF358SOyLjO9Pn8+gTAlEETuqtwVOK1g4Rlib6WOy1+8SkiB0TkKyPsN2HJbS/lc8+dDXlvCFHLeixV6DeOCKKveL+Y9j1Ao9jHABWpyiUhUTBPsogjuIkA8obf59v9iiW3zs73fjsb8iaAd+FNnyGyIN0aQKRj7Ij6N6FPvCQvF1U2CREgWcSRDWCdP3HJLQddFfT2vX0v2jyf++1sKMiWMEWeCvxK6ngR7NB1FXVNLwLcOOTK7RH2z7CcKw0XEeLnB2EiKM8bx6m+YsnU/wvFIG8aePlh5VZZj0Lcc2dBYTaFiryiqtdAeI7tmnNHYgGK9eQlK29vAPDDCep63j82chCJKSQ6G7psMFWjcs4FJl75+S1vuKs4ecCE36M6eNKyKUUfwIWCEGBVxeq5vep61gFdFQ33UHgNAEK/wurfKL+WUDxW44I0cURwHyUMMYsswJEbrvz85ifdizo3DrlsKI411aC7lzpaTRFRyDkQFItrBl/xBsj+EaHRRABq/7R4cqWAequv2FkdCb9pI0rJa2xaID1RRYPXmSMqN/mXlP/bi9d4R3orrLrGIeI4e4nIUQL7A4LIr65aPGV6Ie61M6FgzwUo9jzB7B/4DYE2D55TBZFHw+uBjnWykiRULIEUjSJCvEkJljMgW4owzTOo4bbrmbg5Gxs2FwDLilwbHri62PkJkZQA51edX7aZZ4tTEYyjvucmL5qc+gUJyhcqyWy1qO23QupfFE4KnE+cP0KeWCLEmRQLqDShMsH0KMdQrHcdT/n5FJEQ6UyAXDn46j+JyFUCM0HewTLveUo8M70fe9e6M/5p8JWHgLwSLTzce+9du/CmnwFcNeSqfS3l7UQXTx7zJ7TilKDCKWP+c0p8HDhp2ZQO9RmX1kQ6E6A3Lbrh2qsHX/2pijwK+jOM4G9yuHrI1etRvgLWCrKFotsEi8SPVo18Q9eCE2PduGSriIG0AEIOo8YRIfHMQZH3Sks5ctLim4udnwIZxQFuWHTDv8AMBV6EcAP3UJFqhL1VdGeFnqGuCc3Pg53X1Fyi0wHOGnlWiaoclzJsS5oIX0wwyG02QtcV+Ic2lR90xac3/5Rle2xyyHoWcEW19zgxzq0CUdvCk3j+oOa56xffNA7g6uqrf4GRl935oiuRTM0nXlIOFHFTgA0icsGfFt30SFY3tQkj60jg5Brv0z9UfFelwimKhF+eFI7cScCTD41OseTJcGEjJ8bmc+eNaIQUEb6o6xHWCAgv2pa1S7Hzs0NO08CydWWWVWavMmqeV6QB9GexkbugF7/Bqih5AcDb11vejHN0XD4IzAsgydQvWTAofPa/BuO9ftHkD3O5l00dWRFg4uA/7mKLda4iJzrGbAapQ7iC/Ns711sP0FTOYYL0SBnqTRkDiDIPP4E8Jap3exffOD+beygiGhkRYMLQ64dY6tyIcrRRlUAHJvLkYwM2gXX/AJwTI6WSje7AuSREWA76GvCiZZe86q3xNmd1p0UkREoCXNz7ti4lPdZPFONMUgg80xf8AFRk/SXmOQDCawA/rK747jUAb7W3W5PRw6Li98H/E0T46hRWgsxHmC+WzjdGP7lx4Y0rCnC/RcQgKQEuH+o9XMyGu0D6BrttlQUzjPKBWLLIZzxf2Gb9j1MXT90wYdCE7pZWbC0WFyHm/KD6f/qB4CPYjWqOAqkA62XgNohMAiwx6oj+oLa9xvfjujW3B1cLi2gdxBHAO9JbUV9v36HGnKbwsiBTcKwZUxf/cXEyIVMXT90AbJg0+JoBIY3gIBH1r3JCQC3ooSLyyuQF3jtb4maKyB5RBJi483XVGxq5DPhYPLrjtPne7zMVdOlAb08sDXwkUuWrLgt5L3xSZJkKjqC2ordOHOL9eMpC7zuFuokickfYBztr5P0l3ZpWb3fbZ1d/lYugSTt7f6/BL3uKcMvNNd4J7vMTqq/dT1SfAbYQdMGPXb7Z9YFN6H187RXhQNADc8/25dr5AAZODP124PHY81NrrplhYR0KbFRk583re52X67WKKBwKsiFkwiBvL2z5CrAUXXTLAu+QZHkvG+I90BZeBtb+1GX7Xg/MPbuoBdoQhXlPoC0nKlgKiEjc6Hdj2kLv68BNCj23qv/moFR5i2h5FIQARjkhpEz8jqZ96qbLNlwvyCy/RMxGEW2DvAlw+dDr+2PJyOAiz+zbFnnTflDR+5bX7xf9jYjsnu/1i8gPeRPAqJyowcm/imvunwa31ngXqPJislfOFdE6KIAJ0BOCS7jGeCSrjZeNzXpdk6e0e/51KCJX5DULuHiX63YRI/MBFN68veaPBxSmWkW0FvLTAI51QujxLStq5a+IjoL8ngsQjgdQaG5yPEnfyVdE+0XOGuDi6htGK1IVXNF/9e5FV3bKDyt2duSsAVQY7zpIGfwpov0idwJgHRP8VeexS18oVIWKaF3kZAL+MPSGUQqVgSN5Ydr8y+sKWakiWg85EcASz3gIbwUvPnTZgZGTCVA1xwZDCBs93cteSZe/iPaLrDXAecOm7qZIfwBFX7z9w0uKe/g6MLLWAKJmPBJ8LYvydAvUqYhWRNYEUBgHIEJ9ueUpqv8OjqxMwLnVU3cFGRTcxf9i0fvv+MhOA4geF358Q0KvhiuiIyM7J1Dk2GDot74i8JBHER0cGRPgnKG3DFMYDGCwXiqq/86BjE2ACseFnvpDKar/ToKMNYARjgss/Uu9pRXFt212EmREgLNHTNtZYEjwrRyv3FNzXsIvcBbR8ZCRCVAjx4Z+W2hR/XciZKQBfMY/zsGg0FjSpayo/jsR0mqAk4bcsJMfZ1dLBQvz8l2zLlvfGhUronWQlgDGco5RVbFEELGL6r+TIS0BmnDG2SiWSlNJg7/4pa1OhpQEGDfMu43j+PZSVWzklelLryuq/06GlASo9zUeaQu2JYqR4tJvZ0RKAvjFf4xBsFR9HkuK6r8TIikB9h40obtffftZCJbom69+Oq344uVOiKQEMNJwuF+l3BJBoPjUTydFUgL4xYwTBUvFlCDPt2alimg9JHw6uKrq/LJudtNqC6u7JfLBnEX37t3aFSuidZBQA5RRf7Bf6W6JYhXVf6dGQgI4thmHgqiiYhfVfydGgsWg8TbKEQCq+tmiRQ+mfedPER0XcQQYXNVtH6AnAMK/WrtCRbQu4jWAJUeGftqqRfvfyRFPANUjgr9qaz5/5JPWrU4RrY0oAuxcdVo1woDg4bNEf82tiE6IKAIYS46KnCiq/00BMSZAQup/9YIldR+0em2KaHWECTC032+3BUYDILwITzltVakiWg9hAvhL/EeGjkWl+M6fTQQuExCe/jUZ4fU2qU0RrQ4LYGSvsyoQ9gdQeGPx4oc3tG21imgtWAD13fyHoFQAUFT/mxQsAMWE1L86ar3YhvUpopVhwXgb5JcAKG8vXfrQyjauUxGtCGvQoIo9gK0BI1jXtHWFimhdWBZyFIGP+k5c+Plfih9z3MTgUZVtVfSwxUseKb7xaxPE/wNdTWzU9o0tSgAAAABJRU5ErkJggg==';
    Packit 90a5c9
        base_image.onload = function(){
    Packit 90a5c9
            ctx.globalAlpha = 0.15
    Packit 90a5c9
            ctx.drawImage(base_image, (canvas.width/2) - 64 - (lwidth/2), (canvas.height/2) - 128);
    Packit 90a5c9
            ctx.globalAlpha = 1
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    /* Function for drawing line charts
    Packit 90a5c9
     * Example usage:
    Packit 90a5c9
     * quokkaLines("myCanvas", ['Line a', 'Line b', 'Line c'], [ [x1,a1,b1,c1], [x2,a2,b2,c2], [x3,a3,b3,c3] ], { stacked: true, curve: false, title: "Some title" } );
    Packit 90a5c9
     */
    Packit 90a5c9
    function quokkaBars(id, titles, values, options) {
    Packit 90a5c9
        var canvas = document.getElementById(id);
    Packit 90a5c9
        var ctx=canvas.getContext("2d");
    Packit 90a5c9
        // clear the canvas first
    Packit 90a5c9
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    Packit 90a5c9
        var lwidth = 150;
    Packit 90a5c9
        var lheight = 75;
    Packit 90a5c9
        var stack = options ? options.stack : false;
    Packit 90a5c9
        var astack = options ? options.astack : false;
    Packit 90a5c9
        var curve = options ? options.curve : false;
    Packit 90a5c9
        var title = options ? options.title : null;
    Packit 90a5c9
        var noX = options ? options.nox : false;
    Packit 90a5c9
        var verts = options ? options.verts : true;
    Packit 90a5c9
        if (noX) {
    Packit 90a5c9
            lheight = 0;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw a border
    Packit 90a5c9
        ctx.lineWidth = 0.5;
    Packit 90a5c9
        ctx.strokeRect(25, 30, canvas.width - lwidth - 40, canvas.height - lheight - 40);
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw a title if set:
    Packit 90a5c9
        if (title != null) {
    Packit 90a5c9
            ctx.font="15px Arial";
    Packit 90a5c9
            ctx.fillStyle = "#000";
    Packit 90a5c9
            ctx.textAlign = "center";
    Packit 90a5c9
            ctx.fillText(title,(canvas.width-lwidth)/2, 15);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw legend
    Packit 90a5c9
        ctx.textAlign = "left";
    Packit 90a5c9
        var posY = 50;
    Packit 90a5c9
        for (var k in titles) {
    Packit 90a5c9
            var x = parseInt(k)
    Packit 90a5c9
            if (!noX) {
    Packit 90a5c9
                x = x + 1;
    Packit 90a5c9
            }
    Packit 90a5c9
            var title = titles[k];
    Packit 90a5c9
            if (title && title.length > 0) {
    Packit 90a5c9
                ctx.fillStyle = colors[k % colors.length][0];
    Packit 90a5c9
                ctx.fillRect(canvas.width - lwidth + 20, posY-10, 10, 10);
    Packit 90a5c9
                
    Packit 90a5c9
                // Add legend text
    Packit 90a5c9
                ctx.font="12px Arial";
    Packit 90a5c9
                ctx.fillStyle = "#000";
    Packit 90a5c9
                ctx.fillText(title,canvas.width - lwidth + 40, posY);
    Packit 90a5c9
                
    Packit 90a5c9
                posY += 15;
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Find max and min
    Packit 90a5c9
        var max = null;
    Packit 90a5c9
        var min = 0;
    Packit 90a5c9
        var stacked = null;
    Packit 90a5c9
        for (x in values) {
    Packit 90a5c9
            var s = 0;
    Packit 90a5c9
            for (y in values[x]) {
    Packit 90a5c9
                if (y > 0 || noX) {
    Packit 90a5c9
                    s += values[x][y];
    Packit 90a5c9
                    if (max == null || max < values[x][y]) {
    Packit 90a5c9
                        max = values[x][y];
    Packit 90a5c9
                    }
    Packit 90a5c9
                    if (min == null || min > values[x][y]) {
    Packit 90a5c9
                        min = values[x][y];
    Packit 90a5c9
                    }
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            if (stacked == null || stacked < s) {
    Packit 90a5c9
                stacked = s;
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
        if (min == max) {
    Packit 90a5c9
            max++;
    Packit 90a5c9
        }
    Packit 90a5c9
        if (stack) {
    Packit 90a5c9
            min = 0;
    Packit 90a5c9
            max = stacked;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Set number of lines to draw and each step
    Packit 90a5c9
        var numLines = 5;
    Packit 90a5c9
        var step = (max-min) / (numLines+1);
    Packit 90a5c9
        
    Packit 90a5c9
        // Prettify the max value so steps aren't ugly numbers
    Packit 90a5c9
        if (step %1 != 0) {
    Packit 90a5c9
            step = (Math.round(step+0.5));
    Packit 90a5c9
            max = step * (numLines+1);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw horizontal lines
    Packit 90a5c9
        for (x = numLines; x >= 0; x--) {
    Packit 90a5c9
            
    Packit 90a5c9
            var y = 30 + (((canvas.height-40-lheight) / (numLines+1)) * (x+1));
    Packit 90a5c9
            ctx.moveTo(25, y);
    Packit 90a5c9
            ctx.lineTo(canvas.width - lwidth - 15, y);
    Packit 90a5c9
            ctx.lineWidth = 0.25;
    Packit 90a5c9
            ctx.stroke();
    Packit 90a5c9
            
    Packit 90a5c9
            // Add values
    Packit 90a5c9
            ctx.font="10px Arial";
    Packit 90a5c9
            ctx.fillStyle = "#000";
    Packit 90a5c9
            ctx.textAlign = "right";
    Packit 90a5c9
            ctx.fillText( Math.round( ((max-min) - (step*(x+1))) * 100 ) / 100,canvas.width - lwidth + 12, y-4);
    Packit 90a5c9
            ctx.fillText( Math.round( ((max-min) - (step*(x+1))) * 100 ) / 100,20, y-4);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw vertical lines
    Packit 90a5c9
        var sx = 1
    Packit 90a5c9
        var numLines = values.length-1;
    Packit 90a5c9
        var step = (canvas.width - lwidth - 40) / values.length;
    Packit 90a5c9
        while (step < 24) {
    Packit 90a5c9
            step *= 2
    Packit 90a5c9
            sx *= 2
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        if (verts) {
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            for (var x = 1; x < values.length; x++) {
    Packit 90a5c9
                if (x % sx == 0) {
    Packit 90a5c9
                    var y = 35 + (step * (x/sx));
    Packit 90a5c9
                    ctx.moveTo(y, 30);
    Packit 90a5c9
                    ctx.lineTo(y, canvas.height - 10 - lheight);
    Packit 90a5c9
                    ctx.lineWidth = 0.25;
    Packit 90a5c9
                    ctx.stroke();
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Some pre-calculations of steps
    Packit 90a5c9
        var step = (canvas.width - lwidth - 48) / values.length;
    Packit 90a5c9
        var smallstep = (step / titles.length) - 2;
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw X values if noX isn't set:
    Packit 90a5c9
        if (noX != true) {
    Packit 90a5c9
            ctx.beginPath();
    Packit 90a5c9
            for (var i = 0; i < values.length; i++) {
    Packit 90a5c9
                smallstep = (step / (values[i].length-1)) - 2;
    Packit 90a5c9
                zz = 1
    Packit 90a5c9
                var x = 35 + ((step) * i);
    Packit 90a5c9
                var y = canvas.height - lheight + 5;
    Packit 90a5c9
                if (i % sx == 0) {
    Packit 90a5c9
                    ctx.translate(x, y);
    Packit 90a5c9
                    ctx.moveTo(0,0);
    Packit 90a5c9
                    ctx.lineTo(0,-15);
    Packit 90a5c9
                    ctx.stroke();
    Packit 90a5c9
                    ctx.rotate(45*Math.PI/180);
    Packit 90a5c9
                    ctx.textAlign = "left";
    Packit 90a5c9
                    var val = values[i][0];
    Packit 90a5c9
                    if (val.constructor.toString().match("Date()")) {
    Packit 90a5c9
                        val = val.toDateString();
    Packit 90a5c9
                    }
    Packit 90a5c9
                    ctx.fillText(val.toString(), 0, 0);
    Packit 90a5c9
                    ctx.rotate(-45*Math.PI/180);
    Packit 90a5c9
                    ctx.translate(-x,-y);
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        // Draw each line
    Packit 90a5c9
        var stacks = [];
    Packit 90a5c9
        var pstacks = [];
    Packit 90a5c9
        
    Packit 90a5c9
        for (k in values) {
    Packit 90a5c9
            smallstep = (step / (values[k].length)) - 2;
    Packit 90a5c9
            stacks[k] = 0;
    Packit 90a5c9
            pstacks[k] = canvas.height - 40 - lheight;
    Packit 90a5c9
            var beginX = 0;
    Packit 90a5c9
            for (i in values[k]) {
    Packit 90a5c9
                if (i > 0 || noX) {
    Packit 90a5c9
                    var z = parseInt(i);
    Packit 90a5c9
                    var zz = z;
    Packit 90a5c9
                    if (!noX) {
    Packit 90a5c9
                        z = parseInt(i) + 1;
    Packit 90a5c9
                        zz = z - 2;
    Packit 90a5c9
                        if (z > values[k].length) {
    Packit 90a5c9
                            break;
    Packit 90a5c9
                        }
    Packit 90a5c9
                    }
    Packit 90a5c9
                    var value = values[k][i];
    Packit 90a5c9
                    var title = titles[i];
    Packit 90a5c9
                    var color = colors[zz % colors.length][1];
    Packit 90a5c9
                    var fcolor = colors[zz % colors.length][2];
    Packit 90a5c9
                    if (values[k][2] && values[k][2].toString().match(/^#.+$/)) {
    Packit 90a5c9
                        color = values[k][2]
    Packit 90a5c9
                        fcolor = values[k][2]
    Packit 90a5c9
                        smallstep = (step / (values[k].length-2)) - 2;
    Packit 90a5c9
                    }
    Packit 90a5c9
                    var x = ((step) * k) + ((smallstep+2) * zz) + 5;
    Packit 90a5c9
                    var y = canvas.height - 10 - lheight;
    Packit 90a5c9
                    var mdiff = (max-min);
    Packit 90a5c9
                    mdiff = (mdiff == 0) ? 1 : mdiff;
    Packit 90a5c9
                    var height = ((canvas.height - 40 - lheight) / (mdiff)) * value * -1;
    Packit 90a5c9
                    var width = smallstep - 2;
    Packit 90a5c9
                    if (width <= 1) {
    Packit 90a5c9
                        width = 1
    Packit 90a5c9
                    }
    Packit 90a5c9
                    if (stack) {
    Packit 90a5c9
                        width = step - 10;
    Packit 90a5c9
                        y -= stacks[k];
    Packit 90a5c9
                        stacks[k] -= height;
    Packit 90a5c9
                        x = (step * k) + 4;
    Packit 90a5c9
                        if (astack) {
    Packit 90a5c9
                            y = canvas.height - 10 - lheight;
    Packit 90a5c9
                        }
    Packit 90a5c9
                    }
    Packit 90a5c9
                    
    Packit 90a5c9
                            
    Packit 90a5c9
                    // Draw bar
    Packit 90a5c9
                    ctx.beginPath();
    Packit 90a5c9
                    ctx.lineWidth = 2;
    Packit 90a5c9
                    ctx.strokeStyle = color;
    Packit 90a5c9
                    ctx.strokeRect(27 + x, y, width, height);
    Packit 90a5c9
                    var alpha = 0.75
    Packit 90a5c9
                    if (fcolor.r) {
    Packit 90a5c9
                        ctx.fillStyle = 'rgba('+ [parseInt(fcolor.r*255),parseInt(fcolor.g*255),parseInt(fcolor.b*255),alpha].join(",") + ')';
    Packit 90a5c9
                    } else {
    Packit 90a5c9
                        ctx.fillStyle = fcolor;
    Packit 90a5c9
                    }
    Packit 90a5c9
                    ctx.fillRect(27 + x, y, width, height);
    Packit 90a5c9
                    
    Packit 90a5c9
                }
    Packit 90a5c9
            }
    Packit 90a5c9
            
    Packit 90a5c9
    Packit 90a5c9
        }
    Packit 90a5c9
    }
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    ]==]
    Packit 90a5c9
    Packit 90a5c9
    Packit 90a5c9
    status_css = [[
    Packit 90a5c9
        html {
    Packit 90a5c9
        font-size: 14px;
    Packit 90a5c9
        position: relative;
    Packit 90a5c9
        background: #272B30;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        body {
    Packit 90a5c9
            background-color: #272B30;
    Packit 90a5c9
            color: #000;
    Packit 90a5c9
            margin: 0 auto;
    Packit 90a5c9
            min-height: 100%;
    Packit 90a5c9
            font-family: Arial, Helvetica, sans-serif;
    Packit 90a5c9
            font-weight: normal;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .navbarLeft {
    Packit 90a5c9
            background: linear-gradient(to bottom, #F8A900 0%,#D88900 100%);
    Packit 90a5c9
            width: 200px;
    Packit 90a5c9
            height: 30px;
    Packit 90a5c9
            padding-top: 2px;
    Packit 90a5c9
            font-size: 1.35rem;
    Packit 90a5c9
            color: #FFF;
    Packit 90a5c9
            border-bottom: 2px solid #000;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            text-align: center;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .navbarRight {
    Packit 90a5c9
            background: linear-gradient(to bottom, #EFEFEF 0%,#EEE 100%);
    Packit 90a5c9
            width: calc(100% - 240px);
    Packit 90a5c9
            height: 28px;
    Packit 90a5c9
            color: #333;
    Packit 90a5c9
            border-bottom: 2px solid #000;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            font-size: 1.3rem;
    Packit 90a5c9
            padding-top: 4px;
    Packit 90a5c9
            text-align: left;
    Packit 90a5c9
            padding-left: 40px;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .wrapper {
    Packit 90a5c9
            width: 100%;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            background: #33363F;
    Packit 90a5c9
            min-height: calc(100% - 80px);
    Packit 90a5c9
            position: relative;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .serverinfo {
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            width: 200px;
    Packit 90a5c9
            height: calc(100% - 34px);
    Packit 90a5c9
            background: #293D4C;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .skey {
    Packit 90a5c9
            background: rgba(30,30,30,0.3);
    Packit 90a5c9
            color: #C6E7FF;
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
            padding: 2px;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .sval {
    Packit 90a5c9
            padding: 2px;
    Packit 90a5c9
            background: rgba(30,30,30,0.3);
    Packit 90a5c9
            color: #FFF;
    Packit 90a5c9
            font-size: 0.8rem;
    Packit 90a5c9
            border-bottom: 1px solid rgba(200,200,200,0.2);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .charts {
    Packit 90a5c9
            padding: 0px;
    Packit 90a5c9
            width: calc(100% - 220px);
    Packit 90a5c9
            max-width: 1000px;
    Packit 90a5c9
            min-height: 100%;
    Packit 90a5c9
            margin: 0px auto;
    Packit 90a5c9
            position: relative;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            margin-left: 20px;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        pre, code {
    Packit 90a5c9
            font-family: "Courier New", Courier, monospace;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        strong {
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        q, em, var {
    Packit 90a5c9
            font-style: italic;
    Packit 90a5c9
        }
    Packit 90a5c9
        /* h1                     */
    Packit 90a5c9
        /* ====================== */
    Packit 90a5c9
        h1 {
    Packit 90a5c9
            padding: 0.2em;
    Packit 90a5c9
            margin: 0;
    Packit 90a5c9
            border: 1px solid #405871;
    Packit 90a5c9
            background-color: inherit;
    Packit 90a5c9
            color: #036;
    Packit 90a5c9
            text-decoration: none;
    Packit 90a5c9
            font-size: 22px;
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        /* h2                     */
    Packit 90a5c9
        /* ====================== */
    Packit 90a5c9
        h2 {
    Packit 90a5c9
            padding: 0.2em 0 0.2em 0.7em;
    Packit 90a5c9
            margin: 0 0 0.5em 0;
    Packit 90a5c9
            text-decoration: none;
    Packit 90a5c9
            font-size: 18px;
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
            text-align: center;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        #modules {
    Packit 90a5c9
            margin-top:20px;
    Packit 90a5c9
            display:none;
    Packit 90a5c9
            width:400px;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .servers {
    Packit 90a5c9
            
    Packit 90a5c9
            width: 1244px;
    Packit 90a5c9
            background: #EEE;
    Packit 90a5c9
        }
    Packit 90a5c9
    Packit 90a5c9
        tr:nth-child(odd) {
    Packit 90a5c9
            background: #F6F6F6;
    Packit 90a5c9
        }
    Packit 90a5c9
        tr:nth-child(even) {
    Packit 90a5c9
            background: #EBEBEB;
    Packit 90a5c9
        }
    Packit 90a5c9
        td {
    Packit 90a5c9
            padding: 2px;
    Packit 90a5c9
        }
    Packit 90a5c9
        table {
    Packit 90a5c9
            border: 1px solid #333;
    Packit 90a5c9
            padding: 0px;
    Packit 90a5c9
            margin: 5px;
    Packit 90a5c9
            min-width: 360px;
    Packit 90a5c9
            background: #999;
    Packit 90a5c9
            font-size: 0.8rem;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        canvas {
    Packit 90a5c9
            background: #FFF;
    Packit 90a5c9
            margin: 3px;
    Packit 90a5c9
            text-align: center;
    Packit 90a5c9
            padding: 2px;
    Packit 90a5c9
            border-radius: 10px;
    Packit 90a5c9
            border: 1px solid #999;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .canvas_wide {
    Packit 90a5c9
            position: relative;
    Packit 90a5c9
            width: 65%;
    Packit 90a5c9
        }
    Packit 90a5c9
        .canvas_narrow {
    Packit 90a5c9
            position: relative;
    Packit 90a5c9
            width: 27%;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        a {
    Packit 90a5c9
            color: #FFA;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .statsbox {
    Packit 90a5c9
            border-radius: 3px;
    Packit 90a5c9
            background: #3C3E47;
    Packit 90a5c9
            min-width: 150px;
    Packit 90a5c9
            height: 60px;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            margin: 15px;
    Packit 90a5c9
            padding: 10px;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .btn {
    Packit 90a5c9
            background: linear-gradient(to bottom, #72ca72 0%,#55bf55 100%);
    Packit 90a5c9
            border-radius: 5px;
    Packit 90a5c9
            color: #FFF;
    Packit 90a5c9
            text-decoration: none;
    Packit 90a5c9
            padding-top: 6px;
    Packit 90a5c9
            padding-bottom: 6px;
    Packit 90a5c9
            padding-left: 3px;
    Packit 90a5c9
            padding-right: 3px;
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
            text-shadow: 1px 1px rgba(0,0,0,0.4);
    Packit 90a5c9
            margin: 12px;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            clear: none;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .infobox_wrapper {
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            min-width: 200px;
    Packit 90a5c9
            margin: 10px;
    Packit 90a5c9
        }
    Packit 90a5c9
        .infobox_title {
    Packit 90a5c9
            border-top-left-radius: 4px;
    Packit 90a5c9
            border-top-right-radius: 4px;
    Packit 90a5c9
            background: #FAB227;
    Packit 90a5c9
            color: #FFF;
    Packit 90a5c9
            border: 2px solid #FAB227;
    Packit 90a5c9
            border-bottom: none;
    Packit 90a5c9
            font-weight: bold;
    Packit 90a5c9
            text-align: center;
    Packit 90a5c9
            width: 100%;
    Packit 90a5c9
        }
    Packit 90a5c9
        .infobox {
    Packit 90a5c9
            background: #222222;
    Packit 90a5c9
            border: 2px solid #FAB227;
    Packit 90a5c9
            border-top: none;
    Packit 90a5c9
            color: #EFEFEF;
    Packit 90a5c9
            border-bottom-left-radius: 4px;
    Packit 90a5c9
            border-bottom-right-radius: 4px;
    Packit 90a5c9
            float: left;
    Packit 90a5c9
            width: 100%;
    Packit 90a5c9
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        
    Packit 90a5c9
        .serverinfo ul {
    Packit 90a5c9
            margin: 0px;
    Packit 90a5c9
            padding: 0px;
    Packit 90a5c9
            margin-top: 20px;
    Packit 90a5c9
            list-style: none;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .serverinfo ul li .btn {
    Packit 90a5c9
            width: calc(100% - 8px);
    Packit 90a5c9
            margin: 0px;
    Packit 90a5c9
            border: 0px;
    Packit 90a5c9
            border-radius: 0px;
    Packit 90a5c9
            padding: 0px;
    Packit 90a5c9
            padding-top: 8px;
    Packit 90a5c9
            padding-left: 8px;
    Packit 90a5c9
            height: 24px;
    Packit 90a5c9
            background: rgba(0,0,0,0.2);
    Packit 90a5c9
            border-bottom: 1px solid rgba(100,100,100,0.3);
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .serverinfo  ul li:nth-child(1)  {
    Packit 90a5c9
            border-top: 1px solid rgba(100,100,100,0.3);
    Packit 90a5c9
        }
    Packit 90a5c9
        .serverinfo ul li .btn.active {
    Packit 90a5c9
            background: rgba(30,30,50,0.2);
    Packit 90a5c9
            border-left: 4px solid #27FAB2;
    Packit 90a5c9
            padding-left: 4px;
    Packit 90a5c9
            color: #FFE;
    Packit 90a5c9
        }
    Packit 90a5c9
        
    Packit 90a5c9
        .serverinfo ul li .btn:hover {
    Packit 90a5c9
            background: rgba(50,50,50,0.15);
    Packit 90a5c9
            border-left: 4px solid #FAB227;
    Packit 90a5c9
            padding-left: 4px;
    Packit 90a5c9
            color: #FFE;
    Packit 90a5c9
        }
    Packit 90a5c9
    ]]