모듈:Chart: 두 판 사이의 차이

편집 요약 없음
편집 요약 없음
1번째 줄: 1번째 줄:
--<source lang=lua>
--[[
--[[
    keywords are used for languages: they are the names of the actual
keywords are used for languages: they are the names of the actual
    parameters of the template
parameters of the template
]]
]]


local keywords = {
local keywords = {
    barChart = '막대 도표',
barChart = 'bar chart',
    pieChart = '원 도표',
pieChart = 'pie chart',
    width = '넓이',
width = 'width',
    height = '높이',
height = 'height',
    stack = '쌓기',
stack = 'stack',
    colors = '',
colors = 'colors',
    group = '집합',
group = 'group',
    xlegend = 'x 범례',
xlegend = 'x legends',
    tooltip = '말풍선',
yticks = 'y tick marks',
    accumulateTooltip = '말풍선 합계',
tooltip = 'tooltip',
    links = '연결',
accumulateTooltip = 'tooltip value accumulation',
    defcolor = '기본색',
links = 'links',
    scalePerGroup = '집합 축척',
defcolor = 'default color',
    unitsPrefix = '머리 단위',
scalePerGroup = 'scale per group',
    unitsSuffix = '꼬리 단위',
unitsPrefix = 'units prefix',
    groupNames = '집합 이름',
unitsSuffix = 'units suffix',
    hideGroupLegends = '집합 범례 숨김',
groupNames = 'group names',
    slices = '조각합',
hideGroupLegends = 'hide group legends',
    slice = '조각',
slices = 'slices',
    radius = '반지름',
slice = 'slice',
    percent = '백분율',
radius = 'radius',
percent = 'percent',


} -- here is what you want to translate
} -- here is what you want to translate


local defColors = require "모듈:Plotter/DefaultColors"
local defColors = mw.loadData("Module:Chart/Default colors")
local hideGroupLegends
local hideGroupLegends


local function nulOrWhitespace( s )
local function nulOrWhitespace( s )
    return not s or mw.text.trim( s ) == ''
return not s or mw.text.trim( s ) == ''
end
end


local function createGroupList( tab, legends, cols )
local function createGroupList( tab, legends, cols )
    if #legends > 1 and not hideGroupLegends then
if #legends > 1 and not hideGroupLegends then
        table.insert( tab, mw.text.tag( 'div' ) )
table.insert( tab, mw.text.tag( 'div' ) )
        local list = {}
local list = {}
        local spanStyle = "padding:0 1em;background-color:%s;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"
local spanStyle = "padding:0 1em;background-color:%s;border:1px solid %s;margin-right:1em;-webkit-print-color-adjust:exact;"
        for gi = 1, #legends do
for gi = 1, #legends do
            local span = mw.text.tag( 'span', { style = string.format( spanStyle, cols[gi] ) }, '&nbsp;' ) .. ' '..  legends[gi]
local span = mw.text.tag( 'span', { style = string.format( spanStyle, cols[gi], cols[gi] ) }, '&nbsp;' ) .. ' '..  legends[gi]
            table.insert( list, mw.text.tag( 'li', {}, span ) )
table.insert( list, mw.text.tag( 'li', {}, span ) )
        end
end
        table.insert( tab,
table.insert( tab,
            mw.text.tag( 'ul',
mw.text.tag( 'ul',
                {style="width:100%;list-style:none;-webkit-column-width:12em;-moz-column-width:12em;column-width:12em;"},
{style="width:100%;list-style:none;column-width:12em;"},
                table.concat( list, '\n' )
table.concat( list, '\n' )
            )
)
        )
)
        table.insert( tab, '</div>' )
table.insert( tab, '</div>' )
    end
end
end
end


function pieChart( frame )
local function pieChart( frame )
    local res, imslices, args = {}, {}, frame.args
local res, imslices, args = {}, {}, frame.args
    local radius
local radius
    local values, colors, names, legends, links = {}, {}, {}, {}, {}
local values, colors, names, legends, links = {}, {}, {}, {}, {}
    local delimiter = args.delimiter or ':'
local delimiter = args.delimiter or ':'
    local lang = mw.getContentLanguage()
local lang = mw.getContentLanguage()
 
    function getArg( s, def, subst, with )
        local result = args[keywords[s]] or def or ''
        if subst and with then result = mw.ustring.gsub( result, subst, with ) end
        return result
    end
 
    function analyzeParams()
        function addSlice( i, slice )
            local value, name, color, link = unpack( mw.text.split( slice, '%s*' .. delimiter .. '%s*' ) )
            values[i] = tonumber( lang:parseFormattedNumber( value ) )
                or error( string.format( 'Slice %d: "%s", first item("%s") could not be parsed as a number', i, value or '', sliceStr ) )
            colors[i] = not nulOrWhitespace( color ) and color or defColors[i * 2]
            names[i] = name or ''
            links[i] = link
        end
       
        radius = getArg( 'radius', 150 )
        hideGroupLegends = not nulOrWhitespace( args[keywords.hideGroupLegends] )
        local slicesStr = getArg( 'slices' )
        local prefix = getArg( 'unitsPrefix', '', '_', ' ' )
        local suffix = getArg( 'unitsSuffix', '', '_', ' ' )
        local percent = args[keywords.percent]
        local sum = 0
        local i, value = 0
        for slice in mw.ustring.gmatch( slicesStr or '', "%b()" ) do
            i = i + 1
            addSlice( i, mw.ustring.match( slice, '^%(%s*(.-)%s*%)$' ) )
        end
       
        for k, v in pairs(args) do
            local ind = mw.ustring.match( k, '^' .. keywords.slice .. '%s+(%d+)$' )
            if ind then addSlice( tonumber( ind ), v ) end
        end
       
        for _, val in ipairs( values ) do sum = sum + val end
        for i, value in ipairs( values ) do
            local addprec = percent and string.format( ' (%0.1f%%)', value / sum * 100 ) or ''
            legends[i] = mw.ustring.format( '%s: %s%s%s%s', names[i], prefix, lang:formatNum( value ), suffix, addprec )
            links[i] = mw.text.trim( links[i] or mw.ustring.format( '[[#noSuchAnchor|%s]]', legends[i] ) )
        end
    end


    function addRes( ... )
local function getArg( s, def, subst, with )
        for _, v in pairs( { ... } ) do
local result = args[keywords[s]] or def or ''
            table.insert( res, v )
if subst and with then result = string.gsub( result, subst, with ) end
        end
return result
    end
 
    function createImageMap()
        addRes( '{{#tag:imagemap|', 'Image:Circle frame.svg{{!}}' .. ( radius * 2 ) .. 'px' )
        addRes( unpack( imslices ) )
        addRes( 'desc none', '}}' )
    end
   
    function createChart()
    addRes('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="' .. (radius or 200) * 2 .. '" height="' .. (radius or 200) * 2 .. '">')
    addRes('<g transform="translate(' .. (radius or 200) .. ',' .. (radius or 200) .. ')">')
    createSlices()
    addRes('</g>')
    addRes('</svg>')
end
end


createChart()
local function analyzeParams()
local function addSlice( i, slice )
local value, name, color, link = unpack( mw.text.split( slice, '%s*' .. delimiter .. '%s*' ) )
values[i] = tonumber( lang:parseFormattedNumber( value ) )
or error( string.format( 'Slice %d: "%s", first item("%s") could not be parsed as a number', i, value or '', slice ) )
colors[i] = not nulOrWhitespace( color ) and color or defColors[i * 2]
names[i] = name or ''
links[i] = link
end


    function drawSlice(i, q, start)
radius = getArg( 'radius', 150 )
    local color = colors[i]
hideGroupLegends = not nulOrWhitespace( args[keywords.hideGroupLegends] )
    local angle = start * 2 * math.pi
local slicesStr = getArg( 'slices' )
    local sin, cos = math.sin(angle), math.cos(angle)
local prefix = getArg( 'unitsPrefix', '', '_', ' ' )
    local x1, y1 = radius + radius * sin, radius - radius * cos
local suffix = getArg( 'unitsSuffix', '', '_', ' ' )
    local x2, y2 = radius + radius * math.sin(start * 2 * math.pi + 0.02), radius - radius * math.cos(start * 2 * math.pi + 0.02)
local percent = args[keywords.percent]
local sum = 0
local i = 0
for slice in string.gmatch( slicesStr or '', "%b()" ) do
i = i + 1
addSlice( i, string.match( slice, '^%(%s*(.-)%s*%)$' ) )
end


    local path = string.format('M %f %f A %f %f 0 %d 1 %f %f L %f %f', radius, radius, radius, radius, (q - start > 0.5 and 1 or 0), x2, y2, radius, radius)
