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.plotly 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=[0,1], transforms=["square"], zsmooth="best")
pdos_plot = H.plot.pdos(Erange=[-10,10])
plots = [wf_plot, pdos_plot]
Exception ignored in: <function tqdm.__del__ at 0x7f87e4528790>
Traceback (most recent call last):
File "/home/docs/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/tqdm/std.py", line 1152, in __del__
self.close()
File "/home/docs/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/tqdm/notebook.py", line 286, in close
self.disp(bar_style='danger', check_delay=False)
AttributeError: 'tqdm_notebook' object has no attribute 'disp'
info:0: SislInfo:
The plot has been initialized correctly, but the current settings were not enough to generate the figure.
Error: Could not read or generate data for PdosPlot from any of the possible sources.
Here are the errors for each source:
- TB trans: TypeError.expected str, bytes or os.PathLike object, not NoneType
- hamiltonian: ImportError.IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
- siesta output: TypeError.expected str, bytes or os.PathLike object, not NoneType
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
orSubplots
):
[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.plotly.plot:
merge(others, to='multiple', extend_multiples=True, **kwargs) method of sisl.viz.plotly.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 child_plots, so that the result
is the merge of its child_plots 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=[0,1], 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=[0,1], 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)
/tmp/ipykernel_3418/4244640691.py in <module>
----> 1 _.show("png")
~/checkouts/readthedocs.org/user_builds/sisl/checkouts/v0.11.0/sisl/viz/plotly/plot.py in show(self, listen, return_figWidget, *args, **kwargs)
1187 warn(e)
1188
-> 1189 return self.figure.show(*args, **kwargs)
1190
1191 def _ipython_display_(self, return_figWidget=False, **kwargs):
~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/plotly/basedatatypes.py in show(self, *args, **kwargs)
3396 import plotly.io as pio
3397
-> 3398 return pio.show(self, *args, **kwargs)
3399
3400 def to_json(self, *args, **kwargs):
~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/plotly/io/_renderers.py in show(fig, renderer, validate, **kwargs)
387
388 # Mimetype renderers
--> 389 bundle = renderers._build_mime_bundle(fig_dict, renderers_string=renderer, **kwargs)
390 if bundle:
391 if not ipython_display:
~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/plotly/io/_renderers.py in _build_mime_bundle(self, fig_dict, renderers_string, **kwargs)
295 setattr(renderer, k, v)
296
--> 297 bundle.update(renderer.to_mimebundle(fig_dict))
298
299 return bundle
~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/plotly/io/_base_renderers.py in to_mimebundle(self, fig_dict)
126
127 def to_mimebundle(self, fig_dict):
--> 128 image_bytes = to_image(
129 fig_dict,
130 format=self.format,
~/checkouts/readthedocs.org/user_builds/sisl/conda/v0.11.0/lib/python3.8/site-packages/plotly/io/_kaleido.py in to_image(fig, format, width, height, scale, validate, engine)
132 # Raise informative error message if Kaleido is not installed
133 if scope is None:
--> 134 raise ValueError(
135 """
136 Image export using the "kaleido" engine requires the kaleido package,
ValueError:
Image export using the "kaleido" engine requires the kaleido package,
which can be installed using pip:
$ pip install -U kaleido