50

Introduction

I want to create a heatmap using TikZ. You can see the basic idea in the picture below: I basically have a table of values and each value is assigned a color. The picture is the result of the R package ggplot2, which however has some limitations. Thus it was my idea to do the heat maps in LaTeX/TikZ.

Sorry if this gets lengthy but I want to describe unambiguously what I want to do.

What I want to plot

  1. I want to plot a table.
  2. Each cell should have the same size and a value inside it. The values are signed integers. The raw values are printed centered in the cell. Additionally, I want to give the cells a background color, which basically is blue!{value * -0.05} for negative values and red!{value * 0.05} for positive values.
  3. For the colors, I want replace all negative values < -200 by -200, all positive values > 200 by 200.
  4. I want to have a horizontal and a vertical axis with labels.

I will probably generate LaTeX and TikZ code using a preprocessing script. However, I hoped that I could plot the cells with macros to make the result more compact.

How far I got on my own

I have done 1-3 on my own - more or less elegantly. My problem is now how to draw the axes in a nice way.

My questions

  • Can I improve the current code for 1-3?
  • How can I draw the axes elegantly and without having to specify manual coordinates?

Example Data

As requested, here is some example data.

The full data looks as follows. I imported this data into R and then filtered by the values of the dummycol{1,2,3}. I could generate one data file per heat map, however, without problems.

dummycol1 dummycolo2 rowlabel dummycol3 valuecol1 valuecol2 valuecol3 valuecol4
.         .          100      .         -86       -81       -124      -138
.         .          99       .         -90       -96       -122      -108
.         .          98       .         -91       -105      -92       -55

Code

Here is my current code:

\documentclass{article}

\usepackage[utf8]{inputenc}

\usepackage{xcolor}
\usepackage{tikz}
\usetikzlibrary{matrix,positioning}

\usepackage{ifthen}

\newcommand{\trimmed}{foo}
\newcommand{\bgcolor}{foo}
\newcommand{\hmValue}{0}
\newcommand{\transparency}{0}
\newcommand{\hmColor}{}

