Figures
The module aims to solve two main problems: figures that look different from each other because
each notebook set its own rcParams, and figures that look pasted into the paper because they were
authored at screen size and scaled down.
Profiles
apply_style() applies a coherent look throughout. The specific
look is chosen via a profile:
screenLarge (12×6 inches), default font sizes, 100 dpi on display. Intended for notebooks, where a figure is looked at on its own.
printSized to the document, with width taken from
paper.text-width-in, and height fromfigures.print-height-in, 8pt fonts. A figure authored under this profile goes into the paper at 1:1 and its text comes out the same size as the surrounding body text.
from research_helpers.figures import apply_style
apply_style() # the project's profile, defaults to 'screen'
apply_style(profile='print') # paper geometry, paper font sizes
Width and height
The width value is dictated by the document, the width setting is used to indicate
which of the document’s two widths applies:
apply_style(profile='print') # \textwidth, the default
apply_style(profile='print', width='column') # \columnwidth, for one column of a two-column paper
The height is unconstrained, so it is a setting:
[tool.research-helpers.figures]
print-height-in = 3.2 # the default; -mm, -cm and -pt work too
Height can be overridden per call:
apply_style(profile='print', print_height_in=4.5)
Scoped styling
style() works as a context manager over
matplotlib.rc_context. It can be used in cases where one figure needs to differ
without the details leaking into the rest of the notebook:
from research_helpers.figures import style
with style(profile='print', palette='colorblind') as settings:
fig, ax = plt.subplots() # the profile's size is already in rcParams
...
The object returned is the resolved FigureSettings, which
carries profile, palette, font and dpi. The geometry is not on the profile—it
goes into rcParams, so plt.subplots() picks it up with no argument:
>>> apply_style(profile='print')
FigureSettings(profile='print', palette='husl', font='DejaVu Sans', dpi=150, print_height_in=3.2)
>>> plt.rcParams['figure.figsize'], plt.rcParams['font.size']
([6.45, 3.2], 8.0)
Saving versus rendering
save() writes to a file. render()
returns the identical bytes without touching the disk:
save(fig, 'build/figures/learning-curve.png')
data = render(fig) # the same bytes, in memory
render exists for the build pipeline: an emitter registered with
Registry returns its artefact’s content, and drift detection
compares that against the installed file byte for byte. A test asserts the two functions agree,
so the comparison is meaningful.
Both default to tight=True, which crops whitespace from the saved figure.
Note
tight=True means that the saved image will be narrower, so a figure authored
at exactly \textwidth is not saved at exactly \textwidth. This is to prevent
unnecessary white space, and \includegraphics[width=\textwidth]
will scale it correctly. This behaviour might be undesirable in certain situations,
for example if trying to match a figure’s width to a table’s, in which case passing
tight=False will prevent the cropping altogether.
Long tick labels
fit_x() shrinks the x tick labels until they stop overlapping,
rather than rotating them:
fit_x(fig, ax)
This is designed to help in situations where one wants to avoid rotating labels.
Checking current settings
current_settings() resolves the project’s figure settings
without applying anything (useful for troubleshooting):
>>> current_settings().dpi
300
>>> current_settings(dpi=72).dpi # an override, resolved the same way
72