Module:Historical populations

--

-- 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 = '' .. year .. ''

elseif( ifexist(tostring(yearnum) .. ' United States census') ) then

year = '' .. 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 = 'Pop.'

end

-- percentages cell

if( percentages ~= 'off' and percentname == '') then

if( percentages == 'pagr' ) then

percentname = '±% p.a.'

elseif( percentages == 'monthly' ) then

percentname = '±% p.m.'

else

percentname = '±%'

end

end

return {yearname, popname, percentname}

end

-- this function builds the json for the population graph

local function graphjson(data, gwidth, gheight, gtype)

local yearcount = #data

local graphargs = {

['width'] = gwidth,

['height'] = gheight,

['type'] = gtype or 'line',

['yAxisTitle'] = 'Population',

['yAxisMin'] = 0,

['xAxisTitle'] = 'Year',

['xAxisAngle'] = '-45',

['yGrid'] = 'y',

['yAxisFormat'] = ',d',

['x'] = '',

['y'] = ''

}

local firstpoint = true

for offset = 1,yearcount do

local x,y = data[offset][1], data[offset][2]

-- delink if necessary

if x:match('^%s*%[%^%[%*%|([^%[%]]*)%]%]') then

x = x:match('^%s*%[%^%[%*%|([^%[%]]*)%]%]')

end

y = formatnumR(y)

if x and y then

graphargs['x'] = graphargs['x'] .. (firstpoint and '' or ', ') .. x

graphargs['y'] = graphargs['y'] .. (firstpoint and '' or ', ') .. y

firstpoint = false

end

end

local Graph = require('Module:Graph')

return Graph.chart({args = graphargs})

end

local function rendergraph(frame, data, gwidth, gheight, gthumb, gtype)

local graph = frame:extensionTag{name = 'graph', content = graphjson(data, gwidth, gheight, gtype)}

if(gthumb ~= '') then

local graphdiv = mw.html.create('div')

:addClass('thumb')

:addClass(gthumb == 'right' and 'tright' or 'tleft')

:css('clear', 'none')

graphdiv

:tag('div')

:addClass('thumbinner')

:wikitext(graph)

return tostring(graphdiv)

end

return '

' .. 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

local caption = root:tag('caption')

caption

:css('padding', '0.25em')

:css('font-weight', 'bold')

:attr('class', 'caption-purple')

: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', 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:attr('valign', '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

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 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 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 .. '
'

end

end

footnote = footnote .. source

-- setup the data header cols/rows

local head = getheadrow(percentages, popname, yearname, percentname)

-- count the total number of population rows

local argcount = 0

local rowcount = 0

for k, v in pairs( args ) do

if ( (type( k ) == 'number') and (not isempty(args[k])) ) then

if( k >= 1 and math.floor(k) == k and k > argcount) then

argcount = k

end

if( math.fmod(k - 1, 2) == 0 ) then

rowcount = rowcount + 1

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", os.time()))

for r = 1,rowcount do

-- skip blank rows

while(isempty(args[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(args[offset], args[offset + 1] or '', pyear, ppop,

linktype, percentages, current_year) )

pyear = args[offset]

ppop = args[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 200

local gheight= tonumber(graphheight) or 170

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 (170 * cols)

local gheight= tonumber(graphheight) or 170

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