Модуль:Wikidata/Population

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Документация

Модуль для шаблонов {{Wikidata/Population}} и {{Wikidata/Population/Table}}.

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

local p = {};

local DEFAULT_WIDTH = 500;
local DEFAULT_HEIGHT = 250;
local COLLAPSE_IF_MORE_THAN = 30;

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
    
    local validClaims = {}
    for _, claim in ipairs( claims ) do
    	-- проверка на наличие корректного момента времени
    	if claim.qualifiers.P585[1] and claim.qualifiers.P585[1].snaktype == 'value' then
    		table.insert( validClaims, claim )
		end
    end

    if #validClaims == 0 then
        return nil -- TODO error?
    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( validClaims, comparator )

	return validClaims
end

-- тестирование не работает
-- =p.formatProperty(mw.getCurrentFrame():newChild{title="Модуль:Wikidata",args={["property-module"]="Wikidata/Population",["property-function"]="formatPopulationPropertyForGraph",["claim-module"]="Wikidata/Population",["claim-function"]="formatPopulationClaimForGraph",["property"]="p1082[p585][rank:preferred,rank:normal]",["datatype"]="quantity",}}:newChild{title="Модуль:Wikidata/Population"}:newChild{title="Сереседа-де-ла-Сьерра"}) 
function p.formatPopulationPropertyForGraph( context, options )
	local claims = formatPopulationPropertyImpl( context, options );
    -- Обход всех заявлений утверждения и с накоплением оформленых предпочтительных 
    -- заявлений в таблице
    local formattedClaims = {}
    local years = {}

	local count = 0;
	local csv = 'year,month,day,population,formatted';
	
	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);
    	
		if claim.mainsnak.datavalue and not years[ year ] then
			years[ year ] = true;

			local value = string.gsub( claim.mainsnak.datavalue.value.amount, '^%+', '' )
			local formatted = mw.language.getContentLanguage():formatNum( tonumber( value ) );
			
			local line = year .. ',' .. month .. ',' .. day .. ',' .. value .. ',' .. formatted;
	    	csv = csv .. '\\n' .. line;
	    	count = count + 1;
    	end
    end

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

	local graphData = [[{
    "version": 2,
    "width": ]] .. DEFAULT_WIDTH .. [[,
    "height": ]] .. DEFAULT_HEIGHT .. [[,
    "data": [ {
        "name": "table",
        "values": "]] .. csv .. [[",
        "format": {
            "parse": {
                "year": "integer",
                "month": "integer",
                "day": "integer",
                "population": "integer",
                "formatted": "string"
            },
            "type": "csv"
        },
        "transform": [
            {
                "type": "formula",
                "field": "date",
                "expr": "datetime(datum.year,datum.month-1,datum.day)"
            }
        ]
    } ],
    "scales": [
        {
            "name": "x",
            "type": "time",
            "range": "width",
            "nice": "year",
            "domain": { "data": "table",  "field": "date" }
        },
        {
            "name": "y",
            "type": "linear",
            "range": "height",
            "domain": { "data": "table", "field": "population" }
        }
    ],
    "axes": [
        { "type": "x", "scale": "x", "ticks": 10 },
        { "type": "y", "scale": "y", "ticks": 5, "grid": true, "orient": "right", "format": "d" }
    ],
    "marks": [
        {
            "type": "area",
            "from": { "data": "table" },
            "properties": {
                "enter": {
                    "x": { "scale": "x", "field": "date" },
                    "y": { "scale": "y", "value": 0 },
                    "y2": { "scale": "y", "field": "population" },
                    "fill": { "value": "#99B2CC" },
                    "fillOpacity": { "value": 0.35 },
                    "interpolate": { "value": "linear" }
                }
            }
        },
        {
            "type": "line",
            "from": { "data": "table" },
            "properties": {
                "enter": {
                    "x": { "scale": "x", "field": "date" },
                    "y": { "scale": "y", "field": "population" },
                    "stroke": { "value": "#99B2CC" },
                    "strokeWidth": { "value": 3 },
                    "interpolate": {  "value": "linear" }
                }
            }
        },
        {
            "type": "symbol",
            "from": { "data": "table" },
            "properties": {
                "enter": {
                    "x": { "scale": "x", "field": "date" },
                    "y": { "scale": "y", "field": "population" },
                    "stroke": { "value": "#99B2CC" },
                    "fill": { "value": "#fff" },
                    "size": { "value": 10 }
                }
            }
        },
        {
            "type": "text",
            "from": { "data": "table" },
            "properties": {
                "enter": {
                    "x": { "scale": "x", "field": "date", "offset": -5 },
                    "y": { "scale": "y", "field": "population", "offset": -1 },
                    "align": { "value": "left" },
                    "opacity": { "value": "0" },
                    "fill": { "value": "#000000" },
                    "size": { "value": 4 },
                    "text": { "template": "{{datum.formatted}}  ({{datum.year}})" }
                },
                "hover": {
                    "opacity": { "value": "1" }
                },
                "update": {
                    "opacity": { "value": "0" }
                }
            }
        }
    ]
}]]

	local result = options.frame:callParserFunction{ name = '#tag:graph', args = { graphData, mode = 'interactive' } };

	if count > COLLAPSE_IF_MORE_THAN then
		return result;
	else
		-- side-by-side display
		return '<div style="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 years = {}

	local firstTime = false;
	local lastTime = '';

	local count = 0;

	if ( not claims ) then
		return '';
	end

	if options.nolinks == nil then
		options.nolinks = true
	end

    for i, claim in ipairs( claims ) do
    	
    	-- обрезаем выводимую дату до года
    	local timeQualifier = claim.qualifiers.P585[1];
    	if ( timeQualifier.datavalue.value.precision > 9 ) then
    		timeQualifier = deepcopy( timeQualifier );
    		timeQualifier.datavalue.value.precision = 9;
    	end
		local year = string.sub( timeQualifier.datavalue.value.time, 2, 5 );
		if claim.mainsnak.datavalue and not years[ year ] then
			years[ year ] = true

			local time = context.formatSnak( options, timeQualifier )
		    local value = context.formatSnak( options, claim.mainsnak )
	
			if not firstTime then
				firstTime = time
			end
			lastTime = time
			
			-- link to topic (census)
			if claim.qualifiers.P805 and
				claim.qualifiers.P805[1] and
				claim.qualifiers.P805[1].snaktype == 'value'
			then
				local link = mw.wikibase.sitelink( claim.qualifiers.P805[1].datavalue.value.id )
				if link then
					time = '[[' .. link .. '|' .. time .. ']]'
				end
			end
	
			local line = '\n|- role="row"'
			line = line .. '\n! role="rowheader" scope="row" | ' .. time
			line = line .. '\n| role="cell" | ' .. ( value or "" )
			line = line .. '\n| role="cell" | '
			if options.references then
				line = line .. context.formatRefs( options, claim )
			end
	        table.insert( formattedClaims, line )
	        count = count + 1
        end
    end

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

	local out = '<div class="ts-wikidata-population-table'
	if count < 20 then
		out = out .. ' cols-' .. ( math.floor( count / 5 ) + 1 )
	end
	out = out .. '">\n'

	local caption = mw.ustring.format( TABLE_COLLAPSIBLE_HEADER, firstTime, lastTime );
	out = out .. '{| role="table" class="wikitable mw-collapsible'
	if count > COLLAPSE_IF_MORE_THAN then
		out = out .. ' mw-collapsed'
	end
	out = out .. '"\n|+ ' .. caption

	out = out .. '\n|- role="row"'
	out = out .. '\n! role="columnheader" aria-sort="ascending" scope="col" | ' .. TABLE_COLUMN_HEADER_YEAR
	out = out .. '\n! role="columnheader" aria-sort="none" scope="colgroup" colspan="2" | ' .. TABLE_COLUMN_HEADER_POPULATION

	out = out .. '\n|-'
	out = out .. table.concat( formattedClaims, '\n' )
	out = out .. '\n|}';

	out = out .. '\n</div>';
	return out
end

return p;