for k, v in pairs(args) do
    local style = string.format('fill:%s;stroke:%s;stroke-width:1', color, color)
local ind = string.match( k, '^' .. keywords.slice .. '%s+(%d+)$' )
if ind then addSlice( tonumber( ind ), v ) end
end


    addRes(mw.text.tag('path', { d = path, style = style }, ''))
for _, val in ipairs( values ) do sum = sum + val end
for i, value in ipairs( values ) do
local addprec = percent and string.format( ' (%0.1f%%)', value / sum * 100 ) or ''
legends[i] = string.format( '%s: %s%s%s%s', names[i], prefix, lang:formatNum( value ), suffix, addprec )
links[i] = mw.text.trim( links[i] or string.format( '[[#noSuchAnchor|%s]]', legends[i] ) )
end
end
end


    function createSlices()
local function addRes( ... )
    function coordsOfAngle(angle)
for _, v in pairs( { ... } ) do
        return (radius + math.floor(radius * math.cos(angle))) .. ' ' .. (radius - math.floor(radius * math.sin(angle)))
table.insert( res, v )
    end
end
end


    local sum, start = 0, 0
local function createImageMap()
    for _, value in ipairs(values) do
addRes( '{{#tag:imagemap|', 'File:Circle frame.svg{{!}}' .. ( radius * 2 ) .. 'px' )
        sum = sum + value
addRes( unpack( imslices ) )
    end
addRes( 'desc none', '}}' )
end


    for i, value in ipairs(values) do
local function drawSlice( i, q, start )
        local poly = { 'poly ' .. radius .. ' ' .. radius }
local color = colors[i]
        local startC, endC = start / sum, (start + value) / sum
local angle = start * 2 * math.pi
        local startQ, endQ = math.floor(startC * 4 + 1), math.floor(endC * 4 + 1)
local sin, cos = math.abs( math.sin( angle ) ), math.abs( math.cos( angle ) )
local wsin, wcos = sin * radius, cos * radius
local s1, s2, w1, w2, w3, w4, border
if q == 1 then
border = 'left'
w1, w2, w3, w4 = 0, 0, wsin, wcos
s1, s2 = 'bottom', 'left'
elseif q == 2 then
border = 'bottom'
w1, w2, w3, w4 = 0, wcos, wsin, 0
s1, s2 = 'bottom', 'right'
elseif q == 3 then
border = 'right'
w1, w2, w3, w4 = wsin, wcos, 0, 0
s1, s2 = 'top', 'right'
else
border = 'top'
w1, w2, w3, w4 = wsin, 0, 0, wcos
s1, s2 = 'top', 'left'
end


        for q = startQ, math.min(endQ, 4) do
local style = string.format( 'border:solid transparent;position:absolute;%s:%spx;%s:%spx;width:%spx;height:%spx', s1, radius, s2, radius, radius, radius )
            drawSlice(i, q, startC)
if start <= ( q - 1 ) * 0.25 then
        end
style = string.format( '%s;border:0;background-color:%s', style, color )
else
style = string.format( '%s;border-width:%spx %spx %spx %spx;border-%s-color:%s', style, w1, w2, w3, w4, border, color )
end
addRes( mw.text.tag( 'div', { style = style }, '' ) )
end


        for angle = startC * 2 * math.pi, endC * 2 * math.pi, 0.02 do
local function createSlices()
            table.insert(poly, coordsOfAngle(angle))
local function coordsOfAngle( angle )
        end
return ( 100 + math.floor( 100 * math.cos( angle ) ) ) .. ' ' .. ( 100 - math.floor( 100 * math.sin( angle ) ) )
end


        table.insert(poly, coordsOfAngle(endC * 2 * math.pi) .. ' ' .. radius .. ' ' .. radius .. ' ' .. links[i])
local sum, start = 0, 0
        table.insert(imslices, table.concat(poly, ' '))
for _, value in ipairs( values ) do sum = sum + value end
        start = start + values[i]
for i, value in ipairs(values) do
    end
local poly = { 'poly 100 100' }
local startC, endC =  start / sum, ( start + value ) / sum
local startQ, endQ = math.floor( startC * 4 + 1 ), math.floor( endC * 4 + 1 )
for q = startQ, math.min( endQ, 4 ) do drawSlice( i, q, startC ) end
for angle = startC * 2 * math.pi, endC * 2 * math.pi, 0.02 do
table.insert( poly,  coordsOfAngle( angle ) )
end
table.insert( poly, coordsOfAngle( endC * 2 * math.pi ) .. ' 100 100 ' .. links[i] )
table.insert( imslices, table.concat( poly, ' ' ) )
start = start + values[i]
end
end
end


    analyzeParams()
analyzeParams()
    if #values == 0 then error( "no slices found - can't draw pie chart" ) end
if #values == 0 then error( "no slices found - can't draw pie chart" ) end
    addRes( mw.text.tag( 'div', { style = string.format( "max-width:%spx", radius * 2 ) } ) )
addRes( mw.text.tag( 'div', { class = 'chart noresize', style = string.format( 'margin-top:0.5em;max-width:%spx;', radius * 2 ) } ) )
    addRes( mw.text.tag( 'div', { style = string.format( 'position:relative;min-width:%spx;min-height:%spx;max-width:%spx;overflow:hidden;', radius * 2, radius * 2, radius * 2 ) } ) )
addRes( mw.text.tag( 'div', { style = string.format( 'position:relative;min-width:%spx;min-height:%spx;max-width:%spx;overflow:hidden;', radius * 2, radius * 2, radius * 2 ) } ) )
    createSlices()
createSlices()
    addRes( mw.text.tag( 'div', { style = string.format( 'position:absolute;min-width:%spx;min-height:%spx;overflow:hidden;', radius * 2, radius * 2 ) } ) )
addRes( mw.text.tag( 'div', { style = string.format( 'position:absolute;min-width:%spx;min-height:%spx;overflow:hidden;', radius * 2, radius * 2 ) } ) )
    createImageMap()
createImageMap()
    addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
    addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
    createGroupList( res, legends, colors ) -- legends
createGroupList( res, legends, colors ) -- legends
    addRes( '</div>' ) -- close containing div
addRes( '</div>' ) -- close containing div
    return frame:preprocess( table.concat( res, '\n' ) )
return frame:preprocess( table.concat( res, '\n' ) )
end
end




function barChart( frame )
local function barChart( frame )
    local res = {}
local res = {}
    local args = frame.args -- can be changed to frame:getParent().args
local args = frame.args -- can be changed to frame:getParent().args
    local values, xlegends, colors, tooltips, yscales = {}, {}, {}, {} ,{}, {}, {}
local values, xlegends, colors, tooltips, yscales = {}, {}, {}, {}, {}
    local groupNames, unitsSuffix, unitsPrefix, links = {}, {}, {}, {}
local groupNames, unitsSuffix, unitsPrefix, links = {}, {}, {}, {}
    local width, height, stack, delimiter = 500, 350, false, args.delimiter or ':'
local width, height, yticks, stack, delimiter = 500, 350, -1, false, args.delimiter or ':'
    local chartWidth, chartHeight, defcolor, scalePerGroup, accumulateTooltip
local chartWidth, chartHeight, defcolor, scalePerGroup, accumulateTooltip




    local numGroups, numValues
local numGroups, numValues
    local scaleWidth
local scaleWidth


    function validate()
local function validate()
        function asGroups( name, tab, toDuplicate, emptyOK )
local function asGroups( name, tab, toDuplicate, emptyOK )
            if #tab == 0 and not emptyOK then
if #tab == 0 and not emptyOK then
                error( "must supply values for " .. keywords[name] )
error( "must supply values for " .. keywords[name] )
            end
end
            if #tab == 1 and toDuplicate then
if #tab == 1 and toDuplicate then
                for i = 2, numGroups do tab[i] = tab[1] end
for i = 2, numGroups do tab[i] = tab[1] end
            end
end
            if #tab > 0 and #tab ~= numGroups then
if #tab > 0 and #tab ~= numGroups then
                error ( keywords[name] .. ' should contain the same number of items as the number of groups (' .. numGroups .. ')')
