@pilotiq/recharts
Recharts dashboard chart adapter for @pilotiq/pilotiq. Adds the
Chart widget element + a renderer registry hook so your panel can
ship line / bar / pie / doughnut charts on dashboards, resource
header/footer schemas, and custom pages.
#Why a separate package?
Recharts is ~80 KB after tree-shaking and brings in a non-trivial
React-DOM render path. Pulling it into @pilotiq/pilotiq would tax
panels that don't draw charts — the same posture as @pilotiq/tiptap
(rich text) and @pilotiq/codemirror (code editing).
A future @pilotiq/chartjs or @pilotiq/echarts slots in via the same
renderer registry without core churn.
#Installation
pnpm add @pilotiq/recharts rechartsrecharts is a peer dependency — install the version your app pins.
#Setup
Register the plugin on your Pilotiq.make(...) panel (typically app/Pilotiq/AdminPanel.ts):
import { Pilotiq } from '@pilotiq/pilotiq'
import { recharts } from '@pilotiq/recharts'
Pilotiq.make('Admin').plugins([recharts()])Prefer to register manually?
import { registerChartRenderer } from '@pilotiq/recharts'and call it from your client entry (pages/+Layout.tsx). The plugin form is just sugar over that call.
Without one of the two, every Chart widget paints a clear inline error:
No renderer registered for widget type
chart. Install@pilotiq/rechartsand callregisterChartRenderer()at app boot.
Silent rendering would let a missing registration slip into production unnoticed.
#Tailwind setup
@pilotiq/recharts ships Tailwind utility class names, not compiled CSS, so
your app's Tailwind build must scan the package for those classes to be
generated — the same requirement as @pilotiq/pilotiq itself. Skip it and any
class that doesn't already appear elsewhere in your project silently won't
render — notably the responsive md: variants on the chart's time-range
toggle (you'd see the mobile <select> even on desktop).
Tailwind v4 — add an @source to your main CSS, alongside the one you
already have for @pilotiq/pilotiq:
@import "tailwindcss";
@source "../node_modules/@pilotiq/pilotiq/dist";
@source "../node_modules/@pilotiq/recharts/dist";Adjust the relative path so it resolves to the installed package's dist
(monorepo/workspace setups can point at src instead).
Tailwind v3 — add the package to content in tailwind.config:
content: [
'./node_modules/@pilotiq/pilotiq/dist/**/*.js',
'./node_modules/@pilotiq/recharts/dist/**/*.js',
]#Quick example
import { Chart } from '@pilotiq/recharts'
import { Post } from '#models/Post.js'
export class PostsChart extends Chart {
static override label = 'Posts per day'
static override type = 'line' as const
static override color = 'primary' as const
static override maxHeight = 280
static override filters = {
today: 'Today',
week: 'Last 7 days',
month: 'Last 30 days',
}
static override defaultFilter = 'week'
static override async getData(ctx) {
const days = ctx.filter === 'today' ? 1
: ctx.filter === 'month' ? 30
: 7
const rows = await Post.query()
.where('createdAt', '>', new Date(Date.now() - days * 86_400_000))
.orderBy('createdAt')
return {
labels: bucketLabels(rows, days),
datasets: [{ label: 'Posts', data: bucketCounts(rows, days) }],
}
}
}Mount it inside any Page.schema() or Resource.headerSchema():
PostsChart.make().poll(30) // auto-refresh every 30 seconds#Chart types
Eight types are accepted by the type whitelist; v1 ships renderers for the first four:
| Type | v1 renderer | Notes |
|---|---|---|
line |
✅ | Default |
bar |
✅ | Vertical bars |
pie |
✅ | Standard pie slices |
doughnut |
✅ | Pie with innerRadius |
radar |
(later) | Recharts <Radar> mapping |
polar |
(later) | Polar-area variant of radar |
scatter |
(later) | XY scatter |
bubble |
(later) | Scatter with size axis |
Calling .type('unknown') throws at construction so typos surface at
schema build, not at render.
#Data shape
Chart.js-shaped — the renderer normalizes to Recharts internally so existing data-shaping code doesn't need to change if you swap to a different chart adapter later:
{
labels: ['Mon', 'Tue', 'Wed'],
datasets: [
{ label: 'Posts', data: [3, 5, 4] },
{ label: 'Drafts', data: [1, 2, 1], color: 'warning' },
],
}color per dataset is a ChartColor token (primary, success,
warning, destructive, info, default). Resolution order:
per-dataset color → an explicit (non-default) chart-level static color claims the first series → otherwise the theme chart palette
(--chart-1…--chart-5, by series index) drives every series. Leaving
both unset is the recommended path: charts then track the theme editor's
"Chart Color" setting and read cohesively, matching the theme preview.
Chrome is intentionally minimal: line charts render as a soft area-fill (palette stroke + a fade-to-transparent gradient), bars get rounded tops, and there is no value-grid — just a hairline X baseline with muted tick labels and no Y axis. Tooltips use the theme surface tokens.
#Per-chart filter dropdown
static filters = { key: label } renders a <select> in the chart's
header. The selected key rides on ctx.filter so getData can branch
on it; switching options re-fetches via the polling endpoint with
{ filter } in the request body.
static defaultFilter controls the initial selection on the first
SSR pass — set it to one of the keys from filters (otherwise the
SSR run sees ctx.filter === undefined and you'll have to handle that
fallback explicitly).
#Escape hatch: raw Recharts options
static override options = { strokeWidth: 2, dot: false, isAnimationActive: false }options is spread onto the renderer's primary <Line / Bar / Pie>
component. Use this for tweaks beyond the curated fluent surface
(label / type / color / maxHeight / filters) — anything Recharts
accepts works.
#Tests
pnpm -F @pilotiq/recharts test — 22 tests covering the Chart fluent
surface, the static-form fall-through, the type whitelist, server-data
resolution with default-filter fallback, and registration.