JavaScript Function Support
ECharts relies heavily on JavaScript functions for custom tooltips, axis labels, etc. Since json.dumps turns everything into strings, we need a way to mark certain strings as raw JavaScript. The JSFunc wrapper and EChartsEncoder handle this.
JSFunc
def JSFunc(
args:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):
Marker class to flag a string as a raw JavaScript function for ECharts options.
EChartsEncoder
def EChartsEncoder(
skipkeys:bool = False , ensure_ascii:bool = True , check_circular:bool = True , allow_nan:bool = True , sort_keys:bool = False ,
indent:NoneType= None , separators:NoneType= None , default:NoneType= None
):
Custom JSON encoder that flags JSFunc values with a !JS! prefix so the frontend can revive them.
For example, you can use JSFunc to pass a custom tooltip formatter:
opts = {"tooltip" : {"formatter" : JSFunc("function(p) { return p.name + ': $' + p.value; }" )}}
encoded = json.dumps(opts, cls= EChartsEncoder)
assert '!JS!' in encoded
print (encoded)
{"tooltip": {"formatter": "!JS!function(p) { return p.name + ': $' + p.value; }"}}
EChart Component
EChart
def EChart(
options:dict , chart_id:str = None , width:str = '100%' , height:str = '400px' , theme:str = None , hx_get_click:str = None ,
hx_target_click:str = None , hx_click_vals:list = None , hx_click_cb:str = None
):
Render an EChart with support for themes, JS formatters, HTMX click events, and memory cleanup.
preview_echart
def preview_echart(
echart, height:str = '450px'
):
A basic bar chart:
options = {
"xAxis" : {"type" : "category" , "data" : ["Mon" , "Tue" , "Wed" , "Thu" , "Fri" ]},
"yAxis" : {"type" : "value" },
"series" : [{"data" : [120 , 200 , 150 , 80 , 70 ], "type" : "bar" }]
}
chart = EChart(options, chart_id= "demo_bar" )
html = to_xml(chart)
assert 'id="demo_bar"' in html
assert 'echarts.init' in html
preview_echart(chart)
/usr/local/lib/python3.12/site-packages/IPython/core/display.py:447: UserWarning: Consider using IPython.display.IFrame instead
warnings.warn("Consider using IPython.display.IFrame instead")
With dark theme and a JS tooltip formatter:
options_dark = {
"title" : {"text" : "Sales" },
"tooltip" : {
"formatter" : JSFunc("function(p) { return '<b>' + p.name + '</b>: $' + p.value; }" )
},
"xAxis" : {"data" : ["Shirts" , "Sweaters" , "Hats" ]},
"yAxis" : {},
"series" : [{"type" : "bar" , "data" : [5 , 20 , 36 ]}]
}
chart_dark = EChart(options_dark, chart_id= "demo_dark" , theme= "dark" )
html_dark = to_xml(chart_dark)
assert "'dark'" in html_dark
assert '!JS!' in html_dark
print ('Theme and JSFunc working ✓' )
preview_echart(chart_dark)
Theme and JSFunc working ✓
With HTMX click integration:
chart_htmx = EChart(options, chart_id= "demo_htmx" ,
hx_get_click= "/bar-clicked" , hx_target_click= "#result" )
html_htmx = to_xml(chart_htmx)
assert '/bar-clicked' in html_htmx
assert "#result" in html_htmx
print ('HTMX click integration working ✓' )
HTMX click integration working ✓
With custom click fields (e.g. for multiple time series):
chart_fields = EChart(options, chart_id= "demo_fields" ,
hx_get_click= "/clicked" ,
hx_click_vals= ["name" , "seriesIndex" , "dataIndex" , "data" ])
html_fields = to_xml(chart_fields)
assert 'seriesIndex' in html_fields
assert 'dataIndex' in html_fields
print ('Custom click fields working ✓' )
Custom click fields working ✓
With a custom JS click callback for full control:
chart_cb = EChart(options, chart_id= "demo_cb" ,
hx_get_click= "/clicked" ,
hx_click_cb= JSFunc("function(params) { return {x: params.data[0], y: params.data[1], series: params.seriesName}; }" ))
html_cb = to_xml(chart_cb)
assert 'params.data[0]' in html_cb
assert 'params.seriesName' in html_cb
print ('Custom click callback working ✓' )
Custom click callback working ✓
EChartUpdate
EChartUpdate
def EChartUpdate(
chart_id:str , options:dict , merge:bool = True
):
Update an existing chart instance. Set merge=False to replace all options instead of merging.
update = EChartUpdate("demo_bar" , {"series" : [{"data" : [300 , 400 , 500 , 600 , 700 ]}]})
html_update = to_xml(update)
assert 'getInstanceByDom' in html_update
assert '300' in html_update
EChartJS
EChartJS
def EChartJS(
chart_id:str , js_func:str
):
Run a JS function against an existing chart instance. js_func receives (chart, el) as arguments.
# Stash data on the DOM element
js_stash = EChartJS("demo_bar" , "function(chart, el) { el._loadData = chart.getOption().series[0].data; }" )
html_stash = to_xml(js_stash)
assert '_loadData' in html_stash
assert 'getInstanceByDom' in html_stash
# Apply CSS filter
js_blur = EChartJS("demo_bar" , "function(chart, el) { el.style.filter = 'blur(4px)'; }" )
html_blur = to_xml(js_blur)
assert 'blur' in html_blur
print ('EChartJS working ✓' )
EChartOOB
EChartOOB
def EChartOOB(
scripts:VAR_POSITIONAL, sink_id:str = 'script-sink'
):
Wrap one or more EChart Script elements in an OOB-swappable Div for HTMX responses.
oob = EChartOOB(
EChartUpdate("demo_bar" , {"series" : [{"data" : [100 ]}]}),
EChartJS("demo_bar" , "function(chart, el) { el.style.filter = ''; }" )
)
html_oob = to_xml(oob)
assert 'hx-swap-oob="true"' in html_oob
assert 'script-sink' in html_oob
assert html_oob.count('<script>' ) == 2
print ('EChartOOB working ✓' )