Skip to content

Instantly share code, notes, and snippets.

@wtsnjp
Last active May 15, 2020 15:02
Show Gist options
  • Save wtsnjp/42a3379ddb496b3eeaa46c6de75e2e8b to your computer and use it in GitHub Desktop.
Save wtsnjp/42a3379ddb496b3eeaa46c6de75e2e8b to your computer and use it in GitHub Desktop.
% +++
% clean_files = ["%B.aux", "%B.log", "data.csv"]
% clobber_files = ["%B.pdf", "%B.png", "%B-2.png"]
% sequence = ["tex2img"]
%
% [programs.tex2img]
% command = "tex2img"
% opts = [
% '--latex "lualatex -shell-escape"',
% "--no-transparent",
% "--keep-page-size",
% ]
% args = ["%T", "%B.png"]
%
% [programs.latex]
% command = "lualatex"
% opts = ["-shell-escape"]
% +++
\documentclass[12pt,border=4mm]{standalone}
% dependecies
\usepackage{luacode}
\usepackage{download}
\usepackage{pgfplots}
% data file
\def\DataFile{./data.csv}
% download data: always fetch a new one
\begin{luacode}
do
data_file = '\DataFile'
if lfs.isfile(data_file) then
local ok = os.remove(data_file)
if not ok then
io.stderr:write('Warning: failed to delete an existing data.')
end
end
end
\end{luacode}
\download[\DataFile]{%
https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv}
% the main logic
\begin{luacode*}
function string.split(str, delim)
if str:find(delim) == nil then return {str} end
local res = {}
local pat = '(.-)' .. delim .. '()'
local last
for part, pos in string.gmatch(str, pat) do
table.insert(res, part)
last = pos
end
table.insert(res, string.sub(str, last))
return res
end
function table.map(tab, f)
for k, v in ipairs(tab) do
tab[k] = f(v)
end
end
function main()
-- load the data
local f = io.open(data_file, 'r')
local first_date_sig, last_date_sig = 1232, 0
local nof_patients = {}
for l in f:lines() do
local field_date = string.split(l, ',')[5]
local date = string.split(field_date, '-')
if #date == 3 then
local tmp = string.format('%02d/%02d', date[2], date[3])
if nof_patients[tmp] == nil then
local tmp_sig = tonumber(date[2]) * 100 + tonumber(date[3])
if tmp_sig < first_date_sig then first_date_sig = tmp_sig end
if tmp_sig > last_date_sig then last_date_sig = tmp_sig end
nof_patients[tmp] = 1
else
nof_patients[tmp] = nof_patients[tmp] + 1
end
end
end
f:close()
-- make the date list
local date_list = {}
do
first_date_day = first_date_sig % 100
first_date_month = (first_date_sig - first_date_day) / 100
last_date_day = last_date_sig % 100
last_date_month = (last_date_sig - last_date_day) / 100
local days_of_month = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
local function add_date(m, d)
table.insert(date_list, string.format('%02d/%02d', m, d))
end
local nof_days
for m=first_date_month,last_date_month do
nof_days = days_of_month[m]
if m == first_date_month then
for d=first_date_day,nof_days do
add_date(m, d)
end
elseif m == last_date_month then
for d=1,last_date_day do
add_date(m, d)
end
else
for d=1,nof_days do
add_date(m, d)
end
end
end
end
-- build the data
local data = {}
for i, date in ipairs(date_list) do
local num = nof_patients[date]
if num ~= nil then
data[i] = {date, num}
else
data[i] = {date, nil}
end
end
return data
end
function print_period(data)
tex.sprint(string.format('%s--%s', data[1][1], data[#data][1]))
end
function print_coords(data)
for _, tp in ipairs(data) do
tex.sprint(tp[1] .. ',')
end
end
function print_accumulation(data)
local acc = 0
for _, tp in ipairs(data) do
if type(tp[2]) == 'number' then
acc = acc + tp[2]
tex.sprint(string.format('(%s, %d)', tp[1], acc))
end
end
end
function print_double_in(x, start, data)
tex.sprint(string.format('(%s, %d)', start, 1))
local nof_days = 0
for i, tp in ipairs(data) do
if tp[1] == start then
nof_days = #data - i
break
end
end
tex.sprint(string.format('(%s, %f)', data[#data][1],
2 ^ (nof_days / x)))
end
function print_trajectory(data)
local total = 0
local new = 0
for i, tp in ipairs(data) do
if i - 8 > 0 then
local d = data[i - 8][2] or 0
new = new - d
end
if type(tp[2]) == 'number' then
total = total + tp[2]
new = new + tp[2]
tex.sprint(string.format('(%d, %d)', total, new))
end
end
end
data = main()
\end{luacode*}
% setup standalone and pgfplots
\standaloneenv{tikzpicture}
\pgfplotsset{compat=1.16}
%<*> \DoubleIn {<x>} {<start date>}
\newcommand{\DoubleIn}[3][]{
\addplot+ [no markers, #1] coordinates {
\directlua{print_double_in(#2, '#3', data)}
};
\addlegendentry{Double in #2 days};
}
%<*> \TodaysPatients {<date>} {<number of patients>}
\newcommand{\TodaysPatients}[2]{%
\bgroup\escapechar=-1\relax
\directlua{data[\string\#data + 1] = {'#1', #2}}%
\egroup}
\begin{document}
\begin{tikzpicture}
\begin{semilogyaxis}[
title={%
Number of COVID-19 patients \& deaths in Tokyo
(\directlua{print_period(data)})%
},
xlabel={Date},
ylabel={\#Patients},
enlarge x limits=0,
height=100mm,
ymin=1,
xticklabel style={rotate=90},
symbolic x coords/.expand once={%
\directlua{print_coords(data)}
},
grid=both,
major grid style={black!50},
legend pos=north west,
axis lines*=left]
\addplot coordinates {
\directlua{print_accumulation(data)}
};
\addlegendentry{Patients};
\end{semilogyaxis}
\end{tikzpicture}
\begin{tikzpicture}
\begin{loglogaxis}[
title={%
Trajectory of COVID-19 confirmed cases in Tokyo
(\directlua{print_period(data)})%
},
xlabel={Total confirmed cases},
ylabel={New confirmed cases (in a week)},
enlarge x limits=0,
height=100mm,
ymin=1,
grid=both,
major grid style={black!50},
axis lines*=left]
\addplot+ [no markers] coordinates {
\directlua{print_trajectory(data)}
};
\end{loglogaxis}
\end{tikzpicture}
\end{document}
@wtsnjp
Copy link
Author

wtsnjp commented Apr 3, 2020

How to build:

$ lualatex -shell-escape covid19.tex

@wtsnjp
Copy link
Author

wtsnjp commented May 1, 2020

Latest auto-generated figures (thanks @yy-u):

Number of COVID-19 patients in Tokyo

Trajectory of COVID-19 confirmed cases in Tokyo

The source repository of these files is:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment