Module documentation


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 = mw.html.create 'table'
    	:attr('role', 'presentation')
    	:css('width', width)
    	:cssText(barCSS)
    	:css('border-spacing', '0')
    	:tag 'tr'
    for k, v in pairs(series) do
        result:node(inner.renderSeries(v))
    end
    return result:allDone()
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
    return mw.html.create 'td'
    	:attr('title', series.title)
    	:css('width', width)
    	:css('background', series.color)
    	:attr('data-value', series.value)
    	:css('height', '1em')
    	:css('padding', '0')
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