Gantt bars with multicolour diagonal stripes

patternpgfgantt

I have used pgfgantt to produce a simple Gantt chart for the work packages of my project:

\documentclass{article}

\usepackage{pgfgantt}

\begin{document}
\begin{ganttchart}[expand chart=\textwidth]{1}{48}
\gantttitle[]{Year 1}{12}
\gantttitle[]{Year 2}{12}
\gantttitle[]{Year 3}{12}
\gantttitle[]{Year 4}{12}\\

\ganttbar{WP1}{1}{48} \\
\ganttbar{WP2}{4}{15} \\
\ganttbar{WP3}{10}{30} \\
\ganttbar{WP4}{20}{45}       
\end{ganttchart}%
\end{document}

I want to colour-code the bars according to which staff members will be assigned to each work package. There are six staff members in total, each with a unique colour. Up to three staff members will work on each work package. Ideally I'd like to show this with diagonal stripes, since horizontal stripes might be too thin to make out, and vertical stripes would be confused with allocation to the time subdivisions. Basically I'd like to be able to colour the bars like this, with one, two, or three colours per bar:

Gantt chart showing bars which are either solid or have stripes in two or three alternating colours

I have found a few examples here of two-colour stripes, but they don't seem to be easily adaptable to three-colour stripes.

Best Answer

Tikz (or pgf) patterns can solve your problem.

To fill with n-color stripes (n >= 2) using an uncolored pattern (also called "form-only"), you have to apply a pattern n-1 times since an uncolored pattern can only have a single color, which is specified by option pattern color.

Notice how the linked answer uses normal filling and an uncolored pattern in postaction to achieve a two-color stripes. So a three-color stripes will need two postaction.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{patterns.meta}

\begin{document}
\begin{tikzpicture}
  \fill[fill=red,
    % set `line width` to 1/3 of the `distance`
    % set `xshift` according to both `angle` and `line width`
    postaction={
      pattern={Lines[angle=30, distance=9pt, line width=3pt,
        xshift=3pt/sin(30)]},
      pattern color=green}, 
    postaction={
      pattern={Lines[angle=30, distance=9pt, line width=3pt, 
        xshift=-3pt/sin(30)]},
      pattern color=blue},
  ]
    (0,0) rectangle +(2,1);
\end{tikzpicture}
\end{document}

three-color stripes using uncolored pattern

To do the same task using a colored pattern, a new pattern Stripes is defined in the example below. It is similar to the pattern Lines from tikz library patterns.meta, with a new option color series={<comma list of colors>}. The line width option is invalid and now auto computed as 1/n of distance in which n is the length of color series. Hence with distance=6pt, color series={red, blue}, the line width is 3pt; with distance=6pt, color series={red, green, blue}, the line width is 2pt.

color series has initial and default value red, green, blue

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{patterns.meta}

\usepackage{pgfgantt}

\makeatletter
\newlength\pgf@pat@distance % or defined by the more portable \newdimen

