How to colour the pgfgantt canvas based on calendar dates
And so, after three years...
My approach is to draw TikZ
rectangles on the background layer once the Gantt chart has been drawn.
In the example below, I've highlighted the units used in the calendar, so that you can easily redefine the rectangles as you need. (For example, this approach could also be used to define horizontal stripes that would correspond to task groups.)
Note that the coloring of weekdays (Saturday in light red, and Sundays in bold red) is implemented here in a much lighter way that in your original code (i.e. directly in the definition of \pgfcalendarweekdayletter
-- cf. line 27)
What remains to do:
- automatically compute the number of title lines (to be implemented on line 75)
- automatically compute the number of chart lines (to be implemented on line 83)
Edit: Now automatically computes the number of weeks, when does the first week-end starts, and handles correctly if the first day of the calendar is a Sunday.
\documentclass[margin=10pt]{standalone}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{backgrounds,calc,calendar}
\usepackage{pgfgantt}
\usepackage{pgfcalendar}
\usepackage{calc}
\usepackage{ifthen}
%\newcommand{\datenumber}[1]{% to compute the number of days between \mystartdate and a given date. Unused here
% \pgfcalendardatetojulian{#1}{\dtnmbr}%
% \advance\dtnmbr by -\mystrtdt%
% \the\dtnmbr%
%}
%TO BE UPDATED ACCORDING TO YOUR NEEDS
\def\mystartdate{2016-08-29}%starting date of the calendar
\def\myenddate{2016-10-31}%ending date of the calendar
\def\myxunit{.5cm}%width of 1 day
\def\myyunittitle{.5cm}%height of 1 title line
\def\myyunitchart{1cm}%height of 1 chart line
\def\pgfcalendarweekdayletter#1{% define the name of weekdays + formatting
\ifcase#1M\or T\or W\or T\or F\or \textcolor{red!50!white}{S}\or \textcolor{red}{\textbf{S}}\fi
}
%Some calculation for plotting week-ends area
\newcount\myenddatecount
\pgfcalendardatetojulian{\myenddate}{\myenddatecount}
\newcount\mystartdatecount
\pgfcalendardatetojulian{\mystartdate}{\mystartdatecount}
\newcount\mynumberofdays
\mynumberofdays \myenddatecount\relax
\advance \mynumberofdays by -\mystartdatecount\relax% so \mynumberofdays is now the number of days in the calendar
\newcount\mynumberofweeks
\mynumberofweeks\mynumberofdays\relax
\advance \mynumberofweeks by -1\relax
\divide \mynumberofweeks by 7\relax% so we have the number of full weeks
\newcount\myfirstweekday
\pgfcalendarjuliantoweekday{\mystartdatecount}{\myfirstweekday}
\newcount\myfirstweekendshift
\myfirstweekendshift 5\relax
\advance\myfirstweekendshift by -\myfirstweekday\relax
\ifnum \myfirstweekendshift=-1%if first day = sunday
\advance \myfirstweekendshift by 7\relax% the first full weekend will thus begin one week after
\fi
\begin{document}
\begin{ganttchart}[%
hgrid,
vgrid,
x unit = \myxunit,
y unit title = \myyunittitle,
title height = .75,
y unit chart = \myyunitchart,
time slot format=isodate,
canvas/.append style={fill opacity=.1},
]%
{\mystartdate}%
{\myenddate}
\gantttitlecalendar{year}\\
\gantttitlecalendar{month=name}\\
\gantttitlecalendar{day}\\
\gantttitlecalendar{weekday=letter}\\
%So we have 4 title lines
\def\numbttitlelines{4}
\ganttgroup{Group 1}{\mystartdate}{2016-10-14} \\
\ganttbar{Bar 1}{\mystartdate}{2016-09-05}\\
\ganttbar{Bar 2}{2016-09-06}{2016-09-15}\\
\ganttbar{Bar 3}{2016-09-15}{2016-10-12}\\
\ganttmilestone{Milestone}{2016-10-14}%Note that whe didn't add \\ here! (so that last line is not blanck)
%So we have 5 chart lines
\def\numbtchartlines{5}
\begin{scope}[|<->| ,thick] %Display units
\fill [red] (0,0) circle [radius = 2.5pt] node [above left] {origin \texttt{(0,0)}};
\draw [blue] (-1ex,0) --(-1ex,-\myyunittitle) node [midway, right] {\verb| 1*\myyunittitle|};
\draw [blue!33!white] (-1em,0) --(-1em,-\numbttitlelines*\myyunittitle) node [midway, left] {\verb| <# of title lines>*\myyunittitle|};
\draw [green!75!black] (-1ex,-\numbttitlelines*\myyunittitle) --(-1ex,-\numbttitlelines*\myyunittitle-1*\myyunitchart) node [midway, right, anchor=south west] {\verb| 1*\myyunitchart|};
\draw [green] (-1em,-\numbttitlelines*\myyunittitle)--(-1em,-\numbttitlelines*\myyunittitle -\numbtchartlines*\myyunitchart) node [midway, left, anchor=south east] {\verb| <# of chart lines>*\myyunitchart|};
\draw [yellow!50!orange] (0, 4pt-2*\myyunittitle) -- (\myxunit,4pt-2*\myyunittitle) node [right, anchor=base west] {\verb| 1*\myxunit|};
\end{scope}
\begin{scope}[on background layer]%display week-ends
\foreach \i in {0,...,\the\mynumberofweeks}
\fill [red!10]
(\myfirstweekendshift*\myxunit+\i*7*\myxunit,-\numbttitlelines*\myyunittitle)% on the x-absis: shift the number of days before the first Saturday + 7days*\i; on the y-absis: shift down the number of title lines * their height
rectangle
(\myfirstweekendshift*\myxunit+\i*7*\myxunit+2*\myxunit,-\numbttitlelines*\myyunittitle -\numbtchartlines*\myyunitchart);% on the x-absis: add two days (the week-end); on the y-absis: add the number of chartlines*their height
\ifthenelse{\myfirstweekendshift=6}{% if the first day is a sunday, it is not grayed. So draw a rectangle for the first day of the chart.
\fill [red!10]
(0,-\numbttitlelines*\myyunittitle)
rectangle
(1*\myxunit,-\numbttitlelines*\myyunittitle -\numbtchartlines*\myyunitchart);
}{}
\end{scope}
\end{ganttchart}
\end{document}
And so, after two years...
Filling the canvas with the correct pattern is easy, but aligning it is not. I tried.
Drawing the highlights finding the correct anchors is possible but not simple at all, I believe.
I think the less obtrusive way to solve the problem is a clever vgrid
pattern:
\documentclass{standalone}
%\usepackage[a4paper]{geometry}
\usepackage[utf8]{inputenc} \usepackage[T1]{fontenc}
\usepackage{pgfgantt} \usepackage{pgfcalendar}
\newcount\startdate
\newcount\daynum
\pgfcalendardatetojulian{2012-01-09}{\startdate}
%Redefinition from pgfcalendar.code.tex
\def\pgfcalendarweekdayveryshortname#1{%
\translate{\ifcase#1M\or T\or W\or T\or F\or S\or S\fi}%
}
\protected\def\zzz{% Mod from http://tex.stackexchange.com/q/87550/19384
\pgfcalendarjuliantodate{\numexpr\startdate\relax}{\year}{\month}{\day}%
\pgfcalendarjuliantoweekday\startdate\daynum
{\ifnum\daynum=6\color{red}%
\tiny\pgfcalendarweekdayveryshortname{\daynum}%
\else
\tiny\pgfcalendarweekdayveryshortname{\daynum}%
\fi}%
\global\advance\startdate1}
\begin{document}
\begin{tikzpicture}
\begin{ganttchart}[ vgrid = { *5{ black, dotted }%
, *1{ red, dashed
, double distance between line centers = 3mm
, transform canvas = { xshift = 1.5mm } }%
, *1{ red!50!white
, line width={3mm-0.4pt} % 0.4pt = thin
, transform canvas = { xshift = -1.5mm } }%
}
, hgrid
, y unit chart = 5mm
, x unit = 3mm
]{1}{20}
\gantttitle{Plan}{20}\\
\gantttitlelist[ title list options = { var = \y
, evaluate = \y as \x using "{\zzz}" }
]{1,...,20}{1}\\
\ganttbar{Task 1}{2}{5}\\
\ganttlinkedbar{Task 2}{6}{8}\\
\ganttlinkedmilestone{Goal}{9}\\
\end{ganttchart}
\end{tikzpicture}
\end{document}
We use the line on the left of sundays to draw a double dashed line, and the one on the right to highlight the column. Order is important, as double lines don't behave particularly well when dashed and don't allow transparency. We just cover up their middle stroke with the third line in the pattern, aptly thinned by 0.4pt
.
Here is the result:
And so, after six years...
I have found a different solution that is more flexible to insert the background at arbitrary locations. I'm using a vrule which yields the same width as the range you want to highlight. Put it on the background and set the canvas fill to none.
Note that in my POC I also made some changes to display weeks in stead of days. And the blocks i 'blocked' are holiday weeks.
\documentclass[margin=10pt]{standalone}
\usepackage[utf8]{inputenc}
\usepackage{url}
\usepackage{pgfgantt}
\usetikzlibrary{backgrounds}
\newcounter{myWeekNum}
\stepcounter{myWeekNum}
%
\newcommand{\myWeek}{\themyWeekNum
\stepcounter{myWeekNum}
\ifnum\themyWeekNum=53
\setcounter{myWeekNum}{1}
\else\fi
}
%
\def\mystartdate{2019-12-02}%starting date of the calendar
\def\myenddate{2020-06-28}%ending date of the calendar
\def\myxunit{2pt}%width of 1 day
\definecolor{gray}{HTML}{7F7F7F}
\definecolor{blue}{HTML}{1F77B4}
\begin{document}
\newcommand{\holidaytext}{
\begin{scope}[on background layer] % We put all the days we want to block in the background layer
% Double block only need to mention the Holiday once.
\ganttvrule[
vrule/.append style={gray!10, solid, line width=\myxunit*7},
vrule offset=3.5pt,
vrule label node/.append style={black!100, rotate=-45, anchor=north west},
]{Holiday}{2019-12-23}
\ganttvrule[
vrule/.append style={gray!10, solid, line width=\myxunit*7},
vrule offset=3.5pt
]{}{2019-12-30}
% Single block with description
\ganttvrule[
vrule/.append style={gray!10, solid, line width=\myxunit*7},
vrule offset=3.5pt,
vrule label node/.append style={black!100, rotate=-45, anchor=north west}
]{Holiday}{2020-03-23}
% Single block with description
\ganttvrule[
vrule/.append style={gray!10, solid, line width=\myxunit*7},
vrule offset=3.5pt,
vrule label node/.append style={black!100, rotate=-45, anchor=north west}
]{Holiday}{2020-05-18}
\end{scope}
}
\setcounter{myWeekNum}{49}
\ganttset{%
calendar week text={\myWeek{}}%
}
\begin{ganttchart}[
hgrid,
vgrid = {
*{6}{draw=none},dotted
},
time slot format = isodate,
x unit=\myxunit,
y unit chart=15pt,
bar/.append style={rounded corners=2pt, white},
bar left shift=1,
bar right shift=-1,
bar height=.2,
bar top shift=.4,
canvas/.append style={fill=none} % Here we set the canvas fill to non
]{\mystartdate}{\myenddate}
\gantttitlecalendar{year, month=name, week} \\
\ganttbar[bar/.append style={fill=gray}]{Something}{2019-12-02}{2020-06-28} \\
\ganttbar[bar/.append style={fill=gray}]{Some other thing}{2019-12-02}{2019-12-22} \ganttnewline[thin, black]
\ganttbar[bar/.append style={fill=orange}]{Yes it draws on top.}{2019-12-23}{2019-12-29}
\holidaytext
\end{ganttchart}
\end{document}