Модуль:Statistical

Материал из «Знание.Вики»

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

local p = {}
 
local Regions = mw.loadData("Модуль:Statistical/Regions")
bit32 = require( 'bit32' )
 
 	local function LimitDouble(Val)
	    local MaxNumber = 2147483648
    	return Val - (math.floor(Val / MaxNumber) * MaxNumber)
	end
	
	local function shl(Val, Shift)
	    if Shift > 0 then
    	    return  LimitDouble(Val * (2 ^ Shift))
	    else
    	    return Value
	    end 
	end

	local function shr(Val, Shift)
	    if Shift > 0 then 
    		return math.floor(Val / (2 ^ Shift))
	    else
    		return Val
	    end
	end
	-- Код основан на алгоритме SuperFastHash:
    -- http://www.azillionmonkeys.com/qed/hash.html
	local function MakeHash(PlaceName)    
		local dataLength = mw.ustring.len(PlaceName)
    	if dataLength == 0 then return 0 end
		local hash = dataLength
		local remainingBytes =  math.fmod(dataLength, 2)
	    local numberOfLoops = math.floor(dataLength / 2)
	    local currentIndex = 0
    	local tmp = 0
    	while (numberOfLoops > 0) do 
			hash = LimitDouble(hash + mw.ustring.codepoint(PlaceName, currentIndex + 1))
			tmp = bit32.bxor(shl(mw.ustring.codepoint(PlaceName, currentIndex + 2), 11), hash)
			hash = bit32.bxor(shl(hash, 16), tmp)
			hash = LimitDouble(hash + shr(hash, 11))
			currentIndex = currentIndex + 2
			numberOfLoops = numberOfLoops - 1
	    end
		if remainingBytes == 1 then
			hash = LimitDouble(hash + mw.ustring.codepoint(PlaceName, currentIndex + 1))
			hash = bit32.bxor(hash, shl(hash, 10))
			hash = LimitDouble(hash + shr(hash, 1))
	    end
		hash = bit32.bxor(hash, shl(hash, 3))
		hash = LimitDouble(hash + shr(hash, 5))
		hash = bit32.bxor(hash, shl(hash, 4))
		hash = LimitDouble(hash + shr(hash, 17))
		hash = bit32.bxor(hash, shl(hash, 25))
		hash = LimitDouble(hash + shr(hash, 6))
		return hash
	end
 
local function First_less_Second(a, b)  
    local LenA = mw.ustring.len(a)
    local LenB = mw.ustring.len(b)
    for i = 1, (LenA < LenB) and LenA or LenB do
        if mw.ustring.codepoint(a, i, i) ~= mw.ustring.codepoint(b, i, i) then
            return mw.ustring.codepoint(a, i, i) < mw.ustring.codepoint(b, i, i) 
        end
    end
    return LenA < LenB
end
 
function GetHashData(PlaceHash)     
 	local NumPage = math.floor((PlaceHash - 1) / 33554432 + 2)
	if NumPage == 2 and (PlaceHash - 1) < 16777216 then NumPage = 1 end
	local templatename
	if NumPage < 10  then templatename = "Население/STA-00"..NumPage else templatename = "Население/STA-0"..NumPage	end
	local page = mw.title.new(templatename, 10)
	local RawData = page:getContent()
	HashData = RawData:match("|" .. PlaceHash .. "=([^\n<]+)")
	if (HashData=="") then HashData=nil 
	elseif (tonumber(HashData)~=nil) then HashData=tonumber(HashData) end
	return HashData
end

function p.GenerateAndReturnHash(frame)
	local args = frame:getParent().args

    if args == nil then return "Введите название объекта АТД" end
    local PlaceName = args[1]
 
    if PlaceName == nil then return "Введите название объекта АТД" end
    PlaceName = mw.text.trim(PlaceName)
    local PlaceHash = MakeHash(PlaceName)
 	local NumPage = math.floor((PlaceHash - 1) / 33554432 + 2)
	if NumPage == 2 and (PlaceHash - 1) < 16777216 then NumPage = 1 end
	local templatename
	if NumPage < 10  then templatename = "Население/STA-00"..NumPage else templatename = "Население/STA-0"..NumPage	end    
    return PlaceHash..' → '..'[[Шаблон:'..templatename..']]'
