Skip to content

Commit 5aadbe6

Browse files
committed
Doc: add advanced usage example with separate legends
1 parent 05d8b10 commit 5aadbe6

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

doc/advanced.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<!--
2+
SPDX-FileCopyrightText: Blair Bonnett
3+
SPDX-License-Identifier: BSD-3-Clause
4+
-->
5+
6+
# Advanced usage
7+
8+
## Legends in margin
9+
10+
If you set the `separate_legend` option to True (either in the configuration file, or on
11+
a per-figure basis as in this example), then pgfutils will extract all legends from the
12+
figure and save them to separate files, allowing you to place them with TeX. For the
13+
following example, we will create a simple plot with two sinusoids.
14+
15+
```python title="separate_legend.py"
16+
from pgfutils import save, setup_figure
17+
18+
setup_figure(width=1, height=0.2, separate_legend=True)
19+
20+
from matplotlib import pyplot as plt
21+
import numpy as np
22+
23+
# Plot two sine waves.
24+
t = np.arange(0, 1, 2e-3)
25+
s = np.sin(2 * np.pi * 3 * t)
26+
plt.plot(t, s, "-v", label="3 Hz", markevery=30)
27+
s = np.sin(2 * np.pi * 4 * t)
28+
plt.plot(t, s, "-s", label="4Hz", markevery=30)
29+
30+
# Make a legend.
31+
plt.legend()
32+
33+
# And label the plot.
34+
plt.xlabel("Time [s]")
35+
plt.ylabel("Amplitude [V]")
36+
plt.xlim(0, 1)
37+
plt.ylim(0, 1)
38+
plt.yticks([-1, -0.5, 0, 0.5, 1])
39+
plt.grid(True, ls=":")
40+
41+
save()
42+
```
43+
44+
Running `python separate_legend.py` should result in two files being produced:
45+
`separate_legend.pypgf` containing the main plot, and `separate_legend_legend0.pypgf`
46+
containing the legend. If the figure contains multiple legends (such as multiple
47+
sub-plots each having a legend), one file per legend will be produced with increasing
48+
numerical suffixes.
49+
50+
We then need to figure out how to place the legend in the TeX document. If the figures
51+
are floating, then we have to somehow place this once TeX has decide where to place the
52+
figures. The following example uses the [tikzmark][] package to place a marker alongside
53+
the main figure, and then a [shipout hook][] which runs when a page is finalised. This
54+
then places the legend in the margin based on the position of the marker and the
55+
positions provided by the [tikzpagenodes][] package.
56+
57+
```tex title="separate_legend.tex"
58+
\documentclass{book}
59+
60+
\usepackage{lipsum}
61+
\usepackage{tikz}
62+
\usetikzlibrary{tikzmark}
63+
\usepackage{tikzpagenodes}
64+
65+
% Mark and place a legend in the margin aligned to the current spot.
66+
%
67+
% Optional argument: label for the marker. Must be unique within the document.
68+
% Defaults to the filename of the legend, so you only need to specify this if you use
69+
% the same legend file multiple times.
70+
%
71+
% Mandatory argument: filename of the legend.
72+
\NewDocumentCommand{\marginlegend}{O{#2}m}{
73+
% Mark the current location with a reference.
74+
\tikzmark{#1}
75+
76+
% Add hook code which runs when each page is shipped.
77+
\AddToHook{shipout/foreground}[#1]{
78+
% Wait until we are on the page with the original mark.
79+
\iftikzmarkoncurrentpage{#1}
80+
\begin{tikzpicture}[remember picture,overlay]
81+
% Coordinate where we want the y position of the legend anchor.
82+
\coordinate (mainfig) at ([yshift=-5mm]pic cs:#1);
83+
84+
% Coordinate where we want the x position of the legend anchor.
85+
\coordinate (marginpar) at (current page marginpar area.north);
86+
87+
% And put a node containing the legend there.
88+
\node[anchor=north, inner sep=0, outer sep=0] at (marginpar |- mainfig){
89+
\input{#2}
90+
};
91+
\end{tikzpicture}
92+
93+
% Don't need this hook code anymore.
94+
\RemoveFromHook{shipout/foreground}[#1]
95+
\fi
96+
}
97+
}
98+
99+
\begin{document}
100+
101+
\lipsum[1-5]
102+
103+
\begin{figure}
104+
\marginlegend{separate_legend_legend0.pypgf}
105+
\input{separate_legend.pypgf}
106+
\caption{
107+
This is a test figure with two sine waves.
108+
}
109+
\end{figure}
110+
111+
\lipsum[2-6]
112+
113+
\end{document}
114+
```
115+
116+
If you now compile the document (e.g., with `xelatex separate_legend`) then you will see
117+
the main figure is present but there is no legend. As the position of the markers is
118+
written to the auxiliary file, it is not available until the next run (in the same
119+
manner as cross-references or bibliography data). You need to compile multiple times
120+
when the position changes; for an initial build, this will probably require three
121+
compilations. When you no longer get the message
122+
123+
```
124+
LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.
125+
```
126+
127+
then the position is stabilised. You should now have a document with the legend in the
128+
margin alongside the figure. Build tools like `latexmk` should detect this message and
129+
automatically re-run the appropriate number of times.
130+
131+
Note that you can use any TeX macros to figure out the appropriate alignment. For
132+
example, in a two-sided document you could change the alignment based on whether the
133+
current page is an odd page or even page so that the margin is aligned to the side of
134+
the margin closest to the text (this requires the [ifoddpage][] package):
135+
136+
```tex
137+
% Coordinate where we want the x position of the legend anchor.
138+
\ifoddpage
139+
\coordinate (marginpar) at (current page marginpar area.west);
140+
\else
141+
\coordinate (marginpar) at (current page marginpar area.east);
142+
\fi
143+
144+
% And put a node containing the legend there.
145+
\ifoddpage
146+
\node[anchor=north west, inner sep=0, outer sep=0] at (marginpar |- mainfig){
147+
\input{#2}
148+
};
149+
\else
150+
\node[anchor=north east, inner sep=0, outer sep=0] at (marginpar |- mainfig){
151+
\input{#2}
152+
};
153+
\fi
154+
```
155+
156+
[tikzmark]: https://ctan.org/pkg/tikzmark
157+
[shipout hook]: https://www.latex-project.org/help/documentation/ltshipout-doc.pdf
158+
[tikzpagenodes]: https://ctan.org/pkg/tikzpagenodes
159+
[ifoddpage]: https://ctan.org/pkg/ifoddpage

doc/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ SPDX-FileCopyrightText: Blair Bonnett
33
SPDX-License-Identifier: BSD-3-Clause
44
-->
55

6+
# Introduction
7+
68
The [Portable Graphics Format (PGF)][1] is a language for producing vector
79
graphics within TeX documents. There is also a higher-level language TikZ (TikZ
810
ist kein Zeichenprogramm -- TikZ is not a drawing program) which uses PGF.

zensical.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ site_dir = "build/doc"
88
repo_url = "https://github.com/bcbnz/matplotlib-pgfutils"
99

1010
nav = [
11-
"index.md",
11+
{"Introduction" = "index.md"},
1212
"usage.md",
1313
"config.md",
1414
"interactive.md",
1515
"file_tracking.md",
1616
"latexmk.md",
17+
"advanced.md",
1718
]
1819

1920
[project.theme]

0 commit comments

Comments
 (0)