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:
Subclass ``Figure``, adding backend specific functionality.
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