Chart.js โ
Charts built on Chart.js. A controlMonoChart(...) controller projects your rows into chart data โ the same controller-plus-.prop shape as controlMonoTable โ and the <mono-chart-*> elements own the canvas.
ts
import 'mono-helper/ui/chart'
import { controlMonoChart } from 'mono-helper'
const chart = controlMonoChart(rows, {
labelField: 'month',
series: [
{ field: 'sales', label: 'Sales' },
{ field: 'costs', label: 'Costs' },
],
})vue
<mono-chart-bar :control-chart.prop="chart" height="320" title="Sales vs costs" />Install
Chart.js is an optional peer โ it isn't bundled, so nothing pays for it unless you chart something.
sh
pnpm add chart.js@4.5.1The version is pinned exact. If it's missing, the element renders an inline message naming the package and the command rather than failing silently.
Elements โ
| Element | Type |
|---|---|
<mono-chart> | whatever type says โ radar, polarArea, scatter, bubble โฆ |
<mono-chart-bar> | bar |
<mono-chart-line> | line |
<mono-chart-pie> | pie |
<mono-chart-doughnut> | doughnut |
The presets are <mono-chart> with type locked, so the markup states its intent. Everything else โ props, controller binding, lifecycle โ is identical.
Basic โ
labelField names the category axis; each series entry becomes one dataset. Field names accept the same path expressions as the table (Job.Budget, Items.[0].Total).
Chart types โ
One controller can feed several elements โ the projection is shared, only the type differs.
Aggregating rows โ
Charts almost always want summarised data, but a data source hands you raw rows. groupBy buckets them and each series' agg collapses a bucket to one number โ sum (default), avg, count, min or max.
ts
const chart = controlMonoChart(transactions, {
groupBy: 'region',
series: [{ field: 'amount', label: 'Revenue', agg: 'sum' }],
})Reactive data โ
The controller is a subscribable store, exactly like controlMonoTable. When the bound source emits changed, it re-syncs, re-projects and notifies โ and the element updates the existing Chart.js instance instead of recreating the canvas, so the change animates rather than flashing.
Notifications are coalesced on a microtask, so several mutations in one tick produce a single update.
From a DataSource โ
Bind a devextreme DataSource and the controller drains it โ a remote source is paged, but a chart wants the whole set, so it reads every row in chunks rather than charting page one. Set loadAll: false to chart just the current page.
Server-side roll-up ($apply) โ
Draining is the wrong shape for a summary chart: a department-total over 100k rows is ~1000 paged requests carrying every column, to produce a dozen bars. Point the controller at the endpoint with odata and give it an aggregate โ the server groups with OData's $apply and returns one row per bucket, in a single request. A filter folds inside the clause, so it's applied before the aggregation.
Requires the mono-utils peer
The request goes through monoOdataFetch. It's an optional peer loaded on demand โ a chart that never sets odata neither needs nor loads it.
Raw chart.js data โ
Skip the mapping entirely: hand the controller a chart.js { labels, datasets } object and it's used verbatim. A per-dataset type gives you mixed charts.
Theme โ
Charts are wired to the theme like every other component. The default palette is mono color names, not fixed hex, so a chart with no color or colors still follows the active color preset โ and so do its axes, grid, legend and title.
A canvas doesn't re-cascade
Switching theme repaints every other component for free: the class swap re-cascades and CSS redraws. A chart can't โ it's a bitmap painted from values resolved once, so it would keep the old palette. The elements listen for the theme-changed event applyTheme() fires and redraw themselves. If you swap the theme classes by hand instead of calling applyTheme(), dispatch that event yourself:
ts
window.dispatchEvent(new CustomEvent('theme-changed'))Colors โ
Anywhere a colour is accepted you can use a mono color name โ primary, secondary, accent, success, warning, danger, info โ or any CSS colour, mixed freely. Use color for one accent, colors for a palette, or series[].color per dataset; the more specific one wins.
Why names have to be resolved
A <canvas> can't resolve var() โ raw var(--theme-success) paints transparent. The element reads the token off the DOM and hands Chart.js the real value, which is why names work at all.
Customising โ
Every layer is live in one demo: color / colors on the element, series[].color per dataset, --mono-chart-* to retarget what a name means, and raw Chart.js options for everything else.
Anything Chart.js supports is reachable through options on the controller or chart-options on the element โ both are deep-merged over the generated defaults, so you keep the theming and override only what you name.
Overriding the variable is the theme-level lever โ every chart that asked for success follows, with no chart code touched:
css
.sales-dashboard {
--mono-chart-success: #0f9d58;
--mono-chart-danger: #d93025;
}Controller API โ
ts
const chart = controlMonoChart(source, options)source is an array, a devextreme DataSource, a raw chart.js data object, or null.
| Option | Purpose |
|---|---|
type | Default chart type ('bar') |
labelField | Row field for the category axis |
groupBy | Bucket rows by this field before charting |
series[] | { field, label?, agg?, color?, type?, dataset? } โ one dataset each |
colors[] | Palette โ mono color names or CSS colors |
options | Raw chart.js options, deep-merged |
props | The element's own props, declared centrally โ see Element props |
odata | Fetch through monoOdataFetch (configBaseUrl / baseUrl / url / method / options). Add aggregate to roll up on the server โ see Server-side roll-up |
loadAll | Drain a remote source instead of charting one page (default true) |
| Member | Purpose |
|---|---|
items | Rows currently backing the chart |
loading | True while the source loads |
data() / options() | The projected chart.js data / merged options |
props() / setProps(patch) | Read / merge the central element props |
setType / setSeries / setColors / setData | Reproject |
bind(source) / reload() | Swap or re-read the source |
onPointClick | Assignable sink โ receives the label, value and the rows that produced the point |
subscribe(cb) / dispose() | Lifecycle |
onPointClick reports the underlying rows, which is what makes drill-down work: click a bar and you get back the bucket that built it.
ts
chart.onPointClick = ({ label, value, rows }) => {
console.log(label, value, rows) // rows = every record in that bucket
}Element props โ
Declare the element's own props in the controller's props block and every bound <mono-chart*> needs nothing but :control-chart.prop="chart" โ the same arrangement as controlMonoTable({ props }), minus the per-element nesting, since the presets are one element with type fixed.
ts
const chart = controlMonoChart(rows, {
groupBy: 'month',
series: [{ field: 'amount', label: 'Revenue', agg: 'sum' }],
props: { height: 320, legend: 'bottom', stacked: true },
})
chart.props() // { height: 320, legend: 'bottom', stacked: true }
chart.setProps({ legend: 'right' }) // merges, notifies, every bound element re-appliesprops() returns one object with a stable identity, so a controller can drive several charts and keep them in sync. setProps is a merge โ keys you omit keep their current value.
Setting props on the element still works
Writing height / legend / stacked directly on <mono-chart-bar> is still supported. Where both declare the same key, the controller wins; keys the controller never mentions are left to the template.
Server rendering โ
A chart can't be server-rendered โ Chart.js paints onto a canvas at runtime. The SSR build (mono-helper/ui/shadow/chart) emits a correctly-sized empty <canvas> and draws on hydration, so layout doesn't shift. controlMonoChart itself is a pure factory and is safe to import on the server.
Props โ
controlMonoChart is also exported as monoChart, and :control-chart is also accepted as :data-chart โ the older spellings still work.
Import
import { ChartProps } from 'mono-helper'| Prop | Value | Default | Description |
|---|---|---|---|
controlChart | MonoChartController<any> | โ | The chart controller. Bind with `.prop`: `:control-chart.prop="chart"`. |
dataChart | MonoChartController<any> | โ | Renamed โ `:data-chart` / `:dataChart` alias `controlChart` (both work). |
type | 'bar' | 'line' | 'pie' | 'doughnut' | 'radar' | 'polarArea' | 'scatter' | 'bubble' | โ | Chart type. Ignored by the presets, which fix their own. When a controller is bound this overrides the controller's `type`. |
data | MonoChartData | โ | Raw chart.js data, for use WITHOUT a controller. Bind with `.prop`: `:data.prop="{ labels, datasets }"`. |
chartOptions | Record<string, unknown> | โ | Raw chart.js options, merged over the defaults. Bind with `.prop`. |
width | string | number | โ | Canvas box sizing. A CSS length string or a number (px). |
height | string | number | โ | โ |
aspectRatio | number | โ | `width / height` when no explicit height is set. Default `2`. |
legend | 'left' | 'right' | 'top' | 'bottom' | boolean | โ | Show the legend (default `true`), or place it: `top` / `bottom` / `left` / `right`. |
title | string | โ | Chart title rendered by chart.js. |
stacked | boolean | โ | Stack bar/line datasets. Default `false`. |
color | string | โ | Single accent for every dataset โ a mono color name (`primary`, `success`, `danger`, โฆ) or any CSS color. `colors` wins when both are set. |
colors | string | string[] | โ | Palette. A comma-separated attribute (`colors="success,danger,warning"`) or an array via `.prop`. Entries may be mono color names or CSS colors, mixed. |
cssClass | ChartCssClass | โ | Per-part class overrides. Object, or a JSON string via the `css-class` attribute. |
cssClassName | string | โ | A single class added to the root. |
Optional dependency โ
chart.js is declared as an optional peerDependency pinned to 4.5.1, and is loaded with a dynamic import('chart.js/auto') the first time an element renders. It is externalized from the build, so dist/ui/chart.js stays around 13 kB and resolves against your app's copy at runtime โ install it yourself, and nothing else in mono-helper is affected if you don't.