end

function p.GetStat(frame)
    local args
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args
	else
		args = frame
	end

    if args == nil then return "Введите название объекта АТД" end
    local PlaceName = args[1]

	local Format = mw.text.trim ( (args[2] or "Таблица"))
	
    if PlaceName == nil then return "Введите название объекта АТД" end
    PlaceName = mw.text.trim(PlaceName)

    local Region = args['Регион'] or ''

    local check = args['check'] or ''
    if check == '' or check == '0' or check == 'false' then
    	check = false;
    else
    	check = true;
    end
 
	local PlaceHash = MakeHash(PlaceName)
	local function FormatH()    
	    return PlaceHash
	end

    local PlaceData = nil
 	local RegionData = nil
 	local test = PlaceName:match("%((%P+)%)")
 	if Region=='' and test~='' then
 		-- Если в имени населенного пункта есть уточнение с именем региона, то можно не пользоваться индексной таблицей, то есть не вызывать GetHashData
 		local value = Regions[test]
 		if(value) then
 				RegionData = mw.loadData("Модуль:Statistical/"..value)
 				PlaceData = RegionData[PlaceHash]
 		end
 	elseif Region ~= "" then
 		-- магия, позволяющая указывать не только индекс, но и значение напрямую
 		Region = Regions[Region] or Region
 		RegionData = mw.loadData("Модуль:Statistical/"..Region)
 		PlaceData = RegionData[PlaceHash]
 	end

	if PlaceData == nil then
		local RegionPage = GetHashData(PlaceHash)
		if RegionPage == 0 then return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Совпадение хешей. Пожалуйста, укажите регион вручную, например <code><nowiki>{{ Население | " .. PlaceName .. " | " .. Format .." | Регион = Приморский край }}</nowiki></code>" .. ((args.nocat and "") or "[[Категория:Знание.Вики:Статьи с неправильными параметрами шаблона Население]]")}} end 
		if RegionPage ~= nil then
			if type(tonumber(RegionPage)) == "number" then
				-- очень плохая вещь, ибо GetHashData очень дорогая по памяти функция
				PlaceHash=tonumber(RegionPage)
				RegionPage = GetHashData(PlaceHash) 
			end
			if type(RegionPage) == "table" then RegionPage = RegionPage[PlaceName] end
			if RegionPage ~= nil then 
				RegionData = mw.loadData("Модуль:Statistical/"..RegionPage) 
				if RegionData ~= nil then PlaceData = RegionData[PlaceHash] end
			end
		end
	end 

    if PlaceData == nil then 
    	if check then
    		return 0;
    	elseif Format == 'Хеш' or Format == 'х'  then
        	return FormatH()
    	else
    		return "#Н/Д"..frame:callParserFunction{name = '#tag:ref', args = {PlaceName .." > Данные не обнаружены. Возможно, страница переименовывалась. Проверьте справочник"..  ((args.nocat and "") or "[[Категория:Знание.Вики:Статьи с неправильными параметрами шаблона Население]]")}} 
    	end
	end
	if check then
		return 1;
	end

    local LastRecord = 0
    for k in pairs(PlaceData) do
    	LastRecord = LastRecord + 1
    	if args[3] ~= nil and PlaceData[LastRecord][1] == tonumber(args[3]) then
			break
    	end
    end
    local NumRecord = LastRecord
 
	local function FormatY()    
	    return PlaceData[NumRecord][1]
	end
 
	local function FormatN()    
	    return PlaceData[NumRecord][2]
	end
 
	local function FormatS(SourceType)  
	    if ( PlaceData[NumRecord][3] == nil or PlaceData[NumRecord][3] == '' ) then
	        return ""
	    else
	        local Source1
	        local Source2 
	        if string.find(PlaceData[NumRecord][3],"%d+[A-Z]+")==1 then
	        	if RegionData['Источники'][PlaceData[NumRecord][3]] ~= nil then
	        		Source1 = RegionData['Источники'][PlaceData[NumRecord][3]][1]
	            	Source2 = PlaceData[NumRecord][3]
	        	else
	        		Source1 = PlaceData[NumRecord][3] .. '[[Категория:Знание.Вики:Статьи с неправильными источниками в модуле Statistical]]'
	            	Source2 = ""
        		end
	        else
	            Source1 = PlaceData[NumRecord][3]
	            Source2 = ""
	        end
	        if string.find(Source1, "https?://")==1 then
	            Source1 = '['..Source1..']'
	        end
			if SourceType == "и" then
				return Source1
			end
	        if Source2 == "" then
	            return frame:callParserFunction{name = '#tag:ref', args = {Source1}}
	        else
	            return frame:callParserFunction{name = '#tag:ref', args = {Source1, name = Source2}}
	        end
	    end
	end        
 
	local function FormatF()
		local lang = mw.language.getContentLanguage()
		return lang:formatNum( PlaceData[NumRecord][2] )
	end
 
	local function FormatT()    
	    if NumRecord > 1 then
	        if PlaceData[NumRecord][2] > PlaceData[NumRecord - 1][2] then
	            return "<span style='color: #0c0; font-weight:bold; font-size: larger;'>↗</span>" 
	        elseif PlaceData[NumRecord][2] < PlaceData[NumRecord - 1][2] then
	            return "<span style='color: red; font-weight:bold; font-size: larger;'>↘</span>"  
	        else
	            return "<span style='color:#0AF;'>→</span>"  
	        end
	    else
	        return ""
	    end            
	end
 
    if Format == 'Год' or Format == 'г' then
        return FormatY()    
    elseif Format == 'Безформат' or Format == 'Число' or Format == 'ч'  then
        return FormatN()    
    elseif Format == 'Ссылка' or Format == 'с'  then
        return FormatS("с")    
    elseif Format == 'Источник' or Format == 'и'  then
        return FormatS("и")    
    elseif Format == 'Формат' or Format == 'ф'  then
        return FormatF()    
    elseif Format == 'ФорматГод' or Format == 'фг'  then
        return FormatF().." ("..FormatY()..")"    
    elseif Format == 'ФорматСсылка' or Format == 'фс'  then
        return FormatF()..FormatS()    
    elseif Format == 'ФорматСсылкаГод' or Format == 'фсг'  then
        return FormatF()..FormatS().." ("..FormatY()..")"
    elseif Format == 'Тренд' or Format == 'т'  then
        return FormatT()..FormatF()    
    elseif Format == 'Значение' or Format == 'ТрендСсылка' or Format == 'тс'  then
        return FormatT()..FormatF()..FormatS()
    elseif Format == 'ТрендСсылкаГод' or Format == 'тсг'  then
        return FormatT()..FormatF()..FormatS().." ("..FormatY()..")"
    elseif Format == 'Хеш' or Format == 'х'  then
        return FormatH()
	elseif Format == 'Диаграмма' or Format == 'д'  then
		local tempHeight = 320
		local tempWidth = 800
		local tempMod = math.fmod (LastRecord, 5)
		if LastRecord < 40 then 
			tempHeight = 200  + 170 * (LastRecord - 1) / 40
			tempWidth = 200 + 600 * (LastRecord - 1) / 40
		end
		local tempGroup = ""
		local tempTooltip = ""
		local tempLegend = ""
		for k in pairs(PlaceData) do 
			NumRecord = k
			tempGroup = tempGroup .. FormatN() .. ":"
			tempTooltip = tempTooltip .. FormatF() .. " (" .. FormatY() .. "):"
			if LastRecord < 5 or math.fmod (k, 5) == tempMod  then tempLegend = tempLegend .. FormatY() end
			tempLegend = tempLegend .. ":"
		end
		tempGroup = string.sub (tempGroup, 1, string.len (tempGroup)-1)
		tempTooltip = string.sub (tempTooltip, 1, string.len (tempTooltip)-1)
		tempLegend = string.sub (tempLegend, 1, string.len (tempLegend)-1)
    	local barChart = require('Модуль:Chart')['bar chart'];
		local Diagram = {
			['height'] = tempHeight,
			['width'] = tempWidth,
			['group 1'] = tempGroup,
			['tooltip 1'] = tempTooltip,
			['colors'] = "#B0C4DE",
			['x legends'] = tempLegend,
			['group names'] = 'Численность населения',
			['default color'] = '#1E90FF'
			}    	
		local cframe = mw.getCurrentFrame();
		return barChart(cframe:newChild{ title=cframe.title, args = Diagram})
	elseif Format == 'График' or Format == 'график'  then
		local csv = "year,population,formatted\\n"
		for k in pairs(PlaceData) do 
			NumRecord = k
			csv = csv .. FormatY() .. "," .. FormatN() .. "," .. FormatF() .. "\\n"
		end
		local json = [[{
  "version": 2,
  "width": 400,
  "height": 200,

  "data": [
    {
      "name": "table",
      "values": "]] .. csv .. [[",
      "format": {
        "parse": {"year": "integer", "population": "integer", "formatted": "string"},
        "type": "csv"
      },
      // Convert year integer (2016) into a date object (2016-01-01)
      "transform": [{ "type": "formula", "field": "date", "expr": "datetime(datum.year,0,1)" }]
    }
  ],

  "scales": [
    // The dates are scaled to the "x" axis - the width of the graph
    {
      "name": "x",
      "type": "time",
      "range": "width",
      "domain": {"data": "table", "field": "date"}
    },
    // The population are scaled to the "y" axis - the height of the graph
    {
      "name": "y",
      "type": "linear",
      "range": "height",
      "domain": {"data": "table", "field": "population"}
    }
  ],

  // Simple axis with horizontal grid lines
  "axes": [
    {"type": "x", "scale": "x", "ticks": 5},
    {"type": "y", "scale": "y", "ticks": 5, "grid": true, "orient": "right", "format": "d"}
  ],

  // The graph is drawn with two elements a thick line at the top, and a semi-transparent area below
  "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"},
          "y": {"scale": "y", "field": "population", "offset": -1},
          "align": {"value": "center"},
          "opacity": {"value": "0"},
          "fill": {"value": "#000000"},
          "fontWeight": {"value": "bold"},
          "size": {"value": 4},
          "text": {"template": "{{datum.formatted}}  ({{datum.year}})"}
        },
        "hover": {"opacity": {"value": "1"}},
        "update": {"opacity": {"value": "0"}}
      }
    }
  ]
}]]
		local cframe = mw.getCurrentFrame()
		return cframe:callParserFunction{ name = '#tag:graph', args = { json, mode = 'interactive' } }
    else
        -- Формирование HTML-таблицы
	    local HTML = mw.html.create('table')

        local MaxData
        if args['Столбцов'] then
            Column = tonumber(args['Столбцов'])
        else
			Column = 7
        end
        if Column > LastRecord then Column = LastRecord end
 
        if args['Оформление'] ~= nil then
            HTML:attr('class', args['Оформление'])
        else
            HTML:attr('class', 'standard') 
        end
 
        local TempRow
        local NumRow = 0
        TempRow = HTML:tag('th'):attr('scope', 'colgroup'):attr('colspan', Column):wikitext(args['Заголовок'] or 'Численность населения')
        for i = 1, math.ceil(LastRecord / Column) do
            TempRow = HTML:tag('tr'):addClass("bright")
            for j = 1, Column do
				NumRecord = (i - 1) * Column + j
                if PlaceData[NumRecord] == nil then
                    TempRow:tag('th'):attr('scope', 'col'):wikitext("")
                else
                    TempRow:tag('th'):attr('scope', 'col'):wikitext(FormatY()..FormatS("с"))
                end
            end
 
            TempRow = HTML:tag('tr'):css('text-align', 'center')
            for j = 1, Column do
				NumRecord = (i - 1) * Column + j
                if PlaceData[NumRecord] == nil then
                    TempRow:tag('td'):wikitext("")
                else
                    TempRow:tag('td'):wikitext(FormatT()..FormatF())
                end
            end
        end            
        return tostring(HTML)
    end
    return 1, Format
end

return p