Download IPython notebook here. Binder badge.

Intro to multiple plots

In this notebook you will learn what multiple plots can do for you and what is the best way to create them.

Multiple better than individual

The power of multiple plots is threefold:

  • Multiple plot is just a coordinator. It doesn’t hold any data related to it’s childplots, which are full instances of Plot themselves. This allows you to grab the separate child plots whenever you want, as well as modifying only some of its child plots very easily.

  • It will create the layout for you without requiring any effort on your side.

  • If all your plots inside a multiple plot need to read from the same data, the plot will coordinate all plots so that they can share the data. Therefore, data will be read only once, saving both time and memory.

Types of multiple plots

There are three ways of combining your plots in the visualization framework, each with its associated class:

  • In the same plot (MultiplePlot): it’s the most basic one. It just takes the traces from all plots and displays them in the same plot.

  • As subplots (SubPlots): Creates a grid of subplots, where each item of the grid contains a plot. Uses plotly’s subplots capabilities.

  • As frames of an animation (Animation): Creates an animation where each child plot is represented in a frame. plotly’s animation capabilities.

[1]:
from sisl.viz import MultiplePlot, Animation, SubPlots

Let’s create a simple tight-binding model for the plots in this notebook.

[2]:
import sisl
import numpy as np

r = np.linspace(0, 3.5, 50)
f = np.exp(-r)

orb = sisl.AtomicOrbital('2pzZ', (r, f))
geom = sisl.geom.graphene(orthogonal=True, atoms=sisl.Atom(6, orb))
geom = geom.move([0, 0, 5])
H = sisl.Hamiltonian(geom)
H.construct([(0.1, 1.44), (0, -2.7)], )

Merging existing plots

This is the most simple way of creating a multiple plot: you just build your plots, and then pass them to the multiple plot constructor.

However, this will miss one key feature of multiple plots. Since you’ve created each plot separately, each plot has its own data, even if they would be able to share it.

Therefore, this is only recommended when the plots are independent from each other.

As an example, from the hamiltonian that we constructed, let’s build a wavefunction plot and a pdos plot:

[3]:
wf_plot = H.plot.wavefunction(i=1, axes="xy", transforms=["square"], zsmooth="best")
pdos_plot = H.plot.pdos(Erange=[-10,10])

plots = [wf_plot, pdos_plot]

And now, we will merge them. There are two main ways to do the merge:

  • Calling the multiple plot class that we want to use (MultiplePlot, Animation or Subplots):

[4]:
# You just pass the plots and then any extra arguments for the plot class (see help(SubPlots))
SubPlots(plots=plots, cols=2)
  • Using the merge method that all plots have.

[5]:
plots[0].merge(plots[1:], to="subplots", cols=2)

Both are exactly equivalent, but this second one is probably better since you don’t need to import the class.

You do need to specify somehow how to merge the plots though! As you may have noticed, there’s a to argument that lets you specify how you want the plots to be merged.

Here are the docs for Plot.merge:

[6]:
help(plots[0].merge)
Help on method merge in module sisl.viz.plot:

merge(others, to='multiple', extend_multiples=True, **kwargs) method of sisl.viz.plots.grid.WavefunctionPlot instance
    Merges this plot's instance with the list of plots provided

    Parameters
    -------
    others: array-like of Plot() or Plot()
        the plots that we want to merge with this plot instance.
    to: {"multiple", "subplots", "animation"}, optional
        the merge method. Each option results in a different way of putting all the plots
        together:
        - "multiple": All plots are shown in the same canvas at the same time. Useful for direct
        comparison.
        - "subplots": The layout is divided in different subplots.
        - "animation": Each plot is converted into the frame of an animation.
    extend_multiples: boolean, optional
        if True, if `MultiplePlot`s are passed, they are splitted into their children, so that the result
        is the merge of its children with the rest.
        If False, a `MultiplePlot` is treated as a solid unit.
    kwargs:
        extra arguments that are directly passed to `MultiplePlot`, `Subplots`
        or `Animation` initialization.

    Returns
    -------
    MultiplePlot, Subplots or Animation
        depending on the value of the `to` parameter.

Let MultiplePlot handle plot creation

As already mentioned, creating your plots beforehand is only a good strategy if plots are independent from each other, in the sense that they can not share data.

In cases where plots can share data, just let MultiplePlot create your plots. It is so easy that you will end up doing it in this way all the time, even in cases where it doesn’t have efficiency benefits :)

Everytime you create a plot, there are three special keyword arguments that you can pass: varying, animate and subplots. These keywords let you easily initialize MultiplePlot, Animation and Subplots instances, respectively.

They can be used in two ways:

  • You can pass a dictionary with the keys of the settings that you want to vary and the value for each “step”.

[7]:
H.plot.wavefunction(axes="xy", transforms=["square"], animate={"i":[1,2], "zsmooth": ["best", False]})

In this case we animated the wavefunction plot to see the squares of wavefunctions 1 and 2. The second one, for some reason, we wanted it to display “a bit” pixelated.

  • You can also pass the list of values as regular settings and then inform the multiple plot keyword (in this case subplots) which settings to vary.

[8]:
H.plot.wavefunction(
    colorscale=["temps", "portland", "peach", "viridis"], i=[0,1,2,3], axes="xy", transforms=["square"], zsmooth="best",
    subplots=["colorscale", "i"], rows=2
)

There you go, four wavefunctions, each one displayed in a different colorscale :)

Remember that these subplots are all sharing the same data, so the eigenstates of the hamiltonian have only been stored once!


This next cell is just to create a thumbnail

[9]:
_.show("png")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 _.show("png")

File ~/checkouts/readthedocs.org/user_builds/sisl/checkouts/v0.12.2/sisl/viz/plot.py:1176, in Plot.show(self, listen, return_figWidget, *args, **kwargs)
   1173     except Exception as e:
   1174         warn(e)
-> 1176 return self._backend.show(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/checkouts/v0.12.2/sisl/viz/backends/plotly/backend.py:42, in PlotlyBackend.show(self, *args, **kwargs)
     41 def show(self, *args, **kwargs):
---> 42     return self.figure.show(*args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.12.2/lib/python3.10/site-packages/plotly/basedatatypes.py:3398, in BaseFigure.show(self, *args, **kwargs)
   3365 """
   3366 Show a figure using either the default renderer(s) or the renderer(s)
   3367 specified by the renderer argument
   (...)
   3394 None
   3395 """
   3396 import plotly.io as pio
-> 3398 return pio.show(self, *args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.12.2/lib/python3.10/site-packages/plotly/io/_renderers.py:388, in show(fig, renderer, validate, **kwargs)
    385 fig_dict = validate_coerce_fig_to_dict(fig, validate)
    387 # Mimetype renderers
--> 388 bundle = renderers._build_mime_bundle(fig_dict, renderers_string=renderer, **kwargs)
    389 if bundle:
    390     if not ipython_display:

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.12.2/lib/python3.10/site-packages/plotly/io/_renderers.py:296, in RenderersConfig._build_mime_bundle(self, fig_dict, renderers_string, **kwargs)
    293             if hasattr(renderer, k):
    294                 setattr(renderer, k, v)
--> 296         bundle.update(renderer.to_mimebundle(fig_dict))
    298 return bundle

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.12.2/lib/python3.10/site-packages/plotly/io/_base_renderers.py:127, in ImageRenderer.to_mimebundle(self, fig_dict)
    126 def to_mimebundle(self, fig_dict):
--> 127     image_bytes = to_image(
    128         fig_dict,
    129         format=self.format,
    130         width=self.width,
    131         height=self.height,
    132         scale=self.scale,
    133         validate=False,
    134         engine=self.engine,
    135     )
    137     if self.b64_encode:
    138         image_str = base64.b64encode(image_bytes).decode("utf8")

File ~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.12.2/lib/python3.10/site-packages/plotly/io/_kaleido.py:133, in to_image(fig, format, width, height, scale, validate, engine)
    131     # Raise informative error message if Kaleido is not installed
    132     if scope is None:
--> 133         raise ValueError(
    134             """
    135 Image export using the "kaleido" engine requires the kaleido package,
    136 which can be installed using pip:
    137     $ pip install -U kaleido
    138 """
    139         )
    141     # Validate figure
    142     # ---------------
    143     fig_dict = validate_coerce_fig_to_dict(fig, validate)

ValueError:
Image export using the "kaleido" engine requires the kaleido package,
which can be installed using pip:
    $ pip install -U kaleido