Plotting

ERLabPy provides a number of plotting functions to help visualize data and create publication quality figures.

Importing

The key module to plotting is erlab.plotting.erplot, which serves as an interface to various plotting functions in ERLabPy, similar to how matplotlib.pyplot works. To import it, use the following code:

[1]:
import matplotlib.pyplot as plt
import erlab.plotting.erplot as eplt

First, let us generate some example data from a simple tight binding model of graphene. A rigid shift of 200 meV has been applied so that the Dirac cone is visible.

[3]:
from erlab.io.exampledata import generate_data

dat = generate_data(bandshift=-0.2, seed=1).T
[4]:
dat
[4]:
<xarray.DataArray (eV: 300, ky: 250, kx: 250)> Size: 150MB
0.1517 0.1233 0.3382 0.3101 0.3476 0.8233 ... 0.117 0.116 0.4569 0.1373 0.1174
Coordinates:
  * kx       (kx) float64 2kB -0.89 -0.8829 -0.8757 ... 0.8757 0.8829 0.89
  * ky       (ky) float64 2kB -0.89 -0.8829 -0.8757 ... 0.8757 0.8829 0.89
  * eV       (eV) float64 2kB -0.45 -0.4482 -0.4464 ... 0.08639 0.08819 0.09

We can see that the generated data is a three-dimensional xarray.DataArray. Now, let’s extract a cut along \(k_y = 0.3\).

[5]:
cut = dat.qsel(ky=0.3)
cut
[5]:
<xarray.DataArray (eV: 300, kx: 250)> Size: 600kB
0.7814 0.3649 0.2043 0.2815 0.7351 ... 0.07689 0.01882 0.0002918 2.866e-07
Coordinates:
  * kx       (kx) float64 2kB -0.89 -0.8829 -0.8757 ... 0.8757 0.8829 0.89
    ky       float64 8B 0.2967
  * eV       (eV) float64 2kB -0.45 -0.4482 -0.4464 ... 0.08639 0.08819 0.09

Plotting 2D data

The fastest way to plot a 2D array like this is to use plot_array. Each axis is automatically labeled.

[6]:
eplt.plot_array(cut)
[6]:
<matplotlib.image.AxesImage at 0x7f99e922bbd0>
../_images/user-guide_plotting_9_1.svg

plot_array takes many arguments that can customize the look of your plot. The following is an example of some of the functionality provided. For all arguments, see the API reference.

[7]:
eplt.plot_array(
    cut, cmap="Greys", gamma=0.5, colorbar=True, colorbar_kw=dict(width=10, ticks=[])
)
[7]:
<matplotlib.image.AxesImage at 0x7f99e8efa410>
../_images/user-guide_plotting_11_1.svg

plot_array can also be accessed (for 2D data) through the qplot accessor.

[8]:
cut.qplot(cmap="Greys", gamma=0.5)
[8]:
<matplotlib.image.AxesImage at 0x7f99e8b70810>
../_images/user-guide_plotting_13_1.svg
[9]:
eplt.plot_array(cut, cmap="Greys", gamma=0.5)

eplt.fermiline()
eplt.mark_points([-0.525, 0.525], ["K", "K"], fontsize=10, pad=(0, 10))
eplt.nice_colorbar(width=10, ticks=[])
[9]:
<matplotlib.colorbar.Colorbar at 0x7f99e818b990>
../_images/user-guide_plotting_14_1.svg

Next, let’s add some annotations! The following code adds a line indicating the Fermi level, labels high symmetry points, and adds a colorbar. Here, unlike the previous example, the colorbar was added after plotting. Like this, adding elements separately instead of using keyword arguments can make the code more readable in complex plots.

Slices

What if we want to plot multiple slices at once? We should create subplots to place the slices. plt.subplots is very useful in managing multiple axes and figures. If you are unfamiliar with the syntax, visit the relevant matplotlib documentation.

Suppose we want to plot constant energy surfaces at specific binding energies, say, at [-0.4, -0.2, 0.0]. We could create three subplots and iterate over the axes.