error ( keywords[name] .. ' must contain the same number of items as the number of groups, but it contains ' .. #tab .. ' items and there are ' .. numGroups .. ' groups')
            end
end
        end
end


        -- do all sorts of validation here, so we can assume all params are good from now on.
-- do all sorts of validation here, so we can assume all params are good from now on.
        -- among other things, replace numerical values with mw.language:parseFormattedNumber() result
-- among other things, replace numerical values with mw.language:parseFormattedNumber() result
 
 
chartHeight = height - 80
numGroups = #values
numValues = #values[1]
defcolor = defcolor or 'blue'
colors[1] = colors[1] or defcolor
scaleWidth = scalePerGroup and 80 * numGroups or 100
chartWidth = width - scaleWidth
asGroups( 'unitsPrefix', unitsPrefix, true, true )
asGroups( 'unitsSuffix', unitsSuffix, true, true )
asGroups( 'colors', colors, true, true )
asGroups( 'groupNames', groupNames, false, false )
if stack and scalePerGroup then
error( string.format( 'Illegal settings: %s and %s are incompatible.', keywords.stack, keywords.scalePerGroup ) )
end
for gi = 2, numGroups do
if #values[gi] ~= numValues then error( keywords.group .. " " .. gi .. " does not have same number of values as " .. keywords.group .. " 1" ) end
end
if #xlegends ~= numValues then error( 'Illegal number of ' .. keywords.xlegend .. '. Should be exactly ' .. numValues ) end
end


local function extractParams()
local function testone( keyword, key, val, tab )
local i = keyword == key and 0 or key:match( keyword .. "%s+(%d+)" )
if not i then return end
i = tonumber( i ) or error("Expect numerical index for key " .. keyword .. " instead of '" .. key .. "'")
if i > 0 then tab[i] = {} end
for s in mw.text.gsplit( val, '%s*' .. delimiter .. '%s*' ) do
table.insert( i == 0 and tab or tab[i], s )
end
return true
end


        chartHeight = height - 80
for k, v in pairs( args ) do
        numGroups = #values
if k == keywords.width then
        numValues = #values[1]
width = tonumber( v )
        defcolor = defcolor or 'blue'
if not width or width < 200 then
        colors[1] = colors[1] or defcolor
error( 'Illegal width value (must be a number, and at least 200): ' .. v )
        scaleWidth = scalePerGroup and 80 * numGroups or 100
end
        chartWidth = width -scaleWidth
elseif k == keywords.height then
        asGroups( 'unitsPrefix', unitsPrefix, true, true )
height = tonumber( v )
        asGroups( 'unitsSuffix', unitsSuffix, true, true )
if not height or height < 200 then
        asGroups( 'colors', colors, true, true )
error( 'Illegal height value (must be a number, and at least 200): ' .. v )
        asGroups( 'groupNames', groupNames, false, false )
end
        if stack and scalePerGroup then
elseif k == keywords.stack then stack = true
            error( string.format( 'Illegal settings: %s and %s are incompatible.', keyword.stack, keyword.scalePerGroup ) )
elseif k == keywords.yticks then yticks = tonumber(v) or -1
        end
elseif k == keywords.scalePerGroup then scalePerGroup = true
        for gi = 2, numGroups do
elseif k == keywords.defcolor then defcolor = v
            if #values[gi] ~= numValues then error( keywords.group .. " " .. gi .. " does not have same number of values as " .. keywords.group .. " 1" ) end
elseif k == keywords.accumulateTooltip then accumulateTooltip = not nulOrWhitespace( v )
        end
elseif k == keywords.hideGroupLegends then hideGroupLegends = not nulOrWhitespace( v )
        if #xlegends ~= numValues then error( 'Illegal number of ' .. keywords.xlegend .. '. Should be exatly ' .. numValues ) end
else
    end
for keyword, tab in pairs( {
group = values,
xlegend = xlegends,
colors = colors,
tooltip = tooltips,
unitsPrefix = unitsPrefix,
unitsSuffix = unitsSuffix,
groupNames = groupNames,
links = links,
} ) do
if testone( keywords[keyword], k, v, tab )
then break
end
end
end
end
end


    function extractParams()
local function roundup( x ) -- returns the next round number: eg., for 30 to 39.999 will return 40, for 3000 to 3999.99 wil return 4000. for 10 - 14.999 will return 15.
        function testone( keyword, key, val, tab )
local ordermag = 10 ^ math.floor( math.log10( x ) )
            i = keyword == key and 0 or key:match( keyword .. "%s+(%d+)" )
local normalized = x /  ordermag
            if not i then return end
local top = normalized >= 1.5 and ( math.floor( normalized + 1 ) ) or 1.5
            i = tonumber( i ) or error("Expect numerical index for key " .. keyword .. " instead of '" .. key .. "'")
return ordermag * top, top, ordermag
            if i > 0 then tab[i] = {} end
end
            for s in mw.text.gsplit( val, '%s*' .. delimiter .. '%s*' ) do
                table.insert( i == 0 and tab or tab[i], s )
            end
            return true
        end


        for k, v in pairs( args ) do
local function calcHeightLimits() -- if limits were passed by user, use them, otherwise calculate. for "stack" there's only one limet.
            if k == keywords.width then
if stack then
                width = tonumber( v )
local sums = {}
                if not width or width < 200 then
for _, group in pairs( values ) do
                    error( 'Illegal width value (must be a number, and at least 200): ' .. v )
for i, val in ipairs( group ) do sums[i] = ( sums[i] or 0 ) + val end
                end
end
            elseif k == keywords.height then
local sum = math.max( unpack( sums ) )
                height = tonumber( v )
for i = 1, #values do yscales[i] = sum end
                if not height or height < 200 then
else
                    error( 'Illegal height value (must be a number, and at least 200): ' .. v )
for i, group in ipairs( values ) do yscales[i] = math.max( unpack( group ) ) end
                end
end
            elseif k == keywords.stack then stack = true
for i, scale in ipairs( yscales ) do yscales[i] = roundup( scale * 0.9999 ) end
            elseif k == keywords.scalePerGroup then scalePerGroup = true
if not scalePerGroup then for i = 1, #values do yscales[i] = math.max( unpack( yscales ) ) end end
            elseif k == keywords.defcolor then defcolor = v
end
            elseif k == keywords.accumulateTooltip then accumulateTooltip = not nulOrWhitespace( v )
            elseif k == keywords.hideGroupLegends then hideGroupLegends = not nulOrWhitespace( v )
            else
                for keyword, tab in pairs( {
                    group = values,
                    xlegend = xlegends,
                    colors = colors,
                    tooltip = tooltips,
                    unitsPrefix = unitsPrefix,
                    unitsSuffix = unitsSuffix,
                    groupNames = groupNames,
                    links = links,
                    } ) do
                        if testone( keywords[keyword], k, v, tab )
                            then break
                        end
                end
            end
        end
    end


    function roundup( x ) -- returns the next round number: eg., for 30 to 39.999 will return 40, for 3000 to 3999.99 wil return 4000. for 10 - 14.999 will return 15.
local function tooltip( gi, i, val )
        local ordermag = 10 ^ math.floor( math.log10( x ) )
if tooltips and tooltips[gi] and not nulOrWhitespace( tooltips[gi][i] ) then return tooltips[gi][i], true end
        local normalized = x /  ordermag
local groupName = mw.text.killMarkers(not nulOrWhitespace( groupNames[gi] ) and groupNames[gi] .. ': ' or '')
        local top = normalized >= 1.5 and ( math.floor( normalized + 1 ) ) or 1.5
local prefix = unitsPrefix[gi] or unitsPrefix[1] or ''
        return ordermag * top, top, ordermag
local suffix = unitsSuffix[gi] or unitsSuffix[1] or ''
    end
return string.gsub(groupName .. prefix .. mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) .. suffix, '_', ' '), false
end


    function calcHeightLimits() -- if limits were passed by user, use them, otherwise calculate. for "stack" there's only one limet.
local function calcHeights( gi, i, val )
        if stack then
local barHeight = math.max( 2, math.floor( val / yscales[gi] * chartHeight + 0.5 ) ) -- add half to make it "round" instead of "trunc", min height to 2 to avoid negative bar sizes
            local sums = {}
local top, base = chartHeight - barHeight, 0
            for _, group in pairs( values ) do
if stack then
                for i, val in ipairs( group ) do sums[i] = ( sums[i] or 0 ) + val end
for j = 1, gi - 1 do
            end
if tonumber(values[j][i]) > 0 then
            local sum = math.max( unpack( sums ) )
base = base + math.max( 2, math.floor( values[j][i] / yscales[gi] * chartHeight + 0.5 ) ) -- sum the "i" value of all the groups below our group, gi, and keep the same calculation for each bar
            for i = 1, #values do yscales[i] = sum end
end
        else
end
            for i, group in ipairs( values ) do yscales[i] = math.max( unpack( group ) ) end
end
        end
