# How to create a electromagnetic spectrum using pgfplots package (together with colormaps)

In my search of a good electromagnetic spectrum using LaTeX I came up short and thus I needed to create my own image. Here's how I did it.

• Well I thought I'd be helpful and answer my own question (how to create a electromagnetic spectrum in latex). Since the "ask question" even have a "answer your own question" option I thought it would be okay? Please forgive my ignorance. Jan 13, 2017 at 9:23
• Unrelated (or somewhat related) but maybe useful: pgf-spectra offers an easy way to draw chemical elements spectrum. For other Underground TikZ libraries/packages see this link. Jan 13, 2017 at 12:23

%
\documentclass[12pt]{article}
\usepackage[dvipsnames,table]{xcolor}
\usepackage{siunitx} % SI-units
\usepackage{pgfplots}
\usepgfplotslibrary{units} % to add units easily to axis
\usepgfplotslibrary{fillbetween} % to fill inbetween curves
\usepgfplotslibrary{colormaps} % to create colormaps
\pgfplotsset{width=12.2cm, height=7cm}
\pgfplotsset{compat=newest} %(making it only compatalbe with
%new releases of pgfplots)
color(0.00000000000000bp)=(violet);
color(8.33333333333333bp)=(blue);
color(16.66666666666670bp)=(cyan);
color(25.00000000000000bp)=(green);
color(33.33333333333330bp)=(yellow);
color(41.66666666666670bp)=(orange);
color(50.00000000000000bp)=(red)
}%

\begin{document}
\begin{tikzpicture}[fill between/on layer={axis grid}]
\begin{axis}[
xlabel={Wavelength},
xticklabel style = {font=\tiny,yshift=0.2ex},
xmin=10^-5,
xmax=10^9,
x unit=\si{\micro\meter},
xmode=log,
ymin=0,
ymax=1,
height=3cm,
yticklabels={},
ytick=\empty,
legend cell align=left,
legend style={at={(0.85,-0.77)},anchor=north}
]
\addplot[draw=none, name path=start, forget plot] coordinates{(10^-5,0)(10^-5,1)};
\addplot[draw=none, name path=gamma, forget plot] coordinates{(10^-3,0)(10^-3,1)};
\addplot[draw=none, name path=xrays, forget plot] coordinates{(10^-2,0)(10^-2,1)};
\addplot[draw=none, name path=uv, forget plot] coordinates{(0.4,0)(0.4,1)};
\addplot[draw=none, name path=visible, forget plot] coordinates{(0.7,0)(0.7,1)};
\addplot[draw=none, name path=ir, forget plot] coordinates{(10^2.5,0)(10^2.5,1)};
\addplot[draw=none, name path=microwave, forget plot] coordinates{(10^5,0)(10^5,1)};
\addplot[violet!20, area legend] fill between[of=start and gamma];
\addlegendentry{$\gamma$-ray}
\addplot[violet!60, area legend] fill between[of=gamma and xrays];
\addplot[violet, area legend] fill between[of=xrays and uv];
\addplot[red, area legend] fill between[of=visible and ir];
\addplot[Bittersweet, area legend] fill between[of=ir and microwave];
\end{axis}
\end{tikzpicture}
\end{document}


Example output:

Based on the existing answer, I also created an approach. It has some issues that could need work, especially for the 'Visible Spectrum' Scale and names of the colors at the bottom. It also basically falls apart for any other wavelength exponents other than the ones specified in the code already.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc, positioning, shapes, backgrounds, fit, arrows}
\usepackage{pgf-spectra}

\usepackage{siunitx}

\usepackage{contour}

\begin{document}

color(0bp)=(violet!25);
color(8.33bp)=(blue!25);
color(16.67bp)=(cyan!25);
color(25bp)=(green!25);
color(33.33bp)=(yellow!25);
color(41.5bp)=(orange!25);
color(50bp)=(red!25)
}%

\begin{tikzpicture}[%
raylabel/.style={font=\scriptsize}
]
\def\minexponent{-6}
\def\maxexponent{6}
\def\spectrumheight{9em}

\pgfmathtruncatemacro{\nextminexponent}{\minexponent + 1}

