2

I just want to fill a cell with a two colors range like a heatmap, see the figure bellow.

enter image description here

  • 5
    Welcome to TeX.SE! Do you really want a table like that you showed? The color hurts ... Can you please add an compilable code resulting in the shown table? – Mensch Nov 15 '17 at 02:59
  • I agree the colour is very painful. Have you seen this post? https://tex.stackexchange.com/questions/174234/latex-tables-cell-value-color-based-on-its-sign-conditional-cell-color – Tina Nov 15 '17 at 03:13
  • 2
    Anyway, look up fadings in the PGF manual. Page 348 has a useful example. – John Kormylo Nov 15 '17 at 04:10
  • 1
    If you're going to use saturated red-purple-blue, it should be against white -- either white text on a coloured background, or just colour the text. With the approach shown here you could mix it with some white – Chris H Nov 15 '17 at 09:42

1 Answers1

16

This question appealed to me: how do you automatically colour the entries of a table based on the values in the cells? That is, how do we make the not-so-random table

\begin{tabular}{ *7H }
-1.0 & -0.9 & -0.8 & -0.7 & -0.6 & -0.5 & -0.4 \\
-0.3 & -0.2 & -0.1 &  0.0 &  0.1 &  0.2 &  0.3 \\
 0.4 &  0.5 &  0.6 &  0.7 &  0.8 &  0.9 &  1.0 \\
