<div data-wf-chart="area"
data-wf-height="280"
data-wf-label="Monthly Revenue"
data-wf-legend
data-wf-format="currency">
<script type="application/json">
{
"labels": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
"datasets": [{ "label": "Revenue", "data": [18400, 21200, 19800, 24600, 26100, 28400, 31200, 29800, 33400, 35100, 38600, 42000] }]
}
</script>
</div>
Charts
Zero-dependency SVG chart suite. Reads CSS custom properties at render time so dark-mode and theme switching are automatic. Declare data inline with a <script type="application/json"> child, fetch it remotely via data-wf-src, or push updates programmatically via el._wfChart.update(data).
Area
Use data-wf-chart="area" for a single-dataset filled area chart. The fill uses a gradient anchored to the series color and adapts automatically in dark mode. Add data-wf-legend to render a series legend below the chart.
Multi-series
Use data-wf-chart="area-multi" to overlay multiple datasets. Each series is rendered as a separate line with its own fill gradient.
<div data-wf-chart="area-multi"
data-wf-height="280"
data-wf-label="Revenue vs Expenses"
data-wf-legend
data-wf-format="currency">
<script type="application/json">
{
"labels": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
"datasets": [
{ "label": "Revenue", "data": [18400, 21200, 19800, 24600, 26100, 28400, 31200, 29800, 33400, 35100, 38600, 42000] },
{ "label": "Expenses", "data": [14200, 15800, 16100, 17400, 18200, 19600, 20800, 21200, 22400, 23100, 24600, 26200] }
]
}
</script>
</div>
Line
Use data-wf-chart="line" for a line chart without the area fill. Add data-wf-curve="straight" to use straight segments instead of the default smooth Catmull-Rom interpolation.
Smooth
<div data-wf-chart="line"
data-wf-height="240"
data-wf-label="Active Users">
<script type="application/json">
{
"labels": ["Jan","Feb","Mar", …],
"datasets": [{ "label": "Users", "data": [4200, 4800, …] }]
}
</script>
</div>
Straight + no grid
<div data-wf-chart="line"
data-wf-height="240"
data-wf-curve="straight"
data-wf-grid="false">
<!-- same JSON -->
</div>
<!-- curve: "smooth" (default) | "straight" -->
<!-- data-wf-grid="false" hides grid lines -->
Multi-series
Use data-wf-chart="line-multi" to draw multiple lines on the same chart. The crosshair and tooltip track all series simultaneously.
<div data-wf-chart="line-multi"
data-wf-height="280"
data-wf-label="Traffic by Channel"
data-wf-legend>
<script type="application/json">
{
"labels": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
"datasets": [
{ "label": "Organic", "data": [3200, 3600, 4100, 4800, 5200, 5800, 6400, 6100, 6800, 7200, 7800, 8400] },
{ "label": "Paid", "data": [1800, 2100, 2400, 2800, 3100, 3400, 3800, 3600, 4000, 4300, 4700, 5100] },
{ "label": "Referral","data": [800, 900, 1100, 1200, 1400, 1600, 1700, 1800, 2000, 2100, 2300, 2600] }
]
}
</script>
</div>
Bar
Use data-wf-chart="bar" for a single-series vertical bar chart. Bars animate up from the baseline on first render.
<div data-wf-chart="bar"
data-wf-height="280"
data-wf-label="Monthly Orders">
<script type="application/json">
{
"labels": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
"datasets": [{ "label": "Orders", "data": [340, 410, 380, 490, 520, 560, 620, 590, 670, 710, 780, 850] }]
}
</script>
</div>
Grouped (multi-series)
Use data-wf-chart="bar-multi" to render grouped bars — one bar per series per category, side by side.
<div data-wf-chart="bar-multi"
data-wf-height="280"
data-wf-label="Quarterly Revenue"
data-wf-legend
data-wf-format="currency">
<script type="application/json">
{
"labels": ["Q1","Q2","Q3","Q4"],
"datasets": [
{ "label": "2024", "data": [59400, 79100, 94400, 125800] },
{ "label": "2023", "data": [48200, 62400, 78100, 101200] }
]
}
</script>
</div>
Horizontal Bar
Use data-wf-chart="bar-horizontal" for a ranked list layout. Category labels are shown on the left; bars grow from left to right. Only the first dataset is rendered.
<div data-wf-chart="bar-horizontal"
data-wf-height="240"
data-wf-label="Top Products by Revenue"
data-wf-format="currency">
<script type="application/json">
{
"labels": ["Analytics Pro","CRM Suite","Email Tools","File Storage","Video Conf"],
"datasets": [{ "label": "Revenue", "data": [48200, 36700, 29400, 24100, 18600] }]
}
</script>
</div>
Stacked
Add data-wf-stacked to an area-multi chart to stack series on top of each other. The Y-axis maximum reflects the column-wise sum. Useful for visualising composition over time.
<div data-wf-chart="area-multi"
data-wf-height="280"
data-wf-label="Traffic by Channel"
data-wf-legend
data-wf-stacked>
<!-- same JSON as area-multi -->
</div>
<!-- data-wf-stacked requires data-wf-chart="area-multi" -->
Doughnut
Use data-wf-chart="doughnut" for a doughnut chart. The center displays the total value. Control the hole size with data-wf-inner-radius (0–1; default 0.65). Only the first dataset is rendered.
Default (0.65)
<div data-wf-chart="doughnut"
data-wf-height="280"
data-wf-label="Revenue by Segment"
data-wf-legend>
<script type="application/json">
{
"labels": ["Enterprise","Mid-Market","SMB","Self-Serve"],
"datasets": [{ "label": "Revenue", "data": [42, 28, 18, 12] }]
}
</script>
</div>
Thin ring (0.82)
<div data-wf-chart="doughnut"
data-wf-inner-radius="0.82"
…>
</div>
<!-- inner-radius: 0–1 (default 0.65)
0 = no hole (same as pie)
0.5 = medium ring
0.8 = thin ring -->
Pie
Use data-wf-chart="pie" for a filled pie chart (no center hole). Hover any slice to see its label and percentage in the tooltip.
<div data-wf-chart="pie"
data-wf-height="280"
data-wf-label="Traffic Sources"
data-wf-legend>
<script type="application/json">
{
"labels": ["Organic","Direct","Paid","Social","Referral"],
"datasets": [{ "label": "Sessions", "data": [38, 24, 18, 12, 8] }]
}
</script>
</div>
Bubble
Use data-wf-chart="bubble" for a three-dimensional scatter plot. Each data point is an object with x, y, and r (radius) values. Multiple datasets can be used to group points by category with different colors.
<div data-wf-chart="bubble"
data-wf-height="300"
data-wf-label="Adoption vs Value"
data-wf-legend>
<script type="application/json">
{
"labels": [],
"datasets": [
{ "label": "Analytics", "data": [{ "x": 72, "y": 85, "r": 18 }] },
{ "label": "CRM", "data": [{ "x": 48, "y": 62, "r": 12 }] },
{ "label": "Email", "data": [{ "x": 91, "y": 78, "r": 22 }] },
{ "label": "Storage", "data": [{ "x": 35, "y": 44, "r": 8 }] },
{ "label": "Video", "data": [{ "x": 58, "y": 91, "r": 15 }] }
]
}
</script>
</div>
<!-- Each point: { "x": number, "y": number, "r": radius }
r is scaled relative to the largest r in the dataset -->
Sparkline
Use data-wf-chart="sparkline" for a compact inline trend line. Supports the shorthand { "data": [...] } format — no labels or dataset wrapper needed. Pair with wf-stat cards to add visual trend context to a metric.
Revenue
$42,000
↗ 14% vs last month
Active Users
9,600
↗ 8% vs last month
Orders
850
↗ 9% vs last month
Bounce Rate
38.4%
↘ 4% vs last month
<div class="wf-stat">
<p class="wf-stat-title">Revenue</p>
<p class="wf-stat-value">$42,000</p>
<!-- Sparkline shorthand: just a flat data array -->
<div data-wf-chart="sparkline" data-wf-height="44" style="margin:.5rem 0">
<script type="application/json">{"data": [18400, 21200, 19800, 24600, 26100, 42000]}</script>
</div>
<p class="wf-stat-desc">
<span class="wf-stat-trend wf-stat-trend-up">↗ 14%</span> vs last month
</p>
</div>
Synced Group
Give multiple charts the same data-wf-synced-group value to share a crosshair — hovering one chart moves the crosshair on all peers in the group. Charts must share the same number of x-axis points for the positions to align.
<!-- Both charts share the same synced-group value -->
<div data-wf-chart="area"
data-wf-synced-group="overview"
…>
</div>
<div data-wf-chart="line"
data-wf-synced-group="overview"
…>
</div>
<!-- Charts must have the same number of x-axis points -->
Live AJAX
Add data-wf-chart-target="#id" to any data-wf-ajax-action trigger. When the server response includes a chartData key the framework calls el._wfChart.update(chartData) automatically — no JS required on the page. A <select> fires on change; a button fires on click.
<!-- Period selector — fires on change, posts action=<value> -->
<select data-wf-ajax-action="getStats"
data-wf-url="/api/api-ajax-action.php"
data-wf-value-key="period"
data-wf-params='{"action":"chart"}' JSON object — query params appended to data-wf-src URL.
data-wf-chart-target="#stats-chart">
<option value="7d">Last 7 days</option>
<option value="30d" selected>Last 30 days</option>
<option value="90d">Last 90 days</option>
</select>
<!-- Chart — inline initial data; AJAX replaces on period change -->
<div id="stats-chart"
data-wf-chart="bar-multi"
data-wf-height="280"
data-wf-label="Revenue & Orders"
data-wf-legend
data-wf-format="compact">
<script type="application/json">
{
"labels": ["Wk 1", "Wk 2", "Wk 3", "Wk 4"],
"datasets": [
{ "label": "Revenue", "data": [18400, 22100, 25800, 31600] },
{ "label": "Orders", "data": [368, 442, 516, 632] }
]
}
</script>
</div>
Server response shape
The endpoint must return a standard type: "positive" response with a top-level chartData key. The value is passed verbatim to chart.update().
{
"type": "positive",
"chartData": {
"labels": ["Wk 1", "Wk 2", "Wk 3", "Wk 4"],
"datasets": [
{ "label": "Revenue", "data": [18400, 22100, 25800, 31600] },
{ "label": "Orders", "data": [368, 442, 516, 632] }
]
}
}
Custom Properties
All colors are resolved from CSS custom properties at render time. Set them on the chart element or any ancestor. Series colors default to the framework's brand palette so dark-mode switching is automatic.
| Property | Default | Description |
|---|---|---|
| --wf-chart-series-1 … -13 | brand palette | Series stroke and fill-gradient colors. Map to --primary-color, --secondary-color, and other semantic tokens. |
| --wf-chart-grid | var(--wf-border) | Horizontal grid line color. |
| --wf-chart-axis | var(--wf-text-muted) | Axis tick label color. |
| --wf-chart-tooltip-bg | var(--wf-surface) | Tooltip background color. |
| --wf-chart-tooltip-border | var(--wf-border) | Tooltip border color. |
| --wf-chart-fill-opacity | 0.15 | Area fill gradient top-stop opacity multiplier. Increase for a more prominent fill. |
| --wf-chart-radius | var(--wf-radius-surface) | Border radius of the skeleton loader shown while remote data is loading. |
Attribute Reference
| Attribute | Values | Description |
|---|---|---|
| data-wf-chart | area · area-multi · bar · bar-multi · bar-horizontal · line · line-multi · doughnut · pie · bubble · sparkline | Required. Determines the renderer. |
| data-wf-height | 300 | SVG height in px. Width is always 100% of the container. |
| data-wf-label | string | Chart title rendered above the SVG. |
| data-wf-legend | presence | Show a series legend below the chart. Color swatches are pulled from the series palette. |
| data-wf-grid | true | Set to "false" to hide horizontal grid lines. |
| data-wf-x-axis | true | Set to "false" to hide x-axis labels. |
| data-wf-y-axis | true | Set to "false" to hide y-axis labels. |
| data-wf-animate | true | Set to "false" to disable entry animation. Respects prefers-reduced-motion. |
| data-wf-curve | smooth · straight | Line interpolation. smooth uses Catmull-Rom cubic Bézier curves. |
| data-wf-fill | presence | Force area fill on line / line-multi charts. |
| data-wf-stacked | presence | Stack series on top of each other. Requires data-wf-chart="area-multi". |
| data-wf-synced-group | string | Share a crosshair with other charts that have the same group name. |
| data-wf-inner-radius | 0.65 | Doughnut hole ratio 0–1. 0 = solid pie, 0.8 = thin ring. |
| data-wf-color | #hex | Single-series color override. Overrides --wf-chart-series-1. |
| data-wf-src | URL | GET-fetch data on init. Shows a skeleton loader while loading. Response must match the standard JSON format. |
| data-wf-format | compact · number · currency · percent | Y-axis tick label format. compact abbreviates large numbers (1K, 1M). |
| data-wf-currency | Currency code | ISO-4217 currency code, e.g. "€", "$", "¥". |
Programmatic API
Access the instance via el._wfChart after the component has initialised.
const chart = document.querySelector('#my-chart')._wfChart
chart.update({ labels: [...], datasets: [...] }) // replace data and redraw
chart.reload() // re-fetch data-wf-src URL
chart.destroy() // remove observers and clean up
Remote data
Add data-wf-src to fetch data on init. A skeleton loader is shown while the request is in flight. The response must be a JSON object with labels and datasets keys. Charts driven by wf-ajax-action can receive live updates via data-wf-chart-target="#id" on the trigger button — the server response must include a chartData key.
<!-- Self-loading via URL -->
<div data-wf-chart="area"
data-wf-src="/api/chart-data.php"
data-wf-height="280">
</div>
<!-- Live update via wf-ajax-action -->
<button data-wf-action="filter"
data-wf-chart-target="#my-chart">
Last 30 days
</button>
<!-- Server response for ajax-action update -->
<!-- { "type": "positive", "chartData": { "labels": [...], "datasets": [...] } } -->