<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Adding new backends
Step1:  In the sisl.viz framework, the rendering part of the visualization is completely detached from the processing part. Because of that, we have the flexibility to add new ways of generating the final product by registering what we call backends.
We will guide you through how you might customize this part of the framework. There are however, very distinct scenarios where you might find yourself. Each of the following sections explains the details of each situation, which are ordered in increasing complexity.
<div class="alert alert-info">
Note
Even if you want to go to the most complex situation, make sure that you first understand the simpler ones!
</div>
Extending an existing backend
This is by far the easiest situation. For example, sisl already provides a backend to plot bands with plotly, but you are not totally happy with the way it's done.
In this case, you grab the provided backend
Step2:  And then create your own class that inherits from it
Step3:  The only thing left to do now is to let BandsPlot know that there's a new backend available. This action is called registering a backend.
Step4:  All good, you can already use your new backend!
Step5:  Now that we know that it can be registered, we can try to add new functionality. But of course, we need to know how the backend works if we need to modify it. All backends to draw bands inherit from BandsBackend, and you can find some information there on how it works. Let's read its documentation
Step6:  <div class="alert alert-info">
Note
This already gives you an overview of how the backend works. If you want to know the very fine details, you can always go to the source code.
</div>
So, clearly PlotlyBandsBackend already contains the draw_gap method, otherwise it would not work. 
From the workflow description, we understand that each band is drawn with the _draw_band method, which calls the generic draw_line method. In plotly, line information is passed as dictionaries that contain several parameters. One of them is, for example, showlegend, which controls whether the line appears in the legend. We can use therefore our plotly knowledge to only show at the legend those bands that are below the fermi level
Step7:  This is not very interesting, but it does its job at illustrating the fact that you can register a slightly modified backend.
You could use your fresh knowledge to, for example draw something after the bands are drawn
Step8:  We finish this section by stating that
Step9:  And also a generic bands backend
Step10:  In these cases, your situation is not that bad. As you saw, the template backends make use of generic functions like draw_line as much as they can, so the effort to implement a plotly bands backend is reduced to those things that can't be generalized in that way.
One thing is for sure, we need to combine the two pieces to create the backend that we want
Step11:  But is this enough? Let's see the documentation of BandsBackend one more time
Step12:  So, there are to things that need to be implemented
Step13:  Quite simple, isn't it? It seems like we are provided with the coordinates of the gap and then we can display it however we want.
Step14:  Let's see our masterpiece
Step15:  Beautiful!
So, to end this section, just two remarks
Step16:  <div class="alert alert-info">
Note
You can always look at the help of each specific method to understand exactly what you need to implement. E.g. `help(Backend.draw_line)`.
</div>
To make it simple, let's say we want to create a backend for "text". This backend will store everything as text in its state, and it will print it on show. Here would be a minimal design
Step17:  This could very well be our generic backend for the "text" framework. Now we can use the knowledge of the previous section to create a backend for the bands plot
Step18:  And everything works great! Note that since the backend is independent of the processing logic, I can use any setting of BandsPlot and it will work
<ASSISTANT_TASK:>
Python Code:
import sisl
import sisl.viz
# This is a toy band structure to illustrate the concepts treated throughout the notebook
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"])
Explanation: Adding new backends
End of explanation
from sisl.viz.backends.plotly import PlotlyBandsBackend
Explanation: In the sisl.viz framework, the rendering part of the visualization is completely detached from the processing part. Because of that, we have the flexibility to add new ways of generating the final product by registering what we call backends.
We will guide you through how you might customize this part of the framework. There are however, very distinct scenarios where you might find yourself. Each of the following sections explains the details of each situation, which are ordered in increasing complexity.
<div class="alert alert-info">
Note
Even if you want to go to the most complex situation, make sure that you first understand the simpler ones!
</div>
Extending an existing backend
This is by far the easiest situation. For example, sisl already provides a backend to plot bands with plotly, but you are not totally happy with the way it's done.
In this case, you grab the provided backend:
End of explanation
class MyOwnBandsBackend(PlotlyBandsBackend):
    pass
Explanation: And then create your own class that inherits from it:
End of explanation
from sisl.viz import BandsPlot
BandsPlot.backends.register("plotly_myown", MyOwnBandsBackend)
# Pass default=True if you want to make it the default backend
Explanation: The only thing left to do now is to let BandsPlot know that there's a new backend available. This action is called registering a backend.
End of explanation
band_struct.plot(backend="plotly_myown")
Explanation: All good, you can already use your new backend!
End of explanation
from sisl.viz.backends.templates import BandsBackend
print(BandsBackend.__doc__)
Explanation: Now that we know that it can be registered, we can try to add new functionality. But of course, we need to know how the backend works if we need to modify it. All backends to draw bands inherit from BandsBackend, and you can find some information there on how it works. Let's read its documentation:
End of explanation
# Create my new backend
class MyOwnBandsBackend(PlotlyBandsBackend):
    
    def _draw_band(self, x, y, *args, **kwargs):
        kwargs["showlegend"] = bool(y.max() < 0)
        super()._draw_band(x, y, *args, **kwargs)
        
# And register it again
BandsPlot.backends.register("plotly_myown", MyOwnBandsBackend)
band_struct.plot(backend="plotly_myown")
Explanation: <div class="alert alert-info">
Note
This already gives you an overview of how the backend works. If you want to know the very fine details, you can always go to the source code.
</div>
So, clearly PlotlyBandsBackend already contains the draw_gap method, otherwise it would not work. 
From the workflow description, we understand that each band is drawn with the _draw_band method, which calls the generic draw_line method. In plotly, line information is passed as dictionaries that contain several parameters. One of them is, for example, showlegend, which controls whether the line appears in the legend. We can use therefore our plotly knowledge to only show at the legend those bands that are below the fermi level:
End of explanation
class MyOwnBandsBackend(PlotlyBandsBackend):
    
    def draw_bands(self, *args, **kwargs):
        super().draw_bands(*args, **kwargs)
        # Now that all bands are drawn, draw a very interesting line at -2eV.
        self.add_hline(y=-2, line_color="red")
    