% based on pattern `Lines`, defined in pgflibrarypatterns.meta.code.tex
\pgfdeclarepattern{
  name=Stripes,
  type=colored,
  parameters={
      \pgfkeysvalueof{/pgf/pattern keys/distance},
      \pgfkeysvalueof{/pgf/pattern keys/angle},
      \pgfkeysvalueof{/pgf/pattern keys/xshift},
      \pgfkeysvalueof{/pgf/pattern keys/yshift},
      \pgfkeysvalueof{/pgf/pattern keys/color series},
  },
  bottom left={%
    \pgfpoint{-.5*\pgf@pat@distance}{-.5*\pgf@pat@distance}},
  top right={%
    \pgfpoint{.5*\pgf@pat@distance}{.5*\pgf@pat@distance}},
  tile size={%
    \pgfpoint
      {\pgfkeysvalueof{/pgf/pattern keys/distance}}%
      {\pgfkeysvalueof{/pgf/pattern keys/distance}}},
  tile transformation={%
    \pgftransformshift{%
      \pgfpoint
        {\pgfkeysvalueof{/pgf/pattern keys/xshift}}%
        {\pgfkeysvalueof{/pgf/pattern keys/yshift}}}%
    \pgftransformrotate{\pgfkeysvalueof{/pgf/pattern keys/angle}}},
  defaults={
    distance/.initial=6pt,
    angle/.initial=0,
    xshift/.initial=0pt,
    yshift/.initial=0pt,
    color series/.initial={red,green,blue},
  },
  code={%
    \pgf@pat@colorseries@parse
    % set line width
    \pgfsetlinewidth{\pgf@pat@distance/\pgfutil@tempcnta}%
    % \pgfutil@tempdima holds the y-coord of a stripe
    \pgfutil@tempdima=\dimexpr-.5\pgf@pat@distance+.5\pgflinewidth\relax
    % draw stripes
    \pgfutil@tempcnta=0
    \pgfutil@for\pgf@pat@temp:=\pgf@pat@colorseries\do{%
      \ifx\pgf@pat@temp\pgfutil@empty
      \else
        \pgfsetstrokecolor{\pgf@pat@temp}%
        \pgfpathmoveto{%
          \pgfpoint{-.5*\pgf@pat@distance}{\pgfutil@tempdima}}%
        \pgfpathlineto{%
          \pgfpoint{ .5*\pgf@pat@distance}{\pgfutil@tempdima}}%
        \pgfusepath{stroke}%
        \advance\pgfutil@tempdima\pgflinewidth
      \fi
    }%
  },
  set up code={%
    \pgfmathsetlength{\pgf@pat@distance}{\pgfkeysvalueof{/pgf/pattern keys/distance}}%
  }
}

% util
\def\pgf@pat@colorseries@parse{%
  % prepare \pgf@pat@colorseries and store its length in \pgfutil@tempcnta
  \let\pgf@pat@colorseries\pgfutil@empty
  \pgfutil@tempcnta=0
  \pgfkeysgetvalue{/pgf/pattern keys/color series}{\pgf@pat@colorseries@temp}%
  \pgfutil@for\pgf@temp:=\pgf@pat@colorseries@temp\do{%
    % strip spaces around color names and skip empty items
    % the total effect is to convert `red, blue,, cyan` to `red,blue,cyan`
    \expandafter\pgfkeys@spdef\expandafter\pgf@temp\expandafter{\pgf@temp}%
    \ifx\pgf@temp\pgfutil@empty
    \else
      \edef\pgf@pat@colorseries{%
        \unexpanded\expandafter{\pgf@pat@colorseries},%
        \unexpanded\expandafter{\pgf@temp}}%
      \advance\pgfutil@tempcnta by 1
    \fi
  }%
  % ensure the resulting color series is never empty
  \ifnum\pgfutil@tempcnta=0
    \def\pgf@pat@colorseries{red,green,blue}%
  \fi
}
\makeatother

\begin{document}
\begin{tikzpicture}
  \fill[pattern={Lines[angle=30]}]
    (0,0) rectangle +(2,1);

  \fill[pattern={Stripes[angle=30]}]
    (0,-1.5) rectangle +(2,1);
  \fill[pattern={Stripes[angle=30, color series={gray, darkgray}]}] 
    (3,-1.5) rectangle +(2,1);
  \fill[pattern={Stripes[angle=30, distance=12pt, color series={cyan, magenta, yellow, black}]}] 
    (6,-1.5) rectangle +(2,1);
  \fill[pattern={Stripes[angle=30, distance=12pt, color series={cyan!50!black!50}]}] 
    (9,-1.5) rectangle +(2,1);
\end{tikzpicture}
\bigskip

% helper option
\pgfqkeys{/pgfgantt}{
  bar pattern/.style={
    bar/.append style={
      pattern={Stripes[angle=45,distance=15pt,color series={#1}]}
    }
  },
  bar pattern/.default={red,green,blue},
}

\noindent
\begin{ganttchart}[expand chart=\textwidth]{1}{48}
  \gantttitle[]{Year 1}{12}
  \gantttitle[]{Year 2}{12}
  \gantttitle[]{Year 3}{12}
  \gantttitle[]{Year 4}{12} \\
  
  \ganttbar[bar pattern]{WP1}{1}{48}                \\
  \ganttbar[bar pattern={blue, green}]{WP2}{4}{15}  \\
  \ganttbar[bar pattern={blue, red}]{WP3}{10}{30}   \\
  \ganttbar[bar pattern={red}]{WP4}{20}{45}
\end{ganttchart}
\end{document}

enter image description here