# Dashboards, Charts, and Polling

> Dashboards, Charts, and Polling: a worked guide from route registry through to a production build.

Source: [https://askrjs.com/docs/guides/dashboards-charts-and-polling](https://askrjs.com/docs/guides/dashboards-charts-and-polling)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

Poll through a cancellable query, retain the last successful rows while refreshing, and give the plot stable row identity and an accessible label.

```tsx
import { createPlot } from '@askrjs/charts';

const Plot = createPlot<MetricRow>();

<Plot.Root data={metrics()} rowKey={(row) => row.timestamp} label="Request rate">
  <Plot.Axis axis="x" />
  <Plot.Axis axis="y" />
  <Plot.Line x="timestamp" y="requests" />
</Plot.Root>
```

## Goal and architecture

Charts come from `@askrjs/charts`, a typed Canvas 2D plotting library with an immutable scene also available for SVG and data export — there's no separate headless/themed split here, `createPlot<Row>()` is the one entry point. A dashboard combines that with `@askrjs/askr/data`'s query layer for live numbers and `invalidateOnInterval()` for polling, so the chart re-renders whenever its backing query refetches rather than on its own timer.

## Implementation

Call `createPlot<RevenueRow>()` once at module scope to get a typed namespace, then compose marks inside `<RevenuePlot.Root data={...} rowKey="id" label="...">` — `Bar`, `Line`, and `Point` marks reference row fields by name, and the type system rejects a numeric channel bound to a string field. For live data, wrap the query driving the chart with `invalidateOnInterval(prefix, { intervalMs, ... })` — `prefix` is a required first argument naming which query keys to invalidate, not an options-object-only call — so it refetches on a schedule, and remember to import `@askrjs/charts/styles` once at the app boundary since the structural and theme-token CSS ships separately from the JS. Import `@askrjs/charts/styles` from a CSS file, not a `.ts`/`.tsx` module — it's a CSS export, not a typed module.

## Failure states

A polling query that errors mid-interval shouldn't tear down the chart and replace it with an error state on every failed poll — treat a transient poll failure differently from the query's initial load failure, or a flaky network turns a dashboard into a strobe light. Mixing primitives from two different `createPlot()` factories inside one `Root` is caught at runtime with a thrown `TypeError`, not statically by TypeScript — primitives from different factories can look structurally identical to the type checker, so don't rely on the compiler to catch this one; the fix if you hit the runtime error is the same, consolidating on one factory per row shape. Empty data (zero rows) should render an explicit empty state inside `Root`'s children, since an empty canvas with no rows looks identical to a still-loading one.

## Verification

Watch a polling-driven chart across several interval ticks and confirm it actually updates when the underlying data changes, not just on first load — it's easy to wire `invalidateOnInterval` to the wrong query key and have the chart look live while it's actually static. Resize the container and confirm the plot redraws correctly rather than clipping, since Canvas-backed charts don't get free reflow the way DOM/SVG ones sometimes do. Finally check the page under a throttled connection to confirm polling backs off or queues sensibly rather than piling up overlapping requests.

## Documentation navigation

[Previous](https://askrjs.com/docs/guides/tables-filters-and-url-state/index.md) | [Next](https://askrjs.com/docs/guides/protected-routes-and-permissions/index.md)