return barHeight, top - base
        for i, scale in ipairs( yscales ) do yscales[i] = roundup( scale * 0.9999 ) end
end
        if not scalePerGroup then for i = 1, #values do yscales[i] = math.max( unpack( yscales ) ) end end
    end


    function tooltip( gi, i, val )
local function groupBounds( i )
        if tooltips and tooltips[gi] and not nulOrWhitespace( tooltips[gi][i] ) then return tooltips[gi][i], true end
local setWidth = math.floor( chartWidth / numValues )
        local groupName = not nulOrWhitespace( groupNames[gi] ) and groupNames[gi] .. ': ' or ''
local setOffset = ( i - 1 ) * setWidth
        local prefix = unitsPrefix[gi] or unitsPrefix[1] or ''
return setOffset, setWidth
        local suffix = unitsSuffix[gi] or unitsSuffix[1] or ''
end
        return mw.ustring.gsub(groupName .. prefix .. mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) .. suffix, '_', ' '), false
    end


    function calcHeights( gi, i, val )
local function calcx( gi, i )
        local barHeight = math.floor( val / yscales[gi] * chartHeight + 0.5 ) -- add half to make it "round" insstead of "trunc"
local setOffset, setWidth = groupBounds( i )
        local top, base = chartHeight - barHeight, 0
if stack or numGroups == 1 then
        if stack then
local barWidth = math.min( 38, math.floor( 0.8 * setWidth ) )
            local rawbase = 0
return setOffset + (setWidth - barWidth) / 2, barWidth
            for j = 1, gi - 1 do rawbase = rawbase + values[j][i] end -- sum the "i" value of all the groups below our group, gi.
end
            base = math.floor( chartHeight * rawbase / yscales[gi] ) -- normally, and especially if it's "stack", all the yscales must be equal.
setWidth = 0.85 * setWidth
        end
local barWidth = math.floor( 0.75 * setWidth / numGroups )
        return barHeight, top - base
local left = setOffset + math.floor( ( gi - 1 ) / numGroups * setWidth )
    end
return left, barWidth
end


    function groupBounds( i )
local function drawbar( gi, i, val, ttval )
        local setWidth = math.floor( chartWidth / numValues )
if val == '0' then return end -- do not show single line (borders....) if value is 0, or rather, '0'. see talkpage
        local setOffset = ( i - 1 ) * setWidth
        return setOffset, setWidth
    end


    function calcx( gi, i )
local color, tooltip, custom = colors[gi] or defcolor or 'blue', tooltip( gi, i, ttval or val )
        local setOffset, setWidth = groupBounds( i )
local left, barWidth = calcx( gi, i )
        if stack or numGroups == 1 then
local barHeight, top = calcHeights( gi, i, val )
            local barWidth = math.min( 38, math.floor( 0.8 * setWidth ) )
            return setOffset + (setWidth - barWidth) / 2, barWidth
        end
        setWidth = 0.85 * setWidth
        local barWidth = math.floor( 0.75 * setWidth / numGroups )
        local left = setOffset + math.floor( ( gi - 1 ) / numGroups * setWidth )
        return left, barWidth
    end


    function drawbar( gi, i, val, ttval )
-- borders so it shows up when printing
        local color, tooltip, custom = colors[gi] or defcolor or 'blue', tooltip( gi, i, ttval or val )
local style = string.format("position:absolute;left:%spx;top:%spx;height:%spx;min-width:%spx;max-width:%spx;background-color:%s;-webkit-print-color-adjust:exact;border:1px solid %s;border-bottom:none;overflow:hidden;",
        local left, barWidth = calcx( gi, i )
left, top, barHeight-1, barWidth-2, barWidth-2, color, color)
        local barHeight, top = calcHeights( gi, i, val )
local link = links[gi] and links[gi][i] or ''
        local style = string.format("position:absolute;left:%spx;top:%spx;height:%spx;min-width:%spx;max-width:%spx;background-color:%s;box-shadow:2px -1px 4px 0 silver;overflow:hidden;",
local img = not nulOrWhitespace( link ) and string.format( '[[File:Transparent.png|1000px|link=%s|%s]]', link, custom and tooltip or '' ) or ''
                        left, top, barHeight, barWidth, barWidth, color)
table.insert( res, mw.text.tag( 'div', { style = style, title = tooltip, }, img ) )
        local link = links[gi] and links[gi][i] or ''
end
        local img = not nulOrWhitespace( link ) and mw.ustring.format( '[[File:Transparent.png|1000px|link=%s|%s]]', link, custom and tooltip or '' ) or ''
        table.insert( res, mw.text.tag( 'div', { style = style, title = tooltip, }, img ) )
    end




    function drawYScale()
local function drawYScale()
        function drawSingle( gi, color, width, single )
local function drawSingle( gi, color, width, yticks, single )
            local yscale = yscales[gi]
local yscale = yscales[gi]
            local _, top, ordermag = roundup( yscale * 0.999 )
local _, top, ordermag = roundup( yscale * 0.999 )
            local numnotches = top <= 1.5 and top * 4
local numnotches = yticks >= 0 and yticks or
                    or top < 4  and top * 2
(top <= 1.5 and top * 4
                    or top
or top < 4  and top * 2
            local valStyleStr =
or top)
                single and 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;padding:0 2px'
local valStyleStr =
                or 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;left:3px;background-color:%s;color:white;font-weight:bold;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;padding:0 2px'
single and 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;padding:0 2px'
            local notchStyleStr = 'position:absolute;height=1px;min-width:5px;top:%spx;left:%spx;border:1px solid %s;'
or 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;left:3px;background-color:%s;color:white;font-weight:bold;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;padding:0 2px'
            for i = 1, numnotches do
local notchStyleStr = 'position:absolute;height=1px;min-width:5px;top:%spx;left:%spx;border:1px solid %s;'
                local val = i / numnotches * yscale
for i = 1, numnotches do
                local y = chartHeight - calcHeights( gi, 1, val )
local val = i / numnotches * yscale
                local div = mw.text.tag( 'div', { style = string.format( valStyleStr, width - 10, y - 10, color ) }, mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) )
local y = chartHeight - calcHeights( gi, 1, val )
                table.insert( res, div )
local div = mw.text.tag( 'div', { style = string.format( valStyleStr, width - 10, y - 10, color ) }, mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) )
                div = mw.text.tag( 'div', { style = string.format( notchStyleStr, y, width - 4, color ) }, '' )
table.insert( res, div )
                table.insert( res, div )
div = mw.text.tag( 'div', { style = string.format( notchStyleStr, y, width - 4, color ) }, '' )
            end
table.insert( res, div )
        end
end
end


        if scalePerGroup then
if scalePerGroup then
            local colWidth = 80
local colWidth = 80
            local colStyle = "position:absolute;height:%spx;min-width:%spx;left:%spx;border-right:1px solid %s;color:%s"
local colStyle = "position:absolute;height:%spx;min-width:%spx;left:%spx;border-right:1px solid %s;color:%s"
            for gi = 1, numGroups do
for gi = 1, numGroups do
                local left = ( gi - 1 ) * colWidth
local left = ( gi - 1 ) * colWidth
                local color = colors[gi] or defcolor
local color = colors[gi] or defcolor
                table.insert( res, mw.text.tag( 'div', { style = string.format( colStyle, chartHeight, colWidth, left, color, color ) } ) )
table.insert( res, mw.text.tag( 'div', { style = string.format( colStyle, chartHeight, colWidth, left, color, color ) } ) )
                drawSingle( gi, color, colWidth )
drawSingle( gi, color, colWidth, yticks )
                table.insert( res, '</div>' )
table.insert( res, '</div>' )
            end
end
        else
else
            drawSingle( 1, 'black', scaleWidth, true )
drawSingle( 1, 'black', scaleWidth, yticks, true )
        end
end
    end
end


    function drawXlegends()
local function drawXlegends()
        local setOffset, setWidth
local setOffset, setWidth
        local legendDivStyleFormat = "position:absolute;left:%spx;top:10px;min-width:%spx;max-width:%spx;text-align:center;veritical-align:top;"
local legendDivStyleFormat = "position:absolute;left:%spx;top:10px;min-width:%spx;max-width:%spx;text-align:center;vertical-align:top;"
        local tickDivstyleFormat = "position:absolute;left:%spx;height:10px;width:1px;border-left:1px solid black;"
local tickDivstyleFormat = "position:absolute;left:%spx;height:10px;width:1px;border-left:1px solid black;"
        for i = 1, numValues do
for i = 1, numValues do
            if not nulOrWhitespace( xlegends[i] ) then
if not nulOrWhitespace( xlegends[i] ) then
                setOffset, setWidth = groupBounds( i )
