Adding new backends
This notebook displays how to integrate a new plotting backend to sisl.viz
.
Let’s create a toy graphene band structure to illustrate the conceps throughout this notebook:
[1]:
import sisl
geom = sisl.geom.graphene(orthogonal=True)
H = sisl.Hamiltonian(geom)
H.construct(
[(0.1, 1.44), (0, -2.7)],
)
band_struct = sisl.BandStructure(H, [[0, 0, 0], [0.5, 0, 0]], 10, ["Gamma", "X"])
The final display in the visualization module is controlled by the Figure
class.
[2]:
from sisl.viz import Figure
And backends are stored in sisl.viz.figure.BACKENDS
. It is just a dictionary containing extensions of the Figure
class for particular plotting frameworks.
[3]:
from sisl.viz.figure import BACKENDS
BACKENDS
[3]:
{'plotly': sisl.viz.figure.plotly.PlotlyFigure,
'matplotlib': sisl.viz.figure.matplotlib.MatplotlibFigure,
'py3dmol': sisl.viz.figure.Py3DmolFigure,
'blender': sisl.viz.figure.BlenderFigure}
Therefore, to add a new backend we must follow two steps: 1. Subclass ``Figure``, adding backend specific functionality. 2. Register the backend.
The documentation of the Figure
class explains what you should do to extend it:
[4]:
help(Figure)
Help on class Figure in module sisl.viz.figure.figure:
class Figure(builtins.object)
| Figure(plot_actions, *args, **kwargs)
|
| Base figure class that all backends should inherit from.
|
| It contains all the plotting actions that should be supported by a figure.
|
| A subclass for a specific backend should implement as many methods as possible
| from the ones where Figure returns NotImplementedError.
| Other methods are optional because Figure contains a default implementation
| using other methods, which should work for most backends.
|
| To create a new backend, one might take the PlotlyFigure as a template.
|
| Methods defined here:
|
| __init__(self, plot_actions, *args, **kwargs)
| Initialize self. See help(type(self)) for accurate signature.
|
| clear(self)
| Clears the figure so that we can draw again.
|
| draw_area_line(self, x, y, name=None, line={}, text=None, dependent_axis=None, row=None, col=None, **kwargs)
| Same as draw line, but to draw a line with an area. This is for example used to draw fatbands.
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| name: str, optional
| the name of the scatter
| line: dict, optional
| specifications for the line style, following plotly standards. The backend
| should at least be able to implement `line["color"]` and `line["width"]`, but
| it is very advisable that it supports also `line["opacity"]`.
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| dependent_axis: str, optional
| The axis that contains the dependent variable. This is important because
| the area is drawn in parallel to that axis.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the scatter. This will of course be framework specific
|
| draw_arrows(self, x, y, dxy, arrowhead_scale=0.2, arrowhead_angle=20, scale: 'float' = 1, annotate: 'bool' = False, row=None, col=None, **kwargs)
| Draws multiple arrows using the generic draw_line method.
|
| Parameters
| -----------
| xy: np.ndarray of shape (n_arrows, 2)
| the positions where the atoms start.
| dxy: np.ndarray of shape (n_arrows, 2)
| the arrow vector.
| arrow_head_scale: float, optional
| how big is the arrow head in comparison to the arrow vector.
| arrowhead_angle: angle
| the angle that the arrow head forms with the direction of the arrow (in degrees).
| scale: float, optional
| multiplying factor to display the arrows. It does not affect the underlying data,
| therefore if the data is somehow displayed it should be without the scale factor.
| annotate:
| whether to annotate the arrows with the vector they represent.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
|
| draw_arrows_3D(self, x, y, z, dxyz, arrowhead_scale=0.3, arrowhead_angle=15, scale: 'float' = 1, row=None, col=None, **kwargs)
| Draws multiple 3D arrows using the generic draw_line_3D method.
|
| Parameters
| -----------
| x: np.ndarray of shape (n_arrows, )
| the X coordinates of the arrow's origin.
| y: np.ndarray of shape (n_arrows, )
| the Y coordinates of the arrow's origin.
| z: np.ndarray of shape (n_arrows, )
| the Z coordinates of the arrow's origin.
| dxyz: np.ndarray of shape (n_arrows, 2)
| the arrow vector.
| arrow_head_scale: float, optional
| how big is the arrow head in comparison to the arrow vector.
| arrowhead_angle: angle
| the angle that the arrow head forms with the direction of the arrow (in degrees).
| scale: float, optional
| multiplying factor to display the arrows. It does not affect the underlying data,
| therefore if the data is somehow displayed it should be without the scale factor.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
|
| draw_balls_3D(self, x, y, z, name=None, markers={}, row=None, col=None, **kwargs)
| Draws points as 3D spheres.
|
| draw_heatmap(self, values, x=None, y=None, name=None, zsmooth=False, coloraxis=None, opacity=None, textformat=None, textfont={}, row=None, col=None, **kwargs)
| Draws a heatmap following the specifications.
|
| draw_line(self, x, y, name=None, line={}, marker={}, text=None, row=None, col=None, **kwargs)
| Draws a line satisfying the specifications
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| name: str, optional
| the name of the line
| line: dict, optional
| specifications for the line style, following plotly standards. The backend
| should at least be able to implement `line["color"]` and `line["width"]`
| marker: dict, optional
| specifications for the markers style, following plotly standards. The backend
| should at least be able to implement `marker["color"]` and `marker["size"]`
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the line. This will of course be framework specific
|
| draw_line_3D(self, x, y, z, name=None, line={}, marker={}, text=None, row=None, col=None, **kwargs)
| Draws a 3D line satisfying the specifications.
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| z: array-like
| the coordinates of the points along the Z axis.
| name: str, optional
| the name of the line
| line: dict, optional
| specifications for the line style, following plotly standards. The backend
| should at least be able to implement `line["color"]` and `line["width"]`
| marker: dict, optional
| specifications for the markers style, following plotly standards. The backend
| should at least be able to implement `marker["color"]` and `marker["size"]`
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the line. This will of course be framework specific
|
| draw_mesh_3D(self, vertices, faces, color=None, opacity=None, name=None, row=None, col=None, **kwargs)
| Draws a 3D mesh following the specifications.
|
| draw_multicolor_area_line(self, x, y, name=None, line={}, text=None, dependent_axis=None, row=None, col=None, **kwargs)
| Draw a line with an area with multiple colours.
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| name: str, optional
| the name of the scatter
| line: dict, optional
| specifications for the line style, following plotly standards. The backend
| should at least be able to implement `line["color"]` and `line["width"]`, but
| it is very advisable that it supports also `line["opacity"]`.
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| dependent_axis: str, optional
| The axis that contains the dependent variable. This is important because
| the area is drawn in parallel to that axis.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the scatter. This will of course be framework specific
|
| draw_multicolor_balls_3D(self, x, y, z, name=None, marker={}, row=None, col=None, **kwargs)
| Draws points as 3D spheres with different colours.
|
| If marker_color is an array of numbers, a coloraxis is created and values are converted to rgb.
|
| draw_multicolor_line(self, *args, line={}, row=None, col=None, **kwargs)
| By default, multicoloured lines are drawn simply by drawing scatter points.
|
| draw_multicolor_line_3D(self, *args, **kwargs)
| Draws a multicoloured 3D line.
|
| draw_multicolor_scatter(self, *args, **kwargs)
| Draws a multicoloured scatter.
|
| Usually the normal scatter can already support this.
|
| draw_multicolor_scatter_3D(self, *args, **kwargs)
| Draws a multicoloured 3D scatter.
|
| Usually the normal 3D scatter can already support this.
|
| draw_multisize_area_line(self, x, y, name=None, line={}, text=None, dependent_axis=None, row=None, col=None, **kwargs)
| Draw a line with an area with multiple colours.
|
| This is already usually supported by the normal draw_area_line.
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| name: str, optional
| the name of the scatter
| line: dict, optional
| specifications for the line style, following plotly standards. The backend
| should at least be able to implement `line["color"]` and `line["width"]`, but
| it is very advisable that it supports also `line["opacity"]`.
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| dependent_axis: str, optional
| The axis that contains the dependent variable. This is important because
| the area is drawn in parallel to that axis.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the scatter. This will of course be framework specific
|
| draw_multisize_balls_3D(self, x, y, z, name=None, marker={}, row=None, col=None, **kwargs)
| Draws points as 3D spheres with different sizes.
|
| Usually supported by the normal draw_balls_3D
|
| draw_multisize_line(self, *args, line={}, row=None, col=None, **kwargs)
| By default, multisized lines are drawn simple by drawing scatter points.
|
| draw_multisize_line_3D(self, *args, **kwargs)
| Draws a multisized 3D line.
|
| draw_multisize_scatter(self, *args, **kwargs)
| Draws a multisized scatter.
|
| Usually the normal scatter can already support this.
|
| draw_multisize_scatter_3D(self, *args, **kwargs)
| Draws a multisized 3D scatter.
|
| Usually the normal 3D scatter can already support this.
|
| draw_scatter(self, x, y, name=None, marker={}, text=None, row=None, col=None, **kwargs)
| Draws a scatter satisfying the specifications
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| name: str, optional
| the name of the scatter
| marker: dict, optional
| specifications for the markers style, following plotly standards. The backend
| should at least be able to implement `marker["color"]` and `marker["size"]`, but
| it is very advisable that it supports also `marker["opacity"]` and `marker["colorscale"]`
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the scatter. This will of course be framework specific
|
| draw_scatter_3D(self, x, y, z, name=None, marker={}, text=None, row=None, col=None, **kwargs)
| Draws a 3D scatter satisfying the specifications
|
| Parameters
| -----------
| x: array-like
| the coordinates of the points along the X axis.
| y: array-like
| the coordinates of the points along the Y axis.
| z: array-like
| the coordinates of the points along the Z axis.
| name: str, optional
| the name of the scatter
| marker: dict, optional
| specifications for the markers style, following plotly standards. The backend
| should at least be able to implement `marker["color"]` and `marker["size"]`
| text: str, optional
| contains the text asigned to each marker. On plotly this is seen on hover,
| other options could be annotating. However, it is not necessary that this
| argument is supported.
| row: int, optional
| If the figure contains subplots, the row where to draw.
| col: int, optional
| If the figure contains subplots, the column where to draw.
| **kwargs:
| should allow other keyword arguments to be passed directly to the creation of
| the scatter. This will of course be framework specific
|
| init_3D(self)
| Called if functions that draw in 3D are going to be called.
|
| init_coloraxis(self, name, cmin=None, cmax=None, cmid=None, colorscale=None, showscale=True, **kwargs)
| Initializes a color axis to be used by the drawing functions
|
| init_figure(self, composite_method: "Literal[None, 'same_axes', 'multiple', 'multiple_x', 'multiple_y', 'subplots', 'animation']" = None, plot_actions=(), init_kwargs: 'dict[str, Any]' = {})
|
| set_axes_equal(self)
| Sets the axes equal.
|
| set_axis(self, **kwargs)
| Sets the axis parameters.
|
| The specification for the axes is exactly the plotly one. This is to have a good
| reference for consistency. Other frameworks should translate the calls to their
| functionality.
|
| show(self)
|
| to(self, key: 'str')
| Converts the figure to another backend.
|
| Parameters
| -----------
| key: str
| the backend to convert to.
|
| ----------------------------------------------------------------------
| Class methods defined here:
|
| fig_has_attr(key: 'str') -> 'bool'
| Whether the figure that this class generates has a given attribute.
|
| Parameters
| -----------
| key
| the attribute to check for.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables
|
| __weakref__
| list of weak references to the object
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __annotations__ = {'_coloraxes': 'dict', '_cols': 'Optional[int]', '_m...
|
| plot_actions = []
Therefore, we need to implement some of the methods of the Figure
class. The more we implement, the more we will support sisl.viz
.
Here’s an example of a very simple backend that just writes text:
[5]:
import numpy as np
class TextFigure(Figure):
def _init_figure(self, *args, **kwargs):
self.text = ""
def clear(self):
self.text = ""
def draw_line(self, x, y, name, **kwargs):
self.text += f"\nLINE: {name}\n{np.array(x)}\n{np.array(y)}"
def draw_scatter(self, x, y, name, **kwargs):
self.text += f"\nSCATTER: {name}\n{np.array(x)}\n{np.array(y)}"
def show(self):
print(self.text)
def _ipython_display_(self):
self.show()
And all that is left now is to register the backend by simply adding it to the BACKENDS
dictionary.
[6]:
BACKENDS["text"] = TextFigure
Let’s plot the bands to check that it works.
[7]:
plot = band_struct.plot()
plot
The default backend has been used, let’s now change it to our new "text"
backend.
[8]:
plot.update_inputs(backend="text")
LINE: Bands
[0. 0.08194034 0.16388068 0.24582102 0.32776136 0.4097017
0.49164204 0.57358238 0.65552272 0.73746306]
[-8.1 -8.07260764 -7.99070941 -7.85514486 -7.66732391 -7.42924537
-7.14352854 -6.81346515 -6.44310336 -6.03738354]
LINE: Bands
[0. 0.08194034 0.16388068 0.24582102 0.32776136 0.4097017
0.49164204 0.57358238 0.65552272 0.73746306]
[-2.7 -2.78082828 -3.00808297 -3.34614692 -3.75661337 -4.20788704
-4.67653718 -5.14555076 -5.60235836 -6.03738354]
LINE: Bands
[0. 0.08194034 0.16388068 0.24582102 0.32776136 0.4097017
0.49164204 0.57358238 0.65552272 0.73746306]
[2.7 2.78082828 3.00808297 3.34614692 3.75661337 4.20788704
4.67653718 5.14555076 5.60235836 6.03738354]
LINE: Bands
[0. 0.08194034 0.16388068 0.24582102 0.32776136 0.4097017
0.49164204 0.57358238 0.65552272 0.73746306]
[8.1 8.07260764 7.99070941 7.85514486 7.66732391 7.42924537
7.14352854 6.81346515 6.44310336 6.03738354]
Not a very visually appealing backend, but it serves the purpose of demonstrating how it is done. Now it is your turn!
Note
For a complex framework you might take inspiration from the already implemented backends in sisl.viz.figure.*
.
[ ]: