<smart-chart>

SmartChart (Preview)

A fully-featured, declarative charting Web Component built on Chart.js and ApexCharts. Fetch data from an API, a smart-data state key, or inline — then add date-range filters, a type switcher, goal lines, threshold annotations, drag-to-zoom, fullscreen, CSV/PNG/JSON export, and live WebSocket streaming — all through HTML attributes. Zero JavaScript required in your templates.

Chart.js + ApexCharts 6 Palettes · 10+ Types Date Range Filter Drag-to-Zoom WebSocket Live Export PNG·CSV·JSON
Data flow
api= / source= / data= / websocket=
date filter
data mapping
Chart.js / ApexCharts
render
<script src="smart-chart.js"></script> <!-- Load AFTER smart-state.js and smart-data.js if using source= -->

Load Order

SmartChart loads Chart.js and the annotation plugin automatically if they aren't already on the page. When using websocket=, it also loads ApexCharts on first message. You never need to include these CDN URLs yourself — but loading them earlier speeds up the first render.

<!-- Optional: pre-load deps for faster first render -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<!-- Only needed for WebSocket live charts -->
<script src="https://cdn.jsdelivr.net/npm/apexcharts@3/dist/apexcharts.min.js"></script>

<!-- Smart state layer (needed only for source= / state-listen=) -->
<script src="smart-state.js"></script>
<script src="smart-data.js"></script>

<!-- SmartChart — NOT type="module", load as regular script -->
<script src="smart-chart.js"></script>

Data Source: API / Fetch

Set api= to a URL. SmartChart fetches with Accept: application/json and auto-maps the response. Add refresh="30s" for polling. When ranges= is also set, SmartChart appends ?range=7d to the URL so your backend can handle server-side filtering too.

<!-- Simplest possible — SmartChart auto-detects x/y fields -->
<smart-chart
  api="/api/revenue/"
  x-field="date"
  y-field="amount"
  default-type="area"
  title="Revenue"
></smart-chart>

<!-- With auto-refresh every 30 seconds -->
<smart-chart
  api="/api/revenue/"
  x-field="date"
  y-field="amount"
  refresh="30s"
  toolbar="refresh,fullscreen"
  title="Revenue"
></smart-chart>

<!-- Nested response — use response-path for { "data": { "items": [...] } } -->
<smart-chart
  api="/api/sales/"
  response-path="data.items"
  x-field="date"
  y-field="sales"
></smart-chart>

# Django view — return flat JSON array directly
def revenue_api(request):
    data = list(Sale.objects.values('date', 'amount'))
    return JsonResponse(data, safe=False)

Data Source: SmartData (source=)

The most powerful pattern. Declare a <smart-data> element that fetches and caches data into a named key in smartState. Multiple charts and a <smart-table> can all read from the same key — one fetch, many consumers. Add state-listen= to automatically re-render when a filter state key changes.

<!-- 1. Declare the data layer — fetches once, refreshes every 5s -->
<smart-data
  key="salesData"
  api="/dummy-chart-data/"
  refresh="5s"
  cache="5s"
></smart-data>

<!-- 2. A filter bar that writes to state key "status" -->
<smart-filter-bar target="salesTable" auto-apply state-key="salesFilters">
  <smart-input name="status" type="select"
    data-options='[{"id":"","name":"All"},{"id":"active","name":"Active"}]'
    state-set="status">
  </smart-input>
</smart-filter-bar>

<!-- 3. Chart reads from same key, re-renders when "status" state changes -->
<smart-chart
  source="salesData"
  state-listen="status"
  x-field="date"
  y-field="sales"
  default-type="area"
  title="Revenue Over Time"
></smart-chart>

<!-- 4. Table reads from same key — one fetch serves both -->
<smart-table source="salesData" id="salesTable"></smart-table>

Data Source: Inline

Pass data directly as a JSON string via data=. Use labels= for custom x-axis labels on flat value arrays. Good for static charts rendered from Django context.

