Mô đun:Medical cases chart

local yesno = require('Module:Yesno')
local barBox = require('Module:Bar box')

local lang = mw.getLanguage("en")
local i18n = require('Module:Medical cases chart/i18n')["vi"]
local monthAbbrs = {}
for i = 1, 12 do
monthAbbrs[i] = lang:formatDate('M', '2020-' .. ('%02d'):format(i))
end

local function is(v)
return (v or ) ~=
end

local p = {}

function p._toggleButton(active, customtoggles, id, label)
local on = active and or ' mw-collapsed'
local off = active and ' mw-collapsed' or
local outString =
.. p._monthStr(label) .. ..
.. p._monthStr(label) ..
return outString
end

function p._monthStr (str)
if str "Jan" then return "Thg 1"
elseif str "Feb" then return "Thg 2"
elseif str "Mar" then return "Thg 3"
elseif str "Apr" then return "Thg 4"
elseif str "May" then return "Thg 5"
elseif str "Jun" then return "Thg 6"
elseif str "Jul" then return "Thg 7"
elseif str "Aug" then return "Thg 8"
elseif str "Sep" then return "Thg 9"
elseif str "Oct" then return "Thg 10"
elseif str "Nov" then return "Thg 11"
elseif str "Dec" then return "Thg 12"
end
return str
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, duration .. '&nbsp;ngày gần nhất')
end

function p._buildTogglesBar(dateList, duration, nooverlap)
local years = }}}
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] =
.. table.concat(monthToggles) ..
end
divs = .. table.concat(yearToggles) .. .. table.concat(monthsDivs)
else
for i, month in ipairs(months) do
monthToggles[monthToggles+1] = p._monthToggleButton(nil, month)
end
divs = .. table.concat(monthToggles) ..
end
divs = divs .. .. p._lastXToggleButton(years, duration, combinationsL) ..

return .. divs ..
end

local numwidth = {n=0, t=2.45, m=3.5, d=3.5, w=4.55, x=5.6}

local bkgClasses = {
'mcc-d', --tử vong
'mcc-r', --hồi phục
'mcc-c', --số ca hoặc altlbl1
'mcc-a2', --altlbl2
'mcc-a3' --altlbl3
}

function p._customBarStacked(args)
local barargs = {}
barargs[1] = args[1]

-- (next update)
-- local function _numwidth(i)
-- return args.numwidth:sub(i,i)
-- end

local function _numwidth(nw)
if nw 'n' then
return 0
elseif nw 't' then
return 2.45
elseif nw 'm' or nw 'd' then
return 3.5
elseif nw 'w' then
return 4.55
elseif nw 'x' then
return 5.6
end

return 3.5
end

if args[7] or args[8] then -- is it acceptable to have one and not the other?
barargs[2] =
.. (args[7] or ) .. ..
.. (args[8] or ) ..
end

if args.numwidth == 4 then
barargs.note2 = (args[9] or args[10]) and (args.numwidth:sub(3,3) ~= 'n' and .. (args[9] or ) .. or ) ..
.. (args[10] or ) .. or

local pad = args.numwidth:sub(3, 3) == 'n' and '0' or '0.3em'
end

width1 = 3.5
width2 = 3.5
if is(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] =
.. (args[7] or ) .. ..
.. (args[8] or ) ..

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 =
.. (args[9] or ) .. ..
.. (args[10] or ) ..
end

for i=1,5 do
barargs[2i + 1] = p._barColors[i]
barargs[2i + 2] = (tonumber(args[i+1]) or 0)/(tonumber(args.divisor) or 1)
barargs['title' .. i] = args[i+1]
end

barargs.align = 'cdcc'
barargs.collapsed = args.collapsed
barargs.id = args.id
barargs.rowstyle = is(tonumber(args.rowheight)) and ('line-height:'..args.rowheight..';') or nil

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 yesno(args['firstright' .. firstright]) == true then
change = '(' .. i18n.na .. ')'
elseif yesno(args['firstright' .. firstright]) == false or not is(args['firstright' .. firstright]) then
if not is(args[1]) and is(args[valuecol]) then
change = '(' .. i18n['='] .. ')'
else
change = is(args[changecol]) and '(' .. args[changecol] .. ')' or
end
end

return change
end

