--
-- This template implements {{Historical populations}}
--
local p = {}
local lang = mw.getContentLanguage()
local Date -- lazy initialization
local function ifexist(page)
if not page then return false end
if mw.title.new(page).exists then return true end
return false
end
local function isempty( s )
return not s or s:match( '^%s*(.-)%s*$' ) == ''
end
local function splitnumandref( s )
s = s:match( '^%s*(.-)%s*$' )
local t1 = mw.text.unstrip(s)
local t2 = s:match( '^([%d][%d,]*)' )
if( t1 == t2 ) then
local t3 = s:match( '^[%d][%d,]*(.-)$' )
return t1, t3
else
return s, ''
end
end
local function formatnumR(num)
return tonumber(lang:parseFormattedNumber(num))
end
local function formatnum(num)
return lang:parseFormattedNumber(num) and lang:formatNum(lang:parseFormattedNumber(num)) or num
end
-- this function creates an array with the {year, population, percent change}
local function getpoprow(year, popstr, pyear, ppopstr, linktype, percentages, current_year)
local pop, popref = splitnumandref( popstr or '')
local ppop, ppopref = splitnumandref( ppopstr or '')
local percent = ''
local yearnum = formatnumR(mw.ustring.gsub(year or '', '^%s*([%d][%d][%.%d]+).*$', '%1') or '')
local pyearnum = formatnumR(mw.ustring.gsub(pyear or '', '^%s*([%d][%d][%.%d]+).*$', '%1') or '')
local popnum = formatnumR(pop)
local ppopnum = formatnumR(ppop)
if( linktype == 'US' or linktype == 'USA' ) then
if( (yearnum or 0) >= 1790 and yearnum <= current_year and math.fmod(math.floor(yearnum), 10) == 0) then
if( yearnum < current_year ) then
year = '[[' .. tostring(yearnum) .. ' United States census|' .. year .. ']]'
elseif( ifexist(tostring(yearnum) .. ' United States census') ) then
year = '[[' .. tostring(yearnum) .. ' United States census|' .. year .. ']]'
end
end
end
if(percentages ~= 'off') then
local pstr = '— '
if(popnum ~= nil and ppopnum ~= nil and (ppopnum > 0)) then
if(percentages == 'pagr') then
pstr = mw.ustring.format('%.2f', 100*math.abs(math.pow(popnum/ppopnum,1/(yearnum-pyearnum)) - 1))
elseif(percentages == 'monthly') then
if Date == nil then Date = require('Module:Date')._Date end
local date1 = Date(year)
local date2 = Date(pyear)
local diff = date1 - date2
local months = (diff.age_days/(365.25/12))
pstr = mw.ustring.format('%.2f', 100*math.abs(math.pow(popnum/ppopnum,1/months) - 1))
else
pstr = mw.ustring.format('%.1f', 100*math.abs(popnum/ppopnum - 1))
end
if( popnum < ppopnum ) then
pstr = '−' .. pstr .. '%'
else
pstr = '+' .. pstr .. '%'
end
elseif(popnum ~= nil and ppopnum ~= nil and (ppopnum == popnum)) then
pstr = mw.ustring.format('%.2f', 0) .. '%'
end
percent = pstr
end
-- strip the fractional part of the year, if there is one
year = mw.ustring.gsub(year or '', '^%s*([%d][%d][%d]+)%.[%d]*', '%1')
return {year, formatnum(pop) .. popref, percent }
end
-- this function creates an array with table header labels
local function getheadrow(percentages, popname, yearname, percentname)
-- year cell
if(yearname == '') then
yearname = 'Year'
end
-- population cell
if(popname == '') then
popname = '<abbr title="Population" class="abbr-header">Pop.</abbr>'
end
-- percentages cell
if( percentages ~= 'off' and percentname == '') then
if( percentages == 'pagr' ) then
percentname = '<abbr title="Per annum growth rate" class="abbr-header">±% p.a.</abbr>'
elseif( percentages == 'monthly' ) then
percentname = '<abbr title="Per month growth rate" class="abbr-header">±% p.m.</abbr>'
else
percentname = '<abbr title="Percent change" class="abbr-header">±%</abbr>'
end
end
return {yearname, popname, percentname}
end
local function rendergraph(frame, data, gwidth, gheight, gthumb, gtype)
-- find some data limits
local minyear, maxyear, maxpop = 999999, 0, 0
local years = {}
local pops = {}
for i = 1, #data do
local year,pop = data[i][1], data[i][2]
-- delink if necessary
if year:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]') then
year = year:match('^%s*%[%[[^%[%]]*%|([^%[%]]*)%]%]')
end
year = year:match('^%s*([%d][%d]*).-$')
year = formatnumR(year)
pop = formatnumR(pop)
if ( year and pop ) then
table.insert(years, year)
table.insert(pops, pop)
minyear = (year < minyear) and year or minyear
maxyear = (year > maxyear) and year or maxyear
maxpop = (pop > maxpop) and pop or maxpop
end
end
local logbound = 10^(math.floor(math.log(maxpop)/math.log(10)+1))
local scalemajor = logbound/10
local scaleminor = scalemajor/2
maxpop = scaleminor*(math.floor(maxpop/scaleminor + 1))
local timeline = {
'Colors=',
' id:lightgrey value:gray(0.9)',
' id:darkgrey value:gray(0.3)',
' id:sfondo value:rgb(1,1,1)',
' id:barra value:rgb(0.664, 0.664, 0.930)', -- dark lavender
'',
'ImageSize = width:' .. tostring(gwidth) .. ' height:' .. tostring(gheight),
'PlotArea = left:44 bottom:27 top:20 right:10',
'DateFormat = x.y',
'Period = from:0 till:' .. tostring(maxpop),
'TimeAxis = orientation:vertical',
'AlignBars = justify',
'ScaleMajor = gridcolor:darkgrey increment:' .. tostring(scalemajor) .. ' start:0',
'ScaleMinor = gridcolor:lightgrey increment:' .. tostring(scaleminor) .. ' start:0',
'BackgroundColors = canvas:sfondo',
'TextData =',
' pos:(' .. tostring(math.floor(gwidth/2)) .. ',20) textcolor:black fontsize:M',
' text:"year"',
' pos:(' .. tostring(math.floor(gwidth/2)-15) .. ',' .. tostring(gheight+2) .. ') textcolor:black fontsize:M',
' text:"population"',
'BarData='
}
local yearlabels = math.max(math.floor(gwidth/60), 1)
local yearscale = math.max(math.floor((maxyear - minyear)/yearlabels),1)
yearscale = 5*math.max(math.floor(yearscale / 5), 1)
local barwidth = math.min(math.max(math.floor(gwidth/((maxyear - minyear)*0.5)), 2),5)
minyear = yearscale*math.floor(minyear/yearscale)
maxyear = yearscale*math.ceil(maxyear/yearscale)
for year = minyear,maxyear do
local text = ( (year % yearscale) == 0) and ' text:' .. year or ''
table.insert(timeline, ' bar:' .. year .. text)
end
table.insert(timeline,'\nPlotData=\n color:barra width:' .. barwidth .. ' align:left\n')
for i = 1, #years do
local year = years[i]
local pop = pops[i]
table.insert(timeline, ' bar:' .. year .. ' from: 0 till:' .. pop)
end
local timeline = frame:extensionTag{ name = 'timeline', content = '\n' .. table.concat(timeline,'\n') .. '\n' }
local graph = mw.html.create('div')
if ( gthumb == '' ) then
graph
:addClass('center')
:wikitext(timeline)
else
graph:addClass('thumb')
if ( gthumb == 'left' ) then
graph:addClass('floatleft')
elseif( gthumb == 'right' ) then
graph:addClass('floatright')
else
graph:addClass('tnone')
:css('margin-left', 'auto')
:css('margin-right', 'auto')
:css('clear', 'both')
end
graph:tag('div')
:addClass('thumbinner')
:css('width', tostring(gwidth + 2) .. 'px')
:css('max-width', tostring(gwidth + 2) .. 'px')
:css('height', tostring(gheight + 2) .. 'px')
:css('max-height', tostring(gheight + 2) .. 'px')
:wikitext(timeline)
end
return tostring(graph)
end
-- this function renders the population table in a vertical format
local function rendervertical(data, head, title, footnote, alignfn, class, style, width, shading, percol, cols, graphpos, graph)
-- define a couple helper functions
local function addrowcell(trow, tag, text, align, shading, style)
cell = trow:tag(tag)
cell
:css('text-align', align)
:css('padding', '1px')
:wikitext(text)
:css('border-bottom', shading ~= 'off' and '1px solid #bbbbbb' or nil)
:cssText(style)
end
local function addheadcell(trow, text, align, width, pad)
cell = trow:tag('th')
cell
:css('border-bottom', '1px solid var(--color-base, #000000)')
:css('padding', pad and ('1px ' .. pad) or '1px')
:css('text-align', align)
:css('width', width)
:wikitext(text)
end
local colspan = 3
local yearcount = #data
local argcount = 2*yearcount
if( isempty(width) ) then
width = '15em'
end
-- override the value of cols if percol has been specified
if( percol > 0 ) then
cols = math.floor( (yearcount - 1) / percol ) + 1
end
-- compute the number of rows per col
local rowspercol = math.floor( (yearcount - 1) / cols ) + 1
-- specify the colspan for the title and footer lines
if( cols > 1 ) then
colspan = cols
else
if (head[3] == '') then
colspan = 2
else
colspan = 3
end
end
-- compute outer table width
local twidth = width
if( (cols > 1) and width:match('^%s*[%d]+[%w]+%s*$') ) then
local widthnum = mw.ustring.gsub( width, '^%s*([%d]+)([%w]+)%s*$', '%1' )
local widthunit = mw.ustring.gsub( width, '^%s*([%d]+)([%w]+)%s*$', '%2' )
twidth = tostring(widthnum*cols) .. widthunit
end
-- create the outer table
local root = mw.html.create('table')
root
:addClass(class)
:css('width', twidth)
:css('border-top-width', '0')
:cssText(style['table'])
-- add title
if( cols <= 1) then
local caption = root:tag('caption')
caption
:css('padding', '0.25em')
:css('font-weight', 'bold')
:attr('class', 'caption-purple')
:wikitext(title)
end
if( cols > 1) then
local caption = root:tag('td')
caption
:css('padding', '0.25em')
:css('font-weight', 'bold')
:attr('colspan', cols)
divcaption = caption:tag('div')
divcaption
:css('margin', '-0.65em -0.565em 0 -0.565em')
:css('border-left', '0')
:css('border-right', '0')
:css('text-align', 'center')
:css('padding', '0.3em 0')
:attr('class', 'caption-purple')
:wikitext(title)
root:attr('role', 'presentation')
end
-- add the graph line (if top graph)
if((graphpos == 'top' or graphpos == 't') and graph ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:attr('colspan', colspan)
:css('border-bottom', '1px solid var(--color-base, #000000)')
:wikitext(graph)
graph = ''
end
-- loop over columns and rows within columns
local offset = 1
local t = root
for c = 1,cols do
-- add inner tables if we are rendering more than one column
if( cols > 1) then
if (c == 1) then
row = root:tag('tr')
row:css('vertical-align', 'top')
cell = row:tag('td')
cell
:css('padding', '0 0.5em')
else
cell = row:tag('td')
cell
:css('padding', '0 0.5em')
:css('border-left', 'solid 1px #aaa')
end
t = cell:tag('table')
t
:css('border-spacing', '0')
:css('width', width)
end
-- start column headers
local hrow = t:tag('tr')
hrow:css('font-size', '95%')
-- year header
addheadcell(hrow, head[1], nil, head[3] ~= '' and '3em' or 'auto', nil, nil)
-- population header
addheadcell(hrow, head[2], 'right', nil, '2px')
-- percentages header
if( head[3] ~= '' ) then
addheadcell(hrow, head[3], 'right', nil, nil)
end
-- end column headers
-- start population rows
for r = 1,rowspercol do
-- generate the row if we have not exceeded the rowcount
-- shade every fifth row, unless shading = off
local s = 'off'
if( math.fmod((c - 1)*rowspercol + r, 5) == 0 and r ~= rowspercol) then
s = shading
end
if(offset <= yearcount) then
-- start population row
local prow = t:tag('tr')
-- year cell
addrowcell(prow, 'th', data[offset][1], 'center', s, style['year'])
-- population cell
addrowcell(prow, 'td', data[offset][2], 'right', s, style['pop'])
-- percentage cell
if( not isempty(head[3]) ) then
addrowcell(prow, 'td', data[offset][3], 'right', s, style['pct'])
end
-- end population row
offset = offset + 1
end
end
end
-- add the graph line (if bottom graph)
if((graphpos == 'bottom' or graphpos == 'b') and graph ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:attr('colspan', colspan)
:css('border-top', '1px solid var(--color-base, #000000)')
:wikitext(graph)
graph = ''
end
-- add the footnote line
if( footnote ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:attr('colspan', colspan)
:css('border-top', '1px solid var(--color-base, #000000)')
:css('font-size', '85%')
:css('text-align', alignfn)
:wikitext(footnote)
end
if((graphpos == 'right' or graphpos == 'r') and graph ~= '') then
root = mw.html.create('table')
:tag('tr')
:css('vertical-align', 'top')
:tag('td')
:wikitext(tostring(root))
:done()
:tag('td')
:wikitext(graph)
:done()
:done()
:done()
graph = ''
end
if((graphpos == 'left' or graphpos == 'l') and graph ~= '') then
root = mw.html.create('table')
:tag('tr')
:css('vertical-align', 'top')
:tag('td')
:wikitext(graph)
:done()
:tag('td')
:wikitext(tostring(root))
:done()
:done()
:done()
graph = ''
end
return graph .. tostring(root)
end
-- this function renders the population table in a horizontal format
local function renderhorizontal(data, head, title, footnote, alignfn, class, style, width, shading, perrow, rows, graphpos, graph)
local row
local cell
local yearcount = #data
local argcount = 2*yearcount
-- override the value of rows if perrow has been specified
if( perrow > 0 ) then
rows = math.floor( (yearcount - 1) / perrow ) + 1
end
-- compute the number of cols per row
local colsperrow = math.floor( (yearcount - 1) / rows ) + 1
-- create the outer table
local root = mw.html.create('table')
root
:addClass(class)
:css('font-size', '90%')
:cssText(style['table'])
-- create title row
row = root:tag('tr')
cell = row:tag('th')
cell
:css('padding', '0.25em')
:attr('colspan', colsperrow + 1)
:wikitext(title)
-- add the graph line (if top graph)
if((graphpos == 'top' or graphpos == 't') and graph ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:attr('colspan', colsperrow + 1)
:css('border-bottom', '1px solid var(--color-base, #000000)')
:wikitext(graph)
graph = ''
end
-- loop over rows and columns within rows
local offset = 1
for r = 1,rows do
local rowoffset = offset
-- render the years
row = root:tag('tr')
cell = row:tag('th')
cell:wikitext(head[1])
:css('border-top', r > 1 and '2px solid #000' or nil)
for c = 1,colsperrow do
cell = row:tag('td')
if(offset <= yearcount) then
cell:wikitext(data[offset][1])
:css('text-align', 'center')
:css('border-top', r > 1 and '2px solid #000' or nil)
:cssText(style['year'])
else
cell:css('border-width', r > 1 and '2px 0 0 0' or 0)
:css('border-top', r > 1 and '2px solid #000' or nil)
end
offset = offset + 1
end
-- render the pop
offset = rowoffset
row = root:tag('tr')
cell = row:tag('th')
cell:wikitext(head[2])
for c = 1,colsperrow do
cell = row:tag('td')
if(offset <= yearcount) then
cell:wikitext(data[offset][2])
:css('text-align', 'right')
:css('padding-right', '2px')
:cssText(style['pop'])
else
cell:css('border-width', 0)
end
offset = offset + 1
end
-- render the percentages
if(head[3] ~= '') then
offset = rowoffset
row = root:tag('tr')
cell = row:tag('th')
cell:wikitext(head[3])
for c = 1,colsperrow do
cell = row:tag('td')
if(offset <= yearcount) then
cell:wikitext(data[offset][3])
:css('text-align', 'right')
:css('padding-right', '2px')
:cssText(style['pct'])
else
cell:css('border-width', 0)
end
offset = offset + 1
end
end
end
-- add the graph line (if bottom graph)
if((graphpos == 'bottom' or graphpos == 'b') and graph ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:attr('colspan', colsperrow + 1)
:css('border-top', '1px solid var(--color-base, #000000)')
:wikitext(graph)
graph = ''
end
-- add the footnote line
if( footnote ~= '') then
row = root:tag('tr')
cell = row:tag('td')
cell
:css('border-top', '2px solid var(--color-base, #000000)')
:css('font-size', '85%')
:css('text-align', alignfn)
:attr('colspan', colsperrow + 1)
:wikitext(footnote)
end
return graph .. tostring(root)
end
-- this is the main function
function p.poptable(frame)
local raw_data = {}
local data = {}
local style = {}
local args = frame.args[1] and frame.args or frame:getParent().args
local title = args['title'] or ''
local align = args['align'] or ''
local clear = args['clear'] or ''
local direction = args['direction'] or ''
local percentages = args['percentages'] or ''
local state = args['state'] or ''
local linktype = args['type'] or ''
local shading = args['shading'] or 'on'
local width = args['width'] or ''
local subbox = args['subbox'] or ''
local popname = args['pop_name'] or ''
local yearname = args['year_name'] or ''
local percentname = args['percent_name'] or ''
local footnote = args['footnote'] or ''
local alignfn = args['align-fn'] or ''
local source = args['source'] or ''
local graphpos = args['graph-pos'] or ''
local graphwidth = args['graph-width'] or ''
local graphheight = args['graph-height'] or ''
local graphtype = args['graph-type'] or 'line'
local datapage = args['data'] or ''
local percol = tonumber(args['percol']) or 0
local cols = tonumber(args['cols']) or 1
local perrow = tonumber(args['perrow']) or 0
local rows = tonumber(args['rows']) or 1
style['year'] = args['year_style']
style['pop'] = args['pop_style']
style['pct'] = args['pct_style']
-- setup classes and styling for outer table
local class = direction == 'horizontal' and 'wikitable' or 'table-pale'
if( state == 'collapsed' ) then
class = class .. ' collapsible collapsed'
end
if( isempty(title) ) then
title = 'Historical population'
end
if( isempty(align) ) then
align = direction ~= 'horizontal' and 'right' or 'center'
end
if( isempty(alignfn) ) then
alignfn = 'left'
end
if( isempty(clear) ) then
clear = align == 'center' and '' or align
end
local margin = '0.5em 0 1em 0.5em'
if( align == 'left' ) then
margin = '0.5em 1em 0.5em 0'
elseif( align == 'none' ) then
margin = '0.5em 1em 0.5em 0'
elseif( align == 'center' ) then
margin = '0.5em auto'
align = ''
end
if( isempty(subbox) ) then
style['table'] =
'border-spacing: 0;' ..
(align ~= '' and 'float:' .. align .. ';' or '') ..
(clear ~= '' and 'clear:' .. clear .. ';' or '') ..
'margin:' .. margin .. ';'
else
style['table'] =
'margin:0;' ..
'border-collapse:collapse;' ..
'border:none;'
end
style['table'] = style['table'] .. (args['table_style'] or '')
-- setup the footer text
if( source ~= '' ) then
source = 'Source: ' .. source
if( footnote ~= '' ) then
footnote = footnote .. '<br/>'
end
end
footnote = footnote .. source
-- setup the data header cols/rows
local head = getheadrow(percentages, popname, yearname, percentname)
-- counts for the total number of population rows
local argcount = 0
local rowcount = 0
if ( not isempty(datapage) ) then
-- load external data page from commons if it is specified
local external_data = mw.ext.data.get(datapage).data
for _, v in ipairs(external_data) do
table.insert(raw_data, tostring(v[1])) -- year
table.insert(raw_data, tostring(v[2])) -- population
rowcount = rowcount + 1
argcount = argcount + 2
end
if ( footnote ~= '' ) then
footnote = footnote .. '<br />'
end
footnote = footnote .. '[[:commons:Data:' .. datapage .. '|Source data]]'
else
-- load data from the args instead
for k, v in pairs( args ) do
if ( type( k ) == 'number' and k >= 1 and math.floor(k) == k ) then
raw_data[k] = v
if ( not isempty(args[k]) ) then
argcount = (k > argcount) and k or argcount
if( math.fmod(k - 1, 2) == 0 ) then
rowcount = rowcount + 1
end
end
end
end
end
-- here is where we build all the data for the table
-- loop over columns and rows within columns
local pyear = ''
local ppop = ''
local offset = 1
local current_year = tonumber(os.date("%Y"))
for r = 1,rowcount do
-- skip blank rows
while(isempty(raw_data[offset]) and offset <= argcount) do
offset = offset + 2
end
-- generate the row if we have not exceeded the rowcount
if(offset <= argcount) then
table.insert(data, getpoprow(raw_data[offset], raw_data[offset + 1] or '', pyear, ppop,
linktype, percentages, current_year) )
pyear = raw_data[offset]
ppop = raw_data[offset+1] or ''
offset = offset + 2
end
end
local graph = ''
graphpos = graphpos:lower()
-- now that we have the data for the table, render it in the requested format
if (direction == 'horizontal') then
if graphpos ~= '' then
local gwidth = tonumber(graphwidth) or math.floor(math.max(math.min(220, rowcount*5), 800))
local gheight= tonumber(graphheight) or 180
local gthumb =
(graphpos == 'r' or graphpos == 'right' and 'right') or
(graphpos == 'l' or graphpos == 'left' and 'left') or ''
graph = rendergraph(frame, data, gwidth, gheight, gthumb, graphtype)
end
return renderhorizontal(data, head, title, footnote, alignfn, class, style, width, shading, perrow, rows, graphpos, graph)
else
if graphpos ~= '' then
local gwidth = tonumber(graphwidth) or (200 * cols)
local gheight= tonumber(graphheight) or 180
local gthumb =
(graphpos == 'r' or graphpos == 'right' and 'right') or
(graphpos == 'l' or graphpos == 'left' and 'left') or ''
graph = rendergraph(frame, data, gwidth, gheight, gthumb, graphtype)
end
return rendervertical(data, head, title, footnote, alignfn, class, style, width, shading, percol, cols, graphpos, graph)
end
end
return p