<!-- Flat array of values -->
<smart-chart
  data="[120, 340, 210, 480, 390, 560]"
  labels='["Jan","Feb","Mar","Apr","May","Jun"]'
  default-type="bar"
  title="Monthly Sales"
  palette="ocean"
></smart-chart>

<!-- Array of objects — same shape as API response -->
<smart-chart
  data='[{"month":"Jan","revenue":1200},{"month":"Feb","revenue":1800}]'
  x-field="month"
  y-field="revenue"
  default-type="line"
></smart-chart>

# Django — render with template variable
<smart-chart
  data=""
  x-field="date"
  y-field="value"
></smart-chart>

Data Source: WebSocket Live

Add websocket= to stream live data. SmartChart loads ApexCharts for the live view — it provides native right-to-left scrolling, smooth entry animations, and a live stats bar showing the latest value, trend delta, and velocity per tick. The WebSocket auto-reconnects with exponential backoff and auto-pauses rendering when the browser tab is hidden.

Live Reconnecting… ← ws-show-status renders these in the subtitle
<!-- append mode: server pushes one point at a time -->
<smart-chart
  websocket="ws://127.0.0.1:8000/ws/chart/random/"
  ws-mode="append"
  ws-max-points="60"
  ws-show-status
  default-type="area"
  type-switcher="area,line,bar"
  palette="vivid"
  title="Live Sales"
  y-field="sales"
></smart-chart>

<!-- replace mode: server pushes full dataset each time -->
<smart-chart
  websocket="ws://127.0.0.1:8000/ws/leaderboard/"
  ws-mode="replace"
  default-type="bar"
  x-field="name"
  y-field="score"
></smart-chart>

# Django Channels consumer — append mode message format
async def websocket_connect(self, event):
    await self.accept()
    while True:
        await self.send(json.dumps({
            "label": datetime.now().strftime("%H:%M:%S"),
            "values": { "sales": random.randint(200, 900) }
        }))
        await asyncio.sleep(1)

# Or use flat format — any fields detected automatically
# { "date": "14:32:07", "sales": 450, "returns": 30 }
Live stats bar: Each series gets a chip showing the latest value, a ▲ +4.2% / ▼ −1.8% trend delta vs the previous point, and a velocity reading (Δ per tick). Rendering pauses automatically when the tab is hidden and resumes on focus — saving CPU.

Chart Types

Set default-type= for the initial type. All Chart.js types are supported.

📊
bar
📈
line
🔷
area
gradient fill
🥧
pie
doughnut
🕸
radar
🔵
scatter
🫧
bubble
step
staircase
📉
horizontalBar
🎯
polarArea

Live Type Switcher

Add type-switcher= to render icon buttons in the chart header that let users switch between chart types in real time. The chart re-renders with the current data — no page reload, no re-fetch.

<smart-chart
  api="/api/revenue/"
  x-field="date"
  y-field="amount"
  default-type="area"
  type-switcher="bar,line,area"
  title="Revenue"
></smart-chart>

// type-switcher renders an icon button per listed type in the header.
// default-type is the initial chart type — it doesn't need to be in the switcher list.

Date Range Filtering

Add ranges= to render range buttons (7d, 30d, 90d, YTD, 1y, all, etc.). SmartChart filters in memory — anchored to the newest data point, not today's date — so your API doesn't need to support filtering. When api= is used, it also appends ?range=7d for server-side filtering if you want it.

<smart-chart
  api="/api/sales/"
  x-field="date"
  y-field="sales"
  ranges="7d,30d,90d,ytd,1y,all"
  default-type="area"
  title="Sales"
></smart-chart>

// Supported range tokens:
// Nd  = last N days     (7d, 30d, 90d)
// Nw  = last N weeks    (2w, 4w)
// Nm  = last N months   (3m, 6m)
// Ny  = last N years    (1y, 2y)
// ytd = year to date
// all = show all data (default when no range selected)

Goal Lines & Threshold Annotations