setOffset, setWidth = groupBounds( i )
                -- setWidth = 0.85 * setWidth
-- setWidth = 0.85 * setWidth
                table.insert( res, mw.text.tag( 'div', { style = string.format( legendDivStyleFormat, setOffset + 5, setWidth - 10, setWidth - 10 ) }, xlegends[i] or '' ) )
table.insert( res, mw.text.tag( 'div', { style = string.format( legendDivStyleFormat, setOffset + 1, setWidth - 2, setWidth - 2 ) }, xlegends[i] or '' ) )
                table.insert( res, mw.text.tag( 'div', { style = string.format( tickDivstyleFormat, setOffset + setWidth / 2 ) }, '' ) )
table.insert( res, mw.text.tag( 'div', { style = string.format( tickDivstyleFormat, setOffset + setWidth / 2 ) }, '' ) )
            end
end
        end
end
    end
end


    function drawChart()
local function drawChart()
        table.insert( res, mw.text.tag( 'div', { style = string.format( 'max-width:%spx;', width ) } ) )
table.insert( res, mw.text.tag( 'div', { class = 'chart noresize', style = string.format( 'margin-top:1em;max-width:%spx;', width ) } ) )
        table.insert( res, mw.text.tag( 'div', { style = string.format("position:relative;min-height:%spx;min-width:%spx;max-width:%spx;", height, width, width ) } ) )
table.insert( res, mw.text.tag( 'div', { style = string.format("position:relative;min-height:%spx;min-width:%spx;max-width:%spx;", height, width, width ) } ) )


        table.insert( res, mw.text.tag( 'div', { style = string.format("float:right;position:relative;min-height:%spx;min-width:%spx;max-width:%spx;border-left:1px black solid;border-bottom:1px black solid;", chartHeight, chartWidth, chartWidth ) } ) )
table.insert( res, mw.text.tag( 'div', { style = string.format("float:right;position:relative;min-height:%spx;min-width:%spx;max-width:%spx;border-left:1px black solid;border-bottom:1px black solid;", chartHeight, chartWidth, chartWidth ) } ) )
        local acum = stack and accumulateTooltip and {}
local acum = stack and accumulateTooltip and {}
        for gi, group in pairs( values ) do
for gi, group in pairs( values ) do
            for i, val in ipairs( group ) do
for i, val in ipairs( group ) do
                if acum then acum[i] = ( acum[i] or 0 ) + val end
if acum then acum[i] = ( acum[i] or 0 ) + val end
                drawbar( gi, i, val, acum and acum[i] )
drawbar( gi, i, val, acum and acum[i] )
            end
end
        end
end
        table.insert( res, '</div>' )
table.insert( res, '</div>' )
        table.insert( res, mw.text.tag( 'div', { style = string.format("position:absolute;height:%spx;min-width:%spx;max-width:%spx;", chartHeight, scaleWidth, scaleWidth, scaleWidth ) } ) )
table.insert( res, mw.text.tag( 'div', { style = string.format("position:absolute;height:%spx;min-width:%spx;max-width:%spx;", chartHeight, scaleWidth, scaleWidth, scaleWidth ) } ) )
        drawYScale()
drawYScale()
        table.insert( res, '</div>' )
table.insert( res, '</div>' )
        table.insert( res, mw.text.tag( 'div', { style = string.format( "position:absolute;top:%spx;left:%spx;width:%spx;", chartHeight, scaleWidth, chartWidth ) } ) )
table.insert( res, mw.text.tag( 'div', { style = string.format( "position:absolute;top:%spx;left:%spx;width:%spx;", chartHeight, scaleWidth, chartWidth ) } ) )
        drawXlegends()
drawXlegends()
        table.insert( res, '</div>' )
table.insert( res, '</div>' )
        table.insert( res, '</div>' )
table.insert( res, '</div>' )
        createGroupList( res, groupNames, colors )
createGroupList( res, groupNames, colors )
        table.insert( res, '</div>' )
table.insert( res, '</div>' )
    end
end


    extractParams()
extractParams()
    validate()
validate()
    calcHeightLimits()
calcHeightLimits()
    drawChart()
drawChart()
    return table.concat( res, "\n" )
return table.concat( res, "\n" )
end
end


return {
return {
    ['bar-chart'] = barChart,
['bar-chart'] = barChart,
    [keywords.barChart] = barChart,
[keywords.barChart] = barChart,
    [keywords.pieChart] = pieChart,
[keywords.pieChart] = pieChart,
}
}
--</source>

2024년 4월 24일 (수) 16:15 판

모듈:Chart막대 도표원 도표를 표현합니다.

막대 도표

변수

변수 이름 설명
구분 값을 구분하는 기본 구분자는 콜론 ( : ) 입니다.
넓이 도표 넓이. 200 이상으로 기본은 500
높이 도표 높이. 200 이상으로 기본은 350
집합 n “n”은 구분하는 번호로 "집합 1", "집합 2" 와 같이 씁니다.
말풍선 n 말풍선은 막대에 표시됩니다. 말풍선이 없다면, “연결”을 말풍선으로 씁니다.
연결 n 막대와 관련된 특정한 연결 문서를 나타냅니다.
쌓기 여러 집합에서 쌓기를 하면 집합 항목을 서로 위에 쌓습니다. 값이 있으면 참이며, 값을 비워두면 쌓지 않습니다.
말풍선 계 “쌓기”일 경우에 쓰는 값으로 참일 경우 블록의 합계값을 말풍선으로 보여줍니다.
각 집합에 쓰는 색(웹 색상)을 정의합니다. 모듈:Plotter/DefaultColors에 26개가 정의되어 있습니다.
x 범례 X 값에 대한 범례를 보입니다.
집합 범례 숨김 참일 경우 집합 범례를 차트 아래 보여줍니다.
집합 축척 집합에 각 Y 비율을 정의할 수 있고, 비워두면 같이 씁니다.
머리 단위 말풍선 머리 단위 입니다. $일 경우 “$500”과 같이 나타납니다.
꼬리 단위 말풍선 꼬리 단위로 Kg일 경우 “88Kg”으로 보입니다.
집합 이름 각 집합 이름입니다.

보기

기본

스크립트 오류: 함수 "막대 도표"가 존재하지 않습니다.
{{ #invoke:Chart | 막대 도표
| 집합 1 = 40 : 50 : 60 : 20
| 집합 2 = 20 : 60 : 12 : 44
| 집합 3 = 55 : 14 : 33 : 5
| 연결 1 = 사과 : 매킨토시 : 골든딜리셔스
| 연결 2 = 바나나 : 살구 : 복숭아
| 연결 3 = 오렌지 : 배 : 곰
| 말풍선 2 = 말풍선 1 : 말풍선 2 : 말풍선 3 : 말풍선 4
| 색 = green : yellow : orange
| 집합 이름 = 사과 : 바나나 : 오렌지
| x 범례 = 전기 : 중기 : 말기 : 후기
}}

쌓기

스크립트 오류: 함수 "막대 도표"가 존재하지 않습니다.
{{ #invoke:Chart | 막대 도표
| 높이 = 250
| 넓이 = 300
| 쌓기 = 1
| 집합 1 = 40 : 50 : 60 : 20
| 집합 2 = 20 : 60 : 12 : 44
| 집합 3 = 55 : 14 : 33 : 5
| 색 = green : yellow : orange
| 집합 이름 = 사과 : 바나나 : 오렌지
| 꼬리 단위 = Kg
| x 범례 = 전기 : 중기 : 말기 : 후기
}}

집합 축척

{{ #invoke:Chart | 막대 도표
| 넓이 = 800
| 집합 1 = 1500000 : 2500000 : 3500000
| 집합 2 = 200 : 5000 : 45000
| 집합 3 = 2000 : 5000 : 20000
| 색 = red : blue : green
| 집합 이름 = 사람 : 차 : 평균 차량가격
| x 범례 = 1920 : 1965 : 2002
| 말풍선 2 = : 1965년에는 자료가 충분치않아 평균 5,000을 씁니다.
| 머리 단위 = : : $
| 집합 축척 = 1
}}

스크립트 오류: 함수 "막대 도표"가 존재하지 않습니다.

여러 집합에서 쌓기