[10]:
energies = [-0.4, -0.2, 0.0]

fig, axs = plt.subplots(1, 3, layout="compressed", sharey=True)
for energy, ax in zip(energies, axs):
    const_energy_surface = dat.qsel(eV=energy)
    eplt.plot_array(const_energy_surface, ax=ax, gamma=0.5, aspect="equal")
../_images/user-guide_plotting_18_0.svg

Here, we plotted each constant energy surface with plot_array. To remove the duplicated y axis labels and add some annotations, we can use clean_labels and label_subplot_properties:

[11]:
fig, axs = plt.subplots(1, 3, layout="compressed", sharey=True)
for energy, ax in zip(energies, axs):
    const_energy_surface = dat.qsel(eV=energy)
    eplt.plot_array(const_energy_surface, ax=ax, gamma=0.5, aspect="equal")

eplt.clean_labels(axs)  # removes shared y labels
eplt.label_subplot_properties(axs, values={"Eb": energies})  # annotates energy
../_images/user-guide_plotting_20_0.svg

Not bad. However, when it gets to multiple slices along multiple datasets, it gets cumbersome. Luckily, ERLabPy provides a function that automates the subplot creation, slicing, and annotation for you: plot_slices, which reduces the same code to a one-liner. See the API reference for a full description of all possible arguments.

[12]:
fig, axs = eplt.plot_slices([dat], eV=[-0.4, -0.2, 0.0], gamma=0.5, axis="image")
../_images/user-guide_plotting_22_0.svg

We can also plot the data integrated over an energy window, in this case with a width of 200 meV by adding the eV_width argument:

[13]:
fig, axs = eplt.plot_slices(
    [dat], eV=[-0.4, -0.2, 0.0], eV_width=0.2, gamma=0.5, axis="image"
)
../_images/user-guide_plotting_24_0.svg

Cuts along constant \(k_y\) can be plotted analogously.

[14]:
fig, axs = eplt.plot_slices([dat], ky=[0.0, 0.1, 0.3], gamma=0.5, figsize=(6, 2))
../_images/user-guide_plotting_26_0.svg

Here, we notice that the first two plots slices through regions with less spectral weight, so the color across the three subplots are not on the same scale. This may be misleading in some occasions where intensity across different slices are important. Luckily, we have a function that can unify the color limits across multiple axes.

The same effect can be achieved by passing on same_limits=True to plot_slices.

[15]:
fig, axs = eplt.plot_slices([dat], ky=[0.0, 0.1, 0.3], gamma=0.5, figsize=(6, 2))
eplt.unify_clim(axs)
../_images/user-guide_plotting_28_0.svg

We can also choose a reference axis to get the color limits from.

[16]:
fig, axs = eplt.plot_slices([dat], ky=[0.0, 0.1, 0.3], gamma=0.5, figsize=(6, 2))
eplt.unify_clim(axs, target=axs.flat[1])
../_images/user-guide_plotting_30_0.svg

What if we want to plot constant energy surfaces and cuts in the same figure? We can create the subplots first and then utilize the axes argument of plot_slices.

[17]:
fig, axs = plt.subplots(2, 3, layout="compressed", sharex=True, sharey="row")
eplt.plot_slices([dat], eV=[-0.4, -0.2, 0.0], gamma=0.5, axes=axs[0, :], axis="image")
eplt.plot_slices([dat], ky=[0.0, 0.1, 0.3], gamma=0.5, axes=axs[1, :])
eplt.clean_labels(axs)
../_images/user-guide_plotting_32_0.svg

2D colormaps

2D colormaps are a method to visualize two data with a single image by mapping one of the data to the lightness of the color and the other to the hue. This is useful when visualizing dichroic or spin-resolved ARPES data [2].

Let us begin with the simulated constant energy contours of Graphene, 0.3 eV below and above the Fermi level.

[18]:
dat0, dat1 = generate_data(
    shape=(250, 250, 2), Erange=(-0.3, 0.3), temp=0.0, seed=1, count=1e6
).T