% Main foreach loop, drawing the wavelengths as powers of 10 in an alternating fashion: even on top, odd at bottom. Then connects them with help lines
\foreach [count=\i, remember=\exponent as \previousexponent, evaluate=\i as \currentposition using int(\i/2)] \exponent in {\minexponent, \nextminexponent, ..., \maxexponent}{
\ifodd\exponent
\def\height{0}
\else
\def\height{\spectrumheight}
\fi

% Anchor at baseline to get all nodes on same baseline.
% https://tex.stackexchange.com/questions/133227/how-to-align-text-in-tikz-nodes-by-baseline#comment300863_133227
\node[anchor=base] (WAVELENGTH_\exponent) at (\exponent, \height) {\contour{white}{\num{e\exponent}}};

\ifnum\i > 1
\ifodd\i
\node (LABEL_\currentposition)
at ($(WAVELENGTH_\exponent)!0.5!(WAVELENGTH_\previousexponent)$)
{};% This is left as a node as opposed to coordinate: fill it out with '\currentposition' for debugging
\else
% Do not draw connection at exponent 1:
\pgfmathparse{\exponent != 1}% \pgfmathparse stores result (0 or 1) in macro \pgfmathresult
\ifnum\pgfmathresult = 1
\draw[help lines]
(WAVELENGTH_\previousexponent) --(WAVELENGTH_\exponent)
node[midway] (CONNECTION_\currentposition) {}% This is left as a node as opposed to coordinate: fill it out with '\currentposition' for debugging
coordinate[at start] (CONNECTION_\currentposition_START)
coordinate[at end] (CONNECTION_\currentposition_END);
\fi
\fi
\fi
}

% Create an arrow shape that fits around all relevant nodes, but do not draw it.
% Draw it manually later to leave out the 'bottom' of the arrow.
% We still need this invisible arrow for lining up of coordinates
\node[
single arrow,
single arrow tip angle=150,% Inner angle of arrow tip
fit={([xshift=-3em]CONNECTION_1_START)(CONNECTION_1_END)(CONNECTION_\maxexponent_START)([xshift=5em]CONNECTION_\maxexponent_END)},
inner sep=0pt
]
(ARROW) {};

\node[align=center] (THERM) at ([yshift=3em]WAVELENGTH_1|-ARROW.after tail) {thermal\\radiation};% Only works because exponent 1 is between -1 and 3
\draw (THERM) -| ([yshift=-1.5em]WAVELENGTH_-1|-THERM);
\draw (THERM) -| ([yshift=-1.5em]WAVELENGTH_3|-THERM);

% On background layer so already drawn arrow and scale lines cover it up nicely
\begin{scope}[on background layer]
\node[
inner sep=0pt,
outer sep=0pt,
(SMALL_VISIBLE_LIGHT) {};

left color=white,
right color=violet!25,
middle color=violet!5,
outer sep=0pt
]
(CONNECTION_3_START) -- (CONNECTION_3_END) -- ([xshift=\pgflinewidth]SMALL_VISIBLE_LIGHT.south west) -- ([xshift=\pgflinewidth]SMALL_VISIBLE_LIGHT.north west) -- cycle;

left color=red!25,
right color=white,
middle color=red!5,
outer sep=0pt,
]
(CONNECTION_5_START) -- (CONNECTION_5_END) -- ([xshift=-\pgflinewidth]SMALL_VISIBLE_LIGHT.south east) -- ([xshift=-\pgflinewidth]SMALL_VISIBLE_LIGHT.north east) -- cycle;
\end{scope}

% Some labels can be drawn automatically at the designated label coordinates:
\foreach [count=\i] \label in {
{Gamma\\rays},
{X-rays},
{},%Skip this one
{infrared}
}{
\node[raylabel, align=center] at (LABEL_\i) {\label};
}

% These do not fit the loop and are drawn manually:
\node[raylabel, align=right, anchor=north] at ([yshift=-1em]$(WAVELENGTH_-2)!0.45!(WAVELENGTH_0)$) {Ultra-\\violet};

\node[raylabel, fill=white] at (CONNECTION_6) {radio waves};

\node[raylabel, left=0.1em of CONNECTION_1, align=right] {cosmic\\rays};

\node[
draw,
fill=black!20,
below=4em of SMALL_VISIBLE_LIGHT,
align=center,
label=above:{\textbf{Visible Spectrum}}
] (FULL_VISIBLE_LIGHT) {%
\pgfspectra[width=13em,height=3em]\\%pgfspectra also has a builtin axis which of course much better than this terrible approach, but it is in nanometer
{\num{0.38} \hfill\num{0.48} \hfill\num{0.58}\hfill \num{0.68} \hfill\num{0.78}}\\
{\hfill blue \hspace{0.1em} green yellow \hfill red \hfill}
};

% Draw 'magnifying' trapeze, on background so it is covered by scale labels
\begin{scope}[on background layer]
\filldraw[help lines, fill=black!10] (FULL_VISIBLE_LIGHT.north east) -- (SMALL_VISIBLE_LIGHT.south east) -- (SMALL_VISIBLE_LIGHT.south west) -- (FULL_VISIBLE_LIGHT.north west) -- cycle;
\end{scope}