{{ #invoke:Chart | 막대 도표
| 넓이 = 800
| 높이 = 550
| 집합 1 = 1:2:3:4:5:4:3:2:1
| 집합 2 = 1:2:3:4:5:4:3:2:1
| 집합 3 = 1:2:3:4:5:4:3:2:1
| 집합 4 = 1:2:3:4:5:4:3:2:1
| 집합 5 = 1:2:3:4:5:4:3:2:1
| 집합 6 = 1:2:3:4:5:4:3:2:1
| 집합 7 = 1:2:3:4:5:4:3:2:1
| 집합 8 = 1:2:3:4:5:4:3:2:1
| 집합 9 = 1:2:3:4:5:4:3:2:1
| 집합 10 = 1:2:3:4:5:4:3:2:1
| 집합 11 = 1:2:3:4:5:4:3:2:1
| 집합 12 = 1:2:3:4:5:4:3:2:1
| 집합 13 = 1:2:3:4:5:4:3:2:1
| 집합 14 = 1:2:3:4:5:4:3:2:1
| 집합 15 = 1:2:3:4:5:4:3:2:1
| 집합 16 = 1:2:3:4:5:4:3:2:1
| 집합 17 = 1:2:3:4:5:4:3:2:1
| 집합 18 = 1:2:3:4:5:4:3:2:1
| 집합 19 = 1:2:3:4:5:4:3:2:1
| 집합 20 = 1:2:3:4:5:4:3:2:1
| 집합 21 = 1:2:3:4:5:4:3:2:1
| 색 = Silver:Gray:Black:Red:Maroon:Yellow:Olive:Lime:Green:Aqua:Teal:Blue:Navy:Fuchsia:Purple:ForestGreen:Tomato:LightSeaGreen:RosyBrown:DarkOliveGreen:MediumVioletRed
| 집합 이름 = 강원도 : 경기도 : 경상남도 : 경상북도 : 남포 : 량강도 : 부산 : 서울 : 자강도 : 전라남도 : 전라북도 : 제주특별자치도 : 충청남도 : 충청북도 : 평안남도 : 평안북도 : 평양 : 함경남도 : 함경북도 : 황해남도 : 황해북도
| x 범례 = 1920 : 1930 : 1940: 1950 : 1960 : 1970 : 1990 : 2000 : 2010
| 머리 단위 = 일금
| 꼬리 단위 = 백만원
| 쌓기 = 1
}}

스크립트 오류: 함수 "막대 도표"가 존재하지 않습니다.

단일 도표

{{ #invoke:Chart | 막대 도표
| 집합 1 = 1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20:21:22:23:24:25:26:27:28:29:30
:31:32:33:34:35:36:37:38:39:40:41:42:43:44:45:46:47:48:49:50:51:52:53:54:55:56:57:58:59
| 꼬리 단위 = 것
| 집합 이름 = 어떤
| x 범례 = ::::1940::::::::::1950::::::::::1960::::::::::1970::::::::::1980::::::::::1990::::
}}

스크립트 오류: 함수 "막대 도표"가 존재하지 않습니다.

원 도표

변수

변수 이름 설명
구분 값을 구분하는 기본 구분자는 콜론 ( : ) 입니다.
반지름 원 반지름으로 픽셀 단위입니다.
조각합 ( 값1 : 이름1 : 색1 : 연결1 ) ( 값2 : 이름2 : 색2 : 연결2 ) ...등의 튜플 로 나타냅니다.
조각 n 조각 1, 조각 2, 따위로 씁니다.
 | 조각 1 = 값1 : 이름1 : 색1 : 연결1
 | 조각 2 = 값2 : 이름2 : 색2 : 연결2
 | ...
백분율 값이 있으면 참이고, 쓰기 싫으면 비워두면 됩니다.
머리 단위 말풍선 머리 단위 입니다. $일 경우 “$500”과 같이 나타납니다.
꼬리 단위 말풍선 꼬리 단위로 Kg일 경우 “88Kg”으로 보입니다.
집합 범례 숨김 참일 경우 집합 범례를 차트 아래 보여줍니다.

보기

스크립트 오류: 함수 "원 도표"가 존재하지 않습니다.
{{#invoke:Chart|원 도표
| 반지름 = 150
| 조각합 = 
    ( 1000000 : 사과 ) 
    ( 2000000 : 바나나  : gold) 
    ( 1440000 : 살구 ) 
    ( 6.4e5 : 배 )
    ( 750,000 : 파인애플 )
| 꼬리 단위 = 톤
| 백분율 = 예
}}
스크립트 오류: 함수 "원 도표"가 존재하지 않습니다.
{{#invoke:Chart|원 도표
|반지름= 200
|꼬리 단위 = _단위
| 조각 1 = 1 : 1
| 조각 2 = 7 : 7
| 조각 3 = 8 : 8
| 조각 4 = 9 : 9
| 조각 5 = 10 : 10
| 조각 6 = 11 : 11
| 조각 7  = 12 : 12
| 조각 8  = 13 : 13
| 조각 9  = 14 : 14
| 조각 10 = 15 : 15
| 조각 11 = 16 : 16
| 조각 12 = 17 : 17
| 조각 13 = 18 : 18
| 조각 14 = 19 : 19
| 조각 15 = 20 : 20
| 조각 16 = 21 : 21
| 조각 17 = 22 : 22
| 조각 18 = 23 : 23
| 조각 19 = 24 : 24
| 조각 20 = 25 : 25
| 조각 21 = 26 : 26
| 조각 22 = 27 : 27
| 조각 23 = 28 : 28
| 조각 24 = 29 : 29
| 조각 25 = 30 : 30
| 조각 26 = 31 : 31
| 백분율 = 예
}}

같이 보기

{{원 도표}}


--[[
	keywords are used for languages: they are the names of the actual
	parameters of the template
]]

local keywords = {
	barChart = 'bar chart',
	pieChart = 'pie chart',
	width = 'width',
	height = 'height',
	stack = 'stack',
	colors = 'colors',
	group = 'group',
	xlegend = 'x legends',
	yticks = 'y tick marks',
	tooltip = 'tooltip',
	accumulateTooltip = 'tooltip value accumulation',
	links = 'links',
	defcolor = 'default color',
	scalePerGroup = 'scale per group',
	unitsPrefix = 'units prefix',
	unitsSuffix = 'units suffix',
	groupNames = 'group names',
	hideGroupLegends = 'hide group legends',
	slices = 'slices',
	slice = 'slice',
	radius = 'radius',
	percent = 'percent',

} -- here is what you want to translate

local defColors = mw.loadData("Module:Chart/Default colors")
local hideGroupLegends

local function nulOrWhitespace( s )
	return not s or mw.text.trim( s ) == ''
end

local function createGroupList( tab, legends, cols )
	if #legends > 1 and not hideGroupLegends then
		table.insert( tab, mw.text.tag( 'div' ) )
		local list = {}
		local spanStyle = "padding:0 1em;background-color:%s;border:1px solid %s;margin-right:1em;-webkit-print-color-adjust:exact;"
		for gi = 1, #legends do
			local span = mw.text.tag( 'span', { style = string.format( spanStyle, cols[gi], cols[gi] ) }, '&nbsp;' ) .. ' '..  legends[gi]
			table.insert( list, mw.text.tag( 'li', {}, span ) )
		end
		table.insert( tab,
			mw.text.tag( 'ul',
				{style="width:100%;list-style:none;column-width:12em;"},
				table.concat( list, '\n' )
			)
		)
		table.insert( tab, '</div>' )
	end
end

