Customising plot styles#
Summary
On this page, you will see how to
use configuration inheritance and YAML anchors to globally define figure and font sizes
use configuration inheritance and YAML anchors to globally define colors and color cyclers
define and use discrete and continuous colormaps from the configuration
globally define the style of your plots
base your plots on matplotlib or seaborn styles
base your plot style on a custom RC file
use the
PlotHelper
to set titles, labels, scales, and more
It can be a laborious task to customise plots and figures, especially when they are intended for publication. However, it becomes easy when using a configuration-based approach. You can define figure and font sizes, define colors and colormaps, set axis labels and titles, legend styles, grid line widths, and more, all in a single location, and automatically have these be applied to all your plots. When using configuration inheritance and YAML anchors, appearance and style settings need only be defined once. And since matplotlib (the backend used in Utopia plotting) includes LaTeX support, it becomes easy to produce plots that integrate seamlessly into your tex document.
Example: adjusting figure and font sizes#
Let us look at a simple example: You may wish for your plots to have the same width and font size as the documents in which you are including them. To do this, define the following base style in your plot configuration:
_my_custom_style:
style:
text.usetex: True
mathtext.fontset: stix
figure.figsize: [7.516, 3.758] # figure width and height in inches
font.family: serif
font.size: 10
axes.titlesize: 10
Then, simply base your plot on _my_custom_style
:
my_plot:
based_on:
- _my_custom_style
- # other base configurations ...
Remember: entries are updated recursively in the order in which they are listed, see Plot Configuration Inheritance.
If entries listed after _my_custom_style
set styles that are also set in _my_custom_style
, the entries from _my_custom_style
will be overwritten.
You can make use of this when individual plots differ only in a few aspects from the global style.
Imagine you want your plot to conform to _my_custom_style
in everything but the figure size.
Instead of writing out the entire entry again, you can do this:
my_small_plot:
based_on:
- _my_custom_style
- # other base configurations ...
style:
figure.figsize: [3.758, 3.758] # Overwrites the figure.figsize key from _my_custom_plot
Since the figsize
is redefined in my_small_plot
, this last entry takes precedent.
All the other properties of _my_custom_style
remain in place.
Hierarchical plot configuration inheritance is very useful, especially when used in tandem with YAML anchors. For exmaple, you may want some plots to have full page width, some plots to only have half a page width, and so on. These widths can be defined globally using YAML anchors and used throughout the plots config:
_sizes:
page_width: &full_width 7.516
half_width: &half_width 3.758
a_large_plot:
# ...
style:
figure.figsize: [*full_width, *half_width]
a_small_plot:
# ...
style:
figure.figsize: [*half_width, *half_width]
Now, if you want to change the image widths relative to your page width, it is sufficient to change the definitions in _sizes
– they will automatically be applied to all plots based on that style.
Hint
Plot configuration entries starting with an underscore are ignored by the PlotManager
.
This can be useful when defining YAML anchors that are used in the actual configuration entries.
An alternative and entirely equivalent method is to define some additional base plots and use multiple inheritance:
_my_custom_style:
style:
# some style arguments
_large_size:
based_on: _my_custom_style
style:
figure.figsize: [*full_width, *half_width]
_small_size:
based_on: _my_custom_style
style:
figure.figsize: [*half_width, *half_width]
# This plot will have full page width
my_large_plot:
based_on:
- _large_size
- # others ...
# This plot will have half page width
my_small_plot:
based_on:
- _small_size
- # others ...
Whichever way you choose, you will only need to set the figure widths once.
Hint
If you start implementing many such granular base plot entries (which we do explicitly not discourage), spend a few minutes on devising a descriptive naming convention. As a starting point, we suggest to follow the dantro base plots naming convention.
Colors and color cyclers#
YAML anchors and base configurations are also very useful when using your own custom colors; you can define a global color palette, and use these definitions throughout your configuration:
_pretty_colors:
blue: &blue '#0099CC'
green: &green teal
yellow: &yellow [1, 0.8, 0.4]
red: &red r
my_pretty_plot:
# define your plot
color: *red
Assuming my_pretty_plot
takes a color
kwarg, it will use the color you defined.
You can define colors using any way permitted by the matplotlib specification.
A change in the color definitions will then automatically be applied to all plots.
When creating several plots (possibly in a single figure), you may need to cycle through a given list of colors.
To do this, use the axes.prop_cycle
key, like so:
_my_style:
style:
axes.prop_cycle: cycler('color', ['#AFD8BC', '#FFCC66', '#006666'])
my_plot:
based_on:
- _my_style
- # ...
The colors in your plot will then cycle through the colors you specify. If you want the cycler to be based on a predefined color palette, use fstrings to avoid having to define colors multiple times:
# Define your color palette
_colors &colors:
red: '#CC3333'
green: '#339999'
blue: '#0099CC'
# Define a style
_style:
axes.prop_cycle: !format
fstr: "cycler('color', ['{colors[red]:}', '{colors[green]:}', '{colors[blue]:}'])"
colors: *colors
Colormaps#
Creating custom colormaps is simple with dantro’s ColorManager; whenever you pass a cmap
argument, it will automatically be passed to the ColorManager
, which takes care of the construction.
For instance, you can create a continuous colormap using six colors like this:
cmap:
continuous: true
from_values:
0: crimson
0.2: gold
0.4: mediumseagreen
0.6: teal
0.8: skyblue
1: midnightblue
And voilà, a wonderfully colorful plot emerges:
The keys of the from_values
dictionary are the locations of the colors you define in the colormap, and must be values between 0 and 1.
You can pass as many colors as you like.
And you can also pass additional arguments, such as setting a bad
color, the limits via vmin
and vmax
, setting colors that are out of range (under
/over
), or passing a place_holder
color
for None
values.
See this entry in the dantro docs
for more information.
You can add a norm to the cmap
by passing a norm
dict; for example
cmap:
# as above ...
norm:
name: BoundaryNorm
boundaries: [0, 30, 60, 90, 200]
ncolors: 4
will return discrete bounds.
You can use any of the matplotlib norms, e.g. a logarithmic norm (name: LogNorm
).
For discrete colormaps, simply drop the continuous
argument and pass a dict of colors:
cmap:
from_values:
susceptible: olive
infected: crimson
recovered: teal
Again, take a look at the dantro documentation entry for a full overview.
Lastly of course, you can simply pass the name of a matplotlib or seaborn colormap. No need to re-invent the (color) wheel, especially when perceptually uniform colormaps are required.
cmap: "Paired" # calls the seaborn 'Paired' color palette
Hint
When using the BoundaryNorm together with one of the pre-registered colormaps (e.g., viridis), use the lut
argument (see get_cmap()
) to resample the colormap to have lut entries in the lookup table.
Set lut = <BoundaryNorm.ncolors>
to use the full colormap range.
Using matplotlib or seaborn stylesheets#
The style
keyword sets the matplotlib.rcParams
of your plot, and all keys are interpreted directly as rcParams
.
You can set these yourself, or use predefined matplotlib or seaborn stylesheets using the base_style
key; here is an example based on ggplot
:
my_ggplot:
style:
# choose stylesheet
base_style: ggplot
# adapt rcparams
lines.linewidth : 3
lines.markersize : 10
xtick.labelsize : 16
ytick.labelsize : 16
# ...
The ggplot
style is applied and subsequently modified with a custom linewidth, marker size, and label sizes.
For the base_style
entry, choose the name of a
matplotlib stylesheet.
For valid RC parameters, see the matplotlib customization documentation.
Using a custom rcParams.yml
file#
If you want to share styles across models, you can also create a rcParams.yml
file containing all your style settings, and include it like so:
my_plot:
style:
# Add a base style if you wish
base_style: ~
# Select a file from which to load rcParams
rc_file: /absolute/path/to/rcfile
# Other rcParameters here, overwriting the above.
Remember that entries are overwritten successively: this means that, in the above example, the RC parameters from your file will overwrite the entries of any base_style
you provide, but parameters coming after the rc_file
entry will again overwrite your RC file entries.
Note
You must provide an absolute path to the RC file.
Using the PlotHelper
#
Hint
This is just a short introduction to the PlotHelper
s functionality.
Read the full article on it in the dantro documentation.
With the PlotHelper
, you can easily make further adjustments to your plot, like setting the title, axis labels, axis scales and much more.
Essentially, this makes the matplotlib.pyplot
interface accessible to your plot configuration such that you can control all of the figure aesthetics without touching any python code.
All helper function invocations are added to the helpers
entry in your plot configuration:
my_plot:
# define your plot ...
helpers:
# helpers are configured here
Let’s use that to set a title (via set_title
) and labels (via set_labels
):
my_plot:
# define your plot ...
helpers:
set_title:
title: My over-designed phase diagram
set_labels:
x: 'This is the x-axis'
y:
label: 'And this is the y-axis'
labelpad: 5
Hint
Of course you can also use latex in your labels:
helpers:
set_labels:
x: $\alpha$
y: $\beta$
You could add some horizontal or vertical lines to the plot:
helpers:
set_hv_lines:
hlines:
- pos: 0.05
color: teal
label: Just a horizontal line
- pos: 0.01
color: crimson
linestyle: 'dotted'
label: Livin' in a lonely world
# Let's also add a legend:
set_legend:
title: Lines and Dots
loc: best
Or annotate some noteworthy points in the plot:
helpers:
annotate:
annotations:
- xy: [0.08, 0.16]
xycoords: data
text: Here is the maximum!
xytext: [0.05, 0.10]
arrowprops:
facecolor: skyblue
shrink: 0.05
linewidth: 0
alpha: 0.5
bbox:
facecolor: skyblue
linewidth: 0
alpha: 0.5
boxstyle: round
The struggle with matplotlib ticks is finally over:
helpers:
set_ticks:
x:
major:
locs: [0, 0.1, 0.2, 0.3, 0.4]
labels: ['No', 'more', 'trouble', 'with', 'tick labels']
The above result in this beautiful figure:
These are just some of the possibilities of the PlotHelper
.
Happy Plotting!
Available plot helpers#
The following plot helpers are available:
In [1]: from dantro.plot import PlotHelper
In [2]: hlpr = PlotHelper(out_path="")
In [3]: print("\n".join(hlpr.available_helpers))
align_labels
annotate
autofmt_xdate
call
despine
figcall
set_figlegend
set_hv_lines
set_labels
set_legend
set_limits
set_margins
set_scales
set_suptitle
set_texts
set_tick_formatters
set_tick_locators
set_ticks
set_title
subplots_adjust
Of those, some helpers act on the level of the associated matplotlib.figure.Figure
:
In [4]: print("\n".join(hlpr._FIGURE_HELPERS))
align_labels
set_suptitle
set_figlegend
subplots_adjust
figcall
Missing a plot helper function?
If you are missing a plot helper function, consider opening an issue in the dantro project, we are happy to add more helper functions.
In the meantime, you can use the call
and figcall
helpers to invoke arbitrary functions on the axes or the figure.
See _hlpr_call()
for more information.
What’s the syntax for helper function foo
?
There are two ways to learn about the available syntax for a helper function:
Either have a look at the
PlotHelper
API reference: Helper methods are named_hlpr_<name>
, e.g._hlpr_align_labels()
.Or: Just try invoking a plot helper with some random arguments. The error will lead to a useful error message that also includes the docstring of the helper function.
Of course, as the helper passes many arguments through to the corresponding matplotlib functions, you may also have to have a look at the matplotlib documentation.