Модуль:Statistical
Версия от 22:16, 22 декабря 2021; imported>Игорь Темиров (bold на графике)
Для документации этого модуля может быть создана страница Модуль: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