Module:Historical populations
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:Historical populations/doc
-- -- 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">Pop.</abbr>' end -- percentages cell if( percentages ~= 'off' and percentname == '') then if( percentages == 'pagr' ) then percentname = '<abbr title="Per annum growth rate">±% p.a.</abbr>' elseif( percentages == 'monthly' ) then percentname = '<abbr title="Per month growth rate">±% p.m.</abbr>' else percentname = '<abbr title="Percent change">±%</abbr>' end end return {yearname, popname, percentname} end -- this function builds the json for the population graph local function graphjson(data, gwidth, gheight) local yearcount = #data local graphargs = { ['width'] = gwidth, ['height'] = gheight, ['type'] = '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) local graph = frame:extensionTag{ name = 'templatestyles', args = { src = 'Graph:Chart/styles.css'} } .. frame:extensionTag{name = 'graph', content = graphjson(data, gwidth, gheight)} 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 '<div class="center">' .. graph .. '</div>' 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 black') :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('border-top', '1px #aaa solid') :css('border-left', '1px #aaa solid') :css('border-right', '1px #aaa solid') :css('background-color', 'lavender') :css('padding', '0.25em') :css('font-weight', 'bold') :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 black') :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 black') :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 black') :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 black') :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 black') :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 black') :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 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 'toccolours' 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) -- 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) 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) end return rendervertical(data, head, title, footnote, alignfn, class, style, width, shading, percol, cols, graphpos, graph) end end return p