Jump to content

Module:Sandbox/Julio974fr

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Julio974fr (talk | contribs) at 10:24, 16 February 2023 (Fetching of the previous election from wikidata, not by argument). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
local p = {};
local political_party = require('Module:Political party')
local lang = 'en'

--[[ Get a best ranking statement value from "item" with the property "property".
	 The value is returned through the function "typefunc". ]] -- Function copied from elsewhere
local function getStatementValue(item, property, typefunc)
	local statements = mw.wikibase.getBestStatements(item, property)
	if statements[1] and statements[1].mainsnak.snaktype == 'value' then
		return typefunc(statements[1].mainsnak.datavalue.value)
	end
end

--[[ The "typefunc"s - A series of functions that extract the wanted information
     from a statement value depending on the value type. ]]
local function getAmount(value) return tonumber(value.amount) end
local function getString(value) return value end
local function getItem(value)   return value.id end

function p.getAllPartiesData(electionQid, previousQid, args)
	local partiesData = p.getElectionParties(electionQid, previousQid)
	
	for partyIndex, partyData in ipairs(partiesData) do
		local fetchedPartyData = p.fetchPartyData(partyData.partyQid)
		
		partyData.partyName = fetchedPartyData.partyName
		partyData.partyColor = fetchedPartyData.partyColor
		partyData.partyArticle = fetchedPartyData.partyArticle
		
		if args['name_override_'..(partyData.partyQid)] then -- If name override specified
			partyData.partyName = args['name_override_'..(fetchedPartyData.partyQid)]
		end
		
		partiesData[partyIndex] = partyData
	end
	
	return partiesData
end

local function sortParties(listParties)
	local function comparePartyByVotes(partyA, partyB)
		if (partyA.votes or 0) > (partyB.votes or 0) then
			return true
		elseif (partyA.votes or 0) < (partyB.votes or 0) then
			return false
		else
			return ((partyA.seats or 0) > (partyB.seats))
		end
	end
	table.sort(listParties, comparePartyByVotes)
end

local function findPartyIndex(listParties, partyQid)
	local finalPartyIndex = nil
	for curPartyindex, curParty in ipairs(listParties) do
		if curParty .partyQid == partyQid then
			finalPartyIndex = curPartyindex
			break
		end
	end
	return finalPartyIndex
end

local function initPartyIndex(listParties, partyQid)
	
	local partyIndex = findPartyIndex(listParties, partyQid)
	
	if not partyIndex then
		table.insert(listParties, {partyQid=partyQid, votes = 0, seats = 0})
		partyIndex = #listParties
	end
	
	return partyIndex
end

local function addPartyDataFromStatement(partyData, statement)
	
	if statement.qualifiers.P1111 then
		if not partyData.votes then partyData.votes = 0 end
		partyData.votes = partyData.votes + getAmount(statement.qualifiers.P1111[1].datavalue.value)
	end
	
	if statement.qualifiers.P1410 then
		if not partyData.seats then partyData.seats = 0 end
		partyData.seats = partyData.seats + getAmount(statement.qualifiers.P1410[1].datavalue.value)
	end
	
	if statement.qualifiers.P155 then
		if not partyData.predecessors then partyData.predecessors = {} end
		for _, predecessorStatement in ipairs(statement.qualifiers.P155) do
			table.insert(partyData.predecessors, getItem(predecessorStatement.datavalue.value))
		end
	end
	
	return partyData
end

local function addPrevPartyDataFromStatement(partyData, statement)
	
	if not partyData.prevVotes then partyData.prevVotes = 0 end
	if statement.qualifiers.P1111 then
		partyData.prevVotes = partyData.prevVotes + getAmount(statement.qualifiers.P1111[1].datavalue.value)
	end
	
	if not partyData.prevSeats then partyData.prevSeats = 0 end
	if statement.qualifiers.P1410 then
		partyData.prevSeats = partyData.prevSeats + getAmount(statement.qualifiers.P1410[1].datavalue.value)
	end
	
	return partyData
end

function findPartyPredecessorOverride(listParties, partyQid, partyIndex)
	for tempIndex, tempParty in ipairs(listParties) do
		if tempParty.predecessors then
			for _, tempPredecessor in ipairs(tempParty.predecessors) do
				if tempPredecessor == partyQid then
					return tempIndex
				end
			end
		end
	end
	
	return partyIndex
