# fh-echarts


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Install

``` sh
pip install fh-echarts
```

Or install latest from GitHub:

``` sh
pip install git+https://github.com/markkvdb/fh-echarts.git
```

## Quick Start

Add `echarts_header()` to your FastHTML app headers, then use `EChart()`
to render charts:

``` python
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.

``` python
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"))
```

### Dark Theme

Pass `theme="dark"` (or `"light"`) to use ECharts’ built-in themes.

``` python
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`](https://docs.python.org/3/library/json.html#json.dumps)
would turn these into literal strings. Wrap them in `JSFunc()` and
they’ll be revived as real JS functions in the browser.

``` python
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.

``` python
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"))
```

``` python
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.

``` python
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):

``` python
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:

``` python
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.

``` python
# 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.

``` python
@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`.

``` python
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:

``` python
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 |
