Skip to main content

Live Updates and Drilldowns with Vizly

Dashboards are more useful when they react. Vizly supports two directions of interaction: pushing fresh data into an already-rendered chart, and using chart clicks to drive deeper views.

Live data refresh without reload

Charts embed with a stable id. To swap in new data, update the chart object with set_data() and inject the resulting script. ECharts is not reloaded; only the option is pushed.

import vizly as vz

data = [{"time": "00:00", "value": 10}, {"time": "01:00", "value": 12}]
chart = vz.line(data, x="time", y="value", title="CPU %")

# Initial embed
embed_html = chart.to_html(fragment=True)

# Later, when new measurements arrive:
chart.set_data([{"time": "02:00", "value": 15}, {"time": "03:00", "value": 18}])
update_script = chart.live_update_script(chart.id)

The update_script pushes the new option to the chart the host already rendered. Use it in HTMX swaps, WebSocket callbacks, or anywhere you inject markup.

Partial option updates

For a lighter touch, patch a portion of the option without rebuilding the chart.

chart.set_option_patch({"series": [{"itemStyle": {"color": "#FF6B35"}}]})
update_script = chart.live_update_script(chart.id)

set_option_patch deep-merges into the existing option, so you change only what you need.

Click events and drilldowns

HTML embeds emit a structured vizly:event CustomEvent (and postMessage) when a chart element is clicked. The payload carries the clicked category, value, and series.

chart = vz.bar(data, x="region", y="sales", title="Sales by region", events=True)
embed = chart.to_html(fragment=True)

In the parent page, listen for the event:

window.addEventListener("vizly:event", function (ev) {
const payload = ev.detail; // { name, value, seriesName, ... }
fetch(`/detail?region=${encodeURIComponent(payload.name)}`)
.then((r) => r.text())
.then((html) => {
document.getElementById("detail").innerHTML = html;
});
});

Filtering a sibling chart from a click

Vizly ships a helper that filters a detail table by the click payload. This turns a click into a drilldown with almost no glue code.

from vizly.events import filter_by_click

detail_table = [
{"region": "Northeast", "product": "Widget A", "sales": 42},
{"region": "Northeast", "product": "Widget B", "sales": 38},
{"region": "Southeast", "product": "Widget A", "sales": 30},
]

# Host receives the payload from a bar click on "Northeast"
filtered = filter_by_click(detail_table, payload)
child = vz.bar(filtered, x="product", y="sales", title="Northeast products")

Streamlit click return

In Streamlit, set events=True on st_vizly and the last click payload comes back to Python.

import streamlit as st
from vizly.integrations.streamlit import st_vizly
import vizly as vz

chart = vz.bar(data, x="region", y="sales", title="Sales", events=True)
event = st_vizly(chart, events=True)

if event:
st.write(f"Clicked {event.get('name')}: {event.get('value')}")

HTMX click bridge

For HTMX pages, htmx_event_listener_js() posts click payloads to a server endpoint that returns a replacement fragment.

from vizly.integrations.htmx import htmx_event_listener_js, htmx_chart_fragment

chart = vz.bar(data, x="region", y="sales", title="Sales", events=True)

listener = htmx_event_listener_js(
"/chart/detail",
target="#detail-container",
swap="innerHTML",
)

The listener script lives on the parent page once; the server endpoint returns htmx_chart_fragment(chart) for the target.

Tips

TipDetail
Keep the chart idCapture chart.id from the embed so you can target the live update script later.
set_data for tabular dataUse set_data when the whole dataset changes. Use set_option_patch for cosmetic tweaks.
Payloads are structuredClick payloads include name, value, and seriesName. filter_by_click consumes them directly.
Events on by defaultChart embeds emit vizly:event by default. Pass events=False to disable.