모듈:Chart

위키백과, 우리 모두의 백과사전.
둘러보기로 가기 검색하러 가기
모듈 설명문서[보기] [편집] [역사] [새로 고침]

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

막대 도표[편집]

변수[편집]

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

보기[편집]

기본[편집]

Transparent.png
Transparent.png
Transparent.png
말풍선 1
말풍선 2
말풍선 3
Transparent.png
Transparent.png
Transparent.png
10
20
30
40
50
60
전기
중기
말기
후기
  •   사과
  •   바나나
  •   오렌지
{{ #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 범례 = 전기 : 중기 : 말기 : 후기
}}

쌓기[편집]

25
50
75
100
125
150
전기
중기
말기
후기
  •   사과
  •   바나나
  •   오렌지
{{ #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
}}
1,000,000
2,000,000
3,000,000
4,000,000
10,000
20,000
30,000
40,000
50,000
5,000
10,000
15,000
20,000
1920
1965
2002
  •   사람
  •  
  •   평균 차량가격

여러 집합에서 쌓기[편집]

{{ #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
}}
25
50
75
100
125
150
1920
1930
1940
1950
1960
1970
1990
2000
2010
  •   강원도
  •   경기도
  •   경상남도
  •   경상북도
  •   남포
  •   량강도
  •   부산
  •   서울
  •   자강도
  •   전라남도
  •   전라북도
  •   제주특별자치도
  •   충청남도
  •   충청북도
  •   평안남도
  •   평안북도
  •   평양
  •   함경남도
  •   함경북도
  •   황해남도
  •   황해북도

단일 도표[편집]

{{ #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::::
}}
10
20
30
40
50
60
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”으로 보입니다.
집합 범례 숨김 참일 경우 집합 범례를 차트 아래 보여줍니다.

보기[편집]

사과: 1,000,000톤 (17.2%)바나나: 2,000,000톤 (34.3%)살구: 1,440,000톤 (24.7%)배: 640,000톤 (11.0%)파인애플: 750,000톤 (12.9%)Circle frame.svg
  •   사과: 1,000,000톤 (17.2%)
  •   바나나: 2,000,000톤 (34.3%)
  •   살구: 1,440,000톤 (24.7%)
  •   배: 640,000톤 (11.0%)
  •   파인애플: 750,000톤 (12.9%)
{{#invoke:Chart|원 도표
| 반지름 = 150
| 조각합 = 
    ( 1000000 : 사과 ) 
    ( 2000000 : 바나나  : gold) 
    ( 1440000 : 살구 ) 
    ( 6.4e5 : 배 )
    ( 750,000 : 파인애플 )
| 꼬리 단위 = 톤
| 백분율 = 예
}}
1: 1 단위 (0.2%)7: 7 단위 (1.5%)8: 8 단위 (1.7%)9: 9 단위 (1.9%)10: 10 단위 (2.1%)11: 11 단위 (2.3%)12: 12 단위 (2.5%)13: 13 단위 (2.7%)14: 14 단위 (2.9%)15: 15 단위 (3.2%)16: 16 단위 (3.4%)17: 17 단위 (3.6%)18: 18 단위 (3.8%)19: 19 단위 (4.0%)20: 20 단위 (4.2%)21: 21 단위 (4.4%)22: 22 단위 (4.6%)23: 23 단위 (4.8%)24: 24 단위 (5.0%)25: 25 단위 (5.3%)26: 26 단위 (5.5%)27: 27 단위 (5.7%)28: 28 단위 (5.9%)29: 29 단위 (6.1%)30: 30 단위 (6.3%)31: 31 단위 (6.5%)Circle frame.svg
  •   1: 1 단위 (0.2%)
  •   7: 7 단위 (1.5%)
  •   8: 8 단위 (1.7%)
  •   9: 9 단위 (1.9%)
  •   10: 10 단위 (2.1%)
  •   11: 11 단위 (2.3%)
  •   12: 12 단위 (2.5%)
  •   13: 13 단위 (2.7%)
  •   14: 14 단위 (2.9%)
  •   15: 15 단위 (3.2%)
  •   16: 16 단위 (3.4%)
  •   17: 17 단위 (3.6%)
  •   18: 18 단위 (3.8%)
  •   19: 19 단위 (4.0%)
  •   20: 20 단위 (4.2%)
  •   21: 21 단위 (4.4%)
  •   22: 22 단위 (4.6%)
  •   23: 23 단위 (4.8%)
  •   24: 24 단위 (5.0%)
  •   25: 25 단위 (5.3%)
  •   26: 26 단위 (5.5%)
  •   27: 27 단위 (5.7%)
  •   28: 28 단위 (5.9%)
  •   29: 29 단위 (6.1%)
  •   30: 30 단위 (6.3%)
  •   31: 31 단위 (6.5%)
{{#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
| 백분율 = 예
}}

같이 보기[편집]

{{원 도표}}

--<source lang=lua>
--[[
    keywords are used for languages: they are the names of the actual
    parameters of the template
]]

local keywords = {
    barChart = '막대 도표',
    pieChart = '원 도표',
    width = '넓이',
    height = '높이',
    stack = '쌓기',
    colors = '색',
    group = '집합',
    xlegend = 'x 범례',
    tooltip = '말풍선',
    accumulateTooltip = '말풍선 합계',
    links = '연결',
    defcolor = '기본색',
    scalePerGroup = '집합 축척',
    unitsPrefix = '머리 단위',
    unitsSuffix = '꼬리 단위',
    groupNames = '집합 이름',
    hideGroupLegends = '집합 범례 숨김',
    slices = '조각합',
    slice = '조각',
    radius = '반지름',
    percent = '백분율',

} -- here is what you want to translate

local defColors = require "모듈:Plotter/DefaultColors"
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;box-shadow:2px -1px 4px 0 silver;margin-right:1em;"
        for gi = 1, #legends do
            local span = mw.text.tag( 'span', { style = string.format( spanStyle, 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;-webkit-column-width:12em;-moz-column-width:12em;column-width:12em;"},
                table.concat( list, '\n' )
            )
        )
        table.insert( tab, '</div>' )
    end
end

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()

    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( ... )
        for _, v in pairs( { ... } ) do
            table.insert( res, v )
        end
    end

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

    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, width, border
        local style
        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( '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', { class = 'transborder', style = style }, '' ) )
    end

    function createSlices()
        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', { style = string.format( "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


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, stack, delimiter = 500, 350, false, args.delimiter or ':'
    local chartWidth, chartHeight, defcolor, scalePerGroup, accumulateTooltip


    local numGroups, numValues
    local scaleWidth

    function validate()
        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] .. ' should contain the same number of items as the number of groups (' .. numGroups .. ')')
            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.', keyword.stack, keyword.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 exatly ' .. numValues ) end
    end

    function extractParams()
        function testone( keyword, key, val, tab )
            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.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

    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

    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

    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 = 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 mw.ustring.gsub(groupName .. prefix .. mw.getContentLanguage():formatNum( tonumber( val ) or 0 ) .. suffix, '_', ' '), false
    end

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

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

    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

    function drawbar( gi, i, val, ttval )
        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 )
        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;",
                        left, top, barHeight, barWidth, barWidth, color)
        local link = links[gi] and links[gi][i] or ''
        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()
        function drawSingle( gi, color, width, single )
            local yscale = yscales[gi]
            local _, top, ordermag = roundup( yscale * 0.999 )
            local numnotches = 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 )
                table.insert( res, '</div>' )
            end
        else
            drawSingle( 1, 'black', scaleWidth, true )
        end
    end

    function drawXlegends()
        local setOffset, setWidth
        local legendDivStyleFormat = "position:absolute;left:%spx;top:10px;min-width:%spx;max-width:%spx;text-align:center;veritical-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 + 5, setWidth - 10, setWidth - 10 ) }, xlegends[i] or '' ) )
                table.insert( res, mw.text.tag( 'div', { style = string.format( tickDivstyleFormat, setOffset + setWidth / 2 ) }, '' ) )
            end
        end
    end

    function drawChart()
        table.insert( res, mw.text.tag( 'div', { style = string.format( '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,
}
--</source>