end

function addPartyPredecessorsByComponents(listParties)
	for partyIndex, partyData in ipairs(listParties) do
		local allParts = mw.wikibase.getAllStatements(partyData.partyQid, 'P527')
		for _, predecessorStatement in ipairs(allParts) do
			if not partyData.predecessors then partyData.predecessors = {} end
			table.insert(partyData.predecessors, getItem(predecessorStatement.mainsnak.datavalue.value))
		end
		listParties[partyIndex] = partyData
	end
	return listParties
end

-- Retrieves the list of parties and sorts it by votes/seats
function p.getElectionParties(electionQid, previousQid)
	
	local listParties = {}
	
	-- For each 'candidate' in the election
	local allStatementsInElection = mw.wikibase.getAllStatements(electionQid, 'P726')
	for _, statement in ipairs(allStatementsInElection) do
		local partyQid = statement.mainsnak.datavalue.value.id
		local partyIndex = initPartyIndex(listParties, partyQid)
		listParties[partyIndex] = addPartyDataFromStatement(listParties[partyIndex], statement)
	end
	
	listParties = addPartyPredecessorsByComponents(listParties)
	
	-- For each 'candidate' in the previous election
	if previousQid then
		local allStatementsPrevElection = mw.wikibase.getAllStatements(previousQid, 'P726')
		
		for _, statement in ipairs(allStatementsPrevElection) do
			local partyQid = statement.mainsnak.datavalue.value.id
			
			local partyIndex = findPartyIndex(listParties, partyQid)
			
			partyIndex = findPartyPredecessorOverride(listParties, partyQid, partyIndex)
			
			if not partyIndex then
				partyIndex = findPartyIndex(
					listParties,
					getStatementValue(partyQid, 'P1366', getItem)
				)
			end
			
			if partyIndex then
				listParties[partyIndex] = addPrevPartyDataFromStatement(listParties[partyIndex], statement)
			end
		end
	end
	
	sortParties(listParties)
	
	return listParties
end

local function getPartyColor(partyQid, partyArticleName)
	
	local partyColor = getStatementValue(partyQid, 'P465', getString)
	if partyColor then
		return '#'..partyColor
	end
	
	if partyArticleName == '' then
		return '#ffffff'
	end
	
	partyColor = political_party._fetch({partyArticleName, 'color'})
	partyColor = mw.ustring.gsub(partyColor, '&(#)35;', '%1')
	return partyColor
end

-- For a given party, gets its name, article, and color
function p.fetchPartyData(partyQid)
	
	-- Get party article name
	local partyArticleName, partyLabel
	partyArticleName = mw.wikibase.getSitelink(partyQid)
	if not partyArticleName then
		partyLabel = mw.wikibase.getLabel(partyQid)
	end
	
	-- Get party shortname
	local partyShortnameStatement = mw.wikibase.getBestStatements(partyQid, 'P2561')
	local partyShortname
	for i, v in ipairs(partyShortnameStatement) do
		if v.mainsnak.datavalue.value.language == lang then
			partyShortname = v.mainsnak.datavalue.value.text
		end
	end
	
	-- Make party name/link wikitext
	local partyNameWikitext
	if partyArticleName then
		partyNameWikitext = '[['..partyArticleName..'|'..(partyShortname or partyLabel or partyArticleName)..']]'
	elseif partyLabel then
		partyNameWikitext = partyLabel
	else
		partyNameWikitext = '[[wikidata:'..partyQid..']]'
	end
	
	-- Get party color
	local partyColor = getPartyColor(partyQid, partyArticleName)
	
	return {
		partyQid=partyQid,
		partyName=partyNameWikitext,
		partyColor=partyColor,
		partyArticle=partyArticleName
	}
end

