My goal is to have a year calendar with boxes instead of days that get filled according to the type of exercise I did that day. These exercise would be loaded from a .csv file in a format such as:

2020-01-01;2020-01-01;2020-01-03

2020-01-02; ...

where each column is a particular type of activity and I just need to add the date and the code will do the rest.

This is my work so far:

\documentclass{article}
\usepackage[a4paper, total={8.5in, 11in}]{geometry}
\usepackage{csvsimple}
\usepackage{tikz}
\usetikzlibrary{calendar}
\usepackage{pgfcalendar}

\begin{document}
\def\s{0.85} %size of box around day
\def\ss{6.3ex} %space between days
\def\ds{0.02}

\def\nexer{0.75} %1-1/number of exercise to be put into calendar

\def\cardio{red}
\def\str{blue}
\def\spo{green}
\def\oth{orange}
\begin{tikzpicture}[every calendar/.style={
week list, month label above centered, day xshift=\ss, day yshift=\ss,
day code = {
\draw (-0.5*\s,0.5*\s) -- (0.5*\s,0.5*\s) -- (0.5*\s,-0.5*\s) -- (-0.5*\s,-0.5*\s) -- cycle;
}
}]
\calendar
[dates=2020-01-01 to 2020-01-last]
if (equals=\dcar) {\filldraw[\cardio] (-0.5*\s+\ds,0.5*\s-\ds) --
(0.5*\s-\ds,0.5*\s-\ds) --
(0.5*\s-\ds,-0.5*\s+\s*\nexer+\ds) --
(-0.5*\s+\ds,-0.5*\s+\s*\nexer+\ds) -- cycle;}
if (equals=\dstr) {\filldraw[\str,yshift=-5] (-0.5*\s+\ds,0.5*\s-\ds) --
(0.5*\s-\ds,0.5*\s-\ds) --
(0.5*\s-\ds,-0.5*\s+\s*\nexer+\ds) --
(-0.5*\s+\ds,-0.5*\s+\s*\nexer+\ds) -- cycle;}
if (equals=\dspo) {\filldraw[\spo,yshift=-10] (-0.5*\s+\ds,0.5*\s-\ds) --
(0.5*\s-\ds,0.5*\s-\ds) --
(0.5*\s-\ds,-0.5*\s+\s*\nexer+\ds) --
(-0.5*\s+\ds,-0.5*\s+\s*\nexer+\ds) -- cycle;}
if (equals=\doth) {\filldraw[\oth,yshift=-15] (-0.5*\s+\ds,0.5*\s-\ds) --
(0.5*\s-\ds,0.5*\s-\ds) --
(0.5*\s-\ds,-0.5*\s+\s*\nexer+\ds) --
(-0.5*\s+\ds,-0.5*\s+\s*\nexer+\ds) -- cycle;};}

\end{tikzpicture}
\end{document}


I have run into couple of problems, first of all this code won't work if there are different number of days for each activity, meaning the .csv columns are not the same length. Also csvreader creates this weird effect that the calendar is "doubled" because it draws over itself and I do not know how to get rid of it.

Any help appreciated.

EDIT1: How it looks right now

• Can you provide an example csv file and expected output? Nov 13, 2020 at 1:00
• @AlanXiang I added the output so it should be clear how I would want it to look like. Example .csv file is in the beginning, number of columns corresponds to number of "activities/exercises" and each row is an entry but the problem is that in this system all exercises have to have the same amount of entries. Nov 13, 2020 at 13:27
• In a CSV file, the number of columns is supposed to be fixed. Nov 13, 2020 at 15:09
• @AlanXiang you probably wanted to write rows, in that cases I am looking for a solution that could do it differently, the .csv file can have different format, I just need it to be easy to add new entries. Nov 13, 2020 at 16:51
• I am pretty sure the number of columns is fixed, and the number of rows is mutable. Anyways, I can come up with a solution if you prefer to write all dates in one row. Nov 14, 2020 at 2:24

I think what you want deviate from csv standard. Therefore, you need some custom implementation for your purposes.

\documentclass{article}
\usepackage[a4paper, total={8.5in, 11in}]{geometry}
\usepackage{csvsimple}
\usepackage{tikz}
\usetikzlibrary{calendar}
\usepackage{pgfcalendar}
\usepackage{expl3}

% sample csv file
\begin{filecontents*}{cal.csv}
2020-01-01;2020-01-10;2020-01-03
2020-01-02;2020-01-10;2020-01-05;2020-01-25
2020-01-06;2020-01-16;2020-01-03
2020-01-08;2020-01-09;2020-01-23
\end{filecontents*}

\newlength{\daywidth}
\setlength{\daywidth}{5.0ex} %size of box around day
\newlength{\dayinterval}
\setlength{\dayinterval}{6.3ex} %space between days
\newlength{\dayitemheight}
\setlength{\dayitemheight}{0.8ex}
\newlength{\dayiteminterval}
\setlength{\dayiteminterval}{0.2ex}

\ExplSyntaxOn

\seq_new:N \g_doc_dates_seq

\ior_open:Nn \g_tmpa_ior {#1}
\seq_gclear:N \g_doc_dates_seq
\ior_str_map_inline:Nn \g_tmpa_ior {
\str_set:Nx \l_tmpa_str {\tl_trim_spaces:n {##1}}
\str_if_empty:NF \l_tmpa_str {
\seq_gput_right:NV \g_doc_dates_seq \l_tmpa_str
}
}
\ior_close:N \g_tmpa_ior
}

\cs_set:Npn \doc_draw_command:nnn #1#2#3 {
\node[#1,#2] at (#3.north) {};
}

\cs_generate_variant:Nn \regex_split:nnN {nVN}
\cs_generate_variant:Nn \doc_draw_command:nnn {xnx}

\newcommand{\drawfilerow}[3]{
\iow_term:x {show: \fp_eval:n {-(#2 + 0.5) * (1.0ex)}pt}
\int_compare:nT {1 <= #2 <= \seq_count:N \g_doc_dates_seq} {
\tl_set:Nx \l_tmpa_tl {\seq_item:Nn \g_doc_dates_seq {#2}}
\regex_split:nVN {;} \l_tmpa_tl \l_tmpa_seq
\seq_map_inline:Nn \l_tmpa_seq {
\str_set:Nx \l_tmpa_str {\tl_trim_spaces:n {##1}}
\str_if_empty:NF \l_tmpa_str {
\doc_draw_command:xnx {
minimum~width=0.98\daywidth,
minimum~height=\dayitemheight,
yshift=\fp_eval:n {-(#2 - 1) * (\dayitemheight + \dayiteminterval) -0.2pt}pt,
anchor=north,
inner~sep=0pt,
outer~sep=0pt
} {fill=#3} {#1-\str_use:N \l_tmpa_str};
}
}
}
}

}

\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}[every calendar/.style={
week list,
month label above centered,
day xshift=\dayinterval,
day yshift=\dayinterval,
day code = {
\node[minimum width=\daywidth,
minimum height=\daywidth,
name=\pgfcalendarsuggestedname,
draw=black] {};
}
}]
\calendar (mycal) [dates=2020-01-01 to 2020-01-last];
% use contents from "csv" file
\drawfilerow{mycal}{1}{red};
\drawfilerow{mycal}{2}{blue};
\drawfilerow{mycal}{3}{green};
\drawfilerow{mycal}{4}{orange};
\end{tikzpicture}

\end{document}

• it is perfect! I am not skilled enough with TikZ to understand the \newcommands that you had to implement, can you recommend some resources on this topic? I have browsed before through the TikZ manual which is great but I can see that you also use Regex but lot of the syntax just goes over my head. Nov 15, 2020 at 21:07
• Most of the programming is done with LaTeX3, here is an tutorial that I wrote. As a matter of fact, very little of this code has connection to TikZ. For TikZ/pgf, it is too complicated to study systematically. I would suggest learning from specific examples when you need them. Nov 15, 2020 at 22:40