25

I am working on a pgfgantt document where all timelines are governed by real dates and where the titlelist shows week days based on the TikZ calendar library. I have used and modified the code from Custom calendar using TikZ to achieve this. The dates are converted to running numbers by using the pgfcalendardatetojulian.

What I am now looking to do is to colour the background canvas of the Gantt plot so that weekdays are white and Sundays (or weekends, Sat-Sun and holidays) are highlighted by vertical (light) red stripes. In the picture from the MWE this is equal to the area between the two red dashed lines under each red S (for Sunday). This means using the calendar library to determine what parts of the background canvas should be coloured reddish. The question is how this can be done?

MWE:

\documentclass{article}
\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 https://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}
\noindent\resizebox{\textwidth}{!}{
  \begin{tikzpicture}
    \begin{ganttchart}[vgrid={*5{black,dotted},*2{red,dashed}},hgrid,%
                       y unit chart=5mm,x unit=3mm]{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}

Resulting image but with a reddish shaded area added to show what I would like to achieve for all 'red days':

enter image description here

2
  • @PeterJansson: Still not solved? Or is an answer acceptable?
    – Bobyandbob
    Commented Aug 18, 2018 at 22:04
  • @Bobyandbob, thanks for asking, two good answers, just my response time that is in years rather than shorter Commented Aug 20, 2018 at 8:02

4 Answers 4

10
+100

And so, after three years...

enter image description here

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)

enter image description here

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.

enter image description here

\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}
6

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:

Final result

5

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.

enter image description here

\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}
1
  • 3
    The saga continues... Commented Dec 11, 2019 at 13:38
3

and so, after now 9 years...

I had to solve similar problems as you have, but I've found the usage of the pgfcalendar package very convenient.

With this package, one can iterate over the days and do things depending on certain predicates.

I wanted to color in all weekends between two specified days:

\newcommand{\vrulesWeekend}[2]{
\let\%=\pgfcalendarshorthand
\pgfcalendar{cal}{#1}{#2}{%
  \ifdate{weekend}{%
    \ganttvrule{}{\%y0-\%m0-\%d0}%
  }{}%
}
}

Or color in a specified timeframe, which might represent holidays

\newcommand{\vrulesOverDates}[2]{
\let\%=\pgfcalendarshorthand
\pgfcalendar{cal}{#1}{#2}{%
  \ganttvrule{}{\%y0-\%m0-\%d0}%
}
}

Which would be called like

\begin{scope}[on background layer]
   \begin{scope}[/pgfgantt/vrule/.append style={color=green,opacity=0.5}]
      \vrulesOverDates{2023-02-06}{2023-03-05}
    \end{scope}
    \begin{scope}[/pgfgantt/vrule/.append style={gray,opacity=0.7}]
      \vrulesWeekend{2022-10-03}{2023-03-31}
    \end{scope}
\end{scope}

In order to create rules which are a wide as a column, I took the \ganttvalueof macro from pgfgantt.sty. As such, I can create a style which will fit to the specified settings of the chart.

vrule/.style={gray!20, line width=\ganttvalueof{x unit}}

All in all we can demonstrate this in this MWE

\documentclass[10pt]{standalone}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{tikz}
\usepackage{xcolor}
\usepackage{lmodern}
\usetikzlibrary{mindmap}

\usetikzlibrary{backgrounds,shapes.geometric}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{calc}
\usepackage{pgfcalendar}
\usepackage{pgfgantt}

\newcommand{\vrulesOverDates}[2]{
\let\%=\pgfcalendarshorthand
\pgfcalendar{cal}{#1}{#2}{%
  \ganttvrule{}{\%y0-\%m0-\%d0}%
}
}
\newcommand{\vrulesWeekend}[2]{
\let\%=\pgfcalendarshorthand
\pgfcalendar{cal}{#1}{#2}{%
  \ifdate{weekend}{%
    \ganttvrule{}{\%y0-\%m0-\%d0}%
  }{}%
}
}

\begin{document}
  \begin{tikzpicture}
    \begin{ganttchart}[
        title height=1,y unit title=5mm,
        vgrid={*6{dotted}, {thick,dashed}},
        hgrid,
        vrule offset=.5,
        vrule/.style={gray!20, line width=\ganttvalueof{x unit}},
        time slot format=isodate,
        canvas/.append style={fill=none}
      ]{2022-12-12}{2023-01-15}
      \gantttitlecalendar{month=name, day}\\

      \ganttbar[inline]{FOO}{2022-12-13}{2022-12-19}

      \begin{scope}[on background layer]
        \begin{scope}[/pgfgantt/vrule/.append style={color=blue,opacity=0.5}]
          \vrulesOverDates{2022-12-21}{2023-01-03}
        \end{scope}
        \begin{scope}[/pgfgantt/vrule/.append style={gray,opacity=0.7}]
          \vrulesWeekend{2022-12-12}{2023-01-15}
        \end{scope}
      \end{scope}
    \end{ganttchart}
  \end{tikzpicture}
\end{document}

MWE Picture

Which has worked wonders for me.

2
  • 1
    Please include a picture of the output of your MWE.
    – Unknown
    Commented Jun 22, 2022 at 19:00
  • Included a picture for my MWE.
    – Noiv
    Commented Sep 7, 2022 at 7:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .