Module:Bar

From Meta, a Wikimedia project coordination wiki
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]

Module:Bar generates a coloured bar for bar charts or progress bars with any number of data series. This is intended for use in more general templates.

Usage[edit]

Series[edit]

The bar is invoked with a series of values that represent data series. Each series specifies two or three arguments: the value, the colour, and an optional tooltip title. For example:

{{#invoke:bar|format|4,green,done|2,gray,pending|4,#FCC,not done}}

Total[edit]

You can explicitly specify the total number of values in the bar. If you do and the series add up to a smaller value, an equivalent empty space will be included at the end:

{|
| {{#invoke:bar|format|7,green,done|total=10}}
|-
| {{#invoke:bar|format|4,green,done|total=10}}
|-
| {{#invoke:bar|format|2,green,done|total=10}}
|}

Width[edit]

By default the bar will be set to 100% width, but you can specify any valid CSS width instead:

{|
| {{#invoke:bar|format|7,green,done|width=5em}}
|-
| {{#invoke:bar|format|7,green,done|width=15em}}
|}

Bar CSS[edit]

You can customize the appearance of the bar with CSS:

{{#invoke:bar|format|7,green,done|total=10|width=30em|barCSS=border:1px solid #CCC}}

Examples[edit]

Progress bar[edit]

{|
| {{#invoke:bar|format|7,green,done|total=10|width=30em|barCSS=border:1px solid #CCC}}
| {{#expr:7 / 10 * 100}}%
|}
70%

Stacked bar chart[edit]

{|
|+ Expenses vs profits
|-
| 2011
| {{#invoke:bar|format|100,red,expenses|50,green,profits|total=170|width=30em}}
|-
| 2012
| {{#invoke:bar|format|75,red,expenses|90,green,profits|total=170|width=30em}}
|}
Expenses vs profits
2011
2012

Grouped bar chart[edit]

{|
|+ Expenses vs revenue
|-
| 2011
| {{#invoke:bar|format|150,green,revenue|total=160|width=15em}}
{{#invoke:bar|format|100,red,expenses|total=160|width=15em}}
|-
| 2012
| {{#invoke:bar|format|160,green,revenue|total=160|width=15em}}
{{#invoke:bar|format|75,red,expenses|total=160|width=15em}}
|}
Expenses vs revenue
2011
2012

local p = {}
local inner = {}

--##########
--## Public functions
--##########
--- Render a bar chart.
-- @param frame The arguments passed to the script. See docs on renderFromLua.
function p.format( frame )
    -- extract args
    local width = frame.args['width']
    local barCSS = frame.args['barCSS']
    local zeroWidth = frame.args['zeroWidth']
    local total = frame.args['total']
    
    -- extract bar series from arguments like 'value,color,title'
    local series = {}
    for key, spec in ipairs(frame.args) do
        spec = mw.text.split(spec, ',')
        local data = {value = tonumber(spec[1] or 0), color = spec[2] or '#CCC', title = spec[3] or ''}
        if data['value'] > 0 then
            table.insert(series, data)
        end
    end
    
    return p.renderFromLua(series, total, width, barCSS, zeroWidth)
end

--- Render a bar chart from Lua.
-- @param series A table representing the bars to render, consisting of a sequence of tables like {value = 14, color = '#CCC', title = 'tooltip text'}.
-- @param total (optional) The total number of values represented by all bar series.
-- @param width (optional) The CSS width of the bar.
-- @param barCss (optional) Additional CSS to apply to the rendered bar table.
-- @param zeroWidth (optional) 
function p.renderFromLua(series, total, width, barCSS, zeroWidth)
    -- parse arguments
    width = width or '100%'
    total = tonumber(total or 0)
    zeroWidth = zeroWidth or '1px'
    
    -- calculate total
    local seriesTotal = 0
    for k,v in ipairs(series) do
       seriesTotal = seriesTotal + v['value']
    end
    if total < seriesTotal then
       total = seriesTotal
    end
    
    -- inject empty series for uncharted values
    if(seriesTotal < total) then
        table.insert(series, {value = total - seriesTotal, color = 'transparent', title = ''})
    end
    
    -- inject ratios
    for k,v in ipairs(series) do
       v['total'] = total
       v['ratio'] = inner.getRatio(v['value'], total)
       if v['ratio'] == 0 then
           v['width'] = zeroWidth
      end
    end
    
    -- render
    result = '<table style="width:' .. width .. ';' .. (barCSS or '') .. '" cellspacing="0"><tr>'
    for k, v in pairs(series) do
        result = result .. inner.renderSeries(v)
    end
    result = result .. '</tr></table>'
    return result
end

--##########
--## Private functions
--##########
--- Render an individual bar series.
-- @param series The bar series to render.
function inner.renderSeries(series)
    -- ignore empty series
    if not series.value or series.value == 0 then
        return ''
    end
    
    -- set width
    local width = series.width
    if not(width) then
        width = series.ratio .. '%'
    end
    
    -- format
    local result = mw.ustring.format('<td title="%s" style="width:%s; height:1em; padding:0; background:%s;' .. (series.css or '') .. '" data-value="%s"></td>', series.title, width, series.color, series.value)
    return result
end

--- Get the percentage ratio of two numbers as a decimal value.
-- @param value The number of items in the subset.
-- @param total The total number of items in the set.
function inner.getRatio(value, total)
    if(total == 0) then
       error('the total for a series cannot be zero')
    end
    return math.floor(value / total * 10000) / 100
end

return p