Modulo:Grafico epidemia/sandbox: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
1) Usa classe CSS anziché ripetere stili per padding
mNessun oggetto della modifica
 
(14 versioni intermedie di 3 utenti non mostrate)
Riga 1:
local yesno = require('Module:Yesno')
--[[TODO CSS:
local BarBox = unpack(require('Module:Bar box'))
.grafico-epidemia-padded {
 
padding-left: 0.4em;
local lang = mw.getContentLanguage()
padding-right: 0.4em;
local language = lang:getCode()
}
local i18n = require('Module:Grafico epidemia/i18n')[language]
]]
assert(i18n, 'no chart translations to: ' .. mw.language.fetchLanguageName(language, 'en'))
local getArgs = require('Modulo:Arguments').getArgs
local yesnomonthAbbrs = require('Modulo:Yesno'){}
for i = 1, 12 do
monthAbbrs[i] = lang:formatDate('M', '2020-' .. ('%02d'):format(i))
end
 
local p = {}
 
 
local function is(v)
function p._findIntervalRow(tRows, nTime, nTol, bAll)
return (v or '') ~= ''
-- 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._barColors(n)
function p._toggleButton(active, customtoggles, id, label)
if n == 1 then
local on = active and '' or ' mw-collapsed'
return 'Black' --morti
local off = active and ' mw-collapsed' or ''
elseif n == 2 then
local outString =
return 'Lightgreen' --guarigioni o reclbl
'<span class="mw-collapsible' .. on .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '" ' ..
elseif n == 3 then
'style="border:2px solid lightblue">' .. label .. '</span>' ..
return 'Red' --casi o altlbl1
'<span class="mw-collapsible' .. off .. customtoggles .. '" id="mw-customcollapsible-' .. id .. '">' .. label .. '</span>'
elseif n == 4 then
return outString
return 'Gold' --etichettacategoria4
end
elseif n == 5 then
 
return 'OrangeRed' --etichettacategoria5
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
return nil
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 = {}
if args[1] == nil
then barargs[1] = '⋮' .. (args.note0 or '')
else barargs[1] = lang:formatDate('d-m-Y', args[1]) .. (args.note0 or '')
end
--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 lines = mw.text.split(args.data, '\n')
local frame = mw.getCurrentFrame()
local updatePeriod = 86400 -- temporary implementation only supports daily updates
local lang = mw.getContentLanguage()
 
local function getUnix(timestamp)
return lang:formatDate('U', timestamp)
end
-- some info for changetype 'w'
local sChngTp1 = args.tipovariazione1
local sChngTp2 = args.tipovariazione2
local xData1Key = args.datoperdestra1 or 3
local xData2Key = args.datoperdestra2 or 1
xData1Key = (type(xData1Key) == "number") and (xData1Key+1) or xData1Key
xData2Key = (type(xData2Key) == "number") and (xData2Key+1) or xData2Key
local nPop = not (args.population == nil) and tonumber(args.population) or nil
local bIsW1 = sChngTp1 == 'w' and nPop
local bIsW2 = sChngTp2 == 'w' and nPop
 