goal-line= renders a thick solid green line — the primary target. thresholds= accepts a JSON object of value: color pairs and renders thin dashed warning lines. Both appear in both the inline and fullscreen views.

<smart-chart
  api="/api/sales/"
  x-field="date"
  y-field="sales"
  goal-line="500"
  goal-label="Target (500)"
  thresholds='{"700":"orange","900":"red"}'
  default-type="area"
  title="Sales vs Target"
></smart-chart>

// goal-line — thick 3px solid green line at y=500
//             with "Target (500)" label on the left end
//
// thresholds — { "700": "orange" } → thin dashed orange line at y=700
//              { "900": "red" }    → thin dashed red line at y=900
//              Also accepts hex: { "700": "#f97316" }

Drag-to-Zoom

Drag horizontally on any line, bar, or area chart to zoom into that x-axis range. A blue selection rectangle appears during the drag. Double-click or press ⊖ Reset Zoom to restore the full view. Zoom is active by default — add no-zoom to disable it. Zoom is automatically disabled on pie, doughnut, radar, and WebSocket live charts.

<!-- Zoom is ON by default for bar/line/area/step charts -->
<smart-chart
  api="/api/sales/"
  x-field="date" y-field="sales"
  toolbar="fullscreen"
></smart-chart>
// → Drag on the plot area to zoom in
// → Double-click or click "⊖ Reset zoom" to restore

<!-- Disable zoom -->
<smart-chart api="/api/sales/" no-zoom></smart-chart>

// In fullscreen mode: a "Drag to zoom · Double-click to reset" hint toast
// fades in at the bottom of the chart, then disappears after 3s.

Fullscreen Mode

Add fullscreen to toolbar=. Clicking ⛶ opens a full-viewport overlay with the chart re-rendered at maximum size. Range buttons and the type switcher are duplicated in the fullscreen header. Drag-to-zoom works in fullscreen too. Press Escape or click the backdrop to close.

<smart-chart
  api="/api/revenue/"
  x-field="date" y-field="amount"
  toolbar="refresh,fullscreen"
  ranges="7d,30d,1y,all"
  type-switcher="bar,line,area"
  title="Revenue"
></smart-chart>

// toolbar accepts: refresh, fullscreen (comma-separated)
// export is separate: export="png,csv,json"

Export

Add export= to render export buttons in the header. PNG exports the rendered canvas. CSV exports labels + dataset values as a spreadsheet. JSON exports the original raw response data — exactly what came from the API.

<smart-chart
  api="/api/sales/"
  x-field="date" y-field="sales"
  toolbar="refresh,fullscreen"
  export="png,csv,json"
  title="Sales Data"
></smart-chart>

// PNG  → downloads chart as image (uses canvas.toBase64Image())
// CSV  → Label, Dataset1, Dataset2 … rows for all visible data points
// JSON → raw _rawData as downloaded from the API (current range/filter applied)

Palettes

Set palette= to control dataset colors. Palettes cycle when you have more datasets than colors.

material
nord
monochrome
pastel
ocean
vivid
palette="material"    // Google Material colors (default)
palette="nord"        // Nordic muted tones
palette="monochrome"  // Single-hue indigo shades
palette="pastel"      // Soft pastels — good on dark backgrounds
palette="ocean"       // Deep blue tones
palette="vivid"       // High-contrast neons

Multi-Series Charts

Use datasets= to plot multiple series from the same data. Each entry in the array specifies a field, optional label, color, and per-series type (useful for mixed bar + line charts).

<!-- Two series from the same API response -->
<smart-chart
  api="/api/sales/"
  x-field="date"
  datasets='[
    {"field":"sales",   "label":"Sales",   "type":"bar"},
    {"field":"returns", "label":"Returns", "type":"line","color":"#f87171"}
  ]'
  palette="material"
  title="Sales vs Returns"
></smart-chart>

// datasets fields:
//   field  — key in your data row (required)
//   label  — legend/tooltip label (defaults to field name)
//   color  — CSS color or hex (defaults to palette color)
//   type   — per-series chart type (bar|line|area) for mixed charts