% Draw around arrow manually, leaving its tail open
\draw[draw, thick] (ARROW.after tail) -- (ARROW.before head) -- (ARROW.before tip) -- (ARROW.tip) -- (ARROW.after tip) -- (ARROW.after head) -- (ARROW.before tail);
\end{tikzpicture}
\end{document}


giving an electromagnetic spectrum with some annotations, generated semi-automatically:

Here I present some "improved" versions of the already great answers. The main difference is that with the release of pgf-spectra v2.1.1 now also logarithmic spectra are available.

Because the spectra of visible light are quite small in the images, the difference is only minor, but in other applications that might be very useful.

\documentclass[border=5pt]{standalone}
\usepackage[dvipsnames,table]{xcolor}
\usepackage{siunitx}
\usepackage{pgf-spectra}
\usepackage{pgfplots}
\usepgfplotslibrary{
colormaps,
fillbetween,
units,
}
\pgfplotsset{
compat=1.18,
width=12.2cm,
height=3cm,
}
%    % make the horizontal shading and set the UV and IR colors -->
%    \pgfspectraStyle[gamma=.6] % uncomment to change the gamma
\wlcolor{380}\colorlet{UV}{wlcolor}%
\wlcolor{780}\colorlet{IR}{wlcolor}%
%   \pgfspectraStyleReset% uncomment to reset the style
\begin{document}
\begin{tikzpicture}
\begin{axis}[
xmode=log,
xmin=1e-5,
xmax=1e9,
x unit=\si{\micro\meter},
xlabel={Wavelength},
xticklabel style={font=\tiny,yshift=0.2ex},
ymin=0,
ymax=1,
ytick=\empty,
legend cell align=left,
legend style={at={(0.85,-0.77)},anchor=north},
axis on top,
area legend,
]
\addplot [draw=none,forget plot,name path=start]     coordinates {(1e-5,0)(1e-5,1)};
\addplot [draw=none,forget plot,name path=gamma]     coordinates {(1e-3,0)(1e-3,1)};
\addplot [draw=none,forget plot,name path=xrays]     coordinates {(1e-2,0)(1e-2,1)};
\addplot [draw=none,forget plot,name path=uv]        coordinates {(0.38,0)(0.38,1)};
\addplot [draw=none,forget plot,name path=visible]   coordinates {(0.78,0)(0.78,1)};
\addplot [draw=none,forget plot,name path=ir]        coordinates {(10^2.5,0)(10^2.5,1)};
\addplot [draw=none,forget plot,name path=microwave] coordinates {(1e5,0)(1e5,1)};

\addplot [violet!20] fill between [of=start and gamma];
\addlegendentry{$\gamma$-ray}
\addplot [violet!60] fill between [of=gamma and xrays];
\addplot [UV!50,area legend] fill between [of=xrays and uv];

% forget this plot ...
% (why? Because we can :))

\addplot [IR!50,area legend] fill between [of=visible and ir];
\addplot [IR!50!Bittersweet] fill between [of=ir and microwave];
\end{axis}
\end{tikzpicture}
\end{document}


\documentclass[border=5pt]{standalone}
\usepackage{contour}
\usepackage{tikz}
\usepackage{siunitx}
\usetikzlibrary{
arrows,
backgrounds,
calc,
fit,
positioning,
shapes,
}
\usepackage{pgf-spectra}
\begin{document}
\begin{tikzpicture}[
raylabel/.style={font=\scriptsize}
]

%    % make the horizontal shading and set the UV and IR colors -->
%    \pgfspectraStyle[gamma=.6]     % uncomment to change the gamma
\wlcolor{380}\colorlet{UV}{wlcolor}
\wlcolor{780}\colorlet{IR}{wlcolor}
%    \pgfspectraStyleReset          % uncomment to reset the style

\def\minexponent{-6}
\def\maxexponent{6}
\def\spectrumheight{9em}

\pgfmathtruncatemacro{\nextminexponent}{\minexponent + 1}