function p._ch_proportional(args)
	
	-- Formatting functions (copied from Module:Election_results)
	local lang = mw.getContentLanguage()
	local function fmt(n)
		return n and tonumber(n) and lang:formatNum(tonumber(n)) or nil
	end
	
	local function pct(n, d)
		n, d = tonumber(n), tonumber(d)
		if n and d and d > 0 then
			return string.format('%.2f', n / d * 100)
		end
		return '&ndash;'
	end
	
	local function diff(n, prevN)
		if not n then
			return '&ndash;'
		elseif not prevN then
			return "''New''"
		end
		
		n, prevN = tonumber(n), tonumber(prevN)
		
		if n > prevN then
			return '+'..tostring(n-prevN)
		elseif prevN > n then
			return '−'..tostring(prevN-n)
		else
			return '±0'
		end
	end
	
	local function diffPct(n, d, prevN, prevD)
		if not n or not d then
			return '&ndash;'
		elseif not prevN then
			return "''New''"
		end
		
		n = tonumber(n) / tonumber(d)
		prevN = tonumber(prevN) / tonumber(prevD)
		
		if n > prevN then
			return '+'..string.format('%.2f', (n - prevN) * 100)
		elseif prevN > n then
			return '−'..string.format('%.2f', (prevN - n) * 100)
		else
			return '±0.00'
		end
	end
	
	
	
	local electionQid = args.qid or args.election or args[1]
	local previousElectionQid = getStatementValue(electionQid, 'P155', getItem)
	
	local cols = 0
	local year = 2023 -- TODO: Remove this and use the election's year instead
	
	local totalVotes = getStatementValue(electionQid, 'P1697', getAmount)
	
	local prevTotalVotes
	if previousElectionQid then prevTotalVotes = getStatementValue(previousElectionQid, 'P1697', getAmount) end
	
	-- Table cell functions (to avoid nesting too deep)
	local function beginTable(classes, caption)
		local root = mw.html.create('table')
		root:addClass(classes)
		
		local tableCaption = root:tag('caption')
		tableCaption:wikitext(caption)
		tableCaption:done()
		
		root:done()
		return root
	end
	
	local function addHeaderCell(row, wikitext, colspan)
		local cell = row:tag('th')
		cell:wikitext(wikitext)
		cell:attr('scope', 'col')
		if colspan then cell:attr('colspan', tostring(colspan)) end
		cell:done()
	end
	
	local function addCell(row, wikitext, align)
		local cell = row:tag('td')
		if align then cell:css('text-align', align) end
		cell:wikitext(wikitext)
		cell:done()
	end
	
	local function addColorCell(row, color)
		local cell = row:tag('td')
		cell:css('width', '0px')
		cell:css('background-color', color)
		cell:done()
	end
	
	local function fillTopCell(topCell, wikitext, colspan)
		topCell:wikitext(wikitext)
		topCell:css('text-align', 'center')
		topCell:css('background', '#F8F9FA')
		topCell:attr('colspan', colspan)
		topCell:done()
	end
	
	
	
	
	
	local root = beginTable('wikitable sortable', args.caption)
	
	-- Header
	local topCell = nil
	topCell = root:tag('th')

	-- Table header
	local headerRow = root:tag('tr')
	
	addHeaderCell(headerRow, (args.partytitle or 'Party'), 2)
	addHeaderCell(headerRow, 'Votes')
	addHeaderCell(headerRow, '%')
	if previousElectionQid then addHeaderCell(headerRow, '+/−') end
	addHeaderCell(headerRow, 'Seats')
	if previousElectionQid then addHeaderCell(headerRow, '+/−') end
	
	cols = cols + 5
	if previousElectionQid then cols = cols + 2 end
	
	-- Fetch parties data
	local partiesData = p.getAllPartiesData(electionQid, previousElectionQid, args) -- TODO: Filter party name overrides instead of passing all args
	
	if topCell then
		fillTopCell(
			topCell,
			(args['image'] or require('Module:Sandbox/Julio974fr/parliament_diagram').makeParliamentDiagram(partiesData, year)),
			cols
		)
	end
	
	-- Get parties list and make the rows
	for i, v in ipairs(partiesData) do
		local row = root:tag('tr')
		
		addColorCell(row, v.partyColor)
		addCell(row, v.partyName)
		
		mw.logObject(v)
		addCell(row, fmt(v.votes), 'right')
		addCell(row, pct(v.votes, totalVotes), 'right')
		if previousElectionQid then addCell(row, diffPct(v.votes, totalVotes, v.prevVotes, prevTotalVotes), 'right') end
		addCell(row, v.seats, 'right')
		if previousElectionQid then addCell(row, diff(v.seats, v.prevSeats), 'right') end
	end
	
	return root
end

function p.ch_proportional(frame)
	-- Initialise and populate variables
	local getArgs = require("Module:Arguments").getArgs
	local args = getArgs(frame)
	
	return p._ch_proportional(args)
end

return p