Для документации этого модуля может быть создана страница Модуль:Wikidata/Population/doc

local WDS = require('Module:WikidataSelectors');

local p = {};

local DEFAULT_COLUMNS = 4;
local DEFAULT_WIDTH = 700;
local DEFAULT_HEIGHT = 300;
local COLLAPSE_IF_ROWS_MORE_THAN = 11;

local TABLE_COLLAPSIBLE_HEADER = "Статистика численности населения с %s по %s";
local TABLE_COLUMN_HEADER_YEAR = "Год";
local TABLE_COLUMN_HEADER_POPULATION = "Численность";

local function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

local function formatPopulationPropertyImpl( context, options )
	if ( not context ) then error( 'context not specified' ); end;
	if ( not options ) then error( 'options not specified' ); end;
	if ( not options.entity ) then error( 'options.entity missing' ); end;

    local claims = context.selectClaims( options, options.property );
    if (claims == nil) then
        return nil --TODO error?
    end
    
    for i, j in ipairs(claims) do
    	if ( not j.qualifiers.P585[1]) then return nil end --проверка на наличие момента времени
    end

	local comparator = function(o1, o2)
		local t1 = context.parseTimeFromSnak( o1.qualifiers.P585[1] );
		local t2 = context.parseTimeFromSnak( o2.qualifiers.P585[1] );
		return t1 < t2;
	end
	table.sort( claims, comparator )

	return claims;
end

function p.formatPopulationPropertyForGraph( context, options )
	local claims = formatPopulationPropertyImpl( context, options );
    -- Обход всех заявлений утверждения и с накоплением оформленых предпочтительных 
    -- заявлений в таблице
    local formattedClaims = {}

	local count = 0;
	local csv = 'year,month,day,population';
	
	if ( not claims ) then
		return '';
	end

    for i, claim in ipairs(claims) do
    	-- уточняем даты: для года до середины, для месяца до 15-го числа

    	local p585Value = claim.qualifiers.P585[1].datavalue.value;
    	local p585Precision = p585Value.precision;
    	local p585Time = p585Value.time;
    	if ( p585Precision == 10 ) then
    		-- Set 15-th day of month
    		p585Time = mw.ustring.gsub(p585Time, "\-[0-9]+T", "-15T");
    	elseif ( p585Precision == 9 ) then
    		-- Set to 1-st of July
    		p585Time = mw.ustring.gsub(p585Time, "\-[0-9]+\-[0-9]+T", "-07-01T");
    	end

    	local year, month, day = mw.ustring.gmatch( p585Time, "(\-?[0-9]+)\-([0-9]+)\-([0-9]+)T" )(1);
	    local value = string.gsub( claim.mainsnak.datavalue.value.amount, '^%+', '' );

		local line = year .. ',' .. month .. ',' .. day .. ',' .. value;
    	csv = csv .. '\\n' .. line;
    	count = count + 1;
    end

	if ( count == 0 ) then
		return '';
	end

	local graphData = '{ "version": 2, "width": ' .. DEFAULT_WIDTH .. ', "height": ' .. DEFAULT_HEIGHT .. ', "data": [ { "name": "table", "values": "';
	graphData = graphData .. csv;
    graphData = graphData .. '","format": { "parse": {"year": "integer", "month": "integer", "day": "integer", "population": "integer"}, "type": "csv" },'
    graphData = graphData .. '"transform": [{ "type": "formula", "field": "date", "expr": "datetime(datum.year,datum.month-1,datum.day)" }] } ],';
	graphData = graphData .. '"scales": [{ "name": "x", "type": "time", "range": "width", "nice": "year", "domain": {"data": "table", "field": "date"} },';
	graphData = graphData .. '{ "name": "y", "type": "linear", "range": "height", "domain": {"data": "table", "field": "population"} } ],';
	graphData = graphData .. '"axes": [ {"type": "x", "scale": "x", "ticks": 10}, {"type": "y", "scale": "y", "ticks": 5, "grid": true, "orient": "right", "format": "d"} ],';

	graphData = graphData .. '"marks": [{ "type": "area", "from": {"data": "table"}, "properties": { "enter": {';
    graphData = graphData .. '"x": {"scale": "x", "field": "date"}, "y": {"scale": "y", "value": 0}, "y2": {"scale": "y", "field": "population"}, ';
    graphData = graphData .. '"fill": {"value": "#99B2CC"}, "fillOpacity": {"value": 0.35}, "interpolate": {"value": "monotone"}}}},';
    graphData = graphData .. '{ "type": "line", "from": {"data": "table"}, "properties": { "enter": {';
    graphData = graphData .. '"x": {"scale": "x", "field": "date"}, "y": {"scale": "y", "field": "population"},';
    graphData = graphData .. '"stroke": {"value": "#99B2CC"}, "strokeWidth": {"value": 3}, "interpolate": {"value": "monotone"}}}},';
    graphData = graphData .. '{"type": "symbol","from": {"data": "table"},"properties": {"enter": {';
    graphData = graphData .. '"x": {"scale": "x", "field": "date"},"y": {"scale": "y", "field": "population"},"stroke": {"value": "#99B2CC"},"fill": {"value": "#fff"},"size": {"value": 10}}}}]}';

	local result = options.frame:extensionTag( 'graph', graphData );

	local columns = options.columns or DEFAULT_COLUMNS;
	local perColumn = math.ceil( count / columns );
	if ( perColumn > COLLAPSE_IF_ROWS_MORE_THAN ) then
		return result;
	else
		-- side-by-side display
		return '<div style="display: inline-block; vertical-align: bottom;">' .. result .. '</div>';
	end
