Cool, and thanks for the suggestion. I use .jslink() to update the JSComponent:
import panel as pn
import param
import numpy as np
from panel.custom import JSComponent
pn.extension(js_files={'echarts': 'https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js'})
# Data setup
dataxx = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
data = {
'Apple': [18, 3, 6, 4, 5, 6, 4],
'Banana': [28, 3, 6, 4, 5, 6, 4],
'Cherry': [38, 3, 6, 4, 5, 6, 4],
'Date': np.random.randn(7).cumsum().tolist(),
}
class EChartsComponent(JSComponent):
datax = param.List(default=dataxx)
selected_fruits = param.List(default=['Apple'])
# Inject fruit data as a static field (converted to JSON on Python->JS bridge)
fruit_data = param.Dict(default={k: v for k, v in data.items()})
_esm = """
export function render({ model }) {
let div = document.createElement("div");
div.style.height = "500px";
div.style.width = "100%";
function renderChart() {
if (!window.echarts) { setTimeout(renderChart, 100); return; }
if (!div._echart) {
div._echart = echarts.init(div, null, {width: 1200, height: 500});
}
// Get data from the model
const fruits = model.selected_fruits || [];
const fruitData = model.fruit_data || {};
// Build series
const series = [];
fruits.forEach(fruit => {
if (fruitData[fruit]) {
series.push({
name: fruit,
data: fruitData[fruit],
type: 'line',
smooth: true
});
}
});
const option = {
tooltip: { trigger: 'axis' },
legend: { data: fruits },
xAxis: { type: 'category', data: model.datax },
yAxis: { type: 'value' },
series: series
};
div._echart.setOption(option, true);
}
renderChart();
// React to changes
model.on('selected_fruits', renderChart);
model.on('datax', renderChart);
model.on('fruit_data', renderChart);
return div;
}
"""
# MultiChoice widget
multi = pn.widgets.MultiChoice(
name='Select Fruits',
options=list(data.keys()),
value=['Apple']
)
# Component instance
chart_component = EChartsComponent(datax=dataxx, selected_fruits=multi.value)
# Sync MultiChoice to chart
multi.jslink(chart_component, value='selected_fruits')
app = pn.Column(
"## MultiChoice ECharts Line Chart (Panel JSComponent, all-JS updates)",
pn.pane.Markdown("Select fruits from the dropdown to update the chart. All updates happen in JavaScript!"),
multi,
chart_component
)
app.show()
app.save("p_jslink.html", embed=True)
And also here is the version with ReactiveHTML:
import panel as pn
import param
from panel.reactive import ReactiveHTML
pn.extension(js_files={'echarts': 'https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js'})
import numpy as np
# Data setup
dataxx = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
data = {
'Apple': [18, 3, 6, 4, 5, 6, 4],
'Banana': [28, 3, 6, 4, 5, 6, 4],
'Cherry': [38, 3, 6, 4, 5, 6, 4],
'Date': np.random.randn(7).cumsum().tolist(),
}
# MultiChoice widget
multi = pn.widgets.MultiChoice(
name='Select Fruits',
options=list(data.keys()),
value=['Apple']
)
class CustomComponent(ReactiveHTML):
datax = param.List(default=dataxx)
selected_fruits = param.List(default=['Apple'])
_template = """<div id="chartDom" style="height: 500px; width: 100%;"></div>"""
_scripts = {
"render": """
// Initialize the chart
const config = {width: 1200, height: 500};
state.myChart = echarts.init(chartDom, null, config);
// Store fruit data in state for JavaScript access
state.fruitData = {
'Apple': [18, 3, 6, 4, 5, 6, 4],
'Banana': [28, 3, 6, 4, 5, 6, 4],
'Cherry': [38, 3, 6, 4, 5, 6, 4],
'Date': """ + str(data['Date']) + """
};
// Define the updateChart function first
state.updateChart = () => {
console.log('Updating chart with fruits:', data.selected_fruits);
const series = [];
data.selected_fruits.forEach(fruit => {
if (state.fruitData[fruit]) {
series.push({
name: fruit,
data: state.fruitData[fruit],
type: 'line',
smooth: true
});
}
});
const option = {
tooltip: { trigger: 'axis' },
legend: { data: data.selected_fruits },
xAxis: { type: 'category', data: data.datax },
yAxis: { type: 'value' },
series: series
};
if (state.myChart) {
state.myChart.setOption(option, true);
}
};
// Initial render
state.updateChart();
""",
"selected_fruits": """
// This script runs when selected_fruits parameter changes
if (state.updateChart) {
state.updateChart();
}
"""
}
# Create the chart component
chart_component = CustomComponent(datax=dataxx, selected_fruits=multi.value)
multi.jslink(chart_component, value='selected_fruits')
app = pn.Column(
"## MultiChoice ECharts Line Chart (JavaScript Callbacks)",
pn.pane.Markdown("Select fruits from the dropdown to update the chart. All updates happen in JavaScript!"),
multi,
chart_component
)
app.show()
app.save("p1_jslink.html", embed=True)