BandsPlot.backends.register("plotly_myown", MyOwnBandsBackend)
band_struct.plot(backend="plotly_myown")
Explanation: This is not very interesting, but it does its job at illustrating the fact that you can register a slightly modified backend.
You could use your fresh knowledge to, for example draw something after the bands are drawn:
End of explanation
from sisl.viz.backends.plotly import PlotlyBackend
Explanation: We finish this section by stating that:
To extend a backend, you have to have some knowledge about the corresponding framework (in this case plotly)
You don't need to create a new backend for every modification. You can modify plots interactively however you want after the plot is generated. Creating a backend that extends an existing one is only useful if there are changes that you will always want to do because of personal preference or because you are building a graphical interface, for example.
Creating a backend for a supported framework
Now imagine that, for some reason, sisl didn't provide a PlotlyBandsBackend. However, sisl does have a generic plotly backend:
End of explanation
from sisl.viz.backends.templates import BandsBackend
Explanation: And also a generic bands backend:
End of explanation
class MyPlotlyBandsBackend(BandsBackend, PlotlyBackend):
    pass
Explanation: In these cases, your situation is not that bad. As you saw, the template backends make use of generic functions like draw_line as much as they can, so the effort to implement a plotly bands backend is reduced to those things that can't be generalized in that way.
One thing is for sure, we need to combine the two pieces to create the backend that we want:
End of explanation
print(BandsBackend.__doc__)
Explanation: But is this enough? Let's see the documentation of BandsBackend one more time:
End of explanation
help(BandsBackend.draw_gap)
Explanation: So, there are to things that need to be implemented: draw_spin_textured_band and draw_gap.
We won't bother to give our backend support for spin texture representations, but the draw_gap method is compulsory, so we have no choice. Let's understand what is expected from this method:
End of explanation
class MyPlotlyBandsBackend(BandsBackend, PlotlyBackend):
    
    def draw_gap(self, ks, Es, color, name, **kwargs):
        
        self.draw_line(
            ks, Es, name=name,
            text=f"{Es[1]- Es[0]:.2f} eV",
            mode="lines+markers",
            line={"color": color},
            marker_symbol = ["triangle-up", "triangle-down"],
            marker={"color": color, "size": 20},
            **kwargs
        )
# Make it the default backend for bands, since it is awesome.
BandsPlot.backends.register("plotly_fromscratch", MyPlotlyBandsBackend, default=True)
Explanation: Quite simple, isn't it? It seems like we are provided with the coordinates of the gap and then we can display it however we want.
End of explanation
band_struct.plot(gap=True)
Explanation: Let's see our masterpiece:
End of explanation
from sisl.viz.backends.templates import Backend
print(Backend.__doc__)
Explanation: Beautiful!
So, to end this section, just two remarks:
We have understood that if the framework is supported, the starting point is to combine the generic backend for the framework (PlotlyBackend) with the template backend of the specific plot (BandsBackend). Afterwards, we may have to tweak things a little.
Knowing how the generic framework backend works helps to make your code simpler. E.g. if you check PlotlyBackend.__doc__, you will find that we could have easily included some defaults for the axes titles.
Creating a backend for a non supported framework
Armed with our knowledge from the previous sections, we face the most difficult of the challenges: there's not even a generic backend for the framework that we want to use.
What we have to do is quite clear, develop our own generic backend. But how? Let's go to the Backend class for help:
End of explanation
class TextBackend(Backend):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.text = ""
    
    def clear(self):
        self.text = ""
        
    def draw_line(self, x, y, name, **kwargs):
        self.text += f"\nLINE: {name}\n{x}\n{y}"
    
    def draw_scatter(self, x, y, name, **kwargs):
        self.text += f"\nSCATTER: {name}\n{x}\n{y}"
        
    def draw_on(self, other_backend):
        # Set the text attribute to the other backend's text, but store ours
        self_text = self.text
        self.text = other_backend.text
        # Make the plot draw the figure
        self._plot.get_figure(backend=self._backend_name, clear_fig=False)
        # Restore our text attribute
        self.text = self_text
    
    def show(self):
        print(self.text)
Explanation: <div class="alert alert-info">
Note
You can always look at the help of each specific method to understand exactly what you need to implement. E.g. `help(Backend.draw_line)`.
</div>
To make it simple, let's say we want to create a backend for "text". This backend will store everything as text in its state, and it will print it on show. Here would be a minimal design:
End of explanation
class TextBandsBackend(BandsBackend, TextBackend):
    
    def draw_gap(self, ks, Es, name, **kwargs):
        self.draw_line(ks, Es, name=name)
        
# Register it, as always
BandsPlot.backends.register("text", TextBandsBackend)
band_struct.plot(backend="text", gap=True, _debug=True)
Explanation: This could very well be our generic backend for the "text" framework. Now we can use the knowledge of the previous section to create a backend for the bands plot:
End of explanation
bands_plot = band_struct.plot(backend="text", gap=True, _debug=True)
bands_plot.update_settings(
    bands_range=[0,1], 
    custom_gaps=[{"from": "Gamma", "to": "Gamma"}, {"from": "X", "to": "X"}]
)
Explanation: And everything works great! Note that since the backend is independent of the processing logic, I can use any setting of BandsPlot and it will work:
End of explanation
<END_TASK>