\multicolumn{7}{c}{Min
   \tikz{\shade[left color=blue,right color=red] 
          (current page.south west) rectangle ++(#1,12pt);}
    Max}
\end{tabular}

produce the following?

enter image description here

[The OP is perhaps mainly asking about drawing a gradient shading, but as John Kormylo says in the comments (and shown above), this is easy to do using tikz.]

In answering this I assumed that these matrices have entries between -1 and 1 -- it is easy to change the code below to work for other domains. To automatically assign a background colour to each cell based on the value of the entry I first use the array package to define a new column type

\newcolumntype{H}{>{\collectcell\Heat}r<{\endcollectcell}}

In turn, the H column type uses the collcell package to apply the the command \Heat to each cell entry. The \Heat macro first maps the cell entry to an integer between 0 and 100 using pgfmath

\pgfmathparse{int(50+50*#1)}% map number in [-1,1] to [0,100]

This number is then used to set the background colour of the cell with \cellcolor{red!\pgfmathresult!blue} -- although, in practice a little expansion hackery is needed here. With these definitions in place the code above does what I wanted.

Here is the full code:

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{collcell}
\usepackage{array}
\usepackage{tikz}
\newcolumntype{H}{>{\collectcell\Heat}r<{\endcollectcell}}

\newcommand\Heat[1]{% \Heat{number in the interval [-1,1]}
    \pgfmathparse{int(50+50*#1)}% map number in [-1,1] to [0,100]
    \edef\HeatCell{\noexpand\cellcolor{red!\pgfmathresult!blue}}%
    \HeatCell$#1$%
}

\begin{document}

\begin{tabular}{ *7H }
-1.0 & -0.9 & -0.8 & -0.7 & -0.6 & -0.5 & -0.4 \\
-0.3 & -0.2 & -0.1 &  0.0 &  0.1 &  0.2 &  0.3 \\
 0.4 &  0.5 &  0.6 &  0.7 &  0.8 &  0.9 &  1.0 \\
\multicolumn{7}{c}{Min
   \tikz{\shade [left color=blue ,right color=red]
         (current page.south west) rectangle ++(#1,12pt);}
    Max}
\end{tabular}

\end{document}

This said, the choice of colours makes the table very garish and unpleasant to look at. A first improvement would be to make the text colour white, which we can do by changing \Heat to:

\newcommand\Heat[1]{% \Heat{number in the interval [-1,1]}
    \pgfmathparse{int(50+50*#1)}% map number in [-1,1] to [0,100]
    \edef\HeatCell{\noexpand\cellcolor{red!\pgfmathresult!blue}}%
    \HeatCell\textcolor{white}{$#1$}%
}

This produces:

enter image description here

We could instead change the colours to Yellow1 and SpringGreen3 (using the x11names option to xcolor), to get:

enter image description here

Edit

Just for fun here's the same code but with a pgfkeys interface for setting the colours and the min/max values via \Heatset{min colour=blue, max colour=red} etc. There is also a slider \Heatset{slider= <width> } for the gradient bar:

\documentclass{article}
\usepackage[table,x11names]{xcolor}
\usepackage{collcell}
\usepackage{array}
\usepackage{tikz}
\usepackage{pgfkeys}

% set up pgfkeys for controlling heat specifications
\pgfkeys{/heat/.is family, /heat,
    min colour/.initial = Yellow1,
    max colour/.initial = SpringGreen3,
    text colour/.initial = black,
    min color/.style = {min colour=#1},% for our friends who can't spell
    max color/.style = {max colour=#1},
    text color/.style = {text colour=#1},
    min/.initial = -1,
    max/.initial = 1,
    slider/.code={%
       \tikz{\shade[left color=\HVal{min colour},%
                    right color=\HVal{max colour}]%
          (current page.south west) rectangle ++(#1,12pt);
       }%
    }%
}
\newcommand\Heatset[1]{\pgfkeys{/heat, #1}}
\newcommand\HVal[1]{\pgfkeysvalueof{/heat/#1}}

\newcolumntype{H}{>{\collectcell\Heat}r<{\endcollectcell}}
\newcommand\Heat[1]{% \Heat{number in the interval [min, max] }
  \pgfmathparse{int(100*(#1-\HVal{min})/(\HVal{max}-\HVal{min}))}% map number to [0,100]
  \edef\HeatCell{\noexpand\cellcolor{\HVal{max colour}!\pgfmathresult!\HVal{min colour}}}%
  \HeatCell\textcolor{\HVal{text colour}}{$#1$}%
}

\begin{document}

\begin{tabular}{ *7H }
-1.0 & -0.9 & -0.8 & -0.7 & -0.6 & -0.5 & -0.4 \\
-0.3 & -0.2 & -0.1 &  0.0 &  0.1 &  0.2 &  0.3 \\
 0.4 &  0.5 &  0.6 &  0.7 &  0.8 &  0.9 &  1.0 \\
 \multicolumn{7}{c}{Min \Heatset{slider=5.5} Max}
\end{tabular}

\bigskip\noindent
\Heatset{max colour=red, min colour=blue, min=0, max=100, text colour=white}
\verb|\Heatset{max colour=red, min colour=blue, min=0, max=100, text colour=white}|
\begin{tabular}{ *7H }
 10 &  90 &  80 &  70 &  60 &  50 &  40 \\
 30 &  20 &  10 &  00 &  10 &  20 &  30 \\
 40 &  50 &  60 &  70 &  80 &  90 &  10 \\
 \multicolumn{7}{c}{Min \Heatset{slider=3.0} Max}
\end{tabular}

\end{document}

This produces:

enter image description here

  • 2
    I have only one problem with your solution...everyone knows that Min is blue and Max is red, as in the temperature analogy! – Steven B. Segletes Nov 15 '17 at 12:59
  • @StevenB.Segletes Well, clearly not everyone! :) I'll succumb to peer-group pressure and change it. –  Nov 15 '17 at 13:10
  • 1
    +1, \edef\HeatCell rather than \xdef\HeatCell works. –  Nov 15 '17 at 13:30
  • @jfbu Thanks. Changed. I'm too heavy handed :) –  Nov 15 '17 at 13:36
  • 1
    the \HeatCell defintion line lacks an % at the end. The space token introduced is not ignored in tabular. –  Nov 15 '17 at 13:43
  • @jfbu Hmm, I was assuming that spaces were ignored since the tabular is aligning the column to the right but it seems you're right and the cells above are slightly pushed to the right. Thanks. I'll fix. –  Nov 15 '17 at 13:48
  • 1
    The reason is that the action of \ignorespaces token inserted by LaTeX's kernel code does not extend to the other side of the \edef, which acts like a "barrier". (by the way this is independent of alignment of column cells) –  Nov 15 '17 at 13:56
  • 1
    actually, with collcell loaded, it has a \collcell@beforeuser which is hardcoded in style file to be macro with content \ignorespaces (except if package option verb). Anyway, the expansion recomposes the standard LaTeX setting with an \ignorespaces first and an \unskip last. –  Nov 15 '17 at 14:07
  • This is the solution I was looking for, thank you so much. There is one more thing, do you know how can I align the rectangle so the bottom part is at the same level at the cell, if you look closer you will see it is even above the level of letters (e.g. min, max) – Roger PA Nov 20 '17 at 19:58
  • @Avenger Using (current page.south west) rectangle ++(#1,12pt) in the shade command works better. Instead of hard coding the 12pt could put \def\lineheight{\the\baselineskip} after \begin{document} and then use (curreant page.south west) rectangle ++(#1,\baselineskip). I've justed updated the answer -- but I have only fixed the last image. –  Nov 20 '17 at 20:31