local function pieChart( frame )
	local res, imslices, args = {}, {}, frame.args
	local radius
	local values, colors, names, legends, links = {}, {}, {}, {}, {}
	local delimiter = args.delimiter or ':'
	local lang = mw.getContentLanguage()

	local function getArg( s, def, subst, with )
		local result = args[keywords[s]] or def or ''
		if subst and with then result = string.gsub( result, subst, with ) end
		return result
	end

	local function analyzeParams()
		local function addSlice( i, slice )
			local value, name, color, link = unpack( mw.text.split( slice, '%s*' .. delimiter .. '%s*' ) )
			values[i] = tonumber( lang:parseFormattedNumber( value ) )
				or error( string.format( 'Slice %d: "%s", first item("%s") could not be parsed as a number', i, value or '', slice ) )
			colors[i] = not nulOrWhitespace( color ) and color or defColors[i * 2]
			names[i] = name or ''
			links[i] = link
		end

		radius = getArg( 'radius', 150 )
		hideGroupLegends = not nulOrWhitespace( args[keywords.hideGroupLegends] )
		local slicesStr = getArg( 'slices' )
		local prefix = getArg( 'unitsPrefix', '', '_', ' ' )
		local suffix = getArg( 'unitsSuffix', '', '_', ' ' )
		local percent = args[keywords.percent]
		local sum = 0
		local i = 0
		for slice in string.gmatch( slicesStr or '', "%b()" ) do
			i = i + 1
			addSlice( i, string.match( slice, '^%(%s*(.-)%s*%)$' ) )
		end

		for k, v in pairs(args) do
			local ind = string.match( k, '^' .. keywords.slice .. '%s+(%d+)$' )
			if ind then addSlice( tonumber( ind ), v ) end
		end

		for _, val in ipairs( values ) do sum = sum + val end
		for i, value in ipairs( values ) do
			local addprec = percent and string.format( ' (%0.1f%%)', value / sum * 100 ) or ''
			legends[i] = string.format( '%s: %s%s%s%s', names[i], prefix, lang:formatNum( value ), suffix, addprec )
			links[i] = mw.text.trim( links[i] or string.format( '[[#noSuchAnchor|%s]]', legends[i] ) )
		end
	end

	local function addRes( ... )
		for _, v in pairs( { ... } ) do
			table.insert( res, v )
		end
	end

	local function createImageMap()
		addRes( '{{#tag:imagemap|', 'File:Circle frame.svg{{!}}' .. ( radius * 2 ) .. 'px' )
		addRes( unpack( imslices ) )
		addRes( 'desc none', '}}' )
	end

	local function drawSlice( i, q, start )
		local color = colors[i]
		local angle = start * 2 * math.pi
		local sin, cos = math.abs( math.sin( angle ) ), math.abs( math.cos( angle ) )
		local wsin, wcos = sin * radius, cos * radius
		local s1, s2, w1, w2, w3, w4, border
		if q == 1 then
			border = 'left'
			w1, w2, w3, w4 = 0, 0, wsin, wcos
			s1, s2 = 'bottom', 'left'
		elseif q == 2 then
			border = 'bottom'
			w1, w2, w3, w4 = 0, wcos, wsin, 0
			s1, s2 = 'bottom', 'right'
		elseif q == 3 then
			border = 'right'
			w1, w2, w3, w4 = wsin, wcos, 0, 0
			s1, s2 = 'top', 'right'
		else
			border = 'top'
			w1, w2, w3, w4 = wsin, 0, 0, wcos
			s1, s2 = 'top', 'left'
		end

		local style = string.format( 'border:solid transparent;position:absolute;%s:%spx;%s:%spx;width:%spx;height:%spx', s1, radius, s2, radius, radius, radius )
		if start <= ( q - 1 ) * 0.25 then
			style = string.format( '%s;border:0;background-color:%s', style, color )
		else
			style = string.format( '%s;border-width:%spx %spx %spx %spx;border-%s-color:%s', style, w1, w2, w3, w4, border, color )
		end
		addRes( mw.text.tag( 'div', { style = style }, '' ) )
	end

	local function createSlices()
		local function coordsOfAngle( angle )
			return ( 100 + math.floor( 100 * math.cos( angle ) ) ) .. ' ' .. ( 100 - math.floor( 100 * math.sin( angle ) ) )
		end

		local sum, start = 0, 0
		for _, value in ipairs( values ) do sum = sum + value end
		for i, value in ipairs(values) do
			local poly = { 'poly 100 100' }
			local startC, endC =  start / sum, ( start + value ) / sum
			local startQ, endQ = math.floor( startC * 4 + 1 ), math.floor( endC * 4 + 1 )
			for q = startQ, math.min( endQ, 4 ) do drawSlice( i, q, startC ) end
			for angle = startC * 2 * math.pi, endC * 2 * math.pi, 0.02 do
				table.insert( poly,  coordsOfAngle( angle ) )
			end
			table.insert( poly, coordsOfAngle( endC * 2 * math.pi ) .. ' 100 100 ' .. links[i] )
			table.insert( imslices, table.concat( poly, ' ' ) )
			start = start + values[i]
		end
	end

	analyzeParams()
	if #values == 0 then error( "no slices found - can't draw pie chart" ) end
	addRes( mw.text.tag( 'div', { class = 'chart noresize', style = string.format( 'margin-top:0.5em;max-width:%spx;', radius * 2 ) } ) )
	addRes( mw.text.tag( 'div', { style = string.format( 'position:relative;min-width:%spx;min-height:%spx;max-width:%spx;overflow:hidden;', radius * 2, radius * 2, radius * 2 ) } ) )
	createSlices()
	addRes( mw.text.tag( 'div', { style = string.format( 'position:absolute;min-width:%spx;min-height:%spx;overflow:hidden;', radius * 2, radius * 2 ) } ) )
	createImageMap()
	addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
	addRes( '</div>' ) -- close "position:relative" div that contains slices and imagemap.
	createGroupList( res, legends, colors ) -- legends
	addRes( '</div>' ) -- close containing div
	return frame:preprocess( table.concat( res, '\n' ) )
end