_, axs = eplt.plot_slices(
    [dat0, dat1],
    order="F",
    subplot_kw={"layout": "compressed", "sharey": "row"},
    axis="scaled",
    label=True,
)
# eplt.label_subplot_properties(axs, values=dict(Eb=[-0.3, 0.3]))
../_images/user-guide_plotting_34_0.svg

Suppose we want to visualize the sum and the normalized difference between the two. The simplest way is to plot them side by side.

[19]:
dat_sum = dat0 + dat1
dat_ndiff = (dat0 - dat1) / dat_sum

eplt.plot_slices(
    [dat_sum, dat_ndiff],
    order="F",
    subplot_kw={"layout": "compressed", "sharey": "row"},
    cmap=["viridis", "bwr"],
    axis="scaled",
)
eplt.proportional_colorbar()
[19]:
<matplotlib.colorbar.Colorbar at 0x7f99e2766fd0>
../_images/user-guide_plotting_36_1.svg

The difference array is noisy for small values of the sum. We can plot using a 2D colomap, where dat_ndiff is mapped to the color along the colormap and dat_sum is mapped to the lightness of the colormap.

[20]:
eplt.plot_array_2d(dat_sum, dat_ndiff)
[20]:
(<matplotlib.image.AxesImage at 0x7f99e2516b50>,
 <matplotlib.colorbar.Colorbar at 0x7f99e2775c10>)
../_images/user-guide_plotting_38_1.svg

The color normalization for each axis can be set independently with lnorm and cnorm. The appearance of the colorbar axes can be customized with the returned Colorbar object.

[21]:
_, cb = eplt.plot_array_2d(
    dat_sum,
    dat_ndiff,
    lnorm=eplt.InversePowerNorm(0.5),
    cnorm=eplt.CenteredInversePowerNorm(0.7, vcenter=0.0, halfrange=1.0),
)
cb.ax.set_xticks(cb.ax.get_xlim())
cb.ax.set_xticklabels(["Min", "Max"])
[21]:
[Text(1.922459256430518e-08, 0, 'Min'), Text(218.36113081275943, 0, 'Max')]
../_images/user-guide_plotting_40_1.svg

Styling figures

You can control the look and feel of matplotlib figures with style sheets and rcParams. In addition to the options provided by matplotlib, ERLabPy provides some style sheets that are listed below. Note that style sheets that change the default font requires the font to be installed on the system. To see how each one looks, try running the code provided by matplotlib.

Style Name

Description

khan

Personal preferences of the package author.

fira

Changes the default font to Fira Sans.

firalight

Changes the default font to Fira Sans Light.

times

Changes the default font to Times New Roman.

nature

Changes the default font to Arial, and tweaks some aspects such as padding and default figure size.

[22]:
with plt.style.context(["nature"]):
    eplt.plot_array(cut, cmap="Greys", gamma=0.5)
findfont: Font family ['cursive'] not found. Falling back to DejaVu Sans.
findfont: Generic family 'cursive' not found because none of the following families were found: Apple Chancery, Textile, Zapf Chancery, Sand, Script MT, Felipa, Comic Neue, Comic Sans MS, cursive
../_images/user-guide_plotting_43_1.svg

Tips

Although matplotlib is a powerful library, it is heavy and slow, and better suited for static plots. For interactive plots, libraries such as Plotly or Bokeh are popular.

The hvplot library is a high-level plotting library that provides a simple interface to Bokeh, Plotly, and Matplotlib. It is particularly useful for interactive plots and can be used with xarray objects. Here are some examples that uses the Bokeh backend:

[23]:
import hvplot.xarray

cut.hvplot(x="kx", y="eV", cmap="Greys", aspect=1.5)
[23]:
[24]:
dat.hvplot(x="kx", y="ky", cmap="Greys", aspect="equal", widget_location="bottom")
[24]:

Note

If you are viewing this documentation online, the slider above will not work. To see the interactive plot, you can run the notebook locally after installing hvplot.

For more information, see the hvplot documentation.