% Main foreach loop, drawing the wavelengths as powers of 10 in an alternating
% fashion: even on top, odd at bottom. Then connects them with help lines
\foreach [
count=\i,
remember=\exponent as \previousexponent,
evaluate=\i as \currentposition using int(\i/2),
] \exponent in {\minexponent,\nextminexponent,...,\maxexponent}{
\ifodd\exponent
\def\height{0}
\else
\def\height{\spectrumheight}
\fi

\node [anchor=base] (WAVELENGTH_\exponent) at (\exponent, \height)
{\contour{white}{\num[print-unity-mantissa = false]{e\exponent}}};

\ifnum\i > 1
\ifodd\i
\node (LABEL_\currentposition)
at ($(WAVELENGTH_\exponent)!0.5!(WAVELENGTH_\previousexponent)$)
% This is left as a node as opposed to coordinate:
% fill it out with '\currentposition' for debugging
{}
;
\else
% Do not draw connection at exponent 1:
% (\pgfmathparse stores result (0 or 1) in macro \pgfmathresult)
\pgfmathparse{\exponent != 1}
\ifnum\pgfmathresult = 1
\draw[help lines]
(WAVELENGTH_\previousexponent) --(WAVELENGTH_\exponent)
% This is left as a node as opposed to coordinate:
% fill it out with '\currentposition' for debugging
node [midway] (CONNECTION_\currentposition) {}
coordinate [at start] (CONNECTION_\currentposition_START)
coordinate [at end] (CONNECTION_\currentposition_END);
\fi
\fi
\fi
}

% Create an arrow shape that fits around all relevant nodes, but do not draw it.
% Draw it manually later to leave out the 'bottom' of the arrow.
% We still need this invisible arrow for lining up of coordinates
\node [
single arrow,
% Inner angle of arrow tip
single arrow tip angle=150,
fit={
([xshift=-3em]CONNECTION_1_START)
(CONNECTION_1_END)
(CONNECTION_\maxexponent_START)
([xshift=5em]CONNECTION_\maxexponent_END)
},
inner sep=0pt
]
(ARROW) {};

% Only works because exponent 1 is between -1 and 3
\node [align=center] (THERM) at ([yshift=3em]WAVELENGTH_1|-ARROW.after tail)
\draw (THERM) -| ([yshift=-1.5em]WAVELENGTH_-1|-THERM);
\draw (THERM) -| ([yshift=-1.5em]WAVELENGTH_3|-THERM);

% On background layer so already drawn arrow and scale lines cover it up nicely
\begin{scope}[on background layer]
\node [
inner sep=0pt,
outer sep=0pt,
fit={
([xshift=-1.9em]WAVELENGTH_0|-ARROW.after tail)
([xshift=-3em]WAVELENGTH_1|-ARROW.before tail)
},
] (SMALL_VISIBLE_LIGHT) {};
left color=white,
right color=UV!25,
middle color=UV!5,
outer sep=0pt
] (CONNECTION_3_START)
-- (CONNECTION_3_END)
-- ([xshift=\pgflinewidth]SMALL_VISIBLE_LIGHT.south west)
-- ([xshift=\pgflinewidth]SMALL_VISIBLE_LIGHT.north west)
-- cycle
;
left color=IR!25,
right color=white,
middle color=IR!5,
outer sep=0pt,
] (CONNECTION_5_START)
-- (CONNECTION_5_END)
-- ([xshift=-\pgflinewidth]SMALL_VISIBLE_LIGHT.south east)
-- ([xshift=-\pgflinewidth]SMALL_VISIBLE_LIGHT.north east)
-- cycle
;
\end{scope}

% Some labels can be drawn automatically at the designated label coordinates:
\foreach [count=\i] \label in {
{Gamma\\rays},
{X-rays},
{},%Skip this one
{infrared}
}{
\node[raylabel, align=center] at (LABEL_\i) {\label};
}

% These do not fit the loop and are drawn manually:
\node [raylabel, align=right, anchor=north] at
([yshift=-1em,xshift=-2.5pt]$(WAVELENGTH_-2)!0.45!(WAVELENGTH_0)$)
{Ultra-\\violet};
\node [raylabel, fill=white] at (CONNECTION_6) {radio waves};
\node [raylabel, left=0.1em of CONNECTION_1, align=right] {cosmic\\rays};
\node [
draw,
fill=black!20,
below=4em of SMALL_VISIBLE_LIGHT,
align=center,
label=above:{\textbf{Visible Spectrum}}
] (FULL_VISIBLE_LIGHT) {%
\pgfspectra[
width=13em,height=3em,axis,axis unit=micron,axis step=100,
axis ticks=4,axis unit precision=2,axis color=black!20,
axis font=\normalsize,axis font color=black,
]\\%
\footnotesize
blue \hspace{0.5em}
green
yellow \hspace{1.5em}
red
};

% Draw 'magnifying' trapeze, on background so it is covered by scale labels
\begin{scope}[on background layer]
\filldraw [help lines, fill=black!10] (FULL_VISIBLE_LIGHT.north east)
-- (SMALL_VISIBLE_LIGHT.south east)
-- (SMALL_VISIBLE_LIGHT.south west)
-- (FULL_VISIBLE_LIGHT.north west)
-- cycle
;
\end{scope}

% Draw around arrow manually, leaving its tail open
\draw [draw, thick] (ARROW.after tail)