options = {
"xAxis": {"type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri"]},
"yAxis": {"type": "value"},
"series": [{"data": [120, 200, 150, 80, 70], "type": "bar"}]
}
preview_echart(EChart(options, chart_id="idx_bar"))fh-echarts
Install
pip install fh-echartsOr install latest from GitHub:
pip install git+https://github.com/markkvdb/fh-echarts.gitQuick Start
Add echarts_header() to your FastHTML app headers, then use EChart() to render charts:
from fasthtml.common import *
from fh_echarts.core import echarts_header, EChart
app, rt = fast_app(hdrs=(echarts_header(),))
@rt('/')
def get():
options = {
"xAxis": {"type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri"]},
"yAxis": {"type": "value"},
"series": [{"data": [120, 200, 150, 80, 70], "type": "bar"}]
}
return Titled("My Chart", EChart(options))
serve()Features
Basic Bar Chart
Pass an ECharts option dict to EChart(). Use preview_echart() to preview charts in notebooks.
Dark Theme
Pass theme="dark" (or "light") to use ECharts’ built-in themes.
options_dark = {
"title": {"text": "Dark Theme"},
"xAxis": {"data": ["A", "B", "C", "D"]},
"yAxis": {},
"series": [{"type": "bar", "data": [10, 25, 15, 30]}]
}
preview_echart(EChart(options_dark, chart_id="idx_dark", theme="dark"))JavaScript Functions with JSFunc
ECharts uses JavaScript functions for custom tooltips, axis labels, etc. Normally json.dumps would turn these into literal strings. Wrap them in JSFunc() and they’ll be revived as real JS functions in the browser.
options_fmt = {
"title": {"text": "Custom Tooltip"},
"tooltip": {
"formatter": JSFunc("function(p) { return '<b>' + p.name + '</b>: $' + p.value.toLocaleString(); }")
},
"xAxis": {"data": ["Shirts", "Sweaters", "Hats", "Shoes"]},
"yAxis": {"axisLabel": {
"formatter": JSFunc("function(v) { return '$' + v; }")
}},
"series": [{"type": "bar", "data": [500, 2000, 360, 1200]}]
}
preview_echart(EChart(options_fmt, chart_id="idx_fmt", theme="dark"))Line and Pie Charts
Any ECharts chart type works — just set "type" in the series.
line_opts = {
"title": {"text": "Temperature (°C)"},
"xAxis": {"type": "category", "data": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]},
"yAxis": {"type": "value", "axisLabel": {
"formatter": JSFunc("function(v) { return v + '°C'; }")
}},
"series": [{"type": "line", "data": [2, 5, 12, 18, 24, 28], "smooth": True}]
}
preview_echart(EChart(line_opts, chart_id="idx_line"))pie_opts = {
"title": {"text": "Browser Share", "left": "center"},
"tooltip": {
"formatter": JSFunc("function(p) { return p.name + ': ' + p.percent + '%'; }")
},
"series": [{
"type": "pie", "radius": "60%",
"data": [
{"value": 65, "name": "Chrome"},
{"value": 18, "name": "Safari"},
{"value": 10, "name": "Firefox"},
{"value": 7, "name": "Other"}
]
}]
}
preview_echart(EChart(pie_opts, chart_id="idx_pie"))HTMX Click Integration
Turn chart clicks into server requests with hx_get_click. By default, name, value, and seriesName are sent as query parameters.
EChart(options, hx_get_click="/bar-clicked", hx_target_click="#result")
@rt('/bar-clicked')
def get(name: str, value: int, seriesName: str):
return P(f"Clicked {name} ({seriesName}): {value}")Use hx_click_vals to select which fields to extract from the click event (useful for multi-series charts):
EChart(options, hx_get_click="/clicked",
hx_click_vals=["name", "seriesIndex", "dataIndex", "data"])For full control, pass a JS callback via hx_click_cb that receives params and returns a values dict:
EChart(options, hx_get_click="/clicked",
hx_click_cb=JSFunc("function(params) { return {x: params.data[0], y: params.data[1]}; }"))
### Dynamic Updates with `EChartUpdate`
Update an existing chart without re-creating it. Return an `EChartUpdate` from a route to merge new options into the chart.
```python
import random
@rt('/')
def get():
options = {
"xAxis": {"data": ["A", "B", "C"]},
"yAxis": {},
"series": [{"type": "bar", "data": [10, 20, 30]}]
}
return Div(
EChart(options, chart_id="updatable"),
Button("Randomize", hx_get="/randomize", hx_target="#update-slot"),
Div(id="update-slot")
)
@rt('/randomize')
def get():
new_data = [random.randint(5, 100) for _ in range(3)]
return EChartUpdate("updatable", {"series": [{"data": new_data}]})Set merge=False to replace all options instead of merging.
Run Arbitrary JS with EChartJS
Execute any JavaScript against a chart instance. The callback receives (chart, el) — the ECharts instance and the DOM element.
# Blur a chart
EChartJS("mychart", "function(chart, el) { el.style.filter = 'blur(4px)'; }")
# Stash current data on the DOM element
EChartJS("mychart", "function(chart, el) { el._loadData = chart.getOption().series[0].data; }")
# Append data to a streaming time series
EChartJS("mychart", """function(chart, el) {
var opt = chart.getOption();
opt.xAxis[0].data.push('6s');
opt.series[0].data.push(42);
chart.setOption(opt);
}""")OOB Updates with EChartOOB
Wrap chart scripts in an HTMX out-of-band swap container. Useful when returning chart updates alongside other HTML from a route.
@rt('/update')
def get():
return Div(
P("Data updated!"),
EChartOOB(
EChartUpdate("mychart", {"series": [{"data": new_data}]}),
EChartJS("mychart", "function(chart, el) { el.style.filter = ''; }")
)
)Memory Cleanup
When HTMX removes a chart from the DOM (e.g. navigating tabs or swapping content), the ECharts instance and ResizeObserver are automatically disposed via the htmx:beforeCleanupElement event. No extra code needed.
Pandas Time Series with ts_options
Use ts_options from fh_echarts.helpers to turn a Pandas DataFrame into a time-series chart with minimal code. It auto-detects datetime columns, numeric series, and handles NaNs. The result is a plain dict you can customise before passing to EChart.
from fh_echarts.helpers import ts_options
import pandas as pd, numpy as np
dates = pd.date_range("2024-01-01", periods=30, freq="D")
df = pd.DataFrame({"date": dates,
"temperature": 20 + 5 * np.random.randn(30).cumsum() * 0.1,
"humidity": 60 + 3 * np.random.randn(30).cumsum() * 0.1})
opts = ts_options(df, x="date")
opts["title"] = {"text": "Weather Station"}
opts["legend"] = {"show": True}
EChart(opts)Works with DataFrames, DatetimeIndex, individual Series, and you can select specific y columns:
ts_options(df, x="date", y="temperature")
ts_options(df.set_index("date"))
ts_options(df.set_index("date")["temperature"])API Reference
| Function | Description |
|---|---|
echarts_header(version) |
CDN script tag for ECharts (default v5.5.0) |
EChart(options, ...) |
Render a chart with optional theme, hx_get_click, hx_target_click, hx_click_vals, hx_click_cb |
EChartUpdate(chart_id, options, merge) |
Update an existing chart instance |
EChartJS(chart_id, js_func) |
Run arbitrary JS against a chart instance; callback receives (chart, el) |
EChartOOB(*scripts, sink_id) |
Wrap scripts in an OOB-swappable Div for HTMX responses |
JSFunc(js_string) |
Mark a string as raw JavaScript (for formatters, callbacks, etc.) |
preview_echart(echart, height) |
Preview a chart in a notebook via iframe |
ts_options(data, x, y, kind) |
Convert a Pandas DataFrame/Series into an ECharts time-series options dict |