local p = {}
local wikidata = require( 'Module:Wikidata' )
local linguistic = require 'Module:Linguistique'
local pieChart = require "Module:Fabricant de camemberts"
local TableChart = require "Module:Fabricant de tables"
local maintenancestr = '' -- stores error messages
-- I18N
local labels = {
validvotes = 'Exprimés',
invalidvotes = 'Blancs ou nuls',
novotes = 'Abstention',
candidate = 'candidat',
percent = '%',
votes = 'votes',
seats = 'sièges',
maintenancecat = 'Page avec des données électorales à vérifier',
invalidtotal = 'Nombre de $typedata invalide ($real trouvés, attendait $expected)',
missingdata = 'Données insuffisantes',
-- reverse translations for inputs
['camembert'] = 'pie',
['sièges'] = 'seats',
['voix'] = 'votes',
['tableau'] = 'table',
}
local function translate(str)
return labels[str] or str or nil
end
-- Error handling
local function invaliddata(msg)
local cat = ''
if mw.getCurrentFrame():preprocess("{{NAMESPACE}}") == '' then
cat = '[[Category:' .. translate('maintenancecat') .. ']]'
end
maintenancestr = maintenancestr .. (msg or '') .. cat .. '<br />'
end
local function invalidtotal(expected, real, typedata)
local msg = translate('invalidtotal')
msg = msg:gsub('$typedata', typedata)
msg = msg:gsub('$expected', tostring(expected))
msg = msg:gsub('$real', tostring(real))
return invaliddata(msg)
end
local methodparams = {
-- method = {label, property for total number, property for qualifiers}
votes = {'votes', 'P1697', 'P1111'},
seats = {'seats', 'P1410', 'P1410'} ,
}
--TABLE function (need dedicated module) ?
local function addRow(obj, vals, typecell)
local row = mw.html.create('tr')
typecell = typecell or 'td'
for i, j in pairs(vals) do
row:tag(typecell):wikitext(j):done()
end
return obj:node(row):done()
end
local function addHeader(obj, vals)
return addRow(obj, vals, 'th')
end
local function showpercents(val)
if val then
-- arrondi à 0.01 %
val = math.floor( val * 10000 + 0.5 ) / 100
return val
end
end
local function validtotal(expected, real, typedata, margin)
if not margin then
margin = .001
end
local difference = expected-real
if math.abs(difference) / expected > margin then
invalidtotal(expected, real, typedata)
end
return nil
end
local function color(item)
local RGB = wikidata.formatStatements{entity = item, property = 'P465', numval = 1}
if RGB then
return '#' .. RGB
end
return '' -- for random color
end
local function getAmount(item, prop)
local val = wikidata.formatStatements{entity = item, property = prop, numval = 1, displayformat = 'raw'}
return tonumber(val or 0) -- what for missing data ?
end
local function getQualifAmount(claim, qualif)
local val = wikidata.getFormattedQualifiers(claim, {qualif}, {numval =1, displayformat = 'raw'})
if val and not tonumber(val) then -- could be because there are several value for the qualif, or speical value, display what happens
invaliddata(translate('invaliddata') .. ': ' .. wikidata.formatStatement(claim) .. val )
return 0
end
return tonumber(val or 0) -- what for missing data ?
end
local function valFromStatement(statement, qualif, total)
local val = getQualifAmount(statement, qualif)
local pct
if val and total then
pct = val / total
end
return {val, pct}
end
local function getData(typedata, method, obj)
local d = {}
method = translate(method)
local params = methodparams[method]
if not params then
return error('invalid counting method: ' .. '"' .. (method or 'none provided') .. '"')
end
if typedata == 'name' then
return translate(params[1])
elseif typedata == 'amount' then
return getQualifAmount(obj, params[3])
elseif typedata == 'totalamount' then
return getAmount(obj, params[2])
end
return error('invalid request: ' .. (typedata or 'none provided'))
end
-- Participation results functions
local function participationchart(valid, invalid, novotes, displayformat)
local chart = pieChart:new()
chart:addSlice{valid, translate('validvotes'), 'green'}
chart:addSlice{novotes, translate('novotes'), 'blue'}
chart:addSlice{invalid, translate('invalidvotes'), 'red'}
displayformat = displayformat or { radius = '100', percent = 'true'}
chart:format(displayformat)
return chart:show()
end
local function participationtable(valid, invalid, novotes)
local total = valid + invalid + novotes
local pctvalid, pctinvalid, pctnovotes = showpercents(valid/total), showpercents(invalid/total), showpercents(novotes/total)
local tab = TableChart:new()
tab:addHeaders{'', translate('votes'), translate('percent')}
tab:addRow{translate('validvotes'), valid, pctvalid}
tab:addRow{translate('invalidvotes'), invalid, pctinvalid}
tab:addRow{translate('novotes'), novotes, pctnovotes}
return tab:show()
end
function p._participation(election, typedisplay, title)
election = mw.wikibase.getEntityObject(election)
local eligible = getAmount(election, 'P1867')
local voting = getAmount(election, 'P1868')
local valid = getAmount(election, 'P1697')
if not (eligible and valid and voting) then
return invaliddata(translate('missingdata'))
end
local novotes = eligible - voting
local invalid = voting - valid
typedisplay = translate(typedisplay)
if (not typedisplay) or (typedisplay == '') then
typedisplay = 'table'
end
local mainstr = ''
if type(typedisplay) ~= 'string' then
return error('bad datatype: ' .. type(typedisplay))
end
if typedisplay == 'table' then
mainstr = mainstr .. participationtable(valid, invalid, novotes)
else
typedisplay = translate(typedisplay)
if typedisplay == 'pie' then
mainstr = mainstr .. participationchart(valid, invalid, novotes)
else
return error('I want a pie or a table, what is a ' .. typedisplay .. '?')
end
end
if title then
mainstr = '<table><tr><th>' .. title .. '</th></tr><tr><td>' .. mainstr .. '</td></tr></table>'
end
return maintenancestr .. mainstr
end
-- Score result functions
local function makePie(data, request, displayformat)
displayformat = displayformat or { radius = '100', percent = 'true'}
local pie = pieChart:new()
for _,candidate in pairs(data) do
local val = candidate.vals[request]
pie:addSlice{val, candidate.name, candidate.color, candidate.name}
end
pie:format(displayformat)
return pie:show()
end
local function makePies(data, requests, displayformat)
local out = ''
for i, request in pairs(requests) do
out = out ..makePie(data, i, displayformat)
end
return out
end
local function resultsTable(data, methods, totals)
local headers = {}
table.insert(headers, translate('candidate'))
local pctheader = translate('percent')
for i, method in pairs(methods) do
table.insert(headers, method)
table.insert(headers, pctheader)
end
local tab = TableChart:new()
tab:addHeaders(headers)
-- CONTENT
for _, candidate in pairs(data) do
local row = {}
local sortkey
table.insert(row, candidate.name)
for i, val in pairs(candidate.vals) do
sortkey = sortkey or -val -- la première colonne de valeur est utilisée comme clé de tri, en négatif pour avoir les gagnants en haut
local pct = val / totals[methods[i]]
pct = showpercents(pct)
if val == 0 then
val, pct = '-', '-'
end
table.insert(row, val)
table.insert(row, pct)
end
tab:addRow(row, sortkey)
end
return tab:show()
end
function p._results(election, typedisplay, methods, title, displayformat, errormargin)
local entity = mw.wikibase.getEntityObject(election)
if type(methods) == 'nil' then
methods = {'votes'}
elseif type(methods) == 'string' then
methods = mw.text.split(methods, '%,%s*')
end
local candidates = wikidata.getClaims{entity = entity, property = 'P726', excludespecial = true}
-- keeps variable to check totals
local targettotals = {}
local gottotals = {}
for i, method in pairs(methods) do
targettotals[method] = getData('totalamount', method, entity)
gottotals[method] = 0
end
local data = {}
-- structure of an entry: { d.datas{valforcolumnA, valforcolumnB}, d.name, d.color, d.qid}
for _, candidate in pairs(candidates) do
local d = {}
d.qid = wikidata.getMainId(candidate)
d.name = wikidata.formatEntity(d.qid)
d.color = color(d.qid)
d.party = wikidata.getFormattedQualifiers{entity = candidate, property = 'P102'}
if name and party then
name = name .. linguistic.inparentheses(party)
end
d.vals = {}
for i, method in pairs(methods) do
local val = getData('amount', method, candidate)
gottotals[method] = gottotals[method] + val
table.insert(d.vals, val)
end
table.insert(data, d)
end
-- CHECK data consistency
for method, data in pairs(targettotals) do
validtotal(data, gottotals[method], method, errormargin)
end
if type(typedisplay) ~= 'string' and (type(typedisplay) ~= 'nil') then
return error('bad datatype: ' .. type(typedisplay))
end
typedisplay = translate(typedisplay)
if (not typedisplay) or (typedisplay == '') then
typedisplay = 'table'
end
local mainstr = ''
if typedisplay == 'table' then
mainstr = resultsTable(data, methods, targettotals or gottotals, displayformat)
elseif typedisplay == 'pie' then
mainstr = makePies(data, methods, displayformat)
else
return error('I want a pie or a table, what is a ' .. typedisplay .. '?')
end
if title then
mainstr = '<table><tr><th>' .. title .. '</th></tr><tr><td>' .. mainstr .. '</td></tr></table>'
end
return maintenancestr .. mainstr
end
-- FRAME FUNCTIONS
local function trimargs(args) -- remove annoying white spaces
local newargs = {}
for i, j in pairs(args) do
if j ~= '' then newargs[i] = j end
end
return args
end
function p.participation(frame)
local args = trimargs(frame.args)
return p._participation(args[1], args['type'], args['title'])
end
function p.results(frame)
local args = trimargs(frame.args)
return p._results(args[1], args['type'], args['method'] or 'votes', args['title'])
end
return p