local function barChart( frame )
	local res = {}
	local args = frame.args -- can be changed to frame:getParent().args
	local values, xlegends, colors, tooltips, yscales = {}, {}, {}, {}, {}
	local groupNames, unitsSuffix, unitsPrefix, links = {}, {}, {}, {}
	local width, height, yticks, stack, delimiter = 500, 350, -1, false, args.delimiter or ':'
	local chartWidth, chartHeight, defcolor, scalePerGroup, accumulateTooltip


	local numGroups, numValues
	local scaleWidth

	local function validate()
		local function asGroups( name, tab, toDuplicate, emptyOK )
			if #tab == 0 and not emptyOK then
				error( "must supply values for " .. keywords[name] )
			end
			if #tab == 1 and toDuplicate then
				for i = 2, numGroups do tab[i] = tab[1] end
			end
			if #tab > 0 and #tab ~= numGroups then
				error ( keywords[name] .. ' must contain the same number of items as the number of groups, but it contains ' .. #tab .. ' items and there are ' .. numGroups .. ' groups')
			end
		end

		-- do all sorts of validation here, so we can assume all params are good from now on.
		-- among other things, replace numerical values with mw.language:parseFormattedNumber() result


		chartHeight = height - 80
		numGroups = #values
		numValues = #values[1]
		defcolor = defcolor or 'blue'
		colors[1] = colors[1] or defcolor
		scaleWidth = scalePerGroup and 80 * numGroups or 100
		chartWidth = width - scaleWidth
		asGroups( 'unitsPrefix', unitsPrefix, true, true )
		asGroups( 'unitsSuffix', unitsSuffix, true, true )
		asGroups( 'colors', colors, true, true )
		asGroups( 'groupNames', groupNames, false, false )
		if stack and scalePerGroup then
			error( string.format( 'Illegal settings: %s and %s are incompatible.', keywords.stack, keywords.scalePerGroup ) )
		end
		for gi = 2, numGroups do
			if #values[gi] ~= numValues then error( keywords.group .. " " .. gi .. " does not have same number of values as " .. keywords.group .. " 1" ) end
		end
		if #xlegends ~= numValues then error( 'Illegal number of ' .. keywords.xlegend .. '. Should be exactly ' .. numValues ) end
	end

	local function extractParams()
		local function testone( keyword, key, val, tab )
			local i = keyword == key and 0 or key:match( keyword .. "%s+(%d+)" )
			if not i then return end
			i = tonumber( i ) or error("Expect numerical index for key " .. keyword .. " instead of '" .. key .. "'")
			if i > 0 then tab[i] = {} end
			for s in mw.text.gsplit( val, '%s*' .. delimiter .. '%s*' ) do
				table.insert( i == 0 and tab or tab[i], s )
			end
			return true
		end

		for k, v in pairs( args ) do
			if k == keywords.width then
				width = tonumber( v )
				if not width or width < 200 then
					error( 'Illegal width value (must be a number, and at least 200): ' .. v )
				end
			elseif k == keywords.height then
				height = tonumber( v )
				if not height or height < 200 then
					error( 'Illegal height value (must be a number, and at least 200): ' .. v )
				end
			elseif k == keywords.stack then stack = true
			elseif k == keywords.yticks then yticks = tonumber(v) or -1
			elseif k == keywords.scalePerGroup then scalePerGroup = true
			elseif k == keywords.defcolor then defcolor = v
			elseif k == keywords.accumulateTooltip then accumulateTooltip = not nulOrWhitespace( v )
			elseif k == keywords.hideGroupLegends then hideGroupLegends = not nulOrWhitespace( v )
			else
				for keyword, tab in pairs( {
					group = values,
					xlegend = xlegends,
					colors = colors,
					tooltip = tooltips,
					unitsPrefix = unitsPrefix,
					unitsSuffix = unitsSuffix,
					groupNames = groupNames,
					links = links,
					} ) do
						if testone( keywords[keyword], k, v, tab )
							then break
						end
				end
			end
		end
	end

	local function roundup( x ) -- returns the next round number: eg., for 30 to 39.999 will return 40, for 3000 to 3999.99 wil return 4000. for 10 - 14.999 will return 15.
		local ordermag = 10 ^ math.floor( math.log10( x ) )
		local normalized = x /  ordermag
		local top = normalized >= 1.5 and ( math.floor( normalized + 1 ) ) or 1.5
		return ordermag * top, top, ordermag
	end

	local function calcHeightLimits() -- if limits were passed by user, use them, otherwise calculate. for "stack" there's only one limet.
		if stack then
			local sums = {}
			for _, group in pairs( values ) do
				for i, val in ipairs( group ) do sums[i] = ( sums[i] or 0 ) + val end
			end
			local sum = math.max( unpack( sums ) )
			for i = 1, #values do yscales[i] = sum end
		else
			for i, group in ipairs( values ) do yscales[i] = math.max( unpack( group ) ) end
		end
		for i, scale in ipairs( yscales ) do yscales[i] = roundup( scale * 0.9999 ) end
		if not scalePerGroup then for i = 1, #values do yscales[i] = math.max( unpack( yscales ) ) end end
	end

	local function tooltip( gi, i, val )
		if tooltips and tooltips[gi] and not nulOrWhitespace( tooltips[gi][i] ) then return tooltips[gi][i], true end
		local groupName = mw.text.killMarkers(not nulOrWhitespace( groupNames[gi] ) and groupNames[gi] .. ': ' or '')
		local prefix = unitsPrefix[gi] or unitsPrefix[1] or ''
		local suffix = unitsSuffix[gi] or unitsSuffix[1] or ''
		return string.gsub(groupName .. prefix .. mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) .. suffix, '_', ' '), false
	end

	local function calcHeights( gi, i, val )
		local barHeight = math.max( 2, math.floor( val / yscales[gi] * chartHeight + 0.5 ) ) -- add half to make it "round" instead of "trunc", min height to 2 to avoid negative bar sizes
		local top, base = chartHeight - barHeight, 0
		if stack then
			for j = 1, gi - 1 do
				if tonumber(values[j][i]) > 0 then
					base = base + math.max( 2, math.floor( values[j][i] / yscales[gi] * chartHeight + 0.5 ) ) -- sum the "i" value of all the groups below our group, gi, and keep the same calculation for each bar 
				end
			end
		end
		return barHeight, top - base
	end

	local function groupBounds( i )
		local setWidth = math.floor( chartWidth / numValues )
		local setOffset = ( i - 1 ) * setWidth
		return setOffset, setWidth
	end

	local function calcx( gi, i )
		local setOffset, setWidth = groupBounds( i )
		if stack or numGroups == 1 then
			local barWidth = math.min( 38, math.floor( 0.8 * setWidth ) )
			return setOffset + (setWidth - barWidth) / 2, barWidth
		end
		setWidth = 0.85 * setWidth
		local barWidth = math.floor( 0.75 * setWidth / numGroups )
		local left = setOffset + math.floor( ( gi - 1 ) / numGroups * setWidth )
		return left, barWidth
	end

	local function drawbar( gi, i, val, ttval )
		if val == '0' then return end -- do not show single line (borders....) if value is 0, or rather, '0'. see talkpage

		local color, tooltip, custom = colors[gi] or defcolor or 'blue', tooltip( gi, i, ttval or val )
		local left, barWidth = calcx( gi, i )
		local barHeight, top = calcHeights( gi, i, val )

		-- borders so it shows up when printing
		local style = string.format("position:absolute;left:%spx;top:%spx;height:%spx;min-width:%spx;max-width:%spx;background-color:%s;-webkit-print-color-adjust:exact;border:1px solid %s;border-bottom:none;overflow:hidden;",
						left, top, barHeight-1, barWidth-2, barWidth-2, color, color)
		local link = links[gi] and links[gi][i] or ''
		local img = not nulOrWhitespace( link ) and string.format( '[[File:Transparent.png|1000px|link=%s|%s]]', link, custom and tooltip or '' ) or ''
		table.insert( res, mw.text.tag( 'div', { style = style, title = tooltip, }, img ) )
	end


	local function drawYScale()
		local function drawSingle( gi, color, width, yticks, single )
			local yscale = yscales[gi]
			local _, top, ordermag = roundup( yscale * 0.999 )
			local numnotches = yticks >= 0 and yticks or
					(top <= 1.5 and top * 4
					or top < 4  and top * 2
					or top)
			local valStyleStr =
				single and 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;padding:0 2px'
				or 'position:absolute;height=20px;text-align:right;vertical-align:middle;width:%spx;top:%spx;left:3px;background-color:%s;color:white;font-weight:bold;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;padding:0 2px'
			local notchStyleStr = 'position:absolute;height=1px;min-width:5px;top:%spx;left:%spx;border:1px solid %s;'
			for i = 1, numnotches do
				local val = i / numnotches * yscale
				local y = chartHeight - calcHeights( gi, 1, val )
				local div = mw.text.tag( 'div', { style = string.format( valStyleStr, width - 10, y - 10, color ) }, mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) )
				table.insert( res, div )
				div = mw.text.tag( 'div', { style = string.format( notchStyleStr, y, width - 4, color ) }, '' )
				table.insert( res, div )
			end
		end

		if scalePerGroup then
			local colWidth = 80
			local colStyle = "position:absolute;height:%spx;min-width:%spx;left:%spx;border-right:1px solid %s;color:%s"
			for gi = 1, numGroups do
				local left = ( gi - 1 ) * colWidth
				local color = colors[gi] or defcolor
				table.insert( res, mw.text.tag( 'div', { style = string.format( colStyle, chartHeight, colWidth, left, color, color ) } ) )
				drawSingle( gi, color, colWidth, yticks )
				table.insert( res, '</div>' )
			end
		else
			drawSingle( 1, 'black', scaleWidth, yticks, true )
		end
	end

	local function drawXlegends()
		local setOffset, setWidth
		local legendDivStyleFormat = "position:absolute;left:%spx;top:10px;min-width:%spx;max-width:%spx;text-align:center;vertical-align:top;"
		local tickDivstyleFormat = "position:absolute;left:%spx;height:10px;width:1px;border-left:1px solid black;"
		for i = 1, numValues do
			if not nulOrWhitespace( xlegends[i] ) then
				setOffset, setWidth = groupBounds( i )
				-- setWidth = 0.85 * setWidth
				table.insert( res, mw.text.tag( 'div', { style = string.format( legendDivStyleFormat, setOffset + 1, setWidth - 2, setWidth - 2 ) }, xlegends[i] or '' ) )
				table.insert( res, mw.text.tag( 'div', { style = string.format( tickDivstyleFormat, setOffset + setWidth / 2 ) }, '' ) )
			end
		end
	end

	local function drawChart()
		table.insert( res, mw.text.tag( 'div', { class = 'chart noresize', style = string.format( 'margin-top:1em;max-width:%spx;', width ) } ) )
		table.insert( res, mw.text.tag( 'div', { style = string.format("position:relative;min-height:%spx;min-width:%spx;max-width:%spx;", height, width, width ) } ) )

		table.insert( res, mw.text.tag( 'div', { style = string.format("float:right;position:relative;min-height:%spx;min-width:%spx;max-width:%spx;border-left:1px black solid;border-bottom:1px black solid;", chartHeight, chartWidth, chartWidth ) } ) )
		local acum = stack and accumulateTooltip and {}
		for gi, group in pairs( values ) do
			for i, val in ipairs( group ) do
				if acum then acum[i] = ( acum[i] or 0 ) + val end
				drawbar( gi, i, val, acum and acum[i] )
			end
		end
		table.insert( res, '</div>' )
		table.insert( res, mw.text.tag( 'div', { style = string.format("position:absolute;height:%spx;min-width:%spx;max-width:%spx;", chartHeight, scaleWidth, scaleWidth, scaleWidth ) } ) )
		drawYScale()
		table.insert( res, '</div>' )
		table.insert( res, mw.text.tag( 'div', { style = string.format( "position:absolute;top:%spx;left:%spx;width:%spx;", chartHeight, scaleWidth, chartWidth ) } ) )
		drawXlegends()
		table.insert( res, '</div>' )
		table.insert( res, '</div>' )
		createGroupList( res, groupNames, colors )
		table.insert( res, '</div>' )
	end

	extractParams()
	validate()
	calcHeightLimits()
	drawChart()
	return table.concat( res, "\n" )
end

return {
	['bar-chart'] = barChart,
	[keywords.barChart] = barChart,
	[keywords.pieChart] = pieChart,
}