Sync Groups

Assign multiple charts to the same sync-group= name. Hovering over a data point on one chart highlights the same index on all other charts in the group simultaneously. Useful for comparison dashboards.

<smart-chart
  api="/api/sales/"
  x-field="date" y-field="sales"
  sync-group="overview"
  title="Sales"
></smart-chart>

<smart-chart
  api="/api/costs/"
  x-field="date" y-field="cost"
  sync-group="overview"
  title="Costs"
></smart-chart>

// Hovering over "Mar 15" on the Sales chart highlights
// "Mar 15" on the Costs chart simultaneously.

<!-- click-state writes clicked point to smartState -->
<smart-chart
  api="/api/sales/"
  click-state="selectedPoint"
></smart-chart>

// After click: smartState.get("selectedPoint")
// → { label: "Mar 15", value: 480, datasetIndex: 0, index: 14 }

Sales Dashboard — Full-Featured

A complete chart combining date ranges, type switcher, goal line, threshold warnings, export, fullscreen, and auto-refresh — the pattern from helper.html.

<smart-data
  key="salesData"
  api="/dummy-chart-data/"
  refresh="5s"
  cache="5s"
></smart-data>

<smart-chart
  default-type="area"
  type-switcher="bar,line,area"
  source="salesData"
  state-listen="status"
  x-field="date"
  y-field="sales"
  palette="pastel"
  goal-line="500"
  goal-label="Target (500)"
  thresholds='{"700":"orange","900":"red"}'
  ranges="7d,30d,90d,ytd,1y,all"
  toolbar="refresh,fullscreen"
  export="png,csv,json"
  title="Revenue Over Time (~Refreshes every 5s)"
></smart-chart>

Live WebSocket Dashboard

Pair a WebSocket chart with a second API-backed chart on the same page. They operate completely independently — the WebSocket chart uses ApexCharts, the static chart uses Chart.js.

<!-- Live streaming chart -->
<smart-chart
  websocket="ws://127.0.0.1:8000/ws/chart/random/"
  ws-mode="append"
  ws-max-points="15"
  ws-show-status
  palette="pastel"
  default-type="area"
  type-switcher="area,line,bar"
  title="Live Sales"
  subtitle="connecting..."
></smart-chart>

<!-- Static chart below uses source= from smart-data -->
<smart-chart
  source="salesData"
  x-field="date"
  y-field="sales"
  default-type="bar"
  title="Historical"
></smart-chart>

# Django Channels routing — routing.py
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from myapp.consumers import ChartConsumer

application = ProtocolTypeRouter({
    "websocket": URLRouter([
        re_path(r"ws/chart/random/", ChartConsumer.as_asgi()),
    ]),
})

SmartData + Filter Bar + Table

The full pattern from helper.html — one smart-data feeds a chart and a table. The filter bar writes to state and both consumers re-render reactively.


<smart-data
  key="salesData"
  api="/dummy-chart-data/"
  refresh="5s"
  cache="5s"
></smart-data>

<smart-filter-bar
  target="salesTable"
  auto-apply
  state-key="salesFilters"
>
  <smart-input
    name="status"
    label="Status"
    type="select"
    data-options='[{"id":"","name":"All"},{"id":"active","name":"Active"}]'
    state-set="status"
  ></smart-input>
</smart-filter-bar>

<smart-chart
  source="salesData"
  state-listen="status"
  x-field="date"
  y-field="sales"
  default-type="area"
  type-switcher="bar,line,area"
  ranges="7d,30d,90d,all"
  palette="pastel"
  goal-line="500"
  goal-label="Target"
  toolbar="refresh,fullscreen"
  export="png,csv,json"
  title="Revenue Over Time"
></smart-chart>

<smart-table
  source="salesData"
  id="salesTable"
></smart-table>

All Attributes

