Мы используем файлы cookie.
Продолжая использовать сайт, вы даете свое согласие на работу с этими файлами.
Module:Medical cases chart/sandbox
Другие языки:

    Module:Medical cases chart/sandbox

    Подписчиков: 0, рейтинг: 0
    local yesno = require('Module:Yesno')
    local BarBox = unpack(require('Module:Bar box/sandbox'))
    
    local lang = mw.getContentLanguage()
    local language = lang:getCode()
    local i18n = require('Module:Medical cases chart/i18n')[language]
    assert(i18n, 'no chart translations to: ' .. mw.language.fetchLanguageName(language, 'en'))
    local monthAbbrs = {}
    for i = 1, 12 do
    	monthAbbrs[i] = lang:formatDate('M', '2020-' .. ('%02d'):format(i))
    end
    
    local p = {}
    
    
    function p._findIntervalRow(tRows, nTime, nTol, bAll)
    	-- Loop backwards in tRows, assuming it to have monotonically increasing nDate entries in forward order.
    	-- The first row with nDate within nTime +-nTol is returned.
    	-- If nDate table entry is nil, the row will be skipped.
    	-- If moving backwards a time stamp is found dating back to before tolerance window, nil is returned.
    	-- If the end of tRows is reached without a match for the tolerance window, also nil is returned.
    	-- With bAll present and true all rows back to the specified time window will be returned, or all rows back to
    	-- the first row, that does not lie beyond nTime +-nTol if no match was found.
    	local tRet = nil
    	if bAll then
    		tRet = {}
    	end
    	for nRowNum = #tRows, 1 ,-1 do
    		if tRows[nRowNum] and tRows[nRowNum].nDate then
    			local nDiff = nTime - tRows[nRowNum].nDate
    			if nDiff > -nTol then
    				if nDiff < nTol then
    					if bAll then
    						tRet[#tRet + 1] = tRows[nRowNum]
    						return tRet
    					else
    						return tRows[nRowNum]
    					end
    				else
    					return bAll and tRet or nil
    				end
    			end
    		end
    		if bAll then
    			tRet[#tRet + 1] = tRows[nRowNum]
    		end
    	end
    	--return tRows[nRowNum]
    	return nil
    end
    
    
    function p._toggleButton(active, customtoggles, id, label)
    	local on  = active and '' or ' mw-collapsed'
    	local off = active and ' mw-collapsed' or ''
    	local outString =
    		'<span class="mw-collapsible' .. on  .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '" ' ..
    		'style="border:2px solid lightblue">' .. label .. '</span>' ..
    		'<span class="mw-collapsible' .. off .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '">' .. label  .. '</span>'
    	return outString
    end
    
    function p._yearToggleButton(year)
    	return p._toggleButton(year.l, ' mw-customtoggle-' .. year.year, year.year, year.year)
    end
    
    function p._monthToggleButton(year, month)
    	local lmon, label = lang:lc(month.mon), month.mon
    	local id = (year or '') .. lmon
    	local customtoggles = ' mw-customtoggle-' .. id
    
    	if month.s then
    		label = label .. '&nbsp;' .. month.s -- "Mmm ##"
    		if month.s ~= month.e then -- "Mmm ##–##"
    			label = label .. '–' .. month.e
    		end
    	else
    		customtoggles = customtoggles .. (month.l and customtoggles .. month.l or '')
    	end
    
    	for i, combination in ipairs(month.combinations) do
    		customtoggles = customtoggles .. ' mw-customtoggle-' .. combination -- up to 2 combinations per month so no need to table.concat()
    	end
    
    	return p._toggleButton(false, customtoggles, id, label)
    end
    
    function p._lastXToggleButton(years, duration, combinationsL)
    	local months, id = years[#years].months, 'l' .. duration
    	local i, customtoggles = #months, {' mw-customtoggle-' .. id}
    
    	if #years > 1 then
    		local year = years[#years].year
    		while months[i].l do
    			customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. year .. lang:lc(months[i].mon) .. '-' .. id
    			if i == 1 then
    				if year == years[#years].year then
    					year = years[#years-1].year
    					months = years[#years-1].months
    					i = #months
    				else -- either first month is also lastX month or lastX spans more than 2 years, which is not intended yet
    					break
    				end
    			else
    				i = i - 1
    			end
    		end
    	else
    		while i > 0 and months[i].l do
    			customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. lang:lc(months[i].mon) .. '-' .. id
    			i = i - 1
    		end
    	end
    
    	for i, combinationL in ipairs(combinationsL) do
    		customtoggles[#customtoggles+1] = ' mw-customtoggle-' .. combinationL -- up to 3 combinationsL in 90 days
    	end
    
    	return p._toggleButton(true, table.concat(customtoggles), id, mw.ustring.format(i18n.lastXDays, duration))
    end
    
    function p._buildTogglesBar(dateList, duration, nooverlap)
    	local years = {{year=dateList[1].year, months={{mon=dateList[1].mon, combinations={}}}}}
    	local months, combinationsL = years[1].months, {}
    
    	local function addMonth(month)
    		if month.mon ~= months[#months].mon then -- new month
    			if month.year ~= years[#years].year then -- new year
    				years[#years+1] = {year=month.year, months={}}
    				months = years[#years].months -- switch months list
    			end
    			months[#months+1] = {mon=month.mon, combinations={}}
    		end
    	end
    	for i = 2, #dateList do -- deduplicate years and months
    		if #dateList[i] == 0 then -- specific date
    			addMonth(dateList[i])
    			months[#months].l = months[#months].l or dateList[i].l -- so that both ...-mon and ...-mon-lX classes are created
    		elseif #dateList[i] == 1 then -- interval within month
    			addMonth(dateList[i][1])
    			months[#months].l = months[#months].l or dateList[i].l
    		else -- multimonth interval
    			for j, month in ipairs(dateList[i]) do
    				addMonth(month)
    				months[#months].combinations[#months[#months].combinations+1] = dateList[i].id
    			end
    			combinationsL[#combinationsL+1] = dateList[i].id:find('-l%d+$') and dateList[i].id
    		end
    	end
    
    	if nooverlap then
    		local lastDate = dateList[#dateList]
    		months[#months].e = tonumber(os.date('%d', lastDate.nDate or lastDate.nEndDate or lastDate.nAltEndDate)) -- end of final month
    
    		local i = #dateList
    		repeat
    			i = i - 1
    		until i == 0 or (dateList[i].mon or dateList[i][1].mon) ~= months[#months].mon
    		if i == 0 then -- start of first and final month
    			months[#months].s = tonumber(os.date('%d', dateList[1].nDate))
    		else
    			months[#months].s = 1
    		end
    	end
    
    	years[#years].l = true -- to activate toggle and respective months bar
    
    	local monthToggles, divs = {}, nil
    	if #years > 1 then
    		local yearToggles, monthsDivs = {}, {}
    		for i, year in ipairs(years) do
    			yearToggles[#yearToggles+1] = p._yearToggleButton(year)
    			monthToggles = {}
    			months = year.months
    			for j, month in ipairs(months) do
    				monthToggles[#monthToggles+1] = p._monthToggleButton(year.year, month)
    			end
    			monthsDivs[#monthsDivs+1] =
    				'<div class="mw-collapsible' .. (year.l and '' or ' mw-collapsed') ..
    				'" id="mw-customcollapsible-' .. year.year .. '">' .. table.concat(monthToggles) .. '</div>'
    		end
    		divs = '<div>' .. table.concat(yearToggles) .. '</div>' .. table.concat(monthsDivs)
    	else
    		for i, month in ipairs(months) do
    			monthToggles[#monthToggles+1] = p._monthToggleButton(nil, month)
    		end
    		divs = '<div>' .. table.concat(monthToggles) .. '</div>'
    	end
    	divs = divs .. '<div>' .. p._lastXToggleButton(years, duration, combinationsL) .. '</div>'
    	return '<div class="nomobile" style="text-align:center">' .. divs .. '</div>'
    end
    
    local numwidth = {n=0, t=2.45, m=3.5, d=3.5, w=4.55, x=5.6}
    
    local bkgClasses = {
    	'mcc-d',	--deaths
    	'mcc-r',	--recoveries
    	'mcc-c',	--cases or altlbl1
    	'mcc-a2',	--altlbl2
    	'mcc-a3'	--altlbl3
    }
    
    function p._customBarStacked(args)
    	local barargs = {}
    
    	barargs[1] = args[1]
    
    	local function _numwidth(i)
    		return args.numwidth:sub(i,i)
    	end
    
    	if args[7] or args[8] then -- is it acceptable to have one and not the other?
    		barargs[2] =
    			'<span class=mcc-r' .. _numwidth(1) .. '>' .. (args[7] or '') .. '</span>' ..
    			'<span class=mcc-l' .. _numwidth(2) .. '>' .. (args[8] or '') .. '</span>'
    	end
    	if #args.numwidth == 4 then
    		barargs.note2 = (args[9] or args[10]) and
    			(args.numwidth:sub(3,3) ~= 'n' and '<span class=mcc-r' .. _numwidth(3) .. '>' .. (args[9] or '') .. '</span>' or '') ..
    			'<span class=mcc-l' .. _numwidth(4) .. '>' .. (args[10] or '') .. '</span>'
    		or ''
    	end
    
    	for i = 1, 5 do
    		barargs[i+2] = args[i+1] / args.divisor
    		barargs['title' .. i] = args[i+1]
    	end
    
    	barargs.align = 'cdcc'
    	barargs.bkgclasses = bkgClasses
    	barargs.collapsed = args.collapsed
    	barargs.id = args.id
    
    	return BarBox.stacked(barargs)
    end
    
    function p._row(args)
    	local barargs = {}
    
    	barargs[1] = (args[1] or '⋮') .. (args.note0 or '')
    	barargs[2] = args[2] or 0
    	barargs[3] = args[3] or 0
    
    	if args['alttot1'] then
    		barargs[4] = args['alttot1']
    	elseif args[4] then
    		barargs[4] = (args[4] or 0) - (barargs[2] or 0) - (barargs[3] or 0)
    	else
    		barargs[4] = 0
    	end
    
    	barargs[5] = args[5] or 0
    
    	if args['alttot2'] then
    		barargs[6] = args['alttot2']
    	elseif args[6] then
    		barargs[6] = (args[6] or 0) - (barargs[2] or 0) - (barargs[3] or 0)
    	else
    		barargs[6] = 0
    	end
    
    	barargs[7] = args[7]
    
    	local function changeArg(firstright, valuecol, changecol)
    		local change = ''
    		if args['firstright' .. firstright] then
    			change = '(' .. i18n.na .. ')'
    		elseif not args[1] and args[valuecol] then
    			change = '(=)'
    		else
    			change = args[changecol] and '(' .. args[changecol] .. ')' or ''
    		end
    		change = change .. (args['note' .. firstright] or '')
    
    		return change ~= '' and change
    	end
    
    	barargs[8] = changeArg(1, 7, 8)
    	barargs[9] = args[9]
    	barargs[10] = changeArg(2, 9, 10)
    
    	barargs.divisor = args.divisor
    	barargs.numwidth = args.numwidth
    
    	local dates
    	if args.collapsible then
    		local duration = args.duration
    		if args.daysToEnd >= duration then
    			barargs.collapsed = true
    		else
    			barargs.collapsed = false
    		end
    
    		if args.nooverlap and args.daysToEnd < duration then
    			barargs.id = 'l' .. duration
    		elseif args.nDate then
    			dates = {year=tonumber(os.date('%Y', args.nDate)), mon=lang:formatDate('M', os.date('%Y-%m', args.nDate)),
    				l=args.daysToEnd < duration and '-l' .. duration, nDate=args.nDate}
    			barargs.id = (args.multiyear and dates.year or '') .. lang:lc(dates.mon) .. (dates.l or '')
    		else
    			local id, y, m, ey, em = {},
    				tonumber(os.date('%Y', args.nStartDate or args.nAltStartDate)),
    				tonumber(os.date('%m', args.nStartDate or args.nAltStartDate)),
    				tonumber(os.date('%Y', args.nEndDate   or args.nAltEndDate  )),
    				tonumber(os.date('%m', args.nEndDate   or args.nAltEndDate  ))
    			dates = {nStartDate=args.nStartDate, nAltStartDate=args.nAltStartDate, nEndDate=args.nEndDate, nAltEndDate=args.nAltEndDate}
    
    			repeat
    				id[#id+1] = (args.multiyear and y or '') .. lang:lc(monthAbbrs[m])
    				dates[#dates+1] = {year=y, mon=monthAbbrs[m]}
    				y = y + math.floor(m / 12)
    				m = m % 12 + 1
    			until y == ey and m > em or y > ey
    
    			dates.l = args.daysToEnd < duration and '-l' .. duration
    			id = table.concat(id, '-') .. (dates.l or '')
    			barargs.id = id
    			dates.id = id
    		end
    	else
    		barargs.collapsed = false
    	end
    
    	return p._customBarStacked(barargs), dates
    end
    
    function p._buildBars(args)
    	local frame = mw.getCurrentFrame()
    	local updatePeriod = 86400 -- temporary implementation only supports daily updates
    
    	local function getUnix(timestamp)
    		return lang:formatDate('U', timestamp)
    	end
    	
    	-- some info for changetype 'w'
    	local sChngTp1 = args.changetype1
    	local sChngTp2 = args.changetype2
    	local xData1Key = args.right1data or 3
    	local xData2Key = args.right2data or 1
    	xData1Key = (type(xData1Key) == "number") and (xData1Key+1) or xData1Key
    	xData2Key = (type(xData2Key) == "number") and (xData2Key+1) or xData2Key
    	local nPop = (args.population ~= nil) and tonumber(args.population) or nil
    	local bIsW1 = sChngTp1 == 'w' and nPop
    	local bIsW2 = sChngTp2 == 'w' and nPop
    
    	local rows, prevRow, tStats = {}, {}, {}
    	local nData1Diff1Max, nData1Diff1MaxDate, nData2Diff1Max, nData2Diff1MaxDate
    	local nData1i7Max, nData1i7MaxDate, nData2i7Max, nData2i7MaxDate
    	for line in mw.text.gsplit(args.data, '\n') do
    		local i, barargs = 1, {}
    		-- line parameter parsing, basic type/missing value handling
    		for parameter in mw.text.gsplit(line, ';') do
    			if parameter:find('^%s*%a') then
    				parameter = mw.text.split(parameter, '=')
    				parameter[1] = mw.text.trim(parameter[1])
    				if parameter[1]:find('^alttot') then
    					parameter[2] = tonumber(frame:callParserFunction('#expr', parameter[2]))
    				else
    					parameter[2] = mw.text.trim(parameter[2])
    					if parameter[1]:find('^firstright') then
    						parameter[2] = yesno(parameter[2])
    					elseif parameter[2] == '' then
    						parameter[2] = nil
    					end
    				end
    				barargs[parameter[1]] = parameter[2]
    			else
    				parameter = mw.text.trim(parameter)
    				if parameter ~= '' then
    					if i >= 2 and i <= 6 then
    						parameter = tonumber(frame:callParserFunction('#expr', parameter))
    						if not parameter then
    							error(('Data parameters 2 to 6 must not be formatted. i=%d, line=%s'):format(i, line))
    						end
    					end
    					barargs[i] = parameter
    				end
    				i = i + 1
    			end
    		end
    
    		local bValid, nDateDiff
    		-- get relevant date info based on previous row
    		if barargs[1] then
    			bValid, barargs.nDate = pcall(getUnix, barargs[1])
    			assert(bValid, 'invalid date "' .. barargs[1] .. '"')
    			if prevRow.nDate or prevRow.nEndDate then
    				nDateDiff = barargs.nDate - (prevRow.nDate or prevRow.nEndDate)
    				if nDateDiff > updatePeriod then
    					if nDateDiff == 2 * updatePeriod then
    						prevRow = {nDate=barargs.nDate-updatePeriod}
    						prevRow[1] = os.date('%Y-%m-%d', prevRow.nDate)
    					else
    						prevRow = {nStartDate=(prevRow.nDate or prevRow.nEndDate)+updatePeriod, nEndDate=barargs.nDate-updatePeriod}
    					end
    					rows[#rows+1] = prevRow
    				end
    			else
    				prevRow.nEndDate = barargs.nDate - updatePeriod
    				if prevRow.nStartDate == prevRow.nEndDate then
    					prevRow.nDate = prevRow.nEndDate
    					prevRow[1] = os.date('%Y-%m-%d', prevRow.nDate)
    				-- as nAltStartDate assumes a minimal multiday interval, it's possible for it to be greater if a true previous span is 1 day
    				elseif prevRow.nAltStartDate and prevRow.nAltStartDate >= prevRow.nEndDate then
    					error('a row in a consecutive intervals group is 1 day long and misses the date parameter')
    				end
    			end
    		else
    			if barargs.enddate then
    				bValid, barargs.nEndDate = pcall(getUnix, barargs.enddate)
    				assert(bValid, 'invalid enddate "' .. barargs.enddate .. '"')
    			end
    			if prevRow.nDate or prevRow.nEndDate then
    				barargs.nStartDate = (prevRow.nDate or prevRow.nEndDate) + updatePeriod
    				if barargs.nStartDate == barargs.nEndDate then
    					barargs.nDate = barargs.nEndDate
    					barargs[1] = os.date('%Y-%m-%d', barargs.nDate)
    				end
    			else
    				prevRow.nAltEndDate = (prevRow.nStartDate or prevRow.nAltStartDate) + updatePeriod
    				barargs.nAltStartDate = prevRow.nAltEndDate + updatePeriod
    				if barargs.nEndDate and barargs.nAltStartDate >= barargs.nEndDate then
    					error('a row in a consecutive intervals group is 1 day long and misses the date parameter')
    				end
    			end
    		end
    
    		-- update tStats if at least one column changetype is 'w'
    		local tBarStats = nil
    		if barargs[1] and (bIsW1 or bIsW2) then
    			bValid, barargs.nDate = pcall(getUnix, barargs[1])
    			assert(bValid, 'invalid date "' .. barargs[1] .. '"')
    			barargs.nDate = tonumber(barargs.nDate)
    			tBarStats = {}
    			local tBarStats1 = tStats[barargs.nDate-86400] -- previous days info
    			local tBarStats7 = tStats[barargs.nDate-604800] -- 7 days before info
    			local tBarStats14 = tStats[barargs.nDate-1209600] -- 14 days before info
    			local nData1 = barargs[xData1Key] or barargs[4]
    			local nData2 = barargs[xData2Key] or barargs[2]
    				
    				
    --	local nData1Diff1Max, nData1Diff1MaxDate, nData12Diff1Max, nData2Diff1MaxDate
    --	local nData1i7Max, nData1i7MaxDate, nData12i7Max, nData2i7MaxDate
    
    			if bIsW1 and nData1 then
    				tBarStats.nData1 = nData1
    				-- if stats exist from day before
    				if (tBarStats1 ~= nil) and (tBarStats1.nData1 ~= nil) then
    					tBarStats.nData1Diff1 = nData1 - tBarStats1.nData1
    					if nData1Diff1Max == nil or nData1Diff1Max < tBarStats.nData1Diff1 then
    						nData1Diff1Max = tBarStats.nData1Diff1
    						nData1Diff1MaxDate = barargs[1]
    					end
    				end
    				-- if stats exist from 7 days before
    				if (tBarStats7 ~= nil) then
    					if (tBarStats7.nData1 ~= nil) then
    						tBarStats.nData1Diff7 = nData1 - tBarStats7.nData1
    						if nData1i7Max == nil or nData1i7Max < tBarStats.nData1Diff7/nPop*100000 then
    							nData1i7Max = tBarStats.nData1Diff7/nPop*100000
    							nData1i7MaxDate = barargs[1]
    						end
    					end
    					if (tBarStats7.nData1Diff1 ~= nil) then
    						tBarStats.nData1P7Diff1 = tBarStats7.nData1Diff1
    					end
    				end
    				-- if stats exist from 14 days before
    				if (tBarStats14 ~= nil) then
    					if (tBarStats14.nData1 ~= nil) then
    						tBarStats.nData1Diff14 = nData1 - tBarStats14.nData1
    					end
    					if (tBarStats14.nData1Diff1 ~= nil) then
    						tBarStats.nData1P14Diff1 = tBarStats14.nData1Diff1
    					end
    				end
    			end
    			if bIsW2 and nData2 then
    				tBarStats.nData2 = nData2
    				-- if stats exist from day before
    				if (tBarStats1 ~= nil) and (tBarStats1.nData2 ~= nil) then
    					tBarStats.nData2Diff1 = nData2 - tBarStats1.nData2
    					if nData2Diff1Max == nil or nData2Diff1Max < tBarStats.nData2Diff1 then
    						nData2Diff1Max = tBarStats.nData2Diff1
    						nData2Diff1MaxDate = barargs[1]
    					end
    				end
    				-- if stats exist from 7 days before
    				if (tBarStats7 ~= nil) then
    					if (tBarStats7.nData2 ~= nil) then
    						tBarStats.nData2Diff7 = nData2 - tBarStats7.nData2
    						if nData2i7Max == nil or nData2i7Max < tBarStats.nData2Diff7/nPop*100000 then
    							nData2i7Max = tBarStats.nData2Diff7/nPop*100000
    							nData2i7MaxDate = barargs[1]
    						end
    					end
    					if (tBarStats7.nData2Diff1 ~= nil) then
    						tBarStats.nData2P7Diff1 = tBarStats7.nData2Diff1
    					end
    				end
    				-- if stats exist from 14 days before
    				if (tBarStats14 ~= nil) then
    					if (tBarStats14.nData2 ~= nil) then
    						tBarStats.nData2Diff14 = nData2 - tBarStats14.nData2
    					end
    					if (tBarStats14.nData2Diff1 ~= nil) then
    						tBarStats.nData2P14Diff1 = tBarStats14.nData2Diff1
    					end
    				end
    			end
    			tStats[barargs.nDate] = tBarStats
    		end
    
    		local function fillCols(col, change)
    			local data = args['right' .. col .. 'data']
    			local changetype = args['changetype' .. col]
    			local value, num, prevnum
    
    			if data == 'alttot1' then
    				num = barargs.alttot1 or barargs[4]
    				prevnum = prevRow.alttot1 or prevRow[4]
    			elseif data == 'alttot2' then
    				num = barargs.alttot2 or barargs[6]
    				prevnum = prevRow.alttot2 or prevRow[6]
    			elseif data then
    				num = barargs[data+1]
    				prevnum = prevRow[data+1]
    			end
    
    			-- changetype w
    			if (tBarStats ~= nil) and (tBarStats["nData"..col] ~= nil) then
    				local nDiff7 = tBarStats["nData"..col.."Diff7"]
    				local sChngCmt = ""
    				if col == 1 and (nData1i7Max ~= nil) then
    					sChngCmt = "all time high: " .. mw.ustring.format('%.1f', nData1i7Max) .. " on " .. nData1i7MaxDate
    				elseif col == 2 and (nData2i7Max ~= nil) then
    					sChngCmt = "all time high: " .. mw.ustring.format('%.1f', nData2i7Max) .. " on " .. nData2i7MaxDate
    				end
    				if nDiff7 == nil then
    					change = i18n.na
    				else
    					change = '<span title="'.. sChngCmt .. '">' .. tostring(mw.ustring.format('%.1f', nDiff7/args.population*100000)) .. '</span>'
    				end
    				local nValue = tBarStats["nData"..col]
    				local nDiff1 = tBarStats["nData"..col.."Diff1"]
    				local nP7Diff1 = tBarStats["nData"..col.."P7Diff1"]
    				local nP14Diff1 = tBarStats["nData"..col.."P14Diff1"]
    				local sCmnt
    				if nDiff1 == nil then
    					sCmnt = ""
    				else
    					sCmnt = "daily change: +" .. lang:formatNum(nDiff1)
    				end
    				if (nP7Diff1 ~= nil) and (sCmnt ~= "") then
    					sCmnt = sCmnt .. ", 7 days before: +" .. lang:formatNum(nP7Diff1)
    				end
    				if (nP14Diff1 ~= nil) and (sCmnt ~= "") then
    					sCmnt = sCmnt .. ", 14 days before: +" .. lang:formatNum(nP14Diff1)
    				end
    				if col == 1 and (nData1Diff1Max ~= nil) and (sCmnt ~= "") then
    					sCmnt = sCmnt .. ", all-time high: +" .. lang:formatNum(nData1Diff1Max) .. " on " .. nData1Diff1MaxDate
    				end
    				if col == 2 and (nData2Diff1Max ~= nil) and (sCmnt ~= "") then
    					sCmnt = sCmnt .. ", all-time high: +" .. lang:formatNum(nData2Diff1Max) .. " on " .. nData2Diff1MaxDate
    				end
    				value = '<span title="' .. sCmnt ..'">' .. lang:formatNum(nValue)  .. '</span>'
    			
    			elseif data and num then -- nothing in column, source found, and data exists
    				value = changetype == 'o' and '' or lang:formatNum(num) -- set value to num if changetype isn't 'o'
    				if not change and not barargs['firstright' .. col] then
    					if prevnum and prevnum ~= 0 then -- data on previous row
    						if num - prevnum ~= 0 then --data has changed since previous row
    							local nChange = num - prevnum
    							if changetype == 'a' then -- change type is "absolute"
    								if nChange > 0 then
    									change = '+' .. lang:formatNum(nChange)
    								end
    							elseif changetype == 'w' and args.population then -- changetype == 'r' or 
    								 -- change type is "r"(olling average over 7 days period) or "w"(eekly incidence per 100.000 pop)
    								if barargs.nDate and rows then
    									-- find data row from 7 days before +- 1 hour
    									local tIntervRow = p._findIntervalRow(rows, barargs.nDate-7*24*3600, 3600, false)
    									local tPrevDayRow = p._findIntervalRow(rows, barargs.nDate-24*3600, 3600, false)
    									if tIntervRow then
    										local nDatCol = (col==1) and 4 or 2
    										local nDiff = tIntervRow[nDatCol] and (num - tIntervRow[nDatCol]) or nil
    										if changetype == 'r' then
    											change = nDiff and ('<span title="7 days rolling average of daily change">r7: ' .. tostring(mw.ustring.format('%.0f', nDiff/7)) .. '</span>') or i18n.na
    										else
    											change = nDiff and ('<span title="7 days incidence per 100,000 population.">' .. tostring(mw.ustring.format('%.1f', nDiff/args.population*100000)) .. '</span>') or i18n.na
    											if tPrevDayRow and tPrevDayRow[nDatCol] then
    												value = '<span title="daily change: +' .. lang:formatNum(num - tPrevDayRow[nDatCol]) .. '>' .. value  .. '</span>'
    											end
    										end
    									else
    										change = i18n.na
    									end
    								else
    									change = i18n.na
    								end
    
    
    							else -- change type is "percent", "only percent" or undefined
    								local percent = 100 * nChange / prevnum -- calculate percent
    								local rounding = math.abs(percent) >= 10 and '%.0f' or math.abs(percent) >= 1 and '%.1f' or '%.2f'
    								percent = tonumber(rounding:format(percent)) -- round to two sigfigs
    
    								if percent > 0 then
    									change = '+' .. lang:formatNum(percent) .. '%'
    								elseif percent < 0 then
    									change = lang:formatNum(percent) .. '%'
    								else
    									change = '='
    								end
    							end
    						else -- data has not changed since previous row
    							change = '='
    						end
    					else -- no data on previous row
    						barargs['firstright' .. col] = true -- set to (n.a.)
    					end
    				end
    			end
    
    			return value, change
    		end
    
    		if not barargs[7] then
    			barargs[7], barargs[8] = fillCols(1, barargs[8])
    		end
    		if not barargs[9] then
    			barargs[9], barargs[10] = fillCols(2, barargs[10])
    		end
    
    		rows[#rows+1] = barargs
    		prevRow = barargs
    	end
    
    	--error(mw.dumpObject(tStats))
    
    	-- calculate and pass repetitive (except daysToEnd) parameters to each row
    	local lastRow = rows[#rows]
    	local total = {lastRow[2] or 0, lastRow[3] or 0, [4]=lastRow[5] or 0}
    	total[3] = lastRow.alttot1 or lastRow[4] and lastRow[4] - total[1] - total[2] or 0
    	total[5] = lastRow.alttot2 or lastRow[6] and lastRow[6] - total[1] - total[2] or 0
    	local divisor = (total[1] + total[2] + total[3] + total[4] + total[5]) / (args.barwidth - 5) --should be -3 if borders didn't go inward
    	local firstDate, lastDate = rows[1].nDate, lastRow.nDate or lastRow.nEndDate
    	local multiyear = os.date('%Y', firstDate) ~= os.date('%Y', lastDate - (args.nooverlap and args.duration * 86400 or 0))
    	if args.collapsible ~= false then
    		args.collapsible = (lastDate - firstDate) / 86400 >= args.duration
    	end
    
    	local bars, dateList = {}, {}
    	for i, row in ipairs(rows) do -- build rows
    		row.divisor = divisor
    		row.numwidth = args.numwidth
    		row.collapsible = args.collapsible
    		row.duration = args.duration
    		row.nooverlap = args.nooverlap
    		row.daysToEnd = (lastDate - (row.nDate or row.nEndDate or row.nAltEndDate)) / 86400
    		row.multiyear = multiyear
    
    		bars[#bars+1], dateList[#dateList+1] = p._row(row)
    	end
    
    	return table.concat(bars, '\n'), dateList
    end
    
    p._barColors = { -- also in styles.css
    	'#A50026',	--deaths
    	'SkyBlue',	--recoveries
    	'Tomato',	--cases or altlbl1
    	'Gold',		--altlbl2
    	'OrangeRed'	--altlbl3
    }
    
    function p._legend0(args)
    	return
    		'<span style="font-size:90%; margin:0px">' ..
    			'<span style="background-color:' .. (args[1] or 'none') ..
    			'; border:' .. (args.border or 'none') ..
    			'; color:' .. (args[1] or 'none') .. '">' ..
    				'&nbsp;&nbsp;&nbsp;&nbsp;' .. '</span>' ..
    			'&nbsp;' .. (args[2] or '') .. '</span>'
    end
    
    function p._chart(args)
    	for key, value in pairs(args) do
    		if ({float=1, barwidth=1, numwidth=1, changetype=1})[key:gsub('%d', '')] then
    			args[key] = value:lower()
    		end
    	end
    
    	local barargs = {}
    
    	barargs.css = 'Module:Medical cases chart/styles.css'
    	barargs.float = args.float or 'right'
    
    	args.barwidth = args.barwidth or 'medium'
    	local barwidth
    	if args.barwidth == 'thin' then
    		barwidth = 120
    	elseif args.barwidth == 'medium' then
    		barwidth = 280
    	elseif args.barwidth == 'wide' then
    		barwidth = 400
    	elseif args.barwidth == 'auto' then
    		barwidth = 'auto'
    	else
    		error('unrecognized barwidth')
    	end
    
    	local function _numwidth(i)
    		local nw = args.numwidth:sub(i,i)
    		return assert(numwidth[nw], 'unrecognized numwidth[' .. i .. ']')
    	end
    
    	args.numwidth = args.numwidth or 'mm'
    	if args.numwidth:sub(1,1) == 'n' or args.numwidth:sub(2,2) == 'n' or args.numwidth:sub(4,4) == 'n' then
    		error('"n" is only allowed in numwidth[3]')
    	end
    
    	local buffer = 0.3 --until automatic numwidth determination
    	local right1width, right2width = _numwidth(1) + 0.3 + _numwidth(2) + buffer, 0
    	if #args.numwidth == 4 then
    		right2width = _numwidth(3) + _numwidth(4) + buffer
    		if args.numwidth:sub(3,3) ~= 'n' then
    			right2width = right2width + 0.3
    		end
    		if args.right2 then
    			right2width = math.ceil(right2width / 0.88 * 100) / 100 -- from td scale to th
    		else
    			right1width = right1width + 0.8 + right2width
    			right2width = 0
    		end
    	end
    	right1width = math.ceil(right1width / 0.88 * 100) / 100
    
    	if tonumber(barwidth) then
    		-- transform colswidth from th to td scale, add it with border-spacing, and finally transform to table scale
    		local relwidth = math.ceil(((7.08 + right1width + right2width) * 0.88 + 0.8 * (args.right2 and 5 or 4)) * 88) / 100
    		barargs.width = 'calc(' .. relwidth .. 'em + ' .. barwidth .. 'px)' --why do the bar borders go inward (no +2)?
    		barargs.barwidth = barwidth .. 'px'
    	else
    		barargs.width = 'auto'
    		barargs.barwidth = 'auto'
    	end
    	barargs.lineheight = args.rowheight
    
    	local title = {}
    
    	local function spaces(n)
    		local nbsp = '&nbsp;'
    		return '<span class="nowrap">' .. nbsp:rep(n) .. '</span>'
    	end
    
    	local location = lang:ucfirst(mw.ustring.gsub(args.location, i18n.the_, ''))
    	local navbartitle = args.outbreak .. i18n._data .. '/' ..
    		(args.location3 and args.location3 .. '/' or '') ..
    		(args.location2 and args.location2 .. '/' or '') ..
    		location .. i18n._medicalCasesChart
    
    	local navbar = require('Module:Navbar')._navbar
    	title[1] = (args.pretitle and args.pretitle .. ' ' or '') ..
    		args.disease .. ' ' .. i18n.casesIn .. ' ' .. args.location ..
    		(args.location2 and ', ' .. args.location2 or '') ..
    		(args.location3 and ', ' .. args.location3 or '') ..
    		(args.posttitle and ' ' .. args.posttitle or '') .. spaces(2) ..'(' ..
    		navbar({navbartitle, titleArg=':' .. mw.getCurrentFrame():getParent():getTitle(), mini=1, nodiv=1}) ..
    		')<br />'
    
    	title[2] = p._legend0({p._barColors[1], i18n.deaths})
    	args.recoveries = args.recoveries == nil and true or args.recoveries
    	title[3] = args.recoveries and spaces(3) .. p._legend0({p._barColors[2], args.reclbl or i18n.recoveries}) or ''
    	title[4] = args.altlbl1 ~= 'hide' and spaces(3) .. p._legend0({p._barColors[3], args.altlbl1 or i18n.activeCases}) or ''
    	title[5] = args.altlbl2 and spaces(3) .. p._legend0({p._barColors[4], args.altlbl2}) or ''
    	title[6] = args.altlbl3 and spaces(3) .. p._legend0({p._barColors[5], args.altlbl3}) or ''
    
    	local togglesbar, buildargs = nil, {}
    
    	args.right1 = args.right1 or i18n.noOfCases
    	args.duration = args.duration or 15
    	args.nooverlap = args.nooverlap or false
    
    	buildargs.barwidth = tonumber(barwidth) or 280
    	buildargs.numwidth = args.numwidth:gsub('d', 'm')
    	if args.datapage then
    		local externalData = require('Module:Medical cases chart/data')._externalData
    		buildargs.data = externalData(args)
    	else
    		buildargs.data = args.data
    	end
    	-- if no right1data and right1 title is cases, use 3rd classification
    	buildargs.right1data = args.right1data or args.right1 == i18n.noOfCases and 3
    	-- if no right2data and right2 title is deaths, use 1st classification
    	buildargs.right2data = args.right2data or (args.right2 == i18n.noOfDeaths or args.right2 == i18n.noOfDeaths2) and 1
    	buildargs.changetype1 = (args.changetype1 or args.changetype or ''):sub(1,1) -- 1st letter
    	buildargs.changetype2 = (args.changetype2 or args.changetype or ''):sub(1,1) -- 1st letter
    	buildargs.collapsible = args.collapsible
    	buildargs.duration = args.duration
    	buildargs.nooverlap = args.nooverlap
    	buildargs.population = args.population
    
    	local dateList
    	barargs.bars, dateList = p._buildBars(buildargs)
    
    	if buildargs.collapsible then
    		togglesbar = p._buildTogglesBar(dateList, args.duration, args.nooverlap)
    	end
    
    	title[7] = togglesbar and '<br />' .. togglesbar or ''
    	barargs.title = table.concat(title)
    
    	barargs.left1 = '<div style="width:7.08em">' .. i18n.date .. '</div>'
    	barargs.right1 = '<div class=center style="width:' .. right1width .. 'em">' .. args.right1 .. '</div>' --center isn't necessary with proper
    	if args.right2 then																					   --numwidth, but better safe than sorry
    		barargs.right2 = '<div class=center style="width:' .. right2width ..'em">' .. args.right2 .. '</div>'
    	end
    
    	barargs.footer = args.footer
    	local box = BarBox.create(barargs)
    	return tostring(box)
    end
    
    local getArgs = require('Module:Arguments').getArgs
    
    function p.barColors(frame)
    	local args = getArgs(frame)
    	return p._barColors[tonumber(args[1])]
    end
    
    function p.chart(frame)
    	local args = getArgs(frame, {
    		valueFunc = function (key, value)
    			if value and value ~= '' then
    				key = key:gsub('%d', '')
    				if ({rowheight=1, duration=1, rightdata=1})[key] then -- if key in {...}
    					return tonumber(value) or value
    				end
    				if ({recoveries=1, collapsible=1, nooverlap=1})[key] then
    					return yesno(value)
    				end
    				return value
    			end
    			return nil
    		end
    	})
    	return p._chart(args)
    end
    
    return p
    

    Новое сообщение