\newcommand{\HeatmapNode}[1]{
  \ifthenelse{#1 < -200}
  {
    \renewcommand{\trimmed}{-200}
  }
  % else
  {
    \ifthenelse{#1 > 200}
    {
      \renewcommand{\trimmed}{200}
    }
    % else
    {
      \renewcommand{\trimmed}{#1}
    }
  }

  \ifthenelse{#1 < 0}
  {
    \pgfmathparse{round(\trimmed * -0.5)}
    \node [fill=red!\pgfmathresult] {#1};
  }
  % else
  {
    \pgfmathparse{round(\trimmed * 0.5)}
    \node [fill=blue!\pgfmathresult] {#1};
  }
}

\begin{document}

\begin{tikzpicture}
  \matrix(A) [nodes={rectangle,minimum width=2cm,minimum height=1cm}]
  {
    \node {0}; & \HeatmapNode{-300} & \HeatmapNode{-200} & \HeatmapNode{-100} \\
    \node {1}; & \HeatmapNode{0} & \HeatmapNode{100} & \HeatmapNode{200} \\
    \node {2}; & \HeatmapNode{300} & \HeatmapNode{40} & \HeatmapNode{800} \\
    \node {};  & \node{30}; & \node{50}; & \node{70}; \\
  };

\end{tikzpicture}

Example of a heat map.

EDIT Adding example data.

6
  • 1
    Can you give us some dummy data to demonstrate what format it's going to be in?
    – Jake
    Feb 17, 2012 at 15:07
  • Also, this could be of interest: tex.stackexchange.com/questions/42444/…
    – Jake
    Feb 17, 2012 at 15:10
  • \newcommand{\HeatmapNode}[1]{\pgfmathparse{round(min(abs(#1),200) * 0.5)}\node [fill=\ifnum#1>0 blue\else red\fi!\pgfmathresult] {#1};}
    – egreg
    Feb 17, 2012 at 15:20
  • @Jake: Added data. However, the related question looks very related, I'll have a look there.
    – Manuel
    Feb 17, 2012 at 15:20
  • 4
    You can use ggplot2 with tikzDevice. Feb 17, 2012 at 15:22

3 Answers 3

37
+50

Here is a solution which maps the numerical data linearly into a colormap of pgfplots.

A colormap is a map where the smallest number is mapped to the first color and the largest number is mapped to the last color. Anything in-between is interpolated linearly, i.e. a number which is in the middle between the smallest and the largest will be mapped to a color "in the middle of the colormap". Note that a colormap can be defined by means of any number of colors, not just two. In that case, the intervals will be glued together (compare pgfplots manual).

This allows continous colors as in your screenshot.

EDIT: added support to change the text color based on the cell color and improved integration into non-string-type column types.

EDIT: in order to keep this a reference, I incorporated bugfixes/improvements from the follow-up question Heatmap of a triangular matrix (or a sparse matrix)

enter image description here

\documentclass{standalone}
\usepackage{colortbl}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\pgfplotstableset{
    /color cells/min/.initial=0,
    /color cells/max/.initial=1000,
    /color cells/textcolor/.initial=,
    %
    % Usage: 'color cells={min=<value which is mapped to lowest color>, 
    %   max = <value which is mapped to largest>}
    color cells/.code={%
        \pgfqkeys{/color cells}{#1}%
        \pgfkeysalso{%
            postproc cell content/.code={%
                %
                \begingroup
                %
                % acquire the value before any number printer changed
                % it:
                \pgfkeysgetvalue{/pgfplots/table/@preprocessed cell content}\value
                \ifx\value\empty
                    \endgroup
                \else
                \pgfmathfloatparsenumber{\value}%
                \pgfmathfloattofixed{\pgfmathresult}%
                \let\value=\pgfmathresult
                %
                % map that value:
                \pgfplotscolormapaccess
                    [\pgfkeysvalueof{/color cells/min}:\pgfkeysvalueof{/color cells/max}]
                    {\value}
                    {\pgfkeysvalueof{/pgfplots/colormap name}}%
                % now, \pgfmathresult contains {<R>,<G>,<B>}
                % 
                % acquire the value AFTER any preprocessor or
                % typesetter (like number printer) worked on it:
                \pgfkeysgetvalue{/pgfplots/table/@cell content}\typesetvalue
                \pgfkeysgetvalue{/color cells/textcolor}\textcolorvalue
                %
                % tex-expansion control
                % see https://tex.stackexchange.com/questions/12668/where-do-i-start-latex-programming/27589#27589
                \toks0=\expandafter{\typesetvalue}%
                \xdef\temp{%
                    \noexpand\pgfkeysalso{%
                        @cell content={%
                            \noexpand\cellcolor[rgb]{\pgfmathresult}%
                            \noexpand\definecolor{mapped color}{rgb}{\pgfmathresult}%
                            \ifx\textcolorvalue\empty
                            \else
                                \noexpand\color{\textcolorvalue}%
                            \fi
                            \the\toks0 %
                        }%
                    }%
                }%
                \endgroup
                \temp
                \fi
            }%
        }%
    }
}

\begin{document}
\pgfplotstabletypeset[
    color cells={min=-300,max=800},
    col sep=comma,
]{
    a,b,c,d
    50,-300,-200,-100
    -20,0,100,200
    5,300,40,800
    3,30,50,70
}
%
%
\pgfplotstabletypeset[
    color cells={min=-300,max=800},
    col sep=comma,
    /pgfplots/colormap={whiteblue}{rgb255(0cm)=(255,255,255); rgb255(1cm)=(0,0,188)},
]{
    a,b,c,d
    50,-300,-200,-100
    -20,0,100,200
    5,300,40,800
    3,30,50,70
}

\pgfplotstabletypeset[
    color cells={min=0,max=0.8,textcolor=-mapped color!80!black},
    /pgfplots/colormap/greenyellow,
    /pgf/number format/sci,
]{
    a   b   c   d
    5e-1    8e-1    4.4e-2  1e-3     
    0.050   -0.300  -0.200  -0.100
    -0.20   0.1 0.100   0.200
    0.005   0.300   0.40    0.800
    0.8 0.030   0.050   0.70
}
\end{document}

The style here assumes that you set the minimum and maximum value manually. If a number is below or above the minimum/maximum, it will be clipped automatically.

After my first edit, the style respects any output format instructions, i.e. you can apply number formatting and/or postprocessing as with any other data in pgfplotstable. It also makes the code a little bit more complex.

The last entry shows that data in the range [0,1] also seems to work reasonably... and it also shows that the text (foreground) color can be changed in dependence on mapped color.

My document shows two different colormaps for the same data. You may want to study the pgfplots manual to learn how to customize colormaps.

19
  • 1
    This really looks like the ultimate heatmap code! :) I'm having trouble with my floating point matrix though whose values are in [0, 1]. Do real numbers work for you? Also, can you explain to the noob that I am why you chose not to predefine col sep and string type?
    – Christian
    Nov 23, 2012 at 13:59
  • @Christian Since I like pretty pictures (especially if they appear to be useful), I added improved support for string type and standard number formatting. This allows to leave string type away entirely. The col sep should not be predefined as part of the color cells style - what if you have a different separator as in my third example (see edit)? Nov 23, 2012 at 16:25
  • Hey great, thanks! But what I meant: wouldn't something like /color cells/col sep/.initial=comma work?
    – Christian
    Nov 23, 2012 at 17:00
  • 1
    Thanks for all the work you put into this. I'm going to give you a bounty as a small token of my recognition. In the meanwhile I found out how to get non-number columns ignored. For the record, so other people don't have to try to understand the horribly complex pgfplotstable manual: for every column you have to reset the style which is done using the first row of that column. If say the first cell contains "x", then put this in the [] along with col sep=comma and all the rest: columns/x/.style={reset styles,string type}.
    – Christian
    Nov 25, 2012 at 22:40
  • 1
    @Christian Now that I stumbled over this post again, I realized that there was a further spurious white space in the pgfplots code. I have fixed that and I removed at the spurious space found by David Carlisle in your link. My answer here reflects that now. Sep 7, 2013 at 2:33
21

I made this little example by following the result of the previous question, but it wasn't straightforward to change it, as I encounter this other problem (btw, thank you to Andrew Stacy for his help finding what was wrong).

Any way, I think this code achieves what you are trying to do. In summary, the code rounds up the number in the cell by a factor of 0.5. However, as Andrew noticed, you need to put some safeguards to prevent the color going out of range ([0,100]). And then you need to circunvent the expansion problem. Thus, the \edef and the \temp macros. Also, I added the white color if the value is above 50, you can tweak it or remove it as you wish.

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{pgfplotstable}

\pgfplotstableset{
    color cells/.style={
        col sep=comma,
        string type,
        postproc cell content/.code={%
                \pgfkeysalso{@cell content=\rule{0cm}{2.4ex}%
                \pgfmathsetmacro\y{min(100,max(0,abs(round(##1 * 0.5))))}%
                \ifnum##1<0\edef\temp{\noexpand\cellcolor{blue!\y}}\temp\fi%
                \ifnum##1>0\edef\temp{\noexpand\cellcolor{red!\y}}\temp\fi%
                \pgfmathtruncatemacro\x\y%
                \ifnum\x>50 \color{white}\fi%
                ##1}%
                }
    }
}

\begin{document}
\begin{table}\caption{Correlation or something}
\centering
\pgfplotstabletypeset[color cells]{
a,b,c,d
50,-300,-200,-100
-20,0,100,200
5,300,40,800
3,30,50,70
}
\end{table}
\end{document}

heatmap

11

If you're not too familiar with \pgfplotstableset and it's somewhat cryptic looking syntax (to me at least) a more readable alternative is to use simple TiKZ nodes with \foreach loops running over a list of lists:

heatmap

\documentclass[tikz]{standalone}

\begin{document}
\begin{tikzpicture}[scale=0.6]
  \foreach \y [count=\n] in {
      {74,25,39,20,3,3,3,3,3},
      {25,53,31,17,7,7,2,3,2},
      {39,31,37,24,3,3,3,3,3},
      {20,17,24,37,2,2,6,5,5},
      {3,7,3,2,12,1,0,0,0},
      {3,7,3,2,1,36,0,0,0},
      {3,2,3,6,0,0,45,1,1},
      {3,3,3,5,0,0,1,23,1},
      {3,2,3,5,0,0,1,1,78},
    } {
      % column labels
      \ifnum\n<10
        \node[minimum size=6mm] at (\n, 0) {\n};
      \fi
      % heatmap tiles
      \foreach \x [count=\m] in \y {
        \node[fill=yellow!\x!purple, minimum size=6mm, text=white] at (\m,-\n) {\x};
      }
    }

  % row labels
  \foreach \a [count=\i] in {a,b,c,d,e,f,g,h,i} {
    \node[minimum size=6mm] at (0,-\i) {\a};
  }
\end{tikzpicture}
\end{document}
4
  • How would you add column labels here?
    – Sterling
    Mar 11, 2022 at 17:05
  • 1
    @Sterling See my updated answer.
    – Janosh
    Mar 11, 2022 at 18:30
  • Hey Casimir, this super helpful! A quick question: how would I want the color to make gradually become red from white - yellow - red in ascending order from 0 to say 10? May 2, 2022 at 22:47
  • @FrankSwanton You could try the xcolor RGB mixing syntax which TikZ supports: fill={rgb:red,1;green,2;blue,5} and replace the numbers with functions of \x. If that doesn't work, I think you'll have to use pgfplot functionality. Maybe this helps.
    – Janosh
    May 3, 2022 at 7:01

You must log in to answer this question.

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