Using External Widgets

pgwidgets provides an ExternalWidget class for embedding content from third-party JavaScript libraries – such as Plotly charts, Bokeh plots, or Leaflet maps – into pgwidgets layout containers.

ExternalWidget participates in pgwidgets layout like any other widget (stretch factors, spacing, resize callbacks), but its content area is managed by external code.

Basic Usage

Create an ExternalWidget, add it to a container, and pass its content element to the third-party library’s rendering function:

let chart = new Widgets.ExternalWidget();
vbox.add_widget(chart, 1);  // stretch=1 fills available space

// Render into the content element
SomeLibrary.render(chart.get_content_element(), data);

The content element is a <div> that fills the widget’s area. Use get_content_element() (not get_element()) so that the outer wrapper styling is preserved.

Handling Resize

The base Widget class installs a ResizeObserver that fires a resize callback whenever the widget’s element changes size. This is useful for libraries that need explicit resize notification:

chart.add_callback('resize', (widget, width, height) => {
    SomeLibrary.resize(chart.get_content_element());
});

Many libraries can also handle resize automatically when configured correctly (see the Plotly and Bokeh examples below).

Using with Plotly

Plotly.js is a high-level charting library. Include it from a CDN or install via npm.

<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

Basic Example

import { Widgets } from "./Widgets.js";

let top = new Widgets.TopLevel({title: "Plotly Demo", resizable: true});
top.resize(700, 500);

let vbox = new Widgets.VBox({spacing: 8, padding: 10});

let label = new Widgets.Label("Plotly chart in a pgwidgets layout");

let chart = new Widgets.ExternalWidget();

vbox.add_widget(label, 0);
vbox.add_widget(chart, 1);
top.set_widget(vbox);
top.show();

// Render the Plotly chart
let data = [{
    x: [1, 2, 3, 4, 5],
    y: [1, 4, 9, 16, 25],
    type: 'scatter',
    mode: 'lines+markers',
    name: 'y = x^2'
}];

let layout = {
    title: 'Simple Plot',
    margin: {l: 50, r: 20, t: 40, b: 40}
};

Plotly.newPlot(chart.get_content_element(), data, layout,
               {responsive: true});

Setting responsive: true in the Plotly config causes it to track its container’s size automatically via its own ResizeObserver. No manual resize wiring is needed.

Multiple Charts in a Splitter

Because ExternalWidget is a regular pgwidgets widget, you can use it with any layout container:

let splitter = new Widgets.Splitter({orientation: 'horizontal'});

let left_chart = new Widgets.ExternalWidget();
let right_chart = new Widgets.ExternalWidget();
splitter.add_widget(left_chart, 1);
splitter.add_widget(right_chart, 1);
top.set_widget(splitter);
top.show();

Plotly.newPlot(left_chart.get_content_element(),
               [{x: [1,2,3], y: [2,5,3], type: 'bar'}],
               {title: 'Bar Chart', margin: {t: 40, b: 40}},
               {responsive: true});

Plotly.newPlot(right_chart.get_content_element(),
               [{values: [30, 50, 20], labels: ['A','B','C'], type: 'pie'}],
               {title: 'Pie Chart', margin: {t: 40, b: 40}},
               {responsive: true});

When the user drags the splitter, both charts resize automatically.

Using with Bokeh

Bokeh is a Python-oriented visualization library that also provides a standalone JavaScript API (BokehJS). Include it from a CDN:

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.7.3.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-api-3.7.3.min.js"></script>

Basic Example

import { Widgets } from "./Widgets.js";

let top = new Widgets.TopLevel({title: "Bokeh Demo", resizable: true});
top.resize(700, 500);

let vbox = new Widgets.VBox({spacing: 8, padding: 10});

let label = new Widgets.Label("Bokeh chart in a pgwidgets layout");

let chart = new Widgets.ExternalWidget();

vbox.add_widget(label, 0);
vbox.add_widget(chart, 1);
top.set_widget(vbox);
top.show();

// Create a Bokeh figure using the BokehJS API
let p = Bokeh.Plotting.figure({
    title: "Simple Plot",
    sizing_mode: "stretch_both",
    x_axis_label: "x",
    y_axis_label: "y",
});

p.line([1, 2, 3, 4, 5], [1, 4, 9, 16, 25],
       {legend_label: "y = x^2", line_width: 2});

Bokeh.Plotting.show(p, chart.get_content_element());

Setting sizing_mode: "stretch_both" tells Bokeh to fill and track its container element automatically.

Embedding from Python (Pyodide)

When using pgwidgets with Pyodide, you can generate Bokeh plots in Python and embed them into an ExternalWidget:

from pgwidgets_js.pyodide import Widgets
from bokeh.plotting import figure
from bokeh.embed import components

top = Widgets.TopLevel(title="Bokeh + Pyodide", resizable=True)
top.resize(700, 500)

vbox = Widgets.VBox(spacing=8, padding=10)
chart = Widgets.ExternalWidget()
vbox.add_widget(chart, 1)
top.set_widget(vbox)
top.show()

# Create the Bokeh figure in Python
p = figure(title="Sine Wave", sizing_mode="stretch_both")
import numpy as np
x = np.linspace(0, 10, 100)
p.line(x, np.sin(x), line_width=2)

# Embed into the ExternalWidget
from js import Bokeh
Bokeh.Plotting.show(p, chart.get_content_element())

Other Libraries

The same pattern works with any library that renders into a DOM element:

  • LeafletL.map(chart.get_content_element())

  • D3d3.select(chart.get_content_element()).append("svg")...

  • Chart.js – append a <canvas> to the content element and pass it to new Chart(canvas, config)

  • Three.jsrenderer.domElement can be appended to the content element

For libraries that don’t track container resize on their own, wire up the resize callback:

chart.add_callback('resize', (widget, width, height) => {
    myLibrary.resize(width, height);
});