Module:Datastaven
Deze module maakt gebruik van TemplateStyles: |
Deze module kan gebruikt worden voor het in een staafdiagram weergeven van klasseringen / aantallen / rangen / posities / standen / eindstanden of iets in die trant. Of iets anders.
Deze module wordt toegepast in Sjabloon:Datastaven. Zie voor een overzicht van parameters de documentatie aldaar.
Gebruik bewerken
Standaard:
{{#invoke:Datastaven|main}}
Speciaal voor eindstanden kan ook gebruik worden gemaakt van:
{{#invoke:Datastaven|main|type=eindstanden}}
Kleuren bewerken
De kleuren van de staven kunnen zowel ingesteld worden op de groepen als op individuele staven. Als er geen kleur is opgegeven wordt er een uit het standaardkleurenpalet gehaald:
(Bewerk)
(Bewerk)
Zie ook bewerken
- Module:Datastaven/Groepen - Een verzameling met voorgedefinieerde groepen die gebruikt kunnen worden met deze module.
require('Module:No globals')
local p = {}
local getArgs = require('Module:Arguments').getArgs
local unpackItem = require('Module:Item').unpack
local _delink = require('Module:Delink')._delink
local _yesno = require('Module:Yesno')
local templatestyles = 'Module:Datastaven/styles.css'
local data = {}
local chartHeight = 180 -- In px
local extraHeight = 20 -- Extra height per extra tier, in px
local barWidth = 3 -- In em
local truncateX = false
local invertY = false
local ySuffix = ''
local colors = {
'#999999', '#eeeeee', '#95291d', '#ea6a1a', '#aabc1e',
'#edf669', '#3ec9a6', '#0b6b61', '#dda0dd', '#ffe4e1',
'#fff943', '#eca72c', '#eee8aa', '#6bd425', '#618b25',
'#e99fa8', '#e36271', '#db0c23', '#7197b6', '#325a8d'
}
local noGroupColor = '#999999'
-- Translations for parameter names
local w = {
type = 'type',
rankings = 'eindstanden',
customLegend = 'aangepasteLegenda',
chartHeight = 'grafiekhoogte',
barWidth = 'staafbreedte',
groups = 'groepen',
note = 'opm',
truncateX = 'xAfkappen',
invertY = 'yOmkeren',
x = 'x',
y = 'y',
yMax = 'yMax',
ySuffix = 'ySuffix',
yLabel = 'yLabel',
yNote = 'yOpm',
group = 'groep',
subgroup = 'afd',
label = 'label',
color = 'kleur',
tier = 'niveau',
value = 'waarde',
domains = 'domeinen',
from = 'van',
till = 'tot',
}
local function isItem(arg)
-- An arg is considered an item if it starts with a pipe character.
return string.find(mw.text.trim(arg), '|', 1, true) == 1
end
local function formatItem(item)
-- Aliases
item[w.x] = item[w.x] or item['1']
item[w.y] = item[w.y] or item['2']
item[w.group] = item[w.group] or item['3']
item['1'] = nil
item['2'] = nil
item['3'] = nil
item[w.y] = tonumber(item[w.y])
item[w.yMax] = tonumber(item[w.yMax])
item[w.tier] = tonumber(item[w.tier])
end
local function yesno(value, default)
if _yesno(value) ~= nil then return _yesno(value) else return default end
end
local function pick(param, item)
-- Picks a parameter from an item or else from it's group or else from data.
local value = item[w[param]]
or item[w.group] and data.groups[item[w.group]][w[param]]
or data[param]
if type(value) == "table" then
-- Pick the value where x is within the domain.
local x = tonumber(item[w.x])
local domains = value[w.domains]
if x and domains and type(domains) == "table" then
for _, h in ipairs(domains) do
if h[w.from] or h[w.till] then
local from = h[w.from] or 0
local till = h[w.till] or 9999
if x > from and x <= till then return h[w.value] end
end
end
end
return value[w.value]
end
return value
end
local function minn(table)
-- Returns the lowest positive numerical index of the given table, or zero
-- if the table has no numerical indices.
local minn, k = nil, nil
repeat
k = next(table, k)
if type(k) == 'number' then
if k == 1 then return 1 end
if minn == nil or k < minn then minn = k end
end
until not k
return minn or 0
end
local function countTiers(tiers)
-- Gives the number of tiers, empty inbetweens included.
return table.maxn(tiers) - minn(tiers) + 1
end
local function rankTiers(tiers)
-- Ranks the tiers bottom up for convenience, since heights will be calculated
-- from the bottom up. Example:
-- [2] = true, [2] = 4,
-- [3] = true, --> [3] = 3,
-- [5] = true, [5] = 1,
local highestTierNumber = table.maxn(tiers)
for n, _ in pairs(tiers) do
tiers[n] = highestTierNumber - n + 1
end
return tiers
end
local function mergeTables(t1, t2)
if t1 and t2 then for k, v in pairs(t2) do t1[k] = v end end
return t1
end
local function importGroups(basename)
if basename == nil or basename == '' then return {} end
return require('Module:Datastaven/Groepen/' .. basename)
end
local function extractData(args)
-- Extract all the data we need from the args.
data = {
barWidth = args[w.barWidth] or barWidth,
note = args[w.note],
truncateX = yesno(args[w.truncateX], truncateX),
invertY = yesno(args[w.invertY], invertY),
yMax = args[w.yMax] or 0,
ySuffix = args[w.ySuffix] or ySuffix,
noGroupColor = args[w.color] or noGroupColor,
customLegend = args[w.customLegend],
bars = {},
groups = {},
tiers = {},
}
-- Import preset groups.
local presetGroups = importGroups(args[w.groups])
-- Extract from inline groups.
for i = 1, 20 do
local arg = args[w.group .. i]
if arg and isItem(arg) then
local group = unpackItem(arg)
if group[w.group] then
group = mergeTables(presetGroups[group[w.group]] or {}, group)
data.groups[group[w.group]] = group -- Add to our groups
group[w.label] = group[w.label] or group[w.group]
group[w.group] = nil
formatItem(group)
end
end
end
-- Extract from items.
for _, arg in ipairs(args) do
if isItem(arg) then
local bar = unpackItem(arg)
formatItem(bar)
table.insert(data.bars, bar)
if bar[w.y] then
data.yMax = math.max(data.yMax, bar[w.y])
end
if bar[w.group] and data.groups[bar[w.group]] == nil then
data.groups[bar[w.group]] = presetGroups[bar[w.group]]
or { [w.label] = bar[w.label] or bar[w.group],
[w.color] = bar[w.color] }
end
local tier = tonumber(pick('tier', bar))
if tier then data.tiers[tier] = true end
end
end
data.tiers = rankTiers(data.tiers)
data.tiersCount = countTiers(data.tiers)
data.chartHeight = args[w.chartHeight] or chartHeight + extraHeight * (data.tiersCount - 1)
return data
end
local function calculateBarHeight(bar)
local y = bar[w.y]
local yMax = pick('yMax', bar)
local tierRank = data.tiers[pick('tier', bar)] or 1
local h = 0
if y then
if data.invertY then
h = (1 - ((y - 1) / yMax)) * 100 -- Height % (within it's tier)
if y > yMax then h = 0 end
else
h = y / yMax * 100
if y > yMax then h = 100 end
end
end
h = (h + tierRank * 100 - 100) / data.tiersCount -- Add heights of lower tiers
h = math.floor(h * 1000) / 1000 -- Truncate number
return h
end
local function pickColor(bar)
local color = pick('color', bar)
if color then return color end
if bar[w.group] then
color = table.remove(colors) or '#fff'
data.groups[bar[w.group]][w.color] = color
else
color = data.noGroupColor
end
return color
end
local function delink(text)
-- Removes (wiki)links from a text.
if not type(text) == 'string' then return text end
return _delink({ text, urls = 'no', comments = 'no', whitespace = 'no' })
end
local function drawTooltip(bar)
local text = mw.html.create()
local x = bar[w.x]
local y = bar[w.y] and bar[w.y] .. pick('ySuffix', bar) or bar[w.yLabel]
local yNote = bar[w.yNote]
if x and x ~= '' then text:tag('b'):wikitext(x) end
if x and (y or yNote) then text:wikitext(' ') end
if y then text:wikitext(y) end
if y and yNote then text:wikitext(' ') end
if yNote then text:wikitext(yNote) end
if bar[w.group] or bar[w.label] then
if tostring(text) ~= '' then text:tag('br') end
text:wikitext(delink(pick('label', bar)))
if bar[w.subgroup] then text:wikitext(' ' .. bar[w.subgroup]) end
end
return mw.html.create()
:newline()
:tag('div')
:addClass('es-tip')
:node(text)
:done()
end
local function drawBars()
local bars = mw.html.create()
for _, bar in ipairs(data.bars) do
local x = bar[w.x]
local y = bar[w.yLabel] or bar[w.y]
local h = calculateBarHeight(bar) .. '%'
local c = pickColor(bar)
if x and data.truncateX then x = string.sub(x, -2) end -- Show only the last two digits of the year
bars
:newline()
:tag('li')
:addClass('es-bar')
:attr('tabindex', 0)
:attr('data-x', x)
:attr('data-y', y)
:css('height', h)
:css('background-color', c)
:node(drawTooltip(bar))
:newline()
end
return bars
end
local function orderItems(item1, item2)
-- Order by tier number (asc).
local t1 = pick('tier', item1) or 1000
local t2 = pick('tier', item2) or 1000
return t1 < t2
end
local function getLegendItems()
local legendItems = {}
local groups = {}
-- Copy data.groups to groups.
for k, g in pairs(data.groups) do groups[k] = g end
-- Start legendItems with the groups chosen in `customLegend`.
if data.customLegend then
local codes = mw.text.split(data.customLegend, "%s*,%s*")
for _, code in pairs(codes) do
table.insert(legendItems, groups[code])
groups[code] = nil
end
end
-- Order the (remaining) groups by tier number, then append to legendItems.
local iGroups = {}
for _, g in pairs(groups) do table.insert(iGroups, g) end
table.sort(iGroups, orderItems)
for _, g in ipairs(iGroups) do table.insert(legendItems, g) end
return legendItems
end
local function drawLegend()
local groups = getLegendItems()
local legend = mw.html.create('div')
:addClass('es-legend')
:tag('ul')
for _, group in pairs(groups) do
legend
:newline()
:tag('li')
:tag('span')
:css('background-color', pick('color', group) or '#fff')
:done()
:wikitext(pick('label', group))
end
return legend:done()
end
local function drawChart(args)
data = extractData(args)
-- return mw.dumpObject(data)
if #data.bars == 0 then
return "''Geen data om weer te geven.''"
end
local chart = mw.html.create()
:tag('div')
:addClass('es-chart')
:css('overflow-y', 'hidden')
:tag('ul')
:addClass('es-grid')
:css('width', data.barWidth * #data.bars .. 'em')
:css('height', data.chartHeight .. 'px')
:node(drawBars())
:allDone()
if data.note then chart:tag('div'):addClass('es-note'):wikitext(data.note) end
chart:node(drawLegend())
return tostring(chart)
end
function p.main(frame)
local args = getArgs(frame)
args[1] = args[1] or '' -- Data won't show when args[1] is absent
-- Settings for rankings.
if args[w.type] == w.rankings then
invertY = true
barWidth = 1.4
truncateX = true
ySuffix = 'e'
end
return frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles } } .. drawChart(args)
end
return p