Heatmap over country like Google Map
Here is a command, called \heatmark
, to produce, in a tikzpicture
, a marker similar to those on the map you link to; the code could probably be improved (edit: I've spruced the original code a bit).
The \heatmark
command takes 4 arguments:
- the position, in the form of
x,y
, - the radius of the inner disk,
- the color of the inner disk,
- the text to be written at the centre of the disk.
For more details on how to draw on top of an external picture (e.g. a map) in TikZ, please refer to Drawing on an image with TikZ.
\documentclass{article}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{tikz}
\newcommand\heatmark[4]{
\def\r{#2} % radius of disk
\def\d{.1*\r} % radius gap between arc segments
\def\angleoffset{10} % half angular gap between arc segments
\def\e{.3*\r} % radial width of arc segments
\def\mycolor{#3} % color of disk
\pgfmathtruncatemacro\n{3} % number of arc segments outside disk
\begin{scope}[shift={(#1)}] % shift to desired position
\fill[\mycolor] circle (\r); % draw inner disk
\foreach \onethirdangle in {0,120,240}{% % rotational symmetry
\begin{scope}[rotate=\onethirdangle]
\foreach \i in {1,...,\n}{%
\pgfmathsetmacro\opac{1.1-\i/\n}
\def\Aone{30+\angleoffset}
\def\Atwo{150-\angleoffset}
\def\Rone{\r+\i*\d+(\i-1)*\e}
\def\Rtwo{\Rone+\e}
\fill[\mycolor,opacity=\opac] % draw one arc segment
({(\Rone)*cos(\Aone)},{(\Rone)*sin(\Atwo)})
arc ({\Aone}:{\Atwo}:{\Rone}) % arc 1
-- ++({\e*cos(\Atwo)},{\e*sin(\Atwo)}) % straight line 1
-- ({(\Rtwo)*cos(\Atwo)},{(\Rtwo)*sin(\Aone)})
arc ({\Atwo}:{\Aone}:{\Rtwo} ) % arc 2
-- cycle; % straight line 2
}
\end{scope}
}
\node[white] {\small #4};
\end{scope}
}
\begin{document}
Original map:\\
\includegraphics[width=0.9\textwidth]{western_europe.png}
Some markers on their own:\\
\begin{tikzpicture}
\heatmark{0,0}{.5}{RubineRed}{300}
\heatmark{2,0}{.3}{YellowOrange!80}{50}
\heatmark{4,0}{.3}{Cerulean}{50}
\end{tikzpicture}
\newpage
Original map with some markers on it:\\
\begin{tikzpicture}
\node[anchor=south west,inner sep=0] (image) at (0,0)
{\includegraphics[width=0.9\textwidth]{western_europe.png}};
\begin{scope}[x={(image.south east)},y={(image.north west)}]
\def\Paris{.45,.5}
\def\Madrid{.25,.2}
\def\Rome{.8,.25}
\heatmark{\Rome}{.5cm}{RubineRed}{300}
\heatmark{\Madrid}{.25cm}{YellowOrange!80}{35}
\heatmark{\Paris}{.3cm}{Cerulean}{50}
\end{scope}
\end{tikzpicture}
\end{document}
Code
\documentclass[tikz,convert=false]{standalone}
\makeatletter
\tikzset{
heat arc width/.initial=+4pt,
heat arc sep/.initial=+1pt,
heat arc rings/.initial=3,
heat arcs/.initial=3,
heat arc sep angle/.initial=20,
heat opacity high/.initial=.8,
heat opacity low/.initial=.2,
heat rotate/.initial=90,
}
\pgfdeclareshape{heat}
%
% Draws a circle around the text
%
{
\savedanchor\centerpoint{%
\pgf@x=.5\wd\pgfnodeparttextbox%
\pgf@y=.5\ht\pgfnodeparttextbox%
\advance\pgf@y by-.5\dp\pgfnodeparttextbox%
}
\saveddimen\innerradius{%
%
% Caculate ``height radius''
%
\pgf@ya=.5\ht\pgfnodeparttextbox%
\advance\pgf@ya by.5\dp\pgfnodeparttextbox%
\pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/inner ysep}}%
\advance\pgf@ya by\pgf@yb%
%
% Caculate ``width radius''
%
\pgf@xa=.5\wd\pgfnodeparttextbox%
\pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/inner xsep}}%
\advance\pgf@xa by\pgf@xb%
%
% Calculate length of radius vector:
%
\pgf@process{\pgfpointnormalised{\pgfqpoint{\pgf@xa}{\pgf@ya}}}%
\ifdim\pgf@x>\pgf@y%
\c@pgf@counta=\pgf@x%
\ifnum\c@pgf@counta=0\relax%
\else%
\divide\c@pgf@counta by 255\relax%
\pgf@xa=16\pgf@xa\relax%
\divide\pgf@xa by\c@pgf@counta%
\pgf@xa=16\pgf@xa\relax%
\fi%
\else%
\c@pgf@counta=\pgf@y%
\ifnum\c@pgf@counta=0\relax%
\else%
\divide\c@pgf@counta by 255\relax%
\pgf@ya=16\pgf@ya\relax%
\divide\pgf@ya by\c@pgf@counta%
\pgf@xa=16\pgf@ya\relax%
\fi%
\fi%
\pgf@x=\pgf@xa%
%
% If necessary, adjust radius so that the size requirements are
% met:
%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/minimum width}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/minimum height}}%
\ifdim\pgf@x<.5\pgf@xb%
\pgf@x=.5\pgf@xb%
\fi%
\ifdim\pgf@x<.5\pgf@yb%
\pgf@x=.5\pgf@yb%
\fi%
}%
\saveddimen\radius{%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
\ifdim\pgf@xb<\pgf@yb%
\advance\pgf@x by\pgf@yb%
\else%
\advance\pgf@x by\pgf@xb%
\fi%
\pgfmathsetcount\c@pgf@counta{\pgfkeysvalueof{/tikz/heat arc rings}}%
\pgfmathsetlength\pgf@xa{\pgfkeysvalueof{/tikz/heat arc width}+\pgfkeysvalueof{/tikz/heat arc sep}}%
\multiply\pgf@xa\c@pgf@counta
\advance\pgf@x\pgf@xa
}%
%
% Anchors
%
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{north west}
\inheritanchor[from=circle]{north east}
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{west}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{mid}
\inheritanchor[from=circle]{mid west}
\inheritanchor[from=circle]{mid east}
\inheritanchor[from=circle]{base}
\inheritanchor[from=circle]{base west}
\inheritanchor[from=circle]{base east}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{south west}
\inheritanchor[from=circle]{south east}
\inheritanchorborder[from=circle]
%
% Background path
%
\backgroundpath{
\pgfutil@tempdima=\innerradius%
\pgfpathcircle{\centerpoint}{\pgfutil@tempdima}%
}%
%
%
%
\behindbackgroundpath{%
\pgfmathsetcount\c@pgf@counta{\pgfkeysvalueof{/tikz/heat arc rings}}% heat arc rings
\pgfmathsetcount\c@pgf@countb{\pgfkeysvalueof{/tikz/heat arcs}}% heat arcs
\pgfmathsetlength\pgf@xa{\pgfkeysvalueof{/tikz/heat arc width}}% heat arc width
\pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/tikz/heat arc sep}}% heat arc sep
\pgfmathsetmacro\pgf@tempa{\pgfkeysvalueof{/tikz/heat arc sep angle}}% heat arc sep angle
\pgfmathsetmacro\pgf@tempb{(360-\c@pgf@countb*\pgf@tempa)/\c@pgf@countb}% heat arc
\pgfmathsetmacro\pgf@tempc{\pgfkeysvalueof{/tikz/heat rotate}}% heat rotate
%
\pgfmathsetmacro\pgf@tempd{\pgfkeysvalueof{/tikz/heat opacity high}}% heat opacity high
\ifnum\c@pgf@counta=1
\def\qrr@heat@opacity@seps{0}%
\else
\pgfmathsetmacro\qrr@heat@opacity@seps
{(\pgf@tempd-\pgfkeysvalueof{/tikz/heat opacity low})%
/(\c@pgf@counta-1)}%
\fi
\pgfutil@tempcnta\z@
\pgfutil@tempcntb\z@
\pgf@ya\innerradius\relax
\pgftransformshift{\centerpoint}
\pgfutil@loop % ring loop
\advance\pgf@ya\pgf@xb % current radius + heat arc sep
\pgf@yb\pgf@ya
\advance\pgf@yb\pgf@xa % current radius + heat arc sep + heat arc width
\pgfsetfillopacity{\pgf@tempd}%
\begingroup
\edef\pgf@marshal{\noexpand\tikzset{heat ring \number\numexpr\pgfutil@tempcnta+1\relax/.try}}%
\pgf@marshal
\tikz@options
{%
\pgfutil@loop
\pgfmathsetmacro\qrr@start@angle{\[email protected]*\pgf@tempb+\pgfutil@tempcntb*(360/\c@pgf@countb)}%
% path
\begingroup
\edef\pgf@marshal{\noexpand\tikzset{heat arc \number\numexpr\pgfutil@tempcntb+1\relax/.try}%
\noexpand\tikzset{heat arc \number\numexpr\pgfutil@tempcnta+1\relax-\number\numexpr\pgfutil@tempcntb+1\relax/.try}}%
\pgf@marshal
\tikz@options
\pgfpathmoveto{\pgfpointpolar{\qrr@start@angle}{\pgf@ya}}%
\pgfpatharc{\qrr@start@angle}{\qrr@start@angle+\pgf@tempb}{\pgf@ya}%
\pgfpathlineto{\pgfpointpolar{\qrr@start@angle+\pgf@tempb}{\pgf@yb}}%
\pgfpatharc{\qrr@start@angle+\pgf@tempb}{\qrr@start@angle}{\pgf@yb}%
\pgfpathclose
\pgfusepathqfill
\endgroup
\advance\pgfutil@tempcntb\@ne
\ifnum\pgfutil@tempcntb<\c@pgf@countb
\pgfutil@repeat
}%
\endgroup
\advance\pgfutil@tempcnta\@ne
\ifnum\pgfutil@tempcnta<\c@pgf@counta
\pgf@ya\pgf@yb
\pgfmathsetmacro\pgf@tempd{\pgf@tempd-\qrr@heat@opacity@seps}%
\pgfutil@repeat
}
}
\tikzset{every heat node/.append style={
outer sep={+\z@},
draw=none
}}
\makeatother
\tikzset{radius=.5pt}
\begin{document}
\tikz[
heat ring 1/.append style={green},
heat arc 1/.append style={blue},
heat arc 2-2/.append style={yellow!70!black}
] {
\node[heat, fill=red] (n) {100};
\fill (n.west) circle[]
(n.east) circle[]
(n.north) circle[]
(n.south) circle[];
\useasboundingbox ([yshift=-2mm]n.south) ([xshift=-2mm]n.west) ([yshift=2mm]n.north) ([xshift=2mm]n.east);
}
\foreach \sep in {0,10,...,120}{%
\tikz{
\node[heat arc sep angle=\sep, heat, fill=red, text=white, minimum size=1cm] (n) {\sep};
\fill (n.west) circle[]
(n.east) circle[]
(n.north) circle[]
(n.south) circle[];
\useasboundingbox ([yshift=-2mm]n.south) ([xshift=-2mm]n.west) ([yshift=2mm]n.north) ([xshift=2mm]n.east);
}%
}%
\foreach \rot in {0,10,...,359}{%
\tikz{
\node[heat rotate=\rot, heat, fill=green, minimum size=1cm] (n) {\rot};
\fill (n.west) circle[]
(n.east) circle[]
(n.north) circle[]
(n.south) circle[];
\useasboundingbox ([yshift=-2mm]n.south) ([xshift=-2mm]n.west) ([yshift=2mm]n.north) ([xshift=2mm]n.east);
}%
}%
\foreach \arcs in {1,...,10}{%
\tikz{
\node[heat arcs=\arcs, heat, fill=blue, text=white, minimum size=1cm] (n) {\arcs};
\fill (n.west) circle[]
(n.east) circle[]
(n.north) circle[]
(n.south) circle[];
\useasboundingbox ([yshift=-2mm]n.south) ([xshift=-2mm]n.west) ([yshift=2mm]n.north) ([xshift=2mm]n.east);
}%
}%
\foreach \arcs in {1,...,10,9,8,...,2}{%
\tikz{
\node[heat arc rings=\arcs, heat, fill=yellow!50!black, minimum size=1cm] (n) {\arcs};
\fill (n.west) circle[]
(n.east) circle[]
(n.north) circle[]
(n.south) circle[];
\useasboundingbox ([yshift=-2mm]n.south) ([xshift=-2mm]n.west) ([yshift=2mm]n.north) ([xshift=2mm]n.east);
}%
}%
\end{document}
Output
(The last one does change its overall size.)