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

class ChatJSComponent(pn.reactive.ReactiveHTML):
    object = param.Dict()

    _template = (
        '<div style="height:100%;width:100%;"><canvas id="canvas_el"></canvas></div>'
    _scripts = {
        "after_layout": """
        "object": """
if (state.chart){
  state.chart = null;
state.chart = new Chart(canvas_el.getContext('2d'), data.object);
    __javascript__ = [

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()

Initially I tried creating the chart in the render function, but the chart did not display. I found out via Google Search and experimentation that the chart needs to be created later in the after_layout function. 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.reactive 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__ = ['']

    _scripts = {
        'render': """
        "create": """
            if ( == undefined){
       = cytoscape({
                container: cy,
                layout: {name: data.layout},
                elements: data.object,
                zoom: data.zoom,
                pan: data.pan,
      'select unselect', function (evt) {
                  data.selected_nodes ='node:selected').map((el)=>{return})
                  data.selected_edges ='edge:selected').map((el)=>{return})
                const mainEle = document.querySelector("body")
                mainEle.addEventListener("scrollend", (event) => {})
        'remove': """
        "object": "{elements: data.object});",
        'layout': "{name: data.layout}).run()",
        "zoom": "",
        "pan": "",
        "style": """

    _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.Param(graph, parameters=["object", "zoom", "pan", "layout", "style", "selected_nodes", "selected_edges"], sizing_mode="fixed", width=300),

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.