Skip to content

Commit 8d6c03d

Browse files
committed
Add support for generating margin and full-width figures.
Classes such as tufte-book support placing figures in the margin notes, as well as full-width figures which span the main text and the margin notes. This commit adds support for generating such figures.
1 parent 7901029 commit 8d6c03d

File tree

5 files changed

+157
-4
lines changed

5 files changed

+157
-4
lines changed

doc/config.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ The default settings for the ``tex`` section are as follows:
4747
engine = xelatex
4848
text_width = 345 points
4949
text_height = 550 points
50+
marginpar_width = 65 points
51+
marginpar_sep = 11 points
5052
num_columns = 1
5153
columnsep = 10 points
5254
```
@@ -74,6 +76,11 @@ dimensions of a TeX document can be measured using the `layouts` package:
7476

7577
This prints a diagram in the output document showing the dimensions.
7678

79+
The `marginpar_width` and `marginpar_sep` refer to the width of the margin
80+
notes and the separator between the main text area and the margin notes. They
81+
are used when generating figures which fit in the margin notes, or which span
82+
the main text area and the margin notes.
83+
7784
The number of columns and the width of the separators between them are only
7885
used when generating figures for multiple-column documents.
7986

doc/usage.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,45 @@ now results in a 4 inch wide figure (i.e., 80% of the 5 inches that would make
9898
up a full two columns).
9999

100100

101+
Margin figures and full-width figures
102+
-------------------------------------
103+
104+
Some LaTeX document classes (e.g., those in the [Tufte-LaTeX][1] project) have
105+
the option to place figures in the margin notes. To generate a figure to fit
106+
within the margin, make sure the `marginpar_width` parameter in your
107+
[configuration](config.md) is correct, and pass `margin=True` when calling the
108+
`setup_figure()` function:
109+
110+
```python
111+
setup_figure(height=0.2, margin=True)
112+
```
113+
114+
This will result in a figure that spans the margin and is 20% of the text
115+
height. If a floating point number is given to the `width` parameter, this is
116+
interpreted relative to the margin width, i.e., to create a figure spanning 80%
117+
of the margin:
118+
119+
```python
120+
setup_figure(width=0.8, height=0.2, margin=True)
121+
```
122+
123+
It is sometimes desirable to create a figure spanning the full width of such a
124+
document, i.e., the main text area, the margin notes, and any separator between
125+
them. To do this, make sure the `marginpar_width` and `marginpar_sep` values in
126+
your [configuration](config.md) are correct, and pass `full_width=True` when
127+
calling the setup function:
128+
129+
```python
130+
setup_figure(height=0.3, full_width=True)
131+
```
132+
133+
Again, a fractional width is interpreted relative to the full width.
134+
135+
If `full_width=True`, then the values of the `margin` and `columns` parameters
136+
are ignored. Similarly, if `margin=True` and `full_width=False`, then the value
137+
of `columns` is ignored.
138+
139+
101140
Saving
102141
------
103142

extras/pgfutils.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ engine = xelatex
4141
# without a unit is assumed to be inches, the default unit of Matplotlib.
4242
text_width = 345 points
4343
text_height = 550 points
44+
marginpar_width = 65 points
45+
marginpar_sep = 11 points
4446

4547
# For multi-column layouts.
4648
num_columns = 1

pgfutils.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ def _config_reset():
348348
'engine': 'xelatex',
349349
'text_width': '345 points',
350350
'text_height': '550 points',
351+
'marginpar_width': '65 points',
352+
'marginpar_sep': '11 points',
351353
'num_columns': '1',
352354
'columnsep': '10 points',
353355
},
@@ -495,7 +497,8 @@ def _list_opened_files():
495497
_interactive = False
496498

497499

498-
def setup_figure(width=1.0, height=1.0, columns=None, **kwargs):
500+
def setup_figure(width=1.0, height=1.0, columns=None, margin=False,
501+
full_width=False, **kwargs):
499502
"""Set up matplotlib figures for PGF output.
500503
501504
This function should be imported and run before you import any matplotlib
@@ -504,16 +507,28 @@ def setup_figure(width=1.0, height=1.0, columns=None, **kwargs):
504507
505508
Parameters
506509
----------
507-
width, height: float or string
510+
width, height : float or string
508511
If a float, the fraction of the corresponding text width or height that
509512
the figure should take up. If a string, a dimension in centimetres
510513
(cm), millimetres (mm), inches (in) or points (pt). For example, '3in'
511514
or '2.5 cm'.
512-
columns: integer, optional
515+
columns : integer, optional
513516
The number of columns the figure should span. This should be between 1
514517
and the total number of columns in the document (as specified in the
515518
configuration). A value of None corresponds to spanning all columns.
516519
Any other value results in a ValueError being raised.
520+
margin : Boolean, default False
521+
If True, a margin figure (i.e., one to fit within the margin notes in
522+
the document) is generated. If the width is a fraction, it is treated
523+
as a fraction of the marginpar_width configuration setting. The height
524+
is still treated as a fraction of the text height. The columns setting
525+
is ignored if this is True.
526+
full_width : Boolean, default False
527+
If True, a full-width figure, i.e., one spanning the main text, the
528+
margin notes, and the separator between them, is generated. A
529+
fractional width is treated relative to the full width. The height is
530+
still treated as a fraction of the text height. The columns and margin
531+
parameters are ignored if this is True.
517532
518533
"""
519534
global _config, _interactive
@@ -620,10 +635,20 @@ def setup_figure(width=1.0, height=1.0, columns=None, **kwargs):
620635
# the width corresponding to the figure parameter being 1.
621636
# First, look up some document properties.
622637
text_width = _config['tex'].getdimension('text_width')
638+
margin_width = _config['tex'].getdimension('marginpar_width')
639+
margin_sep = _config['tex'].getdimension('marginpar_sep')
623640
num_columns = _config['tex'].getint('num_columns')
624641

642+
# Full-width figure.
643+
if full_width:
644+
available_width = text_width + margin_sep + margin_width
645+
646+
# Making a margin figure.
647+
elif margin:
648+
available_width = margin_width
649+
625650
# Columns not specified, or spanning all available.
626-
if columns is None or columns == num_columns:
651+
elif columns is None or columns == num_columns:
627652
available_width = text_width
628653

629654
# More columns than present in the document.

tests/test_setup_figure.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,83 @@ def test_kwargs_rejects_unknown(self):
158158
"""Test setup_figure() rejects unknown configuration options..."""
159159
with raises(KeyError):
160160
setup_figure(width=1, height=1, border_width=3)
161+
162+
163+
def test_setup_margin(self):
164+
"""Test setup_figure() generates margin figures with margin=True..."""
165+
setup_figure()
166+
margin = _config['tex'].getdimension('marginpar_width')
167+
height = _config['tex'].getdimension('text_height')
168+
169+
# Fractional tests.
170+
for w_in in {1.0, 0.5, 1.2}:
171+
for h_in in {0.3, 0.25, 0.5}:
172+
_config_reset()
173+
setup_figure(width=w_in, height=h_in, margin=True)
174+
w, h = matplotlib.rcParams['figure.figsize']
175+
assert w == approx(w_in * margin)
176+
assert h == approx(h_in * height)
177+
178+
# Specific size.
179+
_config_reset()
180+
setup_figure(width="1.8in", height="1.2in", margin=True)
181+
w, h = matplotlib.rcParams['figure.figsize']
182+
assert w == approx(1.8)
183+
assert h == approx(1.2)
184+
185+
186+
def test_setup_full_width(self):
187+
"""Test setup_figure() generates full-width figures with full_width=True..."""
188+
setup_figure()
189+
text = _config['tex'].getdimension('text_width')
190+
sep = _config['tex'].getdimension('marginpar_sep')
191+
margin = _config['tex'].getdimension('marginpar_width')
192+
full = text + sep + margin
193+
height = _config['tex'].getdimension('text_height')
194+
195+
# Fractional tests.
196+
for w_in in {1.0, 0.75, 1.1}:
197+
for h_in in {0.4, 0.35, 0.15}:
198+
_config_reset()
199+
setup_figure(width=w_in, height=h_in, full_width=True)
200+
w, h = matplotlib.rcParams['figure.figsize']
201+
assert w == approx(w_in * full)
202+
assert h == approx(h_in * height)
203+
204+
# Specific size.
205+
_config_reset()
206+
setup_figure(width="5.5in", height="3.6in", full_width=True)
207+
w, h = matplotlib.rcParams['figure.figsize']
208+
assert w == approx(5.5)
209+
assert h == approx(3.6)
210+
211+
212+
def test_setup_arg_priority(self):
213+
"""Test priority of columns/margin/full_width arguments to setup_figure()..."""
214+
setup_figure()
215+
text = _config['tex'].getdimension('text_width')
216+
sep = _config['tex'].getdimension('marginpar_sep')
217+
margin = _config['tex'].getdimension('marginpar_width')
218+
full = text + sep + margin
219+
height = _config['tex'].getdimension('text_height')
220+
221+
# All three: full width should take priority.
222+
_config_reset()
223+
setup_figure(width=1, height=0.4, columns=1, margin=True, full_width=True)
224+
w, h = matplotlib.rcParams['figure.figsize']
225+
assert w == approx(full)
226+
assert h == approx(0.4 * height)
227+
228+
# Margin and full width: full width should take priority.
229+
_config_reset()
230+
setup_figure(width=1, height=0.4, margin=True, full_width=True)
231+
w, h = matplotlib.rcParams['figure.figsize']
232+
assert w == approx(full)
233+
assert h == approx(0.4 * height)
234+
235+
# Margin and columns: margin should take priority.
236+
_config_reset()
237+
setup_figure(width=1, height=0.4, columns=1, margin=True)
238+
w, h = matplotlib.rcParams['figure.figsize']
239+
assert w == approx(margin)
240+
assert h == approx(0.4 * height)

0 commit comments

Comments
 (0)