local bars, rows, months, prevRow, maxparamtStats = {}, {}, {}, '', 1
local nData1Diff1Max, nData1Diff1MaxDate, nData2Diff1Max, nData2Diff1MaxDate
for k, line in pairs(lines) do
local nData1i7Max, nData1i7MaxDate, nData2i7Max, nData2i7MaxDate
local barargs, i = {}, 1
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 = mw.text.trim:find(parameter'^%s*%a') then
if string.find(parameter, '^%a') then
parameter = mw.text.split(parameter, '=')
if parameter[1] == 'alttot1' or mw.text.trim(parameter[1] == 'alttot2' then)
if parameter[1]:find('^alttot') then
parameter[2] = tonumber(frame:callParserFunction('#expr', parameter[2]))
else
if is(parameter[2]) then
maxparamparameter[2] = mathmw.maxtext.trim(maxparam, 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
ifparameter is= mw.text.trim(parameter) then
if parameter ~= '' then
if i >= 2 and i <= 6 then
parameter = tonumber(frame:callParserFunction('#expr', frame:callParserFunction('formatnum',parameter,'R')))
maxparamif = math.max(maxparam,not parameter or 1)then
error(('Data parameters 2 to 6 must not be formatted. i=%d, line=%s'):format(i, line))
end
end
barargs[i] = parameter
if i == 7 or i == 9 then
parameter = tonumber(mw.ustring.match(frame:callParserFunction('formatnum',parameter,'R'), '^%d*'))
maxparam = math.max(maxparam, parameter or 1)
end
end
i = i + 1
end
end
 
local bValid, nDateDiff
function format_thousand(v)
-- get relevant date info based on previous row
local s = string.format("%d", math.floor(v))
if barargs[1] then
local pos = string.len(s) % 3
bValid, barargs.nDate = pcall(getUnix, barargs[1])
if pos == 0 then pos = 3 end
assert(bValid, 'invalid date "' .. barargs[1] .. '"')
return string.sub(s, 1, pos)
if prevRow.nDate or prevRow.nEndDate then
.. string.gsub(string.sub(s, pos+1), "(...)", ".%1")
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 not (tBarStats1 == nil) and not (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 not (tBarStats7 == nil) then
if not (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 not (tBarStats7.nData1Diff1 == nil) then
tBarStats.nData1P7Diff1 = tBarStats7.nData1Diff1
end
end
-- if stats exist from 14 days before
if not (tBarStats14 == nil) then
if not (tBarStats14.nData1 == nil) then
tBarStats.nData1Diff14 = nData1 - tBarStats14.nData1
end
if not (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 not (tBarStats1 == nil) and not (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 not (tBarStats7 == nil) then
if not (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 not (tBarStats7.nData2Diff1 == nil) then
tBarStats.nData2P7Diff1 = tBarStats7.nData2Diff1
end
end
-- if stats exist from 14 days before
if not (tBarStats14 == nil) then
if not (tBarStats14.nData2 == nil) then
tBarStats.nData2Diff14 = nData2 - tBarStats14.nData2
end
if not (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 col == 1 then
if data == args.right1data or'alttot1' 3then
changetype num = argsbarargs.changetype1alttot1 or 'p'barargs[4]
prevnum = prevRow.alttot1 or prevRow[4]
else
elseif data == args.right2data or'alttot2' 1then
changetypenum = argsbarargs.changetype2alttot2 or 'p'barargs[6]
prevnum = prevRow.alttot2 or prevRow[6]
elseif data then
num = barargs[data+1]
prevnum = prevRow[data+1]
end
 
num = tonumber(barargs[tonumber(data) + 1])
-- changetype w
prevnum = tonumber(prevRow[tonumber(data) + 1])
if not (tBarStats == nil) and not (tBarStats["nData"..col] == nil) then
local nDiff7 = tBarStats["nData"..col.."Diff7"]
if num then -- nothing in column, source found, and data exists
valuelocal sChngCmt = num""
if col == 1 and not (nData1i7Max == nil) then
sChngCmt = "all time high: " .. mw.ustring.format('%.1f', nData1i7Max) .. " on " .. nData1i7MaxDate
if not change and yesno(barargs['firstright' .. col] ~= true) then
elseif col == 2 and not (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 not (nP7Diff1 == nil) and not (sCmnt == "") then
sCmnt = sCmnt .. ", 7 days before: +" .. lang:formatNum(nP7Diff1)
end
if not (nP14Diff1 == nil) and not (sCmnt == "") then
sCmnt = sCmnt .. ", 14 days before: +" .. lang:formatNum(nP14Diff1)
end
if col == 1 and not (nData1Diff1Max == nil) and not (sCmnt == "") then
sCmnt = sCmnt .. ", all-time high: +" .. lang:formatNum(nData1Diff1Max) .. " on " .. nData1Diff1MaxDate
end
if col == 2 and not (nData2Diff1Max == nil) and not (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
changelocal nChange = num - prevnum
if changetype == 'a' then -- change type is "absolute"
if changenChange > 0 then
change = '+' .. lang:formatNum(changenChange)
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 * changenChange / prevnum -- calculate percent
local rounding = math.abs(percent) >= 10 and "'%.0f"' or math.abs(percent) >= 1 and "'%.1f"' or "'%.2f"'
percent = tonumber(mw.ustring.rounding:format(rounding, percent)) -- round to two sigfigs
 
if percent > 0 then
change = '+' .. lang:formatNum(percent) .. '%'
Line 112 ⟶ 619:
end
else -- data has not changed since previous row
change = '='
end
else -- no data on previous row
Line 118 ⟶ 625:
end
end
value = format_thousand(value)
end
 
return value, change
end
 
if not is(barargs[7]) then
barargs[7], barargs[8] = fillCols(1, barargs[8])
end
if not is(barargs[9]) then
barargs[9], barargs[10] = fillCols(2, barargs[10])
end
 
rows[#rows+1] = barargs
barargs.prevDate = prevRow[1]
rows[#rows + 1] = barargs
prevRow = barargs
end
 
--error(mw.dumpObject(tStats))
for i=1,#rows do -- build rows
 
rows[i].divisor = tonumber(args.divisor) and tonumber(args.divisor) or maxparam / (0.95 * args.barwidth)
-- calculate and pass repetitive (except daysToEnd) parameters to each row
rows[i].numwidth = args.numwidth
local lastRow = rows[#rows]
rows[i].collapsible = args.collapsible
local total = {lastRow[2] or 0, lastRow[3] or 0, [4]=lastRow[5] or 0}
rows[i].rowsToEnd = #rows - i
total[3] = lastRow.alttot1 or lastRow[4] and lastRow[4] - total[1] - total[2] or 0
rows[i].rowheight = args.rowheight
total[5] = lastRow.alttot2 or lastRow[6] and lastRow[6] - total[1] - total[2] or 0
rows[i].duration = args.duration
local divisor = (total[1] + total[2] + total[3] + total[4] + total[5]) / (args.barwidth - 5) --should be -3 if borders didn't go inward
bars[i] = p._row(rows[i])
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 = {}, {}
return table.concat(bars)
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
function p._row(args)
'#A50026', --deaths
local barargs = {}
'SkyBlue', --recoveries
local rowDate = args.prevDate or ''
'Tomato', --cases or altlbl1
frame = mw.getCurrentFrame()
'Gold', --altlbl2
lang=mw.language.getContentLanguage()
'OrangeRed' --altlbl3
}
if args[1] then
 
if pcall(function () lang:formatDate('', args[1]) end) then
function p._legend0(args)
barargs[1] = args[1]
return
rowDate = args[1]
'<span style="font-size:90%; margin:0px">' ..
else
'<span style="background-color:' .. (args[1] or 'none') ..
barargs[1] = '<strong class="error">Error: Invalid time.</strong>'
'; 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
else
barargs[1] = '⋮'
end
local two = args[2] or 0
barargs[2] = frame:callParserFunction('#expr', two)
 
local threebarargs = args[3] or 0{}
barargs[3] = frame:callParserFunction('#expr', three)
if args['alttot1'] then
barargs[4] = frame:callParserFunction('#expr', args['alttot1'])
elseif args[4] then
barargs[4] = frame:callParserFunction('#expr', (args[4] .. '-' .. two .. '-' .. three) )
else
barargs[4] = 0
end
local five = args[5] or 0
barargs[5] = frame:callParserFunction('#expr', five)
if args['alttot2'] then
barargs[6] = frame:callParserFunction('#expr', args['alttot2'])
elseif args[6] then
barargs[6] = frame:callParserFunction('#expr', (args[6] .. '-' .. two .. '-' .. three) )
else
barargs[6] = 0
end
 
barargs.css = 'Module:Grafico epidemia/styles.css'
if args[7] then
barargs[7].float = string.gsub(args[7],",",".")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')
barargs[7] = ''
end
local function changeArg(firstright, valuecol, changecol)
local change = ''
if yesno(args['firstright' .. firstright]) == true then
change = '(n.a.)'
elseif yesno(args['firstright' .. firstright]) == false or not is(args['firstright' .. firstright]) then
if not is(args[1]) and is(args[valuecol]) then
change = '(=)'
else
change = is(args[changecol]) and '(' .. args[changecol] .. ')' or ''
change = string.gsub(change,"%.",",")
end
end
 
local function _numwidth(i)
return change
local nw = args.numwidth:sub(i,i)
return assert(numwidth[nw], 'unrecognized numwidth[' .. i .. ']')
end
 
args.numwidth = args.numwidth or 'mm'
barargs[8] = changeArg(1,7,8)
if args.numwidth:sub(1,1) == 'n' or args.numwidth:sub(2,2) == 'n' or args.numwidth:sub(4,4) == 'n' then
if args[9] then
error('"n" is only allowed in numwidth[3]')
barargs[9] = string.gsub(args[9],",",".") or ''
else
barargs[9] = ''
end
 
barargs[10] = changeArg(2,9,10)
local buffer = 0.3 --until automatic numwidth determination
local right1width, right2width = _numwidth(1) + 0.3 + _numwidth(2) + buffer, 0
barargs.divisor = args.divisor or 1
if #args.numwidth == 4 then
right2width = _numwidth(3) + _numwidth(4) + buffer
barargs.numwidth = args.numwidth
if args.numwidth:sub(3,3) ~= 'n' then
right2width = right2width + 0.3
local elapsedDays = (lang:formatDate('\U') - lang:formatDate('\U',args[1])) / 86400
if yesno(args.collapsible) == true then
if args.collapsed then
barargs.collapsed = args.collapsed
elseif ( elapsedDays > 15 ) then
barargs.collapsed = 'y'
else
barargs.collapsed = ''
end
if args.destra2 then
right2width = math.ceil(right2width / 0.88 * 100) / 100 -- from td scale to th
if args.id then
barargs.id = args.id
else
right1width = right1width + 0.8 + right2width
barargs.id = mw.ustring.lower(mw.getLanguage('it'):formatDate('M',args[1]))
right2width = 0
if elapsedDays <= 15 then
barargs.id = barargs.id .. '-l15'
end
end
else
barargs.collapsed = ''
barargs.id = ''
end
right1width = math.ceil(right1width / 0.88 * 100) / 100
return p._customBarStacked(barargs)
end
 
if tonumber(barwidth) then
function p._customBarStacked(args)
-- transform colswidth from th to td scale, add it with border-spacing, and finally transform to table scale
barargs = {}
local relwidth = math.ceil(((7.08 + right1width + right2width) * 0.88 + 0.8 * (args.destra2 and 5 or 4)) * 88) / 100
barargs.width = 'calc(' .. relwidth .. 'em + ' .. barwidth .. 'px)' --why do the bar borders go inward (no +2)?
barargs[1] = args[1]
barargs.barwidth = barwidth .. 'px'
else
local function _numwidth(n)
if nbarargs.width == 'nauto' then
barargs.barwidth = 'auto'
return 0
elseif n == 't' then
return 2.45
elseif n == 'm' then
return 3.5
elseif n == 'w' then
return 4.55
elseif n == d then
return 3.5
end
return 3.5
end
barargs.lineheight = args.rowheight
width1 = 3.5
width2 = 3.5
if args.numwidth and args.numwidth ~= '' then
width1 = _numwidth( mw.ustring.sub(args.numwidth,1,1) )
width2 = _numwidth( mw.ustring.sub(args.numwidth,2,2) )
width3 = _numwidth( mw.ustring.sub(args.numwidth,3,3) )
width4 = _numwidth( mw.ustring.sub(args.numwidth,4,4) )
end
barargs[2] =
'<span class="nowrap">' ..
'<span style="width:' .. width1 .. 'em; padding:0 0.3em 0 0; text-align:right; display:inline-block">' .. (args[7] or '') .. '</span>' ..
'<span style="width:' .. width2 .. 'em; text-align:left; display:inline-block">' .. (args[8] or '') .. '</span>\n' ..
'</span>'
if mw.ustring.len(args.numwidth) == 4 then
local padding = '0.3em'
if mw.ustring.sub(args.numwidth,3,3) == 'n' then
padding = '0'
end
barargs.note2 =
'<span style="width:' .. width3 .. 'em; padding:0 ' .. padding .. ' 0 0; text-align:right; display:inline-block">'
.. (args[9] or '') ..
'</span>' ..
'<span style="width:' .. width4 .. 'em; text-align:left; display:inline-block">' ..
(args[10] or '') ..
'</span>'
end
for i=1,5,1 do
barargs[(2*i) + 1] = p._barColors(i)
barargs[(2*i) + 2] = tonumber(mw.ustring.format("%.2f", tonumber(args[i+1])/tonumber(args.divisor) ) )
barargs['title' .. i] = args[i+1]
end
barargs.align = 'cdcc'
barargs.collapsed = args.collapsed
barargs.id = args.id
return p._barStacked(barargs)
end
 
local title = {}
function p._barStacked(args)
 
local function _align(n, default)
local function spaces(n)
if args.align and args.align ~= '' then
local anbsp = mw.ustring.sub(args.align,n,n)'&nbsp;'
return '<span class="nowrap">' .. nbsp:rep(n) .. '</span>'
if a == 'l' then
return 'left'
elseif a == 'c' then
return 'center'
elseif a == 'r' then
return 'right'
elseif a == 'd' then
return default
end
end
return default
end
 
local localita = lang:ucfirst(mw.ustring.gsub(args.localita, i18n.in_, ''))
local output = {}
local navbartitle = args.epidemia .. i18n._data .. '/' ..
if (args.idlocalita3 and args.idlocalita3 ~=.. '/' thenor '') ..
output[1] = '<tr class="mw-collapsible' .(args.localita2 (and yesno(args.collapsed)localita2 and.. ' mw-collapsed/' or '') ..
localita .. i18n._medicalCasesChart
'" id="mw-customcollapsible-' .. args.id .. '"}}>\n'
 
--local navbar = require('Module:Navbar')._navbar
title[1] = (args.pretitolo and args.pretitolo .. ' ' or '') ..
i18n.cases.. ' ' .. args.malattia .. ' ' .. args.preposizione .. ' ' .. args.localita ..
(args.localita2 and ', ' .. args.localita2 or '') ..
(args.localita3 and ', ' .. args.localita3 or '') ..
(args.posttitolo and ' ' .. args.posttitolo 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.guarigioni = args.guarigioni == nil and true or args.guarigioni
title[3] = args.guarigioni 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.destra1 = args.destra1 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:Grafico epidemia/data')._externalData
buildargs.data = externalData(args)
else
buildargs.data = args.data
output[1] = '<tr>\n'
end
-- if no right1data and right1 title is cases, use 3rd classification
buildargs.right1data = args.datoperdestra1 or args.destra1 == i18n.noOfCases and 3
if args[1]=='⋮' then
-- if no right2data and right2 title is deaths, use 1st classification
output[2] =
buildargs.right2data = args.datoperdestra2 or (args.destra2 == i18n.noOfDeaths or args.destra2 == i18n.noOfDeaths2) and 1
'<td ' .. (args.note1 and '' or 'colspan="2" ') ..
buildargs.changetype1 = (args.tipovariazione1 or args.changetype or ''):sub(1,1) -- 1st letter
'class="grafico-epidemia-padded" style="text-align:' .. _align(1,'left') .. '">' ..
buildargs.changetype2 = (args.tipovariazione2 or args.changetype or ''):sub(1,1) -- 1st letter
'⋮'..
buildargs.collapsible = args.collapsible
'</td>\n'
buildargs.duration = args.duration
else
buildargs.nooverlap = args.nooverlap
output[2] =
buildargs.population = args.population
'<td ' .. (args.note1 and '' or 'colspan="2" ') ..
 
'class="grafico-epidemia-padded" style="text-align:' .. _align(1,'left') .. '">' ..
local dateList
mw.text.trim(lang:formatDate("d-m-Y", args[1]) or '') ..
barargs.bars, dateList = p._buildBars(buildargs)
'</td>\n'
 
if buildargs.collapsible then
togglesbar = p._buildTogglesBar(dateList, args.duration, args.nooverlap)
end
 
title[7] = togglesbar and '<br />' .. togglesbar or ''
if args.note1 and args.note1 ~= '' then
barargs.title = table.concat(title)
output[3] =
 
'<td class="grafico-epidemia-padded" style="text-align:' .. _align(2,'right') .. '">' ..
barargs.left1 = '<div style="width:7.08em">' .. i18n.date .. '</div>'
args.note1 ..
barargs.right1 = '<div class=center style="width:' .. right1width .. 'em">' .. args.destra1 .. '</div>' --center isn't necessary with proper
'</td>\n'
if args.destra2 then --numwidth, but better safe than sorry
else
barargs.right2 = '<div class=center style="width:' .. right2width ..'em">' .. args.destra2 .. '</div>'
output[3] = ''
end
 
barargs.footer = args.didascalia
output[4] = '<td style="border-left:1px solid silver; border-right:1px solid silver">\n'
local box = BarBox.create(barargs)
return tostring(box)
for i=1,5,1 do
output[i+4] =
'<div' .. (args['title' .. i] and (' title=' .. args['title' .. i]) or '') ..
' style="background:' .. (args[(2*i) + 1] or 'gray') ..
'; float:left; overflow:hidden; width:' .. mw.text.trim(args[(2*i) + 2] or '0') .. 'px">' ..
'&#8203;' ..
'</div>\n'
end
output[10] = '</td>\n'
output[11] =
'<td ' .. (args.note2 and '' or 'colspan="2" ') ..
'class="grafico-epidemia-padded" style="text-align:' .. _align(3,'left') .. '">' ..
mw.text.trim(args[2] or '') ..
'</td>\n'
if args.note2 and args.note2 ~= '' then
output[12] =
'<td class="grafico-epidemia-padded" style="text-align:' .. _align(4,'right') .. '">' ..
args.note2 ..
'</td>\n'
else
output[12] = ''
end
output[13] = '</tr>\n'
return table.concat(output)
end
 
local getArgs = require('Module:Arguments').getArgs
function p.buildBars(frame)
 
return p._buildBars(frame.args)
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