Skip to content

Commit

Permalink
Styling: add --style and --no-base-style to CLI plotters (#188)
Browse files Browse the repository at this point in the history
* Styling: add --style and --no-base-style to CLI plotters

Matplotlib allows several stylesheets to be overlaid. To get the best
use of this, we should remove any explicit styling from plot functions
and replace them with style contexts. Then our settings can take a
higher priority than MPL defaults, but a lower priority than any user
style preferences. Styles can be selected by inbuilt names and from
user files.

This allows the size and formatting of plots to be customised in a
precise and consistent way for publication. It also simplifies
plotting functions by removing options that can be set by the context.

* Address the more reasonable Codacy failures

Are we really going to use CAPS for all module-level "constants"?

* Add style files to MANIFEST

* Add --fontsize, --linewidth options; tweak default font sizes

Have tweaked suptitle and tick labels to be bigger and smaller than
fontsize, respectively. I don't have a strong opinion on that, but Duc
asked for it indirectly!

* Add --font option

* Add styles directory to setup.py packages

Jenkins test runs are failing to import from euphonic.styles, maybe
it's not being included in the package correctly?

* Implement --figsize, --figsize-unit options

I never liked that Matplotlib uses inches as its natural length
unit. As we have access to an absurdly powerful unit conversion
engine, it would be rude not to use it.

Yes, this means you can specify your figure size in Bohr radii!

* Temporarily restore tight_layout() to check CI results

This tight_layout() seems to be responsible for setting the
xticklabels of an invisible axis (used for label location) from empty
strings to numbers ranging 0 to 1. Those numbers are already in the
reference data so this was causing test failures.

We should consider removing it anyway, but it would be nice to get a
clean CI result first to isolate this from other changes.

* Remove (invisible) x tick values from intensity map script test

* Revert "Remove (invisible) x tick values from intensity map script test"

This reverts commit 19586ec.

* Apply tight_layout() before comparing to script test data

A side-effect of tight_layout() is that it forces "unset" xticklabels
to take their values. (Presumably so that the sizes can be inspected
for collisions.) Now that this has been removed from plot_2d and
delegated to styling, the tick labels are not set at the point the
script test checks them. (Explicit labels such as high-symmetry points
have been, but not numbers, including the 0-1 range of the invisible
axis used to control the x-axis label position.)

The resulting test failures can be addressed by updating the reference
data to have these unset values; but perhaps it is more robust to
check the result of tight_layout(). The test does not see the final
rendered version of the plot, which would have similar labels.

* New docs page: plot customisation

* Customisation docs tweaks

* Customisation docs correction.

The background colour hasn't changed, it's an illusion from the
missing axis outline! I checked the pixels with a colour picker.

* Styling: Tweaks in response to reviewer comments

Thanks @rebeccafair

* Spectrum docs: tweak subsection formatting

Sphinx is having trouble interpreting the cross-references to styling
file. Maybe the ---- for sections is incorrect and throwing it off?

* More RST wrangling

Looks like the highlight command was preventing the heading from being
identified. Fixed in all the script docs for consistency. (Somehow the
index was working anyway, but maybe cross-refs wouldn't?)

* Correct colour in styling docs alt-text

Good spot @mducle

* Add test for style arguments dispatch (internal function)

This ensures good coverage of the input space. We can check that the
appropriate results turn up in plotting outputs with a small
integration test.

* Simple integration test for styling

- Check that some CL options make it to the plot

* Test styling: fontsize from style sheet

This uses the "small" font scaling for xticklabels with a specified
base font size.

This integration test should give a bit more confidence that the
styling system is capable of modifying text without raising an
error. The font names are going to be a bit more fragile (depending on
system fonts) so for now they are not tested.

* Flake8 cleanup

* Plot styling: changelog, plot_1d docs

- Changelog update. (Maybe a bit too verbose?)
- Illustrate use of matplotlib.style.context in user docs for plot_1d

* Styling docs: tweak for more consistency with matplotlib docs

* Move plot styling notes to higher heading level.
  • Loading branch information
ajjackson authored Oct 5, 2021
1 parent 4710816 commit 7b3f8f4
Show file tree
Hide file tree
Showing 27 changed files with 442 additions and 81 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
- New ``Spectrum1D.to_text_file`` and ``Spectrum1DCollection.to_text_file``
methods to write to column text files

- An expanded and consistent set of styling options is made
available for command-line tools that produce plots.

- Consistent styling and advanced changes can be made using
Matplotlib stylesheet files, either as a CLI argument or
using ``matplotlib.style.context()`` in a Python script.

- Improvements:

- Internally, plot theming has been adjusted to rely on Matplotlib
style contexts. This means user changes and style context are more
likely to be respected.

`v0.6.2 <https://github.com/pace-neutrons/Euphonic/compare/v0.6.1...v0.6.2>`_
------

Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ include euphonic/LICENSE
include euphonic/CITATION.cff
include euphonic/data/*
include euphonic/_version.py
include euphonic/styles/*.mplstyle
include versioneer.py
include c/*.h
include c/*.c
1 change: 1 addition & 0 deletions doc/source/cl-tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Command-line Tools
euphonic-powder-map <powder-map-script>
euphonic-optimise-dipole-parameter <dipole-parameter-script>
euphonic-show-sampling <sampling-script>
Customising plots <styling>
3 changes: 2 additions & 1 deletion doc/source/dipole-parameter-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _dipole-parameter-script:
.. highlight:: bash

==================================
euphonic-optimise-dipole-parameter
==================================

.. highlight:: bash

This program is useful for users wanting to efficiently calculate phonon
frequencies on many q-points for polar materials.

Expand Down
6 changes: 4 additions & 2 deletions doc/source/disp-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _disp-script:
.. highlight:: bash

===================
euphonic-dispersion
===================

.. highlight:: bash

The ``euphonic-dispersion`` program can be used to plot dispersion
either along a specific trajectory from precalculated phonon frequencies,
or along a recommended reciprocal space path from force constants. For
Expand All @@ -23,7 +24,8 @@ To see all the command line options, run::

euphonic-dispersion -h

You can also see the available command line options below
You can also see the available command line options below.
For information on advanced plot styling, see :ref:`styling`.

Command Line Options
--------------------
Expand Down
6 changes: 4 additions & 2 deletions doc/source/dos-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _dos-script:
.. highlight:: bash

============
euphonic-dos
============

.. highlight:: bash

The ``euphonic-dos`` program can be used to plot density of states,
partial density of states, and/or neutron-weighted density of states.
It can use pre-calculated frequencies, or use force constants to
Expand All @@ -22,7 +23,8 @@ To see all the command line options, run::

euphonic-dos -h

You can also see the available command line options below
You can also see the available command line options below.
For information on advanced plot styling, see :ref:`styling`.

Command Line Options
--------------------
Expand Down
Binary file added doc/source/figures/plot-styling-custom-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/figures/plot-styling-custom-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/figures/plot-styling-seaborn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions doc/source/intensity-map-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _intensity-map-script:
.. highlight:: bash

======================
euphonic-intensity-map
======================

.. highlight:: bash

The ``euphonic-intensity-map`` program can be used to plot a 2D intensity
map either along a specific trajectory from precalculated phonon frequencies
and eigenvectors, or along a recommended reciprocal space path from force
Expand All @@ -23,7 +24,8 @@ To see all the command line options, run::

euphonic-intensity-map -h

You can also see the available command line options below
You can also see the available command line options below.
For information on advanced plot styling, see :ref:`styling`.

Command Line Options
--------------------
Expand Down
27 changes: 27 additions & 0 deletions doc/source/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,30 @@ Docstrings
.. autofunction:: euphonic.plot.plot_2d

.. autofunction:: euphonic.plot.plot_2d_to_axis

Styling
=======

To produce consistent and beautiful plots, it is recommended to use
`Matplotlib style sheets <https://matplotlib.org/stable/tutorials/introductory/customizing.html#temporary-styling>`_.
The cleanest way to apply this is using a context manager.
Within the indented block, a user-provided combination of style sheets
is applied to any new plots.
These can be built-in themes, file paths or parameter dictionaries,
e.g.:

.. code-block:: py
import matplotlib.pyplot as plt
from euphonic import Spectrum1D
from euphonic.plot import plot_1d, plot_1d_to_axis
dos = Spectrum1D.from_json_file('dos.json')
with plt.style.context(['dark_background', {'lines.linewidth': 2.0}]):
fig = plot_1d(dos)
fig.show()
This approach is used in the Euphonic command-line tools; for more
information see :ref:`styling`. The CLI defaults can be imitated by
using the same style sheet ``euphonic.style.base_style``.
4 changes: 3 additions & 1 deletion doc/source/powder-map-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _powder-map-script:
.. highlight:: bash

======================
euphonic-powder-map
======================

.. highlight:: bash

The ``euphonic-powder-map`` program can be used to sample
spherically-averaged properties from force constants data over a range
of :math:`|q|`. The results are plotted as a 2-dimensional map in :math:`(|q|, \omega)`.
Expand All @@ -23,6 +24,7 @@ To see all the command line options, run::
euphonic-powder-map -h

You can also see the available command line options at the bottom of this page.
For information on advanced plot styling, see :ref:`styling`.

Spherical averaging options
---------------------------
Expand Down
3 changes: 2 additions & 1 deletion doc/source/sampling-script.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.. _sampling-script:
.. highlight:: bash

======================
euphonic-show-sampling
======================

.. highlight:: bash

``euphonic-show-sampling`` can be used to visualise the spherical
sampling schemes implemented in :mod:`euphonic.sampling`. For
example, to see how the 'golden' sphere sampling approach works for
Expand Down
108 changes: 108 additions & 0 deletions doc/source/styling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
.. _styling:

=================
Customising plots
=================

.. highlight:: bash

Command-line options
====================

Several of Euphonic's :ref:`cl-tools` produce 2D plots. A few command-line
arguments are provided to tweak the plot size, font settings and colour maps, e.g.::

euphonic-dos --font Monaco --fontsize 18 --figsize 10 10 --figsize-unit cm quartz.castep_bin

will produce a plot with larger (hopefully monospace) text on a small
square canvas. This may be especially useful with the ``--save-to``
option to create consistent plots for presentation slides and academic
papers.

The ``--font`` option will be passed to the Matplotlib library as the
preferred "sans-serif" option, and the font family will be set to
sans-serif. Unfortunately it can be tricky to identify exactly which
font names are accepted by Matplotlib. We cannot advise on this for
all platforms, so if you have a preferred font it may be worth
searching for help on using this font with Matplotlib.

Using Matplotlib styles
=======================

These and other appearance customisations can be defined as a
Matplotlib style sheet.
`A number of popular styles are predefined in Matplotlib <https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html>`_
and may be accessed by name, e.g.::

euphonic-dos --style seaborn quartz.castep_bin

will yield a plot on a grey background with white gridlines.

.. image:: figures/plot-styling-seaborn.png
:width: 400
:alt: PDOS plot with thin dark blue, red and green lines against a
pale grey background divided by white gridlines. There are no
outlines around the legend (top-right) or the axes; number
values are in a black sans-serif and float near the plot.

``--style=dark_background`` might be preferred for some slide
presentations.

Using custom stylesheets
========================

For a custom scheme, you can `develop your own style file <https://matplotlib.org/stable/tutorials/introductory/customizing.html>`_.
For example, with the following file saved as "custom.mplstyle"

.. code-block:: ini
axes.facecolor: floralwhite
font.family: monospace
text.color: grey
lines.linewidth : 3
xtick.labelsize : smaller
legend.fancybox: True
legend.shadow: True
figure.figsize : 3, 3
figure.facecolor : palegoldenrod
then the command::

euphonic-dos quartz.castep_bin --pdos --style custom.mplstyle

generates a small figure with some "opinionated" styling.

.. image:: figures/plot-styling-custom-1.png
:width: 400
:alt: A square PDOS plot with very thick blue, orange and green data
lines, a pale yellow background and pinkish off-white canvas.
The canvas is surrounded by black lines with numbered ticks.
On the canvas are vertical grey gridlines and a legend with drop
shadow, round corners and grey text. The label text is in a
monospace font.

It is possible to "compose" multiple styles in ascending priority
order, e.g.::

euphonic-dos quartz.castep_bin --pdos --style seaborn custom.mplstyle

In the resulting figure, the customised text and canvas options have
taken priority, but we still get the Seaborn colour sequence for plot
lines. The plot outline, ticks and legend box were removed. (And with
them, the legend customisation!)

.. image:: figures/plot-styling-custom-2.png
:width: 400
:alt: A very similar plot to the above, except that the legend box
is gone (along with its shadow), and the line colours are now
a tasteful blue, green and (desaturated) red combination. The
grid lines are white against a pale orange background.

For a large project, this can be very useful to establish a general
"house style" with variations for certain plot types. However, as seen
above, combining styles can sometimes have unexpected consequences. In
order to prevent conflict between Euphonic's own stylesheet and other
style options, the ``--no-base-style`` argument can be used to remove
the Euphonic defaults. For example, with the ``seaborn`` style this
will restore the horizontal grid lines that are expected to replace
the missing black ticks.
21 changes: 14 additions & 7 deletions euphonic/cli/dispersion.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from argparse import ArgumentParser
from typing import List, Optional

import matplotlib.style

import euphonic
from euphonic.plot import plot_1d
from euphonic.styles import base_style
from euphonic import Spectrum1D
from .utils import (load_data_from_file, get_args, _bands_from_force_constants,
_compose_style,
_get_q_distance, matplotlib_save_or_show, _get_cli_parser,
_calc_modes_kwargs)

Expand Down Expand Up @@ -50,13 +54,16 @@ def main(params: Optional[List[str]] = None) -> None:

spectra = spectrum.split(**split_args) # type: List[Spectrum1D]

_ = plot_1d(spectra,
title=args.title,
x_label=x_label,
y_label=y_label,
y_min=args.e_min, y_max=args.e_max,
lw=1.0)
matplotlib_save_or_show(save_filename=args.save_to)
style = _compose_style(user_args=args,
base=[base_style])

with matplotlib.style.context(style):
_ = plot_1d(spectra,
title=args.title,
x_label=x_label,
y_label=y_label,
y_min=args.e_min, y_max=args.e_max)
matplotlib_save_or_show(save_filename=args.save_to)


def get_parser() -> ArgumentParser:
Expand Down
16 changes: 10 additions & 6 deletions euphonic/cli/dos.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from argparse import ArgumentParser
from typing import List, Optional

from euphonic import (ureg, ForceConstants, QpointPhononModes,
Spectrum1DCollection)
import matplotlib.style

from euphonic import ureg, ForceConstants, QpointPhononModes
from euphonic.util import mp_grid, mode_gradients_to_widths
from euphonic.plot import plot_1d
from euphonic.styles import base_style
from .utils import (load_data_from_file, get_args, matplotlib_save_or_show,
_calc_modes_kwargs,
_calc_modes_kwargs, _compose_style,
_get_cli_parser, _get_energy_bins,
_grid_spec_from_args, _get_pdos_weighting,
_arrange_pdos_groups)
Expand Down Expand Up @@ -74,9 +76,11 @@ def main(params: Optional[List[str]] = None) -> None:
else:
y_label = args.y_label

fig = plot_1d(dos, title=args.title, x_label=x_label, y_label=y_label,
y_min=0, lw=1.0)
matplotlib_save_or_show(save_filename=args.save_to)
style = _compose_style(user_args=args, base=[base_style])
with matplotlib.style.context(style):
_ = plot_1d(dos, title=args.title, x_label=x_label, y_label=y_label,
y_min=0)
matplotlib_save_or_show(save_filename=args.save_to)


def get_parser() -> ArgumentParser:
Expand Down
Loading

0 comments on commit 7b3f8f4

Please sign in to comment.