end

function p.formatPopulationClaimForGraph( context, options, statement )
	local time = context.parseTimeFromSnak( statement.qualifiers.P585[1] );
    local value = string.gsub( statement.mainsnak.datavalue.value.amount, '^%+', '' );

	return os.date("*t", time / 1000).year .. ',' .. value;
end

function p.formatPopulationPropertyForTable( context, options )
	local claims = formatPopulationPropertyImpl( context, options );
    -- Обход всех заявлений утверждения и с накоплением оформленых предпочтительных 
    -- заявлений в таблице
    local formattedClaims = {}

	local firstTime = false;
	local lastTime = '';

	local count = 0;

	if ( not claims ) then
		return '';
	end

    for i, claim in ipairs(claims) do
    	
    	-- обрезаем выводимую дату до года
    	claim = deepcopy( claim );
    	if ( claim.qualifiers.P585[1].datavalue.value.precision > 9 ) then
    		claim.qualifiers.P585[1].datavalue.value.precision = 9;
    	end

		local time = context.formatSnak( options, claim.qualifiers.P585[1] );
	    local value = context.formatSnak( options, claim.mainsnak );

		if ( not firstTime ) then firstTime = time end;
		lastTime = time;

		local line = '\n|-\n! ' .. time
		line = line .. '\n| style="text-align: right; border-right: none;padding-right: 0;" | ' .. value;
		line = line .. '\n| style="text-align: left; border-left: none;padding-left: 0;" | ' .. context.formatRefs( options, claim );
        table.insert( formattedClaims, line )
        count = count + 1;
    end

	if ( count == 0 ) then
		return '';
	end

	local columns = options.columns or DEFAULT_COLUMNS;
	if ( count < columns ) then
		local out = '{| class="wikitable" \n|-\n! ' .. TABLE_COLUMN_HEADER_YEAR .. ' !! colspan=2 | ' .. TABLE_COLUMN_HEADER_POPULATION .. '\n|-';
	    for i, formattedClaim in ipairs(formattedClaims) do
			out = out .. '\n' .. formattedClaim;
		end
		out = out .. '\n|}';
		return out;
	end

	local out = '';

	local perColumn = math.ceil( count / columns );
	if ( perColumn > COLLAPSE_IF_ROWS_MORE_THAN ) then
		local caption = mw.ustring.format( TABLE_COLLAPSIBLE_HEADER, firstTime, lastTime );

		out = out .. '{| class="collapsible collapsed" |\n';
		out = out .. '! colspan=' .. columns .. ' | ' .. caption .. '\n';
		out = out .. '|-';
	else
		out = out .. '{| style="display: inline-block; vertical-align: bottom;" cellpadding=5 |';
	end

    for i, formattedClaim in ipairs(formattedClaims) do
    	if ( i % perColumn == 1 ) then
			out = out .. '\n| align="center" valign="top" | \n{| class="wikitable" \n|-\n! ' .. TABLE_COLUMN_HEADER_YEAR .. ' !! colspan=2 | ' .. TABLE_COLUMN_HEADER_POPULATION .. '\n|-';
		end
		out = out .. '\n' .. formattedClaim;
		if ( i % perColumn == 0 ) then
			out = out .. '\n|}';
		end
	end
	if ( count % perColumn ~= 0 ) then
		out = out .. '\n|}';
	end
	out = out .. '\n|}';
	return out
end

function p.formatPopulationClaimForTable( context, options, statement )
	return '';
end

return p;