Module:Datastaven

Uit Wikipedia, de vrije encyclopedie
Naar navigatie springen Naar zoeken springen
Moduledocumentatie​[bekijk] [bewerk] [ververs] [geschiedenis]

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[brontekst bewerken]

Standaard:

{{#invoke:Datastaven|main}}

Speciaal voor eindstanden kan ook gebruik worden gemaakt van:

{{#invoke:Datastaven|main|type=eindstanden}}

Kleuren[brontekst 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[brontekst bewerken]

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 = {
	'#002a62', '#2c699a', '#db0c23', '#e36271',
	'#e99fa8', '#618b25', '#6bd425', '#eee8aa',
	'#eca72c', '#fff943', '#ffe4e1', '#dda0dd',
	'#0b6b61', '#3ec9a6', '#edf669', '#aabc1e',
	'#ee2b08', '#95291d', '#999999', '#333333'
}
local noGroupColor = '#002a62'
-- Translations for parameter names
local w = {
	type = 'type',
	rankings = 'eindstanden',
	customLegend = 'aangepasteLegenda',
	chartHeight = 'grafiekhoogte',
	barWidth = 'staafbreedte',
	groups = 'groepen',
	truncateX = 'xAfkappen',
	invertY = 'yOmkeren',
	x = 'x',
	y = 'y',
	yMax = 'yMax',
	ySuffix = 'ySuffix',
	group = 'groep',
	subgroup = 'subgroep',
	division = 'afd', -- Alias of subgroup
	label = 'label',
	color = 'kleur',
	tier = 'niveau',
}

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[w.subgroup] = item[w.subgroup] or item[w.division]
	item['1'] = nil
	item['2'] = nil
	item['3'] = nil
	item[w.division] = 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, bar)
	-- Picks a parameter from bar or else from it's group or else from data.
	return bar[w[param]]
		   or bar[w.group] and data.groups[bar[w.group]][w[param]]
		   or data[param]
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,
		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.group] }
			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
	
	if data.invertY then
		h = y and (1 - ((y - 1) / yMax)) * 100		 -- Height % (within it's tier)
		if y > yMax then h = 0 end
	else
		h = y and y / yMax * 100
		if y > yMax then h = 100 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()
	
	if bar[w.x] and bar[w.x] ~= '' then text:tag('b'):wikitext(bar[w.x]) end
	if bar[w.x] and bar[w.y] then text:wikitext('&ensp;') end
	if bar[w.y] then text:wikitext(bar[w.y] .. pick('ySuffix', bar)) end
	
	if bar[w.group] 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.y]
		local h = y and 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('data-x', x)
			:attr('data-y', y)
			:css('height', h)
			:css('background-color', c)
			:node(drawTooltip(bar))
			:newline()
	end
	
	return bars
end

local function orderGroups(g1, g2)
	-- Order by tier number (asc).
	g1[w.tier] = g1[w.tier] or 1000
	g2[w.tier] = g2[w.tier] or 1001
	return g1[w.tier] < g2[w.tier]
end

local function legendItems()
	local legendItems = {}
	
	if data.customLegend then
		local list = mw.text.split(data.customLegend, "%s*,%s*")
		
		for i = 1, #list do
			if data.groups[list[i]] then table.insert(legendItems, data.groups[list[i]]) end
		end
	else
		for _, g in pairs(data.groups) do table.insert(legendItems, g) end
		table.sort(legendItems, orderGroups)
	end
	
	return legendItems
end

local function drawLegend()
	local legend = mw.html.create('div')
		:addClass('es-legend')
		:tag('ul')
	
	for _, group in pairs(legendItems()) do
		legend
			:newline()
			:tag('li')
			:tag('span')
			:css('background-color', group[w.color] or '#fff')
			:done()
			:wikitext(group[w.label])
	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()
		: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.5
		truncateX = true
		ySuffix = 'e'
	end
	
	return frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles } } .. drawChart(args)
end

return p