Create Panes with ReactiveHTML#
In this guide we will show you how to efficiently implement ReactiveHTML
panes to display Python objects.
Creating a ChartJS Pane#
This example will show you the basics of creating a ChartJS pane.
import panel as pn
import param
from panel.custom import PaneBase, ReactiveHTML
class ChatJSComponent(ReactiveHTML):
object = param.Dict()
_template = """
<div style="width: 100%; height:100%"><canvas id="canvas_el"></canvas></div>
"""
_scripts = {
"after_layout": "if (state.chart == null) { self.object() }",
"remove": """
state.chart.destroy();
state.chart = null;
""",
"object": """
if (state.chart) { self.remove() }
state.chart = new Chart(canvas_el.getContext('2d'), data.object);
""",
}
__javascript__ = [
"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"
]
def data(chart_type="line"):
return {
"type": chart_type,
"data": {
"labels": ["January", "February", "March", "April", "May", "June", "July"],
"datasets": [
{
"label": "Data",
"backgroundColor": "rgb(255, 99, 132)",
"borderColor": "rgb(255, 99, 132)",
"data": [0, 10, 5, 2, 20, 30, 45],
}
],
},
"options": {
"responsive": True,
"maintainAspectRatio": False,
},
}
chart_type = pn.widgets.RadioBoxGroup(
name="Chart Type", options=["bar", "line"], inline=True
)
grid = ChatJSComponent(
object=pn.bind(data, chart_type), height=400, sizing_mode="stretch_width"
)
pn.Column(chart_type, grid).servable()
Note that the chart is not created inside the after_layout
callback since ChartJS requires the layout to be fully initialized before render. Dealing with layout issues like this sometimes requires a bit of iteration, if you get stuck, share your question and minimum, reproducible code example on Discourse.
Creating a Cytoscape Pane#
This example will show you how to build a more advanced CytoscapeJS pane.
import param
import panel as pn
from panel.custom import ReactiveHTML
class Cytoscape(ReactiveHTML):
object = param.List()
layout = param.Selector(default="cose", objects=["breadthfirst", "circle", "concentric", "cose", "grid", "preset", "random"])
style = param.String("", doc="Use to set the styles of the nodes/edges")
zoom = param.Number(1, bounds=(0,100))
pan = param.Dict({"x": 0, "y": 0})
data = param.List(doc="Use to send node's data/attributes to Cytoscape")
selected_nodes = param.List()
selected_edges = param.List()
_template = '<div id="cy" style="width: 100%; height: 100%; position: relative; border: 1px solid"></div>'
__javascript__ = ['https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.umd.js']
_scripts = {
"render": """
if (state.cy == undefined) {
state.cy = cytoscape({
container: cy,
layout: {name: data.layout},
elements: data.object,
zoom: data.zoom,
pan: data.pan,
});
state.cy.on('select unselect', function (evt) {
data.selected_nodes = state.cy.elements('node:selected').map(el => el.id())
data.selected_edges = state.cy.elements('edge:selected').map(el => el.id())
});
self.style()
const mainEle = document.querySelector("body")
mainEle.addEventListener("scrollend", (event) => {state.cy.resize().fit()})
};
""",
"remove": """
state.cy.destroy()
delete state.cy
""",
"object": "state.cy.json({elements: data.object});state.cy.resize().fit()",
"layout": "state.cy.layout({name: data.layout}).run()",
"zoom": "state.cy.zoom(data.zoom)",
"pan": "state.cy.pan(data.pan)",
"style": "state.cy.style().resetToDefault().append(data.style).update()",
}
_extension_name = 'cytoscape'
pn.extension('cytoscape', sizing_mode='stretch_width')
elements = [{"data":{"id":'A', "label":'A'}},{"data":{"id":'B', "label":'B'}}, {"data":{"id": "A-B", "source":'A', "target":'B'}}]
graph = Cytoscape(object=elements, sizing_mode="stretch_width", height=600)
pn.Row(
pn.Param(graph, parameters=["object", "zoom", "pan", "layout", "style", "selected_nodes", "selected_edges"], sizing_mode="fixed", width=300),
graph
).servable()
Please notice that we resize
and fit
the graph on scrollend
. This is a hack needed to make the graph show up and fit nicely to the screen.
Hacks like these are sometimes needed and requires a bit of experience to find. If you get stuck share your question and minimum, reproducible code example on Discourse.