barargs[8] = changeArg(1,7,8)
barargs[9] = args[9] or
barargs[10] = changeArg(2,9,10)

barargs.divisor = args.divisor
barargs.numwidth = args.numwidth
barargs.rowheight = args.rowheight

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 em or y > ey

dates.l = args.daysToEnd = 2 and i 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)
elseif prevRow.nAltStartDate and prevRow.nAltStartDate >= prevRow.nEndDate then
error('Một dòng trong khoảng thời gian liên tiếp dài 1 ngày thiếu một ngày')
end
end
else
if barargs.enddate then
bValid, barargs.nEndDate = pcall(getUnix, barargs.enddate)
assert(bValid, 'giá trị enddate không hợp lệ "' .. 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('Một dòng trong khoảng thời gian liên tiếp dài 1 ngày thiếu một ngày')
end
end
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

if is(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 yesno(barargs['firstright' .. col] ~= true) then
if prevnum and prevnum ~= 0 then -- data on previous row
if num - prevnum ~= 0 then --data has changed since previous row
change = num-prevnum
if changetype == 'a' then -- change type is "absolute"
if change > 0 then
change = '+' .. lang:formatNum(change)
end
else -- change type is "percent", "only percent" or undefined
local percent = 100 change / 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
percent = tonumber(mw.ustring.format(rounding, percent)) -- round to two sigfigs

if percent > 0 then
change = '+' .. lang:formatNum(percent) .. '%'
elseif percent = args.duration
end

local bars, dateList = {}, {}
for i, row in ipairs(rows) do -- build rows
row.divisor = tonumber(args.divisor) and tonumber(args.divisor) or maxparam / (0.92 args.barwidth)--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

-- func -> obj
p._barColors = {
'A50026', --tử vong
'SkyBlue', --hồi phục
'Tomato', --số ca hoặc altlbl1
'Gold', --altlbl2
'OrangeRed' --altlbl3
}

function p._legend0(args)
return ..
..
'&nbsp;&nbsp;&nbsp;&nbsp;' .. ..
'&nbsp;' .. (args[2] or ) ..
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 = 'Mô đun: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('barwidth không tồn tại')
end

local function _numwidth(i)
local nw = args.numwidth:sub(i,i)
return assert(numwidth[nw], 'unrecognized numwidth[' .. i .. ']')
end

local numwidth = 120
local right1 = numwidth - 8 -- -8 because of padding
if args.numwidth then
numwidth = _numwidth(1) + 10 + _numwidth(2)
if mw.ustring.len(args.numwidth) == 4 then
numwidth = numwidth + _numwidth(3) + _numwidth(4)
if mw.ustring.sub(args.numwidth,3,3) == 'n' then
numwidth = numwidth + 6
else
numwidth = numwidth + 10
end
end

right1 = _numwidth(1) + 2 + _numwidth(2)
if not args.right2 and mw.ustring.len(args.numwidth) == 4 then
right1 = right1 + _numwidth(3) + _numwidth(4)
if mw.ustring.sub(args.numwidth,3,3) == 'n' then
numwidth = numwidth + 6
else
numwidth = numwidth + 10
end
end
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" chỉ được cho phép trong 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

local barwidth = 280

if tonumber(barwidth) then
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 .. nbsp:rep(n) ..
end

local location = args.location
location = mw.ustring.upper(mw.ustring.sub(location,1,1)) .. mw.ustring.sub(location,2)
local navbartitle = args.outbreak .. '/' ..
(args.location3 and args.location3 .. '/' or ) ..
(args.location2 and args.location2 .. '/' or ) ..
'Biểu đồ số ca nhiễm tại ' .. location

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}) ..
')'
title[2] = p._legend0({p._barColors[1], i18n.noOfDeaths})
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

buildargs.right1data = args.right1data or args.right1 == i18n.noOfCases and 3
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

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 .. togglesbar or

barargs.title = table.concat(title)
barargs.left1 = .. i18n.date ..

barargs.right1 = .. args.right1 ..

if args.right2 then
barargs.right2 = .. args.right2 ..
end

barargs.caption = args.caption or args.footer
local box = barBox._box(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

-- deprecation warning
function p.monthToggleButton(frame)
return "Bản mẫu này không còn được dùng nữa, bạn có thể xóa nó"
end

return p