モジュール:Medical cases chart/sandbox

これはこのページの過去の版です。Doraemonplus (会話 | 投稿記録) による 2020年5月18日 (月) 10:06個人設定で未設定ならUTC)時点の版 (日本語化)であり、現在の版とは大きく異なる場合があります。

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

local language = 'ja-JP' -- local default language

local i18n = require("Module:Medical cases chart/i18n")[language]

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

local p = {}

function p._barColors(n)
	local colors = {
		'DimGrey', --deaths
		'SkyBlue', --recoveries
		'Tomato', --cases or altlbl1
		'Gold', --altlbl2
		'OrangeRed' --altlbl3
	}

	return colors[n]
end

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._customBarStacked(args)
	barargs = {}
	barargs[1] = args[1]

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

		return 3.5
	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] =
		'<span class="cbs-ibr" style="padding:0 0.3em 0 0; width:' .. width1 .. 'em">' .. (args[7] or '') .. '</span>' ..
		'<span class="cbs-ibl" style="width:' .. width2 .. 'em">' .. (args[8] or '') .. '</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 class="cbs-ibr" style="padding:0 ' .. padding .. ' 0 0; width:' .. width3 .. 'em">' .. (args[9] or '') .. '</span>' ..
			'<span class="cbs-ibl" style="width:' .. width4 .. 'em">' .. (args[10] or '') .. '</span>'
	end

	for i=1,5 do
		barargs[2*i + 1] = p._barColors(i)
		barargs[2*i + 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 = {}
	local rowDate = args.prevDate or ''

	if is(args[1]) then
		if pcall(function () mw.getContentLanguage():formatDate('', args[1]) end) then
			barargs[1] = args[1]
			rowDate = args[1]
		else
			barargs[1] = '<strong class="error">' .. i18n.invalidTime .. '</strong>'
		end
	else
		barargs[1] = '⋮'
	end

	barargs[2] = args[2] or 0
	barargs[3] = args[3] or 0

	if is(args['alttot1']) then
		barargs[4] = args['alttot1']
	elseif args[4] then
		barargs[4] = (tonumber(args[4]) or 0) - (tonumber(barargs[2]) or 0) - (tonumber(barargs[3]) or 0)
	else
		barargs[4] = 0
	end

	barargs[5] = args[5] or 0

	if is(args['alttot2']) then
		barargs[6] = args['alttot2']
	elseif args[6] then
		barargs[6] = (tonumber(args[6]) or 0) - (tonumber(barargs[2]) or 0) - (tonumber(barargs[3]) or 0)
	else
		barargs[6] = 0
	end

	barargs[7] = args[7] or ''

	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 or 1
	barargs.numwidth = args.numwidth
	barargs.rowheight = args.rowheight

	if yesno(args.collapsible) == true then
		local duration = tonumber(args.duration) or 15
		if args.collapsed then
			barargs.collapsed = args.collapsed
		elseif args.rowsToEnd >= duration then
			barargs.collapsed = 'y'
		else
			barargs.collapsed = ''
		end

		if args.id then
			barargs.id = args.id
		elseif args.nooverlap and args.rowsToEnd < duration then
			barargs.id = 'l' .. duration
		else
			barargs.id = mw.ustring.lower(mw.getLanguage('ja'):formatDate('M', rowDate))
			if args.rowsToEnd < duration then
				barargs.id = barargs.id .. '-l' .. duration
			end
		end
	else
		barargs.collapsed = ''
		barargs.id = ''
	end

	return p._customBarStacked(barargs)
end

function p._buildBars(args)
	local lines = mw.text.split(args.data, '\n')
	local frame = mw.getCurrentFrame()
	local lang = mw.getContentLanguage()

	local bars, rows, months, prevRow, maxparam = {}, {}, {}, '', 1
	for k, line in pairs(lines) do
		local barargs, i = {}, 1
		for parameter in mw.text.gsplit(line, ';') do
			parameter = mw.text.trim(parameter)
			if string.find(parameter, '^%a') then
				parameter = mw.text.split(parameter, '=')
				if parameter[1] == 'alttot1' or parameter[1] == 'alttot2' then
					parameter[2] = tonumber(frame:callParserFunction('#expr', parameter[2]))
					if is(parameter[2]) then
						maxparam = math.max(maxparam, parameter[2])
					end
				end
				barargs[parameter[1]] = parameter[2]
			else
				if is(parameter) then
					if i >= 2 and i <= 6 then
						parameter = tonumber(frame:callParserFunction('#expr', frame:callParserFunction('formatnum',parameter,'R')))
						maxparam = math.max(maxparam, parameter or 1)
					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 function fillCols(col, change)
			local data = args['right' .. col .. 'data']
			local changetype = args['changetype' .. col]
			local value, num, prevnum

			if data == 'alttot1' then
				num = tonumber(barargs.alttot1 or barargs[4])
				prevnum = tonumber(prevRow.alttot1 or prevRow[4])
			elseif data == 'alttot2' then
				num = tonumber(barargs.alttot2 or barargs[6])
				prevnum = tonumber(prevRow.alttot2 or prevRow[6])
			elseif is(data) then
				num = tonumber(barargs[tonumber(data) + 1])
				prevnum = tonumber(prevRow[tonumber(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(mw.ustring.format(rounding, percent)) -- round to two sigfigs
								
								if percent > 0 then
									change = '+' .. lang:formatNum(percent) .. '%'
								elseif percent < 0 then
									change = lang:formatNum(percent) .. '%'
								else
									change = i18n['=']
								end
							end
						else -- data has not changed since previous row
							change = i18n['=']
						end
					else -- no data on previous row
						barargs['firstright' .. col] = true -- set to (n.a.)
					end
				end
			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
		
		if is(barargs[1]) then
			local e,f,g = pcall(
				function ()
					return mw.getLanguage('ja'):formatDate('M',barargs[1]),
						mw.getLanguage('ja'):formatDate('j',barargs[1])
				end
			)
			if e then
				months[#months+1] = {f,g}
			end
		end
		
		barargs.prevDate = prevRow[1]
		rows[#rows + 1] = barargs
		prevRow = barargs
	end

	for i=1,#rows do -- build rows
		rows[i].divisor = tonumber(args.divisor) and tonumber(args.divisor) or maxparam / (0.95 * args.barwidth)
		rows[i].numwidth = args.numwidth
		rows[i].collapsible = args.collapsible
		rows[i].rowsToEnd = #rows - i
		rows[i].rowheight = args.rowheight
		rows[i].duration = args.duration
		if #months>(args.duration or 0) then
			rows[i].nooverlap = args.nooverlap
		end
		
		bars[i] = p._row(rows[i])
	end
	
	return table.concat(bars), months
end

function p._monthToggleButton(args)
	local month = mw.ustring.lower(mw.ustring.sub(args.month[1] or '',1,3))
	local outString = ''
	local newline = (args.nonewline or false) and '' or '\n'
	if is(month) then
		local collapsed = (args.active == '') and '' or ' mw-collapsed'
		local uncollapsed = (args.active == '') and ' mw-collapsed' or ''
		if args.nooverlap then
			outString = '<span class="mw-collapsible mw-customtoggle-' .. month .. ' %s" id="mw-customcollapsible-' .. month .. '" style="padding:0 8px">%s</span>\n' ..
				'<span class="mw-collapsible mw-customtoggle-' .. month .. ' %s" id="mw-customcollapsible-' .. month .. '" style="border:2px solid lightblue; padding:0 8px">%s</span>' .. newline
			
			if mw.ustring.sub(month,1,1) == 'l' and tonumber(mw.ustring.sub(month,2)) == args.duration then
				--"Last ## days"
				local lastDays = mw.ustring.format(i18n.lastDays, args.duration)
			
				outString = mw.ustring.format( outString,
					collapsed, lastDays,
					uncollapsed, lastDays )
			else
				if i18n.m[month] then
					if is(args.month[2]) and is(args.month[3]) then
						-- "Mmm ##–##"
						month = mw.ustring.gsub(i18n.toggleRange,'$.', {
							['$m'] = i18n.m[month],
							['$s'] = args.month[2],
							['$e'] = args.month[3]})
					else
						--"Mmm"
						month = i18n.m[month]
					end
				end
						
				outString = mw.ustring.format( outString,
					uncollapsed, month,
					collapsed, month )
			end
		elseif mw.ustring.sub(month,1,1) == 'l' and tonumber(mw.ustring.sub(month,2)) == args.duration then
			local customtoggles = {(' mw-customtoggle-l' .. args.duration)}
			for k in pairs(i18n.m) do --list of months
				customtoggles[#customtoggles + 1] = ' mw-customtoggle-' .. k .. '-l' .. args.duration
			end
			
			local lastDays = mw.ustring.format(i18n.lastDays, args.duration)
			
			outString = '<span class="mw-collapsible' .. table.concat(customtoggles) .. collapsed .. '" id="mw-customcollapsible-' .. month .. '" style="padding:0 8px">' .. lastDays .. '</span>\n' ..
				'<span class="mw-collapsible' .. table.concat(customtoggles) .. uncollapsed .. '" id="mw-customcollapsible-' .. month .. '" style="border:2px solid lightblue; padding:0 8px">' .. lastDays .. '</span>' .. newline
			
		else
			local customtoggles = ' mw-customtoggle-' .. month .. ' mw-customtoggle-' .. month .. '-l' .. args.duration
			
			outString = '<span class="mw-collapsible' .. customtoggles .. uncollapsed .. '" id="mw-customcollapsible-' .. month .. '" style="padding:0 8px">' .. (i18n.m[month] or month)  .. '</span>\n' ..
				'<span class="mw-collapsible mw-customtoggle-' .. customtoggles .. collapsed .. '" id="mw-customcollapsible-' .. month .. '" style="border:2px solid lightblue; padding:0 8px">' .. (i18n.m[month] or month)  .. '</span>'  .. newline
		end
	end
	
	return outString
end

function p._chart(args)
	local navbar = require('Module:Navbar')._navbar

	local barargs = {}

	local function _numwidth(p)
		local nw = mw.ustring.sub(args.numwidth or '',p,p)
		if nw == 'n' then
			return 0
		elseif nw == 't' then
			return 40
		elseif nw == 'm' then
			return 55
		elseif nw == 'w' then
			return 70
		elseif nw == 'x' then
			return 85
		elseif nw == 'd' then
			return 55
		end

		return 0
	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

	local barwidth = 280

	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'
	end

	if tonumber(barwidth) then
		barargs.width = 85 + barwidth + numwidth .. 'px'
		barargs.barwidth = barwidth .. 'px'
	else
		barargs.width = 'auto'
		barargs.barwidth = 'auto'
	end

	barargs.float = args.float and args.float or 'right'
	local location = mw.ustring.gsub(args.location, 'the ', '')
	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 '') ..
		location .. '/症例数の推移図表'
	
	
	-- get duration for toggles
	local duration = 15 -- default if manual togglesbar is last 15 days
	if yesno(args.collapsible) == true and ( not is(args.togglesbar) ) then
		duration = tonumber(args.duration) or 15 -- default if auto togglesbar is last 15 days
	end
	
	local months, togglesbar, lastdate = {{}}, '', nil
	if args.rows then
		barargs.bars = args.rows
		if yesno(args.collapsible) == true then
			togglesbar = is(args.togglesbar) and args.togglesbar or ''
		end
	elseif is(args.data) or is(args.datapage) then
		local buildargs = {}
		
		local nooverlap = yesno(args.nooverlap)
		
		buildargs.barwidth = tonumber(barwidth) or 280
		buildargs.data = is(args.datapage) and require('Module:Medical cases chart/data')._externalData(args) or args.data
		buildargs.divisor = args.divisor
		buildargs.numwidth = args.numwidth
		buildargs.collapsible = args.collapsible
		buildargs.right1data = args.right1data or -- if no right1data and right1 title is default, use 3rd classification
				not args.right1 and 3
		buildargs.right2data = args.right2data or -- if no right2data and right2 title is deaths, use 1st classification
				(args.right2 == i18n.noOfDeaths or args.right2 == i18n.noOfDeaths2) and 1
		buildargs.changetype1 = mw.ustring.sub(args.changetype1 or (args.changetype or ''),1,1) -- 1st letter
		buildargs.changetype2 = mw.ustring.sub(args.changetype2 or (args.changetype or ''),1,1) -- 1st letter
		buildargs.rowheight = args.rowheight
		buildargs.duration = duration
		if is(args.togglesbar) then
			buildargs.nooverlap = false
		else
			buildargs.nooverlap = nooverlap
		end
		
		barargs.bars, monthList = p._buildBars(buildargs)
		
		local lastRow = #monthList
		if nooverlap == true then
			if #monthList <= duration then
				nooverlap = false
			else
				lastRow = #monthList - duration
			end
		end

		for i=1,(lastRow) do -- deduplicate months
			if monthList[i][1] ~= months[#months][1] then --new month
				if #months > 1 and i > 1 then
					months[#months][3] = monthList[i-1][2] --store end of previous month
				end
				months[#months+1] = monthList[i] --store start of this month
			end
		end
		months[#months][3] = monthList[lastRow][2] --store end of final month
		
		-- automatically generate toggles
		if yesno(args.collapsible) == true then
			if is(args.togglesbar) then
				togglesbar = args.togglesbar
			else
				local toggles = {}
				for i=1,#months do
					toggles[#toggles+1] = p._monthToggleButton({month=months[i], duration=duration, nooverlap=nooverlap})
				end
				toggles[#toggles+1] = p._monthToggleButton({month={('l' .. duration)}, duration=duration, nooverlap=nooverlap})
				togglesbar = '<div class="nomobile" style="text-align:center">\n' .. table.concat(toggles) .. '</div>'
			end
		end
	end
	
	local title = {}
	title[1] = (args.pretitle and args.pretitle .. '' or '') ..
		(args.location3 and '' .. args.location3 or '') ..
		(args.location2 and '' .. args.location2 or '') ..
		args.location .. '' .. i18n.casesIn .. '' .. args.disease ..
		(args.posttitle and 'の症例数' .. args.posttitle or 'の症例数') .. '<span class="nowrap">&nbsp;&nbsp;</span>(' ..
		navbar({[1] = navbartitle, titleArg = ':' .. mw.getCurrentFrame():getParent():getTitle(), mini = 1, nodiv = 1}) ..
		')<br />'
	title[2] = p._legend0({[1] = p._barColors(1), [2] = '死亡'})
	if yesno(args.recoveries) == false then
		title[3] = ''
	else
		title[3] = '<span class="nowrap">&nbsp;&nbsp;&nbsp;</span>' .. p._legend0({[1] = p._barColors(2), [2] = args.reclbl or i18n.recoveries})
	end
	title[4] = '<span class="nowrap">&nbsp;&nbsp;&nbsp;</span>' .. p._legend0({[1] = p._barColors(3), [2] = args.altlbl1 or i18n.activeCases})
	if args.altlbl2 then
		title[5] = '<span class="nowrap">&nbsp;&nbsp;&nbsp;</span>' .. p._legend0({[1] = p._barColors(4), [2] = args.altlbl2})
	else
		title[5] = ''
	end
	if args.altlbl3 then
		title[6] = '<span class="nowrap">&nbsp;&nbsp;&nbsp;</span>' .. p._legend0({[1] = p._barColors(5), [2] = args.altlbl3}) ..'\n'
	else
		title[6] = '\n'
	end
	title[7] = togglesbar
	

	barargs.title = table.concat(title)
	barargs.left1 =
		'<div class="center" style="width:77px">' .. -- 85-8 because of padding
			"'''" .. i18n.date .. "'''" ..
		'</div>'

	barargs.right1 =
		'<div class="center" style="width:' .. right1 .. 'px">' ..
			"'''" .. (args.right1 or i18n.noOfCases) .. "'''" ..
		'</div>'

	if args.right2 then
		local right2 = _numwidth(3) + _numwidth(4)
		if mw.ustring.sub(args.numwidth,3,3) == 'n' then
			right2 = right2 - 2
		else
			right2 = right2 + 2
		end

		barargs.right2 =
		'<div class="center" style="width:' ..right2 ..'px">' ..
			"'''" .. args.right2 .. "'''" ..
		'</div>'
	end

	barargs.caption = args.caption
	barargs.css = 'Template:Medical_cases_chart/styles.css'
	return barBox._box(barargs)
end

function p.chart(frame)
	local args = getArgs(frame, {
		valueFunc = function (key, value)
			if value then
				value = mw.text.trim(value)
				if ({['numwidth']=1,['barwidth']=1,['recoveries']=1,['changetype']=1})[mw.ustring.gsub(key,"%A","")] then
					value = mw.ustring.lower(value) --make numwidth, barwidth, recoveries, and changetype lower case
				end
				if is(value) then
					return value
				end
			end
			return nil
		end
	})
	return p._chart(args)
end

function p.barColors(frame)
	return p._barColors(tonumber(frame:getParent().args[1]))
end

function p.buildBars(frame)
	local bars = p._buildBars(frame.args)
	return bars
end

function p.monthToggleButton(frame)
	local args = {}
	args.month = {frame:getParent().args.month or frame:getParent().args[1] or 'l15'}
	args.active = frame:getParent().args.active or 'true'
	args.duration = frame:getParent().args.duration or 15
	args.nonewline = true
	return p._monthToggleButton(args)
end

return p