AttributeTypeDescriptionDefault
apistringURL to fetch chart data from. SmartChart adds ?range= when a range is selected.
sourcestringKey in smartState (set by <smart-data>). Chart re-renders when this key updates.
dataJSONInline data as a JSON array of numbers or objects.
labelsJSONX-axis labels for flat data= arrays.
websocketstringWebSocket URL for live streaming data (uses ApexCharts). Auto-reconnects with exponential backoff.
ws-modestringappend — server pushes one point at a time. replace — server pushes full dataset.append
ws-max-pointsnumberMax data points to keep in the rolling window before oldest are dropped.200
ws-show-statusbooleanShow ⬤ Live / ↺ Reconnecting in the subtitle area.false
x-fieldstringKey in each data row to use as the x-axis label. Required for date range filtering.auto
y-fieldstringKey in each data row to use as the single y-axis dataset.auto
datasetsJSONArray of {field, label?, color?, type?} for multi-series or mixed charts.
response-pathstringDot-path to extract nested data from API response, e.g. data.items.
default-typestringInitial chart type: bar · line · area · pie · doughnut · radar · scatter · bubble · step · horizontalBar · polarArea.bar
type-switcherstringComma-separated list of chart types to render as icon switcher buttons.
palettestringmaterial · nord · monochrome · pastel · ocean · vivid.material
titlestringChart card heading.
subtitlestringSmaller text below the title.
heightnumberCanvas height in pixels.320
tensionnumberLine/area curve tension 0–1.0.4
point-radiusnumberData point dot radius.3
goal-linenumberY value for the goal annotation (thick solid green line).
goal-labelstringLabel for the goal line pill.Goal: {value}
thresholdsJSONObject of {"value": "color"} pairs for threshold warning lines.
rangesstringComma-separated range tokens. Renders filter buttons. Requires x-field= to be a date.
toolbarstringComma-separated toolbar items: refresh · fullscreen.
exportstringComma-separated export formats: png · csv · json.
refreshstringAuto-poll interval for api= mode. Format: 30s · 5m · 1h.
state-listenstringRe-render the chart when this smartState key changes.
click-statestringWrite clicked data point to this smartState key.
sync-groupstringGroup name for synchronized tooltip highlighting across multiple charts.
no-zoombooleanDisable drag-to-zoom. Zoom is automatically disabled for pie/doughnut/radar and WebSocket charts.false

Events

EventdetailFires when
smart-chart-loaded{ key }Data has been fetched and the chart has finished rendering.
smart-chart-click{ label, value, datasetIndex, index }User clicks a data point on the chart.
smart-chart-ws-status{ state }WebSocket connection state changes. state is "live" · "reconnecting" · "closed".
const chart = document.querySelector('smart-chart');

chart.addEventListener('smart-chart-click', (e) => {
  console.log('Clicked:', e.detail.label, e.detail.value);
});

chart.addEventListener('smart-chart-ws-status', (e) => {
  // e.detail.state is "live" | "reconnecting" | "closed"
  updateStatusBadge(e.detail.state);
});

Data Formats

SmartChart auto-detects the format. It handles four shapes: flat arrays, object arrays with explicit x-field/y-field, object arrays with auto-detected numeric fields, and pre-formatted Chart.js objects.

// A: Flat array (use labels= for x-axis)
[120, 340, 210, 480]

// B: Object array — explicit x/y (most common Django API response)
[
  { "date": "2026-01-01", "sales": 480 },
  { "date": "2026-01-02", "sales": 390 }
]

// C: Object array — multi-column (auto-detected)
[
  { "date": "2026-01-01", "sales": 480, "returns": 30 }
]
// → SmartChart plots all numeric columns as separate series

// D: Pre-formatted Chart.js object (passed through directly)
{ "labels": ["Jan","Feb"], "datasets": [...] }

// WebSocket append message format:
{ "label": "14:32:07", "values": { "sales": 450 } }
// or flat (SmartChart uses x-field= for the label key):
{ "date": "14:32:07", "sales": 450 }