Module:Chart

--

--

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',

tooltip = 'tooltip',

links = 'links',

defcolor = 'default color',

scalePerGroup = 'scale per group',

unitsPrefix = 'units prefix',

unitsSuffix = 'units suffix',

groupNames = 'group names',

slices = 'slices',

slice = 'slice',

radius = 'radius',

percent = 'percent',

} -- here is what you want to translate

local defColors = require "Module:Plotter/DefaultColors"

local function nulOrWhitespace (s)

return not s or mw.text.trim (s) ==

end

local function createGroupList (tab, legends, cols)

if #legends > 1 then

table.insert (tab, mw.text.tag ( 'div') )

local list =

local spanStyle = "padding:0 1em;background-color:% s;box-shadow:inset 0 0 1px 1px rgba (0,0,0,0.2);margin-right:1em;" -- 替換投影。原 “box-shadow:2px -1px 4px 0 silver” 投影太難看。

for gi = 1, #legends do

local span = mw.text.tag ('span', { style = string.format ( spanStyle, colsgi) }, '' ) .. ' '.. legendsgi

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

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 = argskeywordss 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') )

valuesi = 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 ) )

colorsi = not nulOrWhitespace (color) and color or defColorsi * 2

namesi = name or

linksi = link

end

radius = getArg ('radius', 150)

local slicesStr = getArg ('slices')

local prefix = getArg ('unitsPrefix', , '_', ' ')

local suffix = getArg ('unitsSuffix', , '_', ' ')

local percent = argskeywords.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

legendsi = mw.ustring.format ('% s: % s% s% s% s', namesi, prefix, lang:formatNum ( value), suffix, addprec )

linksi = mw.text.trim (linksi or mw.ustring.format ( '% s', legendsi) )

end

end

function addRes (...)

for _, v in pairs ({ ...} ) do

table.insert (res, v)

end

end

function createImageMap

addRes (.. ( radius * 2) .. 'px' )

addRes (unpack ( imslices) )

addRes ('desc none', '}}' )

end

function drawSlice (i, q, start)

local color = colorsi

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 ' ) -- close "position:relative" div that contains slices and imagemap.

addRes ( ) -- close "position:relative" div that contains slices and imagemap.

createGroupList (res, legends, colors) -- legends

addRes ( ) -- 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, ':'

local chartWidth, chartHeight, defcolor, scalePerGroup

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" .. keywordsname)

end

if #tab == 1 and toDuplicate then

for i = 2, numGroups do tabi = tab1 end

end

if #tab > 0 and #tab ~= numGroups then

error (keywordsname .. ' 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 = #values1

defcolor = defcolor or 'blue'

colors1 = colors1 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 #valuesgi ~= 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 tabi = end

for s in mw.text.gsplit (val, '% s' .. delimiter .. '% s') do

table.insert (i == 0 and tab or tabi, 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 = 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 ithem, 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 sumsi = (sumsi or 0) + val end

end

local sum = math.max (unpack ( sums) )

for i = 1, #values do yscalesi = sum end

else

for i, group in ipairs (values) do yscalesi = math.max (unpack ( group) ) end

end

for i, scale in ipairs (yscales) do yscalesi = roundup (scale) end

if not scalePerGroup then for i = 1, #values do yscalesi = math.max (unpack ( yscales) ) end end

end

function tooltip (gi, i, val)

if tooltips and tooltipsgi and not nulOrWhitespace (tooltipsgii) then return tooltipsgii, true end

local groupName = not nulOrWhitespace (groupNamesgi) and groupNamesgi .. ': ' or

local prefix = unitsPrefixgi or unitsPrefix1 or

local suffix = unitsSuffixgi or unitsSuffix1 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 /yscalesgi * 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 + valuesji end -- sum the "i" value of all the groups below our group, gi.

base = math.floor (chartHeight * rawbase /yscalesgi) -- 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 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)

local color, tooltip, custom = colorsgi or defcolor or 'blue', tooltip (gi, i, 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;overflow:hidden;",-- 刪投影。

left, top, barHeight, barWidth, barWidth, color)

local link = linksgi and linksgii or

local img = not nulOrWhitespace (link) and mw.ustring.format (, 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 = yscalesgi

local _, top, ordermag = roundup (yscale * 0.999)

local numnotches = top ' )

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 (xlegendsi) 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) }, xlegendsi 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) } ) )

for gi, group in pairs (values) do

for i, val in ipairs (group) do

drawbar (gi, i, val)

end

end

table.insert (res,)

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

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

table.insert (res,)

createGroupList (res, groupNames, colors)

table.insert (res,)

end

extractParams

validate

calcHeightLimits

drawChart

return table.concat (res, "\n")

end

return {

'bar-chart' = barChart,

keywords.barChart = barChart,

keywords.pieChart = pieChart,

}

--