aiRA data visualisation companion v0.2
The charts, tables and dashboards aiRA draws, specified the way the aiRA Design System specifies everything else. This release covers the core chart set, the specialist charts, the analytics settings and table patterns Power BI users expect, and the three parts every chart shares.
Every colour, type style, space and radius on this page is a design-system token, read from the live site. Where a chart needs something the system does not have, it is marked Proposed and collected under Proposed tokens. The written rules behind these specs, including integrity, freshness, layouts and platforms, live in the foundations draft.
Toggle Light and Dark in the top bar: every chart re-resolves its tokens. Hover or tap any mark for its tooltip, or tab to a chart and use the arrow keys.
How to use this page
Each block follows the design system's component shape: a live example, when to use it, anatomy with tokens, settings, states, accessibility, copy, do and don't, and an engineering handoff.
| If you are | Read |
|---|---|
| The aiRA agent choosing a chart | The chooser in the foundations draft, then the block here. Use when and Don't use when are decision rules, not suggestions. |
| A designer speccing a screen | The example and anatomy. Every part names its token. |
| An engineer | Switch to Developer view for settings and the handoff snippet. Names marked Proposed are not in the library yet. |
Rules every block on this page follows
- The title is the chart's name. What it shows ("Daily redemptions"), never the finding. The finding goes in aiRA's answer next to the chart, and in annotations and highlights on it.
- Trust line on every chart: the table aiRA queried and the time the query ran, in the org timezone. Nothing else.
- Direct labels first. A key only for colour scales, size, collisions and small multiples.
- Never red on chart marks. Down is
--aira-delta-negativeorange; red stays for errors. - View as table on every chart. Card actions appear on hover and keyboard focus, and in a menu on touch; no value lives only in a tooltip.
- One colour means indigo. A single series uses
--aira-viz-1; channel colours only appear when channels are compared. - Hover dims the rest. The active mark keeps its colour and the others fade. No outlines.
- Org locale for every number: this page's demo org is Indian, so lakh, crore and ₹.
Chart card
The frame every chart sits in: title, subtitle, optional key, plot, trust line and actions. It is a composition of AiraCard, so no chart ever needs its own header.
Example
Column chart. HSR Layout led with 18,420 redemptions, then Indiranagar 15,230, Koramangala 11,910, Whitefield 9,640, Jayanagar 7,120 and Malleshwaram 5,310. Use the arrow keys to move between values.
| Store | Redemptions |
|---|---|
| HSR Layout | 18,420 |
| Indiranagar | 15,230 |
| Koramangala | 11,910 |
| Whitefield | 9,640 |
| Jayanagar | 7,120 |
| Malleshwaram | 5,310 |
Hover the card for its actions. Resize the window: below 480px the columns become horizontal bars, as the chooser requires for narrow space. One highlight (HSR Layout) in --aira-viz-1, the rest in context grey. Every bar is labelled, so there is no value axis.
Use when
Any chart, on any surface: chat answer, artifact panel, dashboard tile.
A KPI tile. AiraMetricTile has its own anatomy.
Anatomy
| Part | Type | Colour | Rule |
|---|---|---|---|
| Title | --aira-t-label-md | --aira-text-primary | The chart's name: what it shows, as a short noun phrase. Never the finding. One line |
| Subtitle | --aira-t-caption | --aira-text-tertiary | Measure · unit · period · filters |
| Actions | .iconbtn sm; CSV is a small tertiary button | --aira-text-tertiary | View as table, copy, CSV, full screen, in that order. They appear on hover and keyboard focus, and stay while the table view is open; on touch they sit in a menu. Borderless, scoped to this chart |
| Key | See Chart key | Only where the key rules allow | |
| Plot | Marks, axes, annotations | ||
| Trust line | --aira-t-caption | --aira-text-tertiary | Source: the table aiRA queried (write_db_100783.points_aira) · As of time in org timezone. Nothing else. Never optional |
| Surface | --aira-surface-artifact-card-bg | --aira-radius-md, 1px --aira-border-default, padding --aira-sp-4, parts spaced --aira-sp-3 |
Behaviour
| Action | What happens |
|---|---|
| Hover or focus a mark | The other marks fade to --aira-proposed-viz-dim. The tooltip opens beside the mark and never leaves the card: above it, then below, then beside, pushed back inside the card's edges. |
| View as table | The plot swaps for its table in place; the button becomes View as chart. |
| Copy | Copies the data as tab-separated text. The copy icon becomes a tick in --aira-text-success for 1.5 seconds, then reverts. No text note on the page. If copying is blocked, a toast says to select the table instead. |
| CSV | Downloads this chart's data as a CSV file. The button always names the format. |
| Full screen | The same chart, wider and taller, in a window over the page: title, subtitle, key, plot and trust line. Tooltips work inside it. It keeps the card's actions (view as table, copy, CSV) in its header next to close; View as table swaps the chart for its table, never both at once. Escape or the close button returns focus to the card. |
| On touch | The actions sit in a "…" menu on the card: view as table, copy, Download CSV, Open full screen. Tapping the card also opens full screen, whose own "…" menu has the same actions except full screen. |
States
- Loading:
AiraSkeletonin the plot's shape with "Drawing this chart…"; title and subtitle already shown. - No data: the plot area holds a message and a next step, never a blank chart.
- Error: "Couldn't draw this chart", the reason in plain language ("The query took too long to finish."), and Try again. No codes.
- Old snapshot: the card offers Re-run when the result is over 24 hours old.
Accessibility
- The plot is one tab stop with
role="img", named by the title and described by a text summary. - Arrow keys move between values; each is announced ("3 of 6. Koramangala, 11,910 redemptions").
- View as table is a real button with
aria-pressed.
Do / don't
Name the chart for what it shows: "Redemptions by store".
Write the finding as the title: "HSR Layout led September redemptions". The finding goes in aiRA's answer.
Say the period is partial when it is.
Drop the trust line to save space.
Confirm a copy with a tick on the button.
Write "Copied as a table" under the chart.
Engineering handoff
<AiraChartCard
title="Redemptions by store"
subtitle="Redemptions · count · 1–23 Sep 2026 · all tiers"
source="write_db_100783.redemptions_aira" // the table aiRA queried
asOf={queryRanAt} // formatted in org timezone
table={rows} // powers "View as table" and copy
>
<AiraBarChart data={rows} xKey="store" yKey="redemptions" highlight="HSR Layout" />
</AiraChartCard>Chart key
A key makes the reader look away, remember a colour and look back. So the first choice is always a direct label; the key is the fallback, and it has three forms.
Direct labels (the default)
Line chart of active members by tier. Silver rose from 11,200 to 12,400, Gold from 6,800 to 8,000, Platinum from 2,100 to 2,600. Use the arrow keys to move between values.
| Month | Silver | Gold | Platinum |
|---|---|---|---|
| Mar 2026 | 11,200 | 6,800 | 2,100 |
| Apr 2026 | 11,000 | 7,000 | 2,200 |
| May 2026 | 11,500 | 7,300 | 2,300 |
| Jun 2026 | 11,900 | 7,100 | 2,300 |
| Jul 2026 | 12,200 | 7,600 | 2,400 |
| Aug 2026 | 12,400 | 8,000 | 2,600 |
Names sit at the line ends in text colour; the dot carries the series colour. Tiers take locked sequential steps by rank (Silver seq-4, Gold seq-5, Platinum seq-6).
Inline key (paired and stacked charts)
A row between subtitle and plot. 8px square swatch for areas, 12×2px line for lines. Label text is neutral, never the series colour.
Scale key (colour scales, always shown)
Heatmaps, cohort grids and filled maps. Low and high ends labelled, and the middle on diverging scales.
When a key appears
| Case | Key |
|---|---|
| One series; bars and columns; donut, waffle, treemap; KPI tiles; sparklines | None |
| Several lines; stacked bars; paired bars and dumbbells | Direct labels, or an inline key in the subtitle |
| Heatmaps, cohort grids, filled maps | Scale key, always |
| Bubble size | Size key, always |
| Different kinds of marks together: bars with a line, a shaded band behind a line, coloured bars beside grey ones | Name every mark type on the chart: a direct label where it fits ("Expected range" on the band), otherwise an inline key |
| Direct labels would collide | Inline key |
| Small multiples | One shared key for the set |
Rules
- Top-left, between subtitle and plot, in a row. Never at the right: aiRA's surfaces are narrow.
- In data order (stack order, or lines by last value). Never alphabetical.
- Clicking an item may highlight that series; it works from the keyboard and never replaces View as table.
Engineering handoff
<AiraChartKey items={[
{ label: "Email", color: "var(--aira-cat-email)" },
{ label: "Push", color: "var(--aira-cat-push)" },
]} />
<AiraChartKey scale={{ steps: seqTokens(6), low: "Under 20%", high: "60% or more" }} />Chart tooltip
Adds detail on hover, tap or keyboard focus. It never holds the only copy of a value: anything essential is already on the chart or in View as table.
Example
Every chart on this page uses the live version. Hover, tap, or tab to a chart and press the arrow keys.
Anatomy
| Part | Type | Colour | Rule |
|---|---|---|---|
| Header | --aira-t-label-sm | --aira-text-primary | The category or date, with year |
| Row | --aira-t-caption + value in --aira-t-label-sm | Name --aira-text-secondary, value --aira-text-primary | Swatch, name, value. Full precision, not compact |
| Note | --aira-t-caption | --aira-text-tertiary | Optional: comparison, range level, group size |
| Surface | --aira-bg-surface, --aira-border-default | --aira-radius-sm, --aira-shadow-md, padding --aira-sp-2 / --aira-sp-3 |
Behaviour
| Input | Behaviour |
|---|---|
| Highlight | The active mark stays at full colour; every other mark in the chart fades to 35% (Proposed --aira-proposed-viz-dim). No outline |
| Mouse | Shows on hover with --aira-motion-fast, no delay. Beside the mark, flipping at the edges, never covering it |
| Touch | Tap a mark to show; tap elsewhere to dismiss. Placed above the finger |
| Keyboard | Arrow keys move between marks; the tooltip follows and the value is announced. Escape hides |
| Lines | One tooltip per period listing every series, with a 1px --aira-viz-baseline crosshair |
| Limit | 6 rows; beyond that the top 5 and "and 4 more" |
Engineering handoff
// Built on the AiraTooltip surface; chart blocks render it for you.
<AiraBarChart tooltip={{ rows: ["redemptions"], note: "1–23 Sep 2026" }} … />Bar chart
The workhorse. Length on a shared baseline is the most accurately read encoding there is, so when the question is "how do these compare", the answer is a bar chart unless something rules it out. AiraBarChart ships today; the variants below are its settings.
Column and horizontal
Column chart. HSR Layout led with 18,420 redemptions, then Indiranagar 15,230, Koramangala 11,910, Whitefield 9,640, Jayanagar 7,120 and Malleshwaram 5,310. Use the arrow keys to move between values.
| Store | Redemptions |
|---|---|
| HSR Layout | 18,420 |
| Indiranagar | 15,230 |
| Koramangala | 11,910 |
| Whitefield | 9,640 |
| Jayanagar | 7,120 |
| Malleshwaram | 5,310 |
Columns for up to 12 short categories or for periods; horizontal bars for long labels or more than 12 categories. This example switches by itself below 480px.
Paired
Paired bar chart. HSR Layout 52,400 in Q2 against 46,100 in Q1; Indiranagar 43,000 against 39,800; Koramangala 33,900 against 31,200; Whitefield 27,300 against 26,900. Use the arrow keys to move between values.
| Store | Q2 2026 | Q1 2026 | Change |
|---|---|---|---|
| HSR Layout | 52,400 | 46,100 | +13.7% |
| Indiranagar | 43,000 | 39,800 | +8.0% |
| Koramangala | 33,900 | 31,200 | +8.7% |
| Whitefield | 27,300 | 26,900 | +1.5% |
The current period is indigo; the comparison period is context grey, so the eye reads "now" first. Two groups only; more becomes a dot plot or small multiples.
100% stacked
100% stacked bar. WhatsApp share of redemptions is 38% for Platinum, 41% Gold, 44% Silver and 47% Base, the largest in every tier. Use the arrow keys to move between values.
| Tier | Push | SMS | ||
|---|---|---|---|---|
| Platinum | 38% | 27% | 20% | 15% |
| Gold | 41% | 25% | 19% | 15% |
| Silver | 44% | 22% | 18% | 16% |
| Base | 47% | 19% | 17% | 17% |
Four channels compared, so channel colours apply here. A single-series bar is always indigo.
Variance
Variance bar chart of sales against plan: HSR Layout +8.2%, Jayanagar +4.6%, Indiranagar +2.1%, Koramangala −4.6%, Whitefield −11.8%. Use the arrow keys to move between values.
| Store | Sales vs plan |
|---|---|
| HSR Layout | +8.2% |
| Jayanagar | +4.6% |
| Indiranagar | +2.1% |
| Koramangala | −4.6% |
| Whitefield | −11.8% |
Use when
Comparing values across categories, or totals across a few periods. The default answer to "which is biggest".
A trend over many periods: use a line. Two or three parts of a whole with a headline total: a donut.
Variants
| Variant | When | Status |
|---|---|---|
| Column | Up to 12 short categories, or up to 12 periods | Stable |
| Horizontal | Long labels or more than 12 categories | Stable |
| Paired | Two values per category | Proposed setting |
| Stacked | Totals and their make-up, 3 to 5 segments | Proposed setting |
| 100% stacked | Shares compared across groups | Proposed setting |
| Variance | Difference from plan or last year | Proposed setting |
| Lollipop | 15 or more items, where solid bars crowd | Proposed setting |
| With a same-unit line | Actual bars with a target or average line | Proposed setting |
Anatomy
| Part | Token | Rule |
|---|---|---|
| Bar | --aira-viz-1; context grey for the rest when one bar is the story | 70% of its slot, 40px at most; square at zero, 2px radius at the value end |
| Baseline | 1px --aira-viz-baseline | Always at zero |
| Value labels | --aira-t-caption, --aira-text-secondary; highlighted bar in --aira-t-label-sm primary | With 12 bars or fewer label every bar and drop the value axis |
| Category labels | --aira-t-caption, secondary | Never rotated: switch to horizontal instead; truncate past 24 characters |
| Gridlines | 1px --aira-viz-grid | Only when there is a value axis |
| Segments | 1px surface gap | Stacked variants |
Settings
| Prop | Values | Rule |
|---|---|---|
orientation | vertical · horizontal · auto | Auto switches by label length, count and width |
highlight | category | That bar in indigo, the rest context grey |
compare | { key, label } | Paired: the comparison period in context grey |
stack | none · stacked · percent | Percent labels each segment over 36px wide; text token by contrast |
variance | { baseline: "plan" | "last year" } | Bars grow from a 1px --aira-text-secondary zero line; delta colours with signs; sorted high to low |
better | higher · lower | Colour follows good or bad |
Do / don't
Sort by value unless the categories have their own order (time, tier).
Start the value axis above zero. A bar's length is its value.
Keep one colour for one series: indigo.
Put a number on every bar and keep the value axis as well.
Engineering handoff
<AiraBarChart data={stores} xKey="store" yKey="redemptions" orientation="auto" highlight="HSR Layout" />
<AiraBarChart data={stores} xKey="store" yKey="q2" compare={{ key: "q1", label: "Q1 2026" }} />
<AiraBarChart data={tiers} xKey="tier" series={channels} stack="percent" orientation="horizontal" />
<AiraBarChart data={stores} xKey="store" yKey="vsPlan" variance={{ baseline: "plan" }} better="higher" />Line chart
The default for anything over time. AiraLineChart ships today; forecast, anomaly markers and moving average are its new settings.
Lines with direct labels
Line chart of active members by tier. Silver rose from 11,200 to 12,400, Gold from 6,800 to 8,000, Platinum from 2,100 to 2,600. Use the arrow keys to move between values.
| Month | Silver | Gold | Platinum |
|---|---|---|---|
| Mar 2026 | 11,200 | 6,800 | 2,100 |
| Apr 2026 | 11,000 | 7,000 | 2,200 |
| May 2026 | 11,500 | 7,300 | 2,300 |
| Jun 2026 | 11,900 | 7,100 | 2,300 |
| Jul 2026 | 12,200 | 7,600 | 2,400 |
| Aug 2026 | 12,400 | 8,000 | 2,600 |
Forecast
Line chart with forecast. Monthly redemptions rose from 31,000 in September 2025 to 42,000 in August 2026. The forecast is 43,000 in September, 47,000 in October and 52,000 in November, likely between 45,000 and 59,000. Use the arrow keys to move between values.
| Month | Actual | Forecast | Likely range |
|---|---|---|---|
| Jun 2026 | 39,000 | ||
| Jul 2026 | 41,000 | ||
| Aug 2026 | 42,000 | ||
| Sep 2026 | 43,000 | 40,000 to 46,000 | |
| Oct 2026 | 47,000 | 42,000 to 52,000 | |
| Nov 2026 | 52,000 | 45,000 to 59,000 |
More settings, each with its own example: anomaly markers, moving average and reference lines.
Use when
A measure over time, especially more than 12 periods, or several series over the same periods.
Categories with no order: a line between them implies a connection. Use a bar.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Line | --aira-viz-1, 2px, round joins | Four lines at most; then highlight and grey or small multiples |
| Highlighted line | 2.5px | The one the story is about |
| Context lines | Context grey, 1.5px | |
| Points | 8px with 1px surface ring | Only with 12 points or fewer, on the highlighted line, or the last point |
| Direct labels | --aira-t-label-sm, secondary, at line ends | The dot carries the colour; the text stays neutral |
| Crosshair | 1px --aira-viz-baseline | Snaps to the nearest period; one tooltip lists every series |
| Partial period | Proposed pattern | The last, incomplete period is marked, never shown as a drop |
Settings
| Prop | Values | Rule |
|---|---|---|
forecast | { points, low, high } | Dashed 2px line in the series colour, joined to the last actual point; band in --aira-seq-2 |
today | boolean | 1px --aira-viz-baseline vertical line labelled "Today" |
anomalies | max 3 | See Anomaly markers |
movingAverage | { window } | See Moving average |
referenceLines | [{ kind, value }] | See Reference lines |
baseline | zero · auto | Auto may start above zero; the axis shows where it starts |
Rules
- At least two full cycles of history before forecasting; horizon no longer than a third of the history shown.
- aiRA's answer states the forecast range, not only the point.
- Size the chart so the main slope sits near 45°.
Engineering handoff
<AiraLineChart data={months} xKey="month" series={tiers} directLabels />
<AiraLineChart data={months} xKey="month" yKey="redemptions" forecast={{ points: next3, low: "p10", high: "p90" }} today />Bullet chart
Actual against a target, in a single row per item. It replaces the gauge: same answer, a fraction of the space, and rows compare against each other.
Example
Bullet chart. Platinum 46% against a 45% target; Gold 38% against 40%; Silver 29% against 35%; Base 18% against 20%. Use the arrow keys to move between values.
| Tier | Redemption rate | Target | Against target |
|---|---|---|---|
| Platinum | 46% | 45% | +1 pt |
| Gold | 38% | 40% | −2 pts |
| Silver | 29% | 35% | −6 pts |
| Base | 18% | 20% | −2 pts |
Use when
A value has a target, a plan or a benchmark, for one item or several.
There is no target: use a bar. Or the question is about change over time: use a line with a target line.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Bar | --aira-viz-1 | 12px tall, square at zero, 2px radius at the end |
| Target mark | --aira-text-primary | 2px wide, 20px tall, centred on the bar |
| Value | --aira-t-label-sm, --aira-text-primary | Right of the plot |
| Against target | --aira-delta-* with sign | "+1 pt" or "−6 pts": points, since both are rates |
| Axis | --aira-viz-grid, --aira-viz-baseline at zero | 3 or 4 ticks |
| Bands (optional) | Proposed | Poor, fair, good ranges behind the bar; to be designed |
Settings
| Prop | Values |
|---|---|
data | rows |
valueKey, targetKey | field names |
better | higher · lower, so colour follows good or bad |
unit | percent · number · currency |
Copy
- Title: the chart's name, "Redemption rate against target". aiRA's answer carries the finding: "Only Platinum is above its target."
- Rates compared with rate targets are points, not percent.
Do / don't
Sort by tier order when tiers have one, otherwise by distance from target.
Draw a gauge because someone asked for one. Say "Showing this as a bullet chart; gauges take more space for the same number."
Engineering handoff
<AiraBulletChart data={tiers} labelKey="tier" valueKey="rate" targetKey="target" unit="percent" better="higher" />Waterfall chart
Explains what moved a total from one value to another. The standard answer to "why did revenue change?"
Example
Waterfall chart. July revenue 42.0 lakh. New members added 2.4 lakh, returning members 1.9 lakh, lapsed members took away 0.8 lakh and discounts 0.4 lakh, ending at 45.1 lakh in August. Use the arrow keys to move between values.
| Step | Change (₹ lakh) | Running total (₹ lakh) |
|---|---|---|
| July | 42.0 | |
| New members | +2.4 | 44.4 |
| Returning members | +1.9 | 46.3 |
| Lapsed members | −0.8 | 45.5 |
| Discounts | −0.4 | 45.1 |
| August | 45.1 |
The value axis starts at ₹36L because a waterfall reads the steps, not the totals from zero. The start and end bars say so with their labels; this is the one bar-like chart where a non-zero start is honest.
Use when
A total changed between two points and the parts of the change are known: revenue bridge, points liability, member count.
The parts overlap or do not add up to the change. Then show a sorted bar of drivers and say they overlap.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Start and end bars | --aira-viz-1 | From the axis floor, labelled with their totals |
| Up step | --aira-delta-positive | Floating, label with + sign |
| Down step | --aira-delta-negative | Floating, label with − sign. Never red |
| Connector | 1px dashed --aira-viz-baseline | From each bar's end to the next bar's start |
| Order | Increases first, largest first, then decreases, largest first |
States
- More than 7 steps: group the smallest into "Other changes".
- A step of zero stays, labelled "0", so the list is complete.
Do / don't
Check that start plus steps equals end, and say so in aiRA's answer when rounding means it does not.
Colour increases green when an increase is bad, such as points liability. Colour follows good or bad.
Engineering handoff
<AiraWaterfallChart start={{ label: "July", value: 42.0e5 }} end={{ label: "August", value: 45.1e5 }} steps={drivers} better="higher" unit="currency" />Funnel chart
Campaign stages from sent to redeemed, as aligned bars on one baseline with the conversion between steps. Never trapezoids: the shape hides the real drop-offs.
Example
Funnel. Sent 1,20,000; delivered 1,14,600, 95.5% of sent; opened 71,050, 62% of delivered; clicked 18,470, 26% of opened; redeemed 6,280, 34% of clicked. Use the arrow keys to move between values.
| Stage | Members | Of previous step | Of sent |
|---|---|---|---|
| Sent | 1,20,000 | — | 100% |
| Delivered | 1,14,600 | 95.5% | 95.5% |
| Opened | 71,050 | 62.0% | 59.2% |
| Clicked | 18,470 | 26.0% | 15.4% |
| Redeemed | 6,280 | 34.0% | 5.2% |
One series, so one colour: aiRA indigo, --aira-viz-1. The channel is named in the subtitle; channel colours only appear when several channels are compared.
Use when
Members move through ordered stages and each stage is a subset of the one before.
Stages are not subsets (people can skip one). Use a bar chart of each stage and say so.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Stage label | --aira-t-caption, --aira-text-secondary | Left column |
| Bar | --aira-viz-1 | All bars start on one baseline. One series takes indigo even when it is one channel |
| Count | --aira-t-label-sm, --aira-text-primary | Full value; compact under 420px |
| Step rate | --aira-t-caption, --aira-text-tertiary | "62% of delivered", under each bar after the first |
Copy
- Title: the campaign and "funnel". The end-to-end rate or the biggest drop goes in aiRA's answer.
- Stage names in the customer's words, not event names.
Do / don't
Show the attribution window in the subtitle.
Compare two campaigns in one funnel. Use small multiples, or a table of step rates.
Engineering handoff
<AiraFunnelChart stages={stages} labelKey="stage" valueKey="members" />Heatmap and cohort grid
Two categories and one measure, shown as a grid of cells coloured by value. The cohort variant is the retention triangle every loyalty team asks for.
Example: cohort grid
Cohort grid. Month-one retention was 52% for April joiners, 55% May, 49% June, 58% July and 61% August. April joiners fell to 31% by month five. Use the arrow keys to move between values.
| Cohort | Month 1 | Month 2 | Month 3 | Month 4 | Month 5 |
|---|---|---|---|---|---|
| Apr 2026 | 52% | 41% | 36% | 33% | 31% |
| May 2026 | 55% | 44% | 38% | 35% | |
| Jun 2026 | 49% | 39% | 34% | ||
| Jul 2026 | 58% | 46% | |||
| Aug 2026 | 61% |
Cell text switches between --aira-text-primary and --aira-text-inverse by computed contrast on each step, in each theme. Empty cells are months that have not happened yet, not missing data.
Use when
Rows and columns are both categories (day by hour, store by month, join month by months since).
One of the axes is continuous and the exact values matter: use a line or small multiples.
Variants
| Variant | Rows × columns | Palette |
|---|---|---|
| Heatmap | Any two categories | Sequential; diverging when there is a meaningful middle |
| Cohort grid | Join period × periods since | Sequential, triangle shape |
| Calendar heatmap | Weeks × weekdays | Sequential |
| RFM grid | Recency × frequency | Sequential, segment names printed |
Anatomy
| Part | Token | Rule |
|---|---|---|
| Cell | --aira-seq-1 to -6 | 1px surface gap between cells. Six bins, stated in the key |
| Value | --aira-t-caption | Printed in every cell that fits; text token chosen by contrast |
| Scale key | See Chart key | Always shown |
| Row and column labels | --aira-t-caption | Rows --aira-text-secondary, columns --aira-text-tertiary |
Do / don't
Say what 100% means in the subtitle ("% of join-month cohort").
Use a rainbow or two unrelated hues for a sequential measure.
Engineering handoff
<AiraHeatmap variant="cohort" data={cohorts} rowKey="cohort" colKey="month" valueKey="retained" bins={6} unit="percent" />Scatter chart
Two measures across many items, to see whether they move together and which items stand apart. The bubble variant adds a third measure as size.
Example
Scatter chart of 72 Gold members. Basket size falls as days since last purchase rise. 12 members with baskets over 6,000 rupees have not bought in more than 60 days. Use the arrow keys to move between values.
| Member | Days since last purchase | Average basket (₹) |
|---|---|---|
| 2000 | 64 | 6,420 |
| 2013 | 71 | 6,880 |
| 2026 | 66 | 7,120 |
| 2039 | 78 | 6,310 |
| … | … | … |
Highlight and grey: the members the annotation names are in --aira-viz-1, everyone else in context grey. Dots are 6px with a 1px surface ring so overlaps stay visible.
Use when
Many items, two measures, and the question is about relationship or outliers.
One measure is time: use a line. Fewer than about 15 items: use a table or dot plot.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Dot | --aira-viz-1 or context grey | 6px, 1px --aira-surface-artifact-card-bg ring. Opacity only past 200 points |
| Hit area | 16px radius, bigger than the dot | |
| Axes | --aira-viz-grid | Both titled, units in the title |
| Annotation | --aira-t-caption, --aira-text-secondary | Names the group that matters, with a 1px leader |
| Trend line | 1px --aira-text-tertiary | Only when asked for or clearly real; never implies cause |
| Bubble size | Size key | Area, not radius, scales with value |
Copy
- The highlighted group is named in an annotation on the chart ("12 high spenders lapsing"), not in the title.
- Always show n. Describe association: "bigger baskets go with more recent purchases", never "recency drives spend".
Engineering handoff
<AiraScatterChart data={members} xKey="daysSince" yKey="basket" highlight={(m) => m.daysSince >= 60 && m.basket >= 6000} sampleSize={72} />Dot plot and dumbbell
Values as dots on a shared scale. The dumbbell joins two values per row, so the gap is the thing you see.
Example: dumbbell
Dumbbell chart of open rates. Platinum email 42%, push 58%, gap 16 points. Gold 36% and 49%. Silver 31% and 38%. Base 22% and 24%. Use the arrow keys to move between values.
| Tier | Push | Gap | |
|---|---|---|---|
| Platinum | 42% | 58% | +16 pts |
| Gold | 36% | 49% | +13 pts |
| Silver | 31% | 38% | +7 pts |
| Base | 22% | 24% | +2 pts |
Channel colours are locked: email is always --aira-cat-email, push always --aira-cat-push. The key is two direct labels on the first row.
Use when
Two values per category and the gap matters, especially with more than 8 categories where paired bars crowd.
More than two values per row: use small multiples.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Dot | Series or channel token | 10px, 1px surface ring |
| Connector | 2px --aira-viz-baseline | Dumbbell only |
| Gap label | --aira-t-label-sm, --aira-text-primary | Right column, in points for rates |
| Order | By gap, or by tier order where tiers have one | |
| Axis | May start above zero: dots have no length |
Engineering handoff
<AiraDotPlot variant="dumbbell" data={tiers} labelKey="tier" series={[{ key: "email", channel: "email" }, { key: "push", channel: "push" }]} unit="percent" />Donut and pie charts
Use sparingly. Angle is one of the weakest encodings people read, so a donut or pie is only for two or three parts when the rough proportion is the point. Past three parts it becomes a sorted bar, a 100% stacked bar or a waffle. The donut is preferred: its centre holds the headline total.
Example
Donut chart of 42,100 redemptions: points 54%, voucher 28%, tier upgrade 18%. Use the arrow keys to move between values.
| Reward type | Share | Redemptions |
|---|---|---|
| Points | 54% | 22,734 |
| Voucher | 28% | 11,788 |
| Tier upgrade | 18% | 7,578 |
Use when
Two or three parts, the rough split is the point, and there is a total worth showing.
Four or more parts, or parts that are close in size: angle is too weak to compare them. Use a sorted bar.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Segments | --aira-viz-1, -2, -3 in order of size | 1.5px surface gap; start at 12 o'clock, clockwise, largest first |
| Labels | --aira-t-label-sm, --aira-text-secondary | Name and share beside each segment. No key |
| Centre | --aira-t-h2 + --aira-t-caption | The total, compact, with its unit word |
| Hole | Inner radius about 62% of outer |
Rules carried from the design system
- Two or three slices only. The agent never draws a fourth; it draws a sorted bar and says why in one line.
- Never 3D, never an exploded (pulled-out) slice, never a second ring.
- A single insight per chart.
- Pie only when there is no total worth showing; otherwise the donut.
Do / don't
Say so in aiRA's answer when shares do not sum to 100% after rounding.
Use a pie for close values such as 34%, 33% and 33%. Nobody can see the difference; use a bar.
Engineering handoff
<AiraDonutChart data={mix} labelKey="type" valueKey="share" total={{ value: 42100, label: "redemptions" }} />Test result
The result of an A/B or campaign test: each variant's difference from control, with its likely range. It says plainly when a result could be chance.
Example
Test result. Variant B, a 7 pm reminder, lifted redemption by 2.1 points, likely between 1.2 and 3.0: a clear result. Variant C, a 9 am reminder, lifted it by 0.6 points, likely between minus 0.4 and 1.6: could be chance. Use the arrow keys to move between values.
| Variant | Lift vs control | Likely range | Verdict |
|---|---|---|---|
| B · 7 pm reminder | +2.1 pts | +1.2 to +3.0 pts | Clear result |
| C · 9 am reminder | +0.6 pts | −0.4 to +1.6 pts | Could be chance |
Use when
Any randomised comparison of a variant with a control.
A before-and-after comparison without a control group. That is a change over time, not a test; use a line and describe association.
States
| State | Mark | Copy |
|---|---|---|
| Clear result | Range fully on one side of zero; --aira-delta-* | "Variant B lifted redemption by 2.1 pts (likely 1.2 to 3.0 pts)" |
| Could be chance | Range crosses zero; context grey | "No clear winner yet. The difference could be chance." |
| Still running | Context grey, progress in the subtitle | "Test running, day 5 of 14. Results so far are not reliable." |
| Too few members | Context grey | "1,240 members per group so far; about 4,000 needed to be sure." |
Anatomy
| Part | Token | Rule |
|---|---|---|
| Zero line | 1px --aira-text-secondary | Stronger than gridlines: it is the "no effect" line |
| Range | 2px line with end caps | The 80% likely range; the level is in the tooltip |
| Point | 10px dot | The measured lift |
| Verdict | --aira-t-label-sm | Right column: the lift, or "Could be chance" |
Copy
- Say "likely range", not "confidence interval"; "could be chance", not "not statistically significant". Technical terms go in the tooltip and the SQL tab.
- Never declare a winner early because a running result looks good.
Engineering handoff
<AiraTestResult metric="redemption rate" unit="points" variants={[{ name: "B · 7 pm reminder", lift: 2.1, low: 1.2, high: 3.0 }, …]} groupSize={12400} status="complete" />Slope chart
Change between exactly two points, for many items at once. The angle of each line is the change.
Example
Slope chart of tier share. Platinum rose from 18% to 22%, Gold from 34% to 38%. Silver fell from 28% to 25% and Base from 20% to 15%. Use the arrow keys to move between values.
| Tier | Q1 2026 | Q2 2026 | Change |
|---|---|---|---|
| Platinum | 18% | 22% | +4 pts |
| Gold | 34% | 38% | +4 pts |
| Silver | 28% | 25% | −3 pts |
| Base | 20% | 15% | −5 pts |
All lines are indigo: direction is already carried by the slope and the signed label. Colour by up or down only when up is always good; here a shrinking Base share is good news too.
Use when
Exactly two points in time (or two conditions) and several items.
Three or more points: use a line or bump chart. One item: a KPI tile with a delta.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Lines | --aira-viz-1, 2px | Highlight and grey when one item is the story |
| End dots | 8px, 1px surface ring | |
| Labels | Left --aira-t-caption; right --aira-t-label-sm, --aira-text-primary | Name and value at both ends; signed change on the right; labels spaced at least 15px |
| Axes | 1px --aira-viz-baseline | Two vertical rules, period labels on top |
Do / don't
Leave at least 14px between labels on each side; nudge colliding labels apart.
Use a slope chart when the two points are months apart and something happened between them. Show the line.
Engineering handoff
<AiraSlopeChart data={tiers} labelKey="tier" from={{ key: "q1", label: "Q1 2026" }} to={{ key: "q2", label: "Q2 2026" }} unit="percent" />Bump chart
Rank over time. It replaces Power BI's ribbon chart, whose ribbons hide the rank changes they are meant to show.
Example
Bump chart of store rank. HSR Layout ranked first every month. Whitefield rose to third in June and fell to fifth in July and August. Use the arrow keys to move between values.
| Store | Mar | Apr | May | Jun | Jul | Aug |
|---|---|---|---|---|---|---|
| HSR Layout | 1 | 1 | 1 | 1 | 1 | 1 |
| Indiranagar | 2 | 3 | 2 | 2 | 2 | 2 |
| Koramangala | 3 | 2 | 3 | 4 | 4 | 3 |
| Whitefield | 4 | 4 | 4 | 3 | 5 | 5 |
| Jayanagar | 5 | 5 | 5 | 5 | 3 | 4 |
Use when
Rank matters more than value, for up to 10 items over time.
The gaps between items matter: rank hides that first may be far ahead. Use a line.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Highlighted line | --aira-viz-1, 2.5px | The item the story is about |
| Other lines | Context grey, 1.5px | |
| Dots | Every period | So each rank is readable |
| Labels | --aira-t-label-sm at the right end | Names in final rank order |
| Rank axis | 1 at the top | Say "1 = highest" in the subtitle |
Engineering handoff
<AiraBumpChart data={ranks} xKey="month" seriesKey="store" rankKey="rank" highlight="Whitefield" />Box plot
Compares how values spread across groups: the middle half, the median, the range and the outliers.
Example
Box plot of basket size. Platinum median 5,600 rupees, middle half 4,200 to 7,100. Gold median 4,000. Silver median 2,900. Base median 1,900. Use the arrow keys to move between values.
| Tier | Median | Middle half | Range | Outliers |
|---|---|---|---|---|
| Platinum | ₹5,600 | ₹4,200 to ₹7,100 | ₹1,800 to ₹9,800 | 2 |
| Gold | ₹4,000 | ₹3,100 to ₹5,200 | ₹1,200 to ₹7,600 | 1 |
| Silver | ₹2,900 | ₹2,200 to ₹3,800 | ₹800 to ₹5,600 | 1 |
| Base | ₹1,900 | ₹1,300 to ₹2,600 | ₹400 to ₹4,100 | 0 |
Most readers have not been taught box plots, so the first row labels its median in words, and the tooltip says "middle half" rather than "interquartile range".
Use when
Comparing spreads across 2 to 8 groups with enough values each (30 or more).
Few values, or readers unfamiliar with the form: use a strip plot, which shows every value.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Box | Fill --aira-seq-2, 1.5px --aira-viz-1 outline | The middle half |
| Median | 2px --aira-text-primary | Labelled on the first row |
| Whiskers | 1.5px --aira-viz-1 with caps | Rule stated in the tooltip |
| Outliers | Hollow 6px dots |
Engineering handoff
<AiraBoxPlot data={baskets} groupKey="tier" valueKey="basket" unit="currency" />Strip plot
Every value as a dot, grouped by category, with the median marked. The honest alternative to a box plot when readers need to see the actual people.
Example
Strip plot of days between purchases. Median 12 days for Platinum, 19 Gold, 28 Silver and 41 Base. Use the arrow keys to move between values.
| Tier | Median days | Members |
|---|---|---|
| Platinum | 12 | 40 |
| Gold | 19 | 40 |
| Silver | 28 | 40 |
| Base | 41 | 40 |
Use when
Up to about 300 values in total, when individual values matter.
Thousands of values: dots merge. Use a box plot or histogram.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Dot | --aira-viz-1, 6px, 1px surface ring | Random vertical jitter within the row |
| Median | 2px --aira-text-primary tick | |
| Axis | Titled, units in the title |
Engineering handoff
<AiraStripPlot data={members} groupKey="tier" valueKey="daysBetween" median />Waffle chart
A single percentage made tangible: 100 squares, one per percent. Reads more naturally than a pie for "how many of every hundred".
Example
Waffle chart: 38 of 100 squares filled. 38% of active members redeemed in August. Use the arrow keys to move between values.
| Status | Share |
|---|---|
| Redeemed | 38% |
| Did not redeem | 62% |
Use when
One share of a whole, for an audience that reads counts better than percentages.
Comparing several shares: use a bar. Values under 1%: squares cannot show them.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Filled square | --aira-viz-1 | Filled left to right, top to bottom |
| Empty square | --aira-viz-grid | |
| Gap | 3px | |
| Label | --aira-t-h2 + caption | The value beside the grid |
Engineering handoff
<AiraWaffleChart value={38} label="of active members redeemed" />Treemap
Parts of a whole in a hierarchy, as nested rectangles. For broad patterns, not exact comparison: area is a weak encoding.
Example
Treemap of spend. Apparel 32% (men 14%, women 12%, kids 6%), beauty 18% (skin care 10%, make-up 8%), grocery 14%, home 12%, tech 8%, pharmacy 8%, other 8%. Use the arrow keys to move between values.
| Category | Sub-category | Share |
|---|---|---|
| Apparel | Men | 14% |
| Apparel | Women | 12% |
| Apparel | Kids | 6% |
| Beauty | Skin care | 10% |
| Beauty | Make-up | 8% |
| Grocery | 14% | |
| Home | 12% | |
| Tech | 8% | |
| Pharmacy | 8% | |
| Other | 8% |
Six categories take viz-1 to viz-6 by size; the seventh becomes "Other" in context grey. Sub-categories share their parent's colour, split by 2px surface gaps.
Use when
Many parts, two levels at most, and the question is "what dominates".
Exact comparison between similar parts: use a sorted bar.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Rectangles | --aira-viz-1 to -6 by top-level category | Squarified layout, largest top-left |
| Gaps | 2px surface | |
| Labels | --aira-t-label-sm + share | Only in rectangles over 64 × 36px; the rest in the tooltip |
| Other | Context grey | Past six categories |
Engineering handoff
<AiraTreemap data={categories} labelKey="name" valueKey="share" childrenKey="kids" />Pareto chart
Concentration: how few items make most of the total. Bars and the cumulative line share one axis, % of total, so there is no second axis.
Example
Pareto chart of 12 stores. The top 6 stores make 80.4% of sales; HSR Layout alone makes 21.9%. Use the arrow keys to move between values.
| Store | Share | Cumulative |
|---|---|---|
| HSR Layout | 21.9% | 21.9% |
| Indiranagar | 18.1% | 40.0% |
| Koramangala | 14.2% | 54.2% |
| Whitefield | 11.4% | 65.6% |
| Jayanagar | 8.5% | 74.0% |
| Malleshwaram | 6.3% | 80.4% |
| BTM Layout | 5.0% | 85.4% |
| Electronic City | 4.3% | 89.6% |
| Hebbal | 3.7% | 93.3% |
| Yelahanka | 2.9% | 96.2% |
| Marathahalli | 2.3% | 98.5% |
| Banashankari | 1.5% | 100% |
Three mark types, so a key names all three: bars, the grey bars and the running-total line. The classic Pareto chart has two axes; showing bars as % of total puts both on one scale, which keeps the no-dual-axis rule without losing anything.
Use when
"Which few items matter most": stores, SKUs, members, complaint reasons.
Items are roughly equal: the chart just shows a straight line. Say so in a sentence instead.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Bars | --aira-viz-1 for the items reaching 80%; context grey for the rest | Sorted high to low |
| Cumulative line | 1.5px --aira-text-secondary | Neutral, so it does not compete with the bars |
| 80% line | 1px dashed --aira-text-secondary | Labelled "80% of sales" |
| Axis | One, 0 to 100% | Never a second axis |
Engineering handoff
<AiraParetoChart data={stores} labelKey="store" valueKey="sales" threshold={80} />Sankey chart
Movement between states. For loyalty, the question it answers best is who moved up and down a tier.
Example
Sankey of tier migration. 4,200 members moved up a tier and 1,700 moved down; 44,200 stayed in their tier. Use the arrow keys to move between values.
| From | To | Members |
|---|---|---|
| Platinum | Platinum | 1,800 |
| Platinum | Gold | 300 |
| Gold | Platinum | 600 |
| Gold | Gold | 5,600 |
| Gold | Silver | 600 |
| Silver | Gold | 1,400 |
| Silver | Silver | 9,000 |
| Silver | Base | 800 |
| Base | Silver | 2,200 |
| Base | Base | 27,800 |
Flows are coloured by what they mean: up in --aira-delta-positive, down in --aira-delta-negative, staying in context grey. Tier nodes keep their locked steps.
Use when
Up to 8 states on each side, and the flows between them are the story.
More states, or more than two steps: use a migration matrix table, which stays readable.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Nodes | Locked tier steps, 12px wide | Same order on both sides |
| Flows | Delta colours by direction; context grey for no change | Width = members |
| Labels | --aira-t-caption, secondary | Tier and total outside each node |
| Hover | Dims every other flow | The tooltip names from, to and count |
Engineering handoff
<AiraSankeyChart flows={migration} sourceKey="from" targetKey="to" valueKey="members" order={tierOrder} colorBy="direction" />Maps
Where a pattern is geographic. Regions use a tile-grid map by default: one equal square per state, in a rough geographic layout.
Example: tile-grid map
Tile-grid map of redemption rate by state. Karnataka 46% and Tamil Nadu 43% lead; Meghalaya 12%, Bihar 14% and Assam 15% trail. Use the arrow keys to move between values.
| State | Redemption rate |
|---|---|
| Karnataka | 46% |
| Tamil Nadu | 43% |
| Kerala | 39% |
| Maharashtra | 38% |
| Telangana | 37% |
| Andhra Pradesh | 35% |
| Delhi | 34% |
| Goa | 33% |
| Gujarat | 31% |
| Haryana | 29% |
| Punjab | 27% |
| Uttarakhand | 26% |
| Himachal Pradesh | 25% |
| Madhya Pradesh | 24% |
| Rajasthan | 23% |
| Uttar Pradesh | 22% |
| West Bengal | 21% |
| Chhattisgarh | 19% |
| Odisha | 18% |
| Jharkhand | 17% |
| Jammu and Kashmir | 16% |
| Assam | 15% |
| Bihar | 14% |
| Meghalaya | 12% |
Why tiles: every state gets the same size, so Rajasthan does not outweigh Kerala just by area, and there are no boundaries to get wrong. Maps published in India must show the official national boundaries; a generic map library often does not.
Use when
The geography itself is the point: regional clusters, coverage gaps, catchments.
A "where" question a sorted bar of regions answers just as well, which is most of them.
Anatomy
| Part | Token | Rule |
|---|---|---|
| Tile | --aira-seq-1 to -6, 2px radius, 4px gap | Pale steps get a 1px --aira-viz-baseline outline |
| Tile text | State code in --aira-t-label-sm, value in caption | Text token chosen by contrast |
| Scale key | Always | Bins stated |
| Values | Rates only | Never totals: a total map just shows where people live |
Variants
| Variant | When | Status |
|---|---|---|
| Tile-grid map | Regions, states, zones | Default |
| Filled boundary map | Only with approved official boundary data from the product | Allowed, needs approved data |
| Store dot map | Store locations inside a city, dot size by value, size key | Allowed, needs approved base map |
Do / don't
Show full state names in the tooltip and table.
Use a free web map tile service with unknown boundary depiction for Indian orgs.
Engineering handoff
<AiraMap variant="tiles" layout="india-states" data={states} idKey="code" valueKey="rate" unit="percent" />Anomaly markers
A line chart setting that marks values outside their expected range. Finding the unusual day is exactly what an analyst like aiRA should do; the chart shows the what, and aiRA's prose offers the possible why.
Example
Line chart of daily redemptions in August with the expected range. 18 August had 3,220 redemptions, 2.3 times expected. 26 August had 560, 0.4 times expected. Use the arrow keys to move between values.
| Day | Redemptions | Note |
|---|---|---|
| 18 Aug 2026 | 3,220 | Unusually high: 2.3× expected |
| 26 Aug 2026 | 560 | Unusually low: 0.4× expected |
The band is --aira-seq-1. Anomalies are a filled 12px dot in the series colour, with a 2px surface ring and a factual label. Never red: an unusual day is information, not an error.
Rules
- Mark at most three anomalies per chart; more means the expected range is wrong.
- Labels state the fact ("2.3× expected"). A possible cause goes in aiRA's prose, worded as possible ("A preview SMS went out that morning").
- State how "expected" was worked out in the band's tooltip.
| Prop | Values |
|---|---|
expected | { low, high } per point, or "auto" |
anomalies | max 3, each with a label |
Engineering handoff
<AiraLineChart data={days} xKey="day" yKey="redemptions" expected={{ low: "p10", high: "p90" }} anomalies={flagged} />Reference lines and error bars
What Power BI's analytics pane adds: target, average, median and percentile lines, and error bars for values measured on a sample.
Example
Column chart of NPS with likely ranges. Platinum 52, likely 46 to 58, clearly above the target of 45. Gold 44, likely 39 to 49, could be either side. Silver 38 and Base 29 are below. Use the arrow keys to move between values.
| Tier | NPS | Likely range |
|---|---|---|
| Platinum | 52 | 46 to 58 |
| Gold | 44 | 39 to 49 |
| Silver | 38 | 33 to 43 |
| Base | 29 | 22 to 36 |
Only the bar whose whole range clears the target is indigo. Gold's value is close to target, but its range crosses it, so it stays grey.
Line styles
| Line | Style | Label |
|---|---|---|
| Target | 1px dashed --aira-text-secondary | "Target 45", right end |
| Average | 1px dotted --aira-text-tertiary | "Average 41" |
| Median | 1px dotted --aira-text-tertiary | "Median 38" |
| Percentile | 1px dotted --aira-text-tertiary | "Top 10%: 62" |
| Min and max | Not drawn by default | Say them in the tooltip; lines at the extremes mostly add clutter |
| Error bars | 1.5px --aira-text-secondary with 12px caps | Level in the tooltip |
Rules
- Two reference lines per chart at most.
- Error bars on any value from a sample: surveys, tests, estimates.
- Say "likely range" in the interface.
Engineering handoff
<AiraBarChart data={tiers} xKey="tier" yKey="nps" errorBars={{ low: "low", high: "high" }} referenceLines={[{ kind: "target", value: 45 }, { kind: "average", value: 41 }]} />Moving average and calculations
Daily loyalty data swings with the week. A moving average shows the trend underneath without hiding the real days.
Example
Line chart of daily redemptions from June to August with a 7-day moving average rising from about 1,300 to about 1,500. Use the arrow keys to move between values.
| Period | 7-day average |
|---|---|
| First week of June | about 1,300 |
| Last week of August | about 1,500 |
The daily line stays, thin and in context grey; the average is the emphasis. A moving average is always labelled with its window.
Power BI visual calculations, and when each is honest
| Calculation | Use it for | Rule |
|---|---|---|
| Moving average | Smoothing weekly or daily swings | Name the window ("7-day average"); window at least one full cycle |
| Running total | Progress toward a period goal | Subtitle says "cumulative since 1 Jan"; never presented as growth, because it can only go up |
| Percent of total | Shares | Say "share" and the total it is a share of |
| Rank | Leader boards | Use a bump chart over time; show the gap to the next item in the tooltip |
| Period over period | Deltas | Always name the comparison; like for like on partial periods |
Engineering handoff
<AiraLineChart data={days} xKey="day" yKey="redemptions" movingAverage={{ window: 7 }} showRaw="context" />Result tables
How aiRA shows query results as tables in the artifact panel, whether it returns one table or several. The format, tabs and behaviour are the Artifact-preview page's. Alignment follows this page's rule instead: numbers right, words left.
Example
| Member ID | Tier | Points issued | Points redeemed | Last transaction |
|---|
| Month | Issued (B) | Expired (B) | Redeemed (B) |
|---|
Switch tables with the tabs, page through the 62,76,905 rows, and change the page size. The member rows are sample data.
Use when
A query returns rows: one table, or several tables from one cell.
A handful of numbers that answer the question. Use a chart, a KPI tile or a sentence in the answer.
Anatomy
| Part | Type | Colour | Rule |
|---|---|---|---|
| Cell bar | Only when there are two or more tables. Holds Export all to FTP, which covers every table in the cell. No count of tables | ||
| Tabs | --aira-t-label-md | Selected --aira-text-link with a 3px --aira-border-brand underline; others --aira-text-tertiary | One per table, named by the table. Hover is a neutral --aira-border-strong underline that never outweighs the selected tab. Too many tabs scroll sideways, and the edge clips the last one |
| Table toolbar | --aira-t-body-sm | --aira-text-tertiary for the count | Inside the table's own panel, so its actions read as this table's: "Total 62,76,905 rows" in the org locale, then CSV, copy and Export to FTP |
| Header | --aira-t-label-md (14/20 medium) | --aira-text-primary on --aira-bg-surface-alt | 1px --aira-border-default below. Padding --aira-sp-3 × --aira-sp-5. Stays in place while rows scroll. Sentence case |
| Cells | --aira-t-body-sm (13/20) | --aira-text-primary | Padding --aira-sp-4 × --aira-sp-5. 1px --aira-border-subtle between rows. Hover --aira-bg-hover. No zebra, no colour, no bars in cells |
| Body | The table sits flat in the panel, with no border or box around it. It is at most 560px tall and scrolls inside; wide tables scroll sideways | ||
| Footer | --aira-t-label-sm | --aira-text-secondary; current page --aira-text-link with a --aira-border-brand outline | Only when the rows run past one page. 10 / page menu (10, 20, 50, 100; a custom menu, never a native select) on the left, pages with ‹ › and … on the right |
Alignment
| Column holds | Align | Examples |
|---|---|---|
| Words, names, labels | Left | Tier, store, campaign |
| Dates and times | Left | 18 Sep 2026 |
| IDs and codes | Left | M-104233 |
| Numbers, currency, percentages | Right, with tabular numerals | 12,400 · ₹2.4L · 23.12% |
| Headings | Same as their column | "Points issued" sits right over its numbers |
The Artifact-preview page left-aligns every column. This page's rule wins: right-aligned figures line up their place values, so they can be compared down the column.
Behaviour
- One table: no tabs and no cell bar. The table toolbar sits directly above the table.
- Several tables: the first tab is the table that answers the question. Each tab keeps its own page and page size when you switch away and back.
- Paging starts at 10 rows. Changing the page size goes back to page 1, and every page change scrolls the table to its top.
- CSV downloads every row of that table. Copy copies the rows on screen as tab-separated text, and its icon becomes a tick for 1.5 seconds.
- Charts and tables together: the charts come first, then the tables in tabs below them (see Chart preview panel).
States
- Loading: skeleton rows under the real header.
- No rows: "No rows match this query" with the filter or period that caused it, and a way to change it.
- Error: plain language and Try again. No codes.
Accessibility
- Tabs are a
tablist; the arrow keys move between tabs. - Pages are real buttons; the current page carries
aria-current="page". - The page-size menu is a
listboxthat works from the keyboard: arrows, Enter and Escape. - Every icon button has an accessible name that includes the table, such as "Copy Points by member".
Do / don't
Keep each table's actions in its own toolbar.
Put per-table actions on the tab bar, where they read as covering every table.
Show the full row count, in the org locale.
Write "showing the first 8". The pager says where you are.
Scorecard
Several metrics against their targets in one view: Power BI's goals visual, built from a table, bullet bars and sparklines. The standard format for a loyalty programme's monthly review.
Example
| Goal | Value | Target | Progress | 6-month trend | Status |
|---|---|---|---|---|---|
| Active members | 50,100 | 52,000 | Behind · 96% of target | ||
| Redemption rate | 38% | 40% | Behind · −2 pts | ||
| Repeat purchase rate | 31% | 30% | Ahead · +1 pt | ||
| Tier upgrades (quarter) | 4,200 | 3,500 | Ahead · 120% of target | ||
| Points liability (lower is better) | ₹42.2L | under ₹45.0L | On track · ₹2.8L headroom |
Status is plain text with the gap, not a coloured chip: table cells stay monochrome. The one visual cue per row is the bullet bar, with the target as a dark tick.
Use when
3 to 10 goals reviewed together, each with a target.
Metrics without targets: a KPI tile row. One goal: a bullet chart.
Anatomy
| Column | Content | Rule |
|---|---|---|
| Goal | Metric name, with "(lower is better)" where it applies | Text, left |
| Value and target | Org-locale numbers | Right-aligned |
| Progress | Bullet bar: --aira-viz-1 bar, --aira-text-primary target tick | One scale per row, stated in the tooltip |
| Trend | Sparkline, 1.5px --aira-viz-1, last point marked | Same period for every row |
| Status | Ahead · On track · Behind, plus the gap | Plain text, never colour alone |
Engineering handoff
<AiraScorecard goals={goals} columns={["value", "target", "progress", "trend", "status"]} />Matrix
Values across two dimensions with an expandable row hierarchy, as Power BI's stepped matrix does. Every figure is a number, right-aligned; there are no bars inside cells.
Example
| Zone and store | Jun | Jul | Aug | Total | Share of total |
|---|---|---|---|---|---|
| South | 40,120 | 42,860 | 45,210 | 1,28,190 | 71.0% |
| HSR Layout | 16,980 | 17,640 | 18,420 | 53,040 | 29.4% |
| Indiranagar | 13,420 | 14,390 | 15,230 | 43,040 | 23.8% |
| Koramangala | 9,720 | 10,830 | 11,560 | 32,110 | 17.8% |
| West | 16,340 | 17,410 | 18,640 | 52,390 | 29.0% |
| Andheri | 9,110 | 9,760 | 10,420 | 29,290 | 16.2% |
| Baner | 7,230 | 7,650 | 8,220 | 23,100 | 12.8% |
| Total | 56,460 | 60,270 | 63,850 | 1,80,580 | 100% |
Group rows collapse and expand with a borderless chevron button. Shares are plain right-aligned percentages: a table is for exact values, so bars stay out of the cells. When the pattern matters more than the numbers, use a chart.
Rules
- Group and total rows in
--aira-fw-medium; child rows indented by--aira-sp-12. - Two levels of grouping at most in a chat answer; three on a dashboard.
- No data bars in cells: the percentage alone. If the pattern matters more than the numbers, show a chart instead.
- Conditional colour only as a heatmap table (see Heatmap); never traffic-light cells.
Engineering handoff
<AiraDataTable data={rows} groupBy={["zone"]} expandable columns={[…, { key: "share", unit: "percent" }]} />Chart preview panel
How several charts, or charts with tables, appear in the preview panel beside the chat on desktop web. Every chart is its own chart card; the panel only arranges the cards. Try every case in the live prototype. Mobile rules come later.
Example
Store and tier activity
Column chart of redemptions by store. HSR Layout led with 18,420. Use the arrow keys to move between values.
| Store | Redemptions |
|---|---|
| HSR Layout | 18,420 |
| Indiranagar | 15,230 |
| Koramangala | 11,910 |
| Whitefield | 9,640 |
| Jayanagar | 7,120 |
| Malleshwaram | 5,310 |
Line chart of active members by tier. Silver rose from 11,200 to 12,400, Gold from 6,800 to 8,000, Platinum from 2,100 to 2,600. Use the arrow keys to move between values.
| Month | Silver | Gold | Platinum |
|---|---|---|---|
| Mar 2026 | 11,200 | 6,800 | 2,100 |
| Aug 2026 | 12,400 | 8,000 | 2,600 |
The panel at the width it opens at, 610px. Hover a card for its actions. With five or more charts, a Charts menu joins the toolbar.
Where charts appear
| Surface | What it holds |
|---|---|
| Chat answer | One chart at most, after one or two sentences with the finding. |
| Artifact card in the chat | When an answer has more than one chart, or charts with tables: the artifact's name with its version underneath. Clicking it opens the preview panel. |
| Preview panel | The charts and tables of one answer, arranged as below. |
| Dashboard | The same chart cards in a grid (see Dashboards). |
Layout
| Panel | Columns | Rule |
|---|---|---|
| Default, beside the chat: about 610px, up to 780px | One | Cards stack in one column you scroll. Two cards side by side would be about 280px each, too narrow for a 12-month axis. |
| Widened: the chat is hidden and content is 900px or more | Two | Charts marked half width pair up: short comparisons, rankings, tier bars. The first chart and every trend over time stay full width. |
| Phone | Waiting on the mobile rules. |
How many charts
| Charts | Toolbar | Notes |
|---|---|---|
| 1 | Preview / Code only | The card's own CSV button covers the download. |
| 2–4 | Preview / Code · Download all as CSV | Download all gives a .zip with one CSV file for each chart and table. Its tooltip says so. |
| 5–8 | Adds a Charts menu | A text menu with one arrow and no leading icon. It lists the chart names; picking one scrolls to that card. |
| More than 8 | aiRA offers to build a dashboard instead, or to show the four that matter most. |
No count label anywhere: no "5 charts", no "2 charts · 2 tables". The stacked cards already show it.
Order and content
- The chart that answers the question comes first, full width, then the supporting charts.
- Charts before tables. Tables go below the charts in underline tabs, in the Result tables format.
- Every card keeps its full anatomy: title, subtitle with the period, trust line. There is no shared "data from" line for the whole panel.
- One chart per card. A chart with two y-axes becomes two cards on the same months. A grid of subplots becomes one card per chart.
- Preview / Code: Code shows the code aiRA ran. It returns chart data for the product to draw, never an image of the charts.
States
- Drawing: each card shows its title and subtitle, a skeleton in the plot's shape and "Drawing this chart…", and fills in as its own query finishes.
- One chart fails: only that card changes. It reads "Couldn't draw this chart", then "The query took too long to finish.", then a Try again button. The other cards are untouched.
Do / don't
Give every chart its own card, with its own actions and trust line.
Send a matplotlib image with several charts drawn into it. Nothing in it can be hovered, read by a screen reader or downloaded as data.
Name the format on every download: "Download all as CSV", "CSV".
Label a button "Download" or "Download all" and leave people guessing what they get.
Dashboards
A dashboard is chart cards in a grid, re-run live when it opens. Each tile follows the Chart card block exactly: the same title, subtitle, trust line, actions, hover and full screen as in the preview panel. These rules arrange the tiles.
Reading order
- Header: title, period, filters and the as-of time.
- Row 1: two to four KPI tiles.
- Row 2: the main trend, full width. This is the chart that answers the dashboard's main question.
- Row 3: breakdowns, two or three across.
- Row 4: the detail table, in the Result tables format.
Grid
| Tile | Columns at lg and xl | Use |
|---|---|---|
| KPI | 3 (four across) or 4 (three across) | Single numbers |
| Full | 12 | The main trend, detail tables |
| Half | 6 | Two related breakdowns side by side |
| Third | 4 | Three small breakdowns |
- 12 columns at
lgandxl, 8 atmd, 4 atsm. - Gap between tiles
--aira-sp-4; page padding--aira-sp-6. - Tiles in a row share one height, and their plots share one baseline, so axes line up.
- No chart tile narrower than 4 columns.
Consistency across the page
| Rule | Why |
|---|---|
| One period for the whole dashboard, stated once in the header. A tile that differs says so in its subtitle | Readers assume tiles are comparable |
| The same category keeps the same colour in every tile | Otherwise Gold is indigo in one chart and green in the next |
| Charts meant to be compared share a scale | Two bars of equal height must mean equal values |
| One comparison basis for all deltas: previous period or last year | Mixed bases make the KPI row contradict itself |
| Every tile keeps its own trust line | Tiles can come from different tables |
Limits
- Up to 6 charts visible in one screen at
lg, and up to 12 on a dashboard. - More than that: group them into tabs, one question per tab.
- One highlight colour per dashboard for "this is the story".
Filters and cross-highlighting
- Dashboard filters sit in the header and apply to every tile. Applied filters stay visible as chips, with clear all.
- A tile with its own filter names it in its subtitle ("Gold tier only").
- Changing a filter re-runs the queries and updates every as-of time.
- Clicking a bar highlights that item in every tile; it does not filter. The highlight shows as a removable chip in the header, and Escape clears it.
- Asking a follow-up about a data point opens a new chat, never a conversation inside the dashboard.
Mobile
On iOS and Android, aiRA runs in the phone's browser with the same code as desktop; a native app comes later. Every chart is the same chart card as on web, with the same title, subtitle, plot, key and trust line. Only the controls around the card change for touch. Try it in the live prototype.
Example
Store and tier activity
Bar chart of redemptions by store. HSR Layout led with 18,420.
| Store | Redemptions |
|---|---|
| HSR Layout | 18,420 |
| Indiranagar | 15,230 |
| Koramangala | 11,910 |
| Whitefield | 9,640 |
| Jayanagar | 7,120 |
| Malleshwaram | 5,310 |
Line chart of active members by tier.
| Month | Silver | Gold | Platinum |
|---|---|---|---|
| Mar 2026 | 11,200 | 6,800 | 2,100 |
| Apr 2026 | 11,050 | 6,950 | 2,180 |
| May 2026 | 11,500 | 7,200 | 2,250 |
| Jun 2026 | 11,800 | 7,050 | 2,300 |
| Jul 2026 | 12,100 | 7,600 | 2,450 |
| Aug 2026 | 12,400 | 8,000 | 2,600 |
Tap a chart to open it full screen. Touch and hold to read exact values.
A 375px phone. Tap a card to open it full screen, or its "…" for the menu.
What stays the same
- The chart card's anatomy, titles, colours, keys and trust line.
- The Result tables format, alignment by column type, and monochrome cells.
- Loading, error and empty states, word for word.
- The visible size and shape of every button. Only the tap area grows (see Touch targets).
What changes on touch
| Part | Web | Mobile |
|---|---|---|
| Panel header | Title left; widen and close at the right | Title centred; close at the top right. No widen |
| Preview / Code | Shown | Not shown |
| Toolbar | In the panel toolbar | A row under the header: Charts menu on the left (five charts or more), Download all as CSV on the right (Download CSV for one chart) |
| Columns | One, or two when widened | Always one |
| Card actions | On hover and keyboard focus | A "…" button on each card opens a bottom sheet: View as table, Copy data, Download CSV, Open full screen, Cancel |
| Opening full screen | The full-screen button | Tap the card, or Open full screen in its menu. There is always a visible way in, never only the gesture |
| Full screen | A window over the page | Fills the phone. "…", a divider and close at the top right. The chart is taller; View as table fills the screen with the first column fixed |
| Reading values | Hover | Touch and hold a mark, slide to move along the chart, lift to clear after 1.5 seconds. A hint under the cards says so |
| Charts menu | A dropdown | A bottom sheet listing the chart names |
| Tables | CSV, copy and Export to FTP | CSV and copy; Export to FTP stays on desktop. With more than three columns the first column stays fixed, and a note says how many columns scroll sideways. The pager shows fewer page numbers |
| Copy confirmation | Tick on the copy button | Tick on the "…" button that opened the menu |
Touch targets
- Buttons look exactly as on web: 28px on cards, 32px in headers, the same radius and fill.
- Each gets an invisible 44px tap area around it. Neighbouring buttons sit at least 12px apart so tap areas never overlap.
- No hover on touch. A tapped button shows only a pressed state (
--aira-bg-pressed) while the finger is down, so it never stays grey. - Sheets and full screen respect the safe areas (
--aira-safe-*) for the notch and the home indicator.
Charts at phone width
- Twelve labelled columns do not fit, so a column chart becomes horizontal bars when its card is narrower than 480px. The data and labels stay the same.
- The month axis shows every second or third month, and the year again wherever it changes.
- Half-width pairing never applies: every chart is full width.
- Direct labels stay; when line ends would collide, they keep at least 15px apart.
Do / don't
Keep the chart card identical to web, and change only the controls around it.
Draw a separate mobile chart, or drop the trust line to save space.
Give every gesture a visible control too: tap to open full screen, and Open full screen in the menu.
Put close on the left because a platform sheet does. It sits at the top right on every platform.
Power BI coverage
Every visual on Microsoft's Power BI visualizations overview, checked 23 Sep 2026, and where it lives in aiRA.
| Power BI | In aiRA | Status |
|---|---|---|
| Bar and column charts | Bar chart | Stable block |
| Line charts | Line chart | Stable block |
| Area charts | Line chart, area setting | Stable block |
| Combo charts | Bar chart with a same-unit line; different units become two stacked charts | Rule |
| Ribbon charts | Bump chart | This release |
| Waterfall charts | Waterfall chart | Core release |
| Pie and donut | Donut chart (2 or 3 parts) | Core release |
| Treemaps | Treemap | This release |
| Funnel charts | Funnel chart | Core release |
| Scatter, bubble, dot plot | Scatter chart, dot plot | Core release |
| Tables | Data table | Stable block |
| Matrix | Matrix with expandable rows | This release |
| Maps | Tile-grid map; boundary and dot maps with approved data | This release |
| Cards | KPI tile (AiraMetricTile) | Stable block |
| KPI visual | KPI tile with target, or bullet chart | Core release |
| Gauge | Bullet chart | Core release |
| Goals | Scorecard | This release |
| Decomposition tree | Drill sequence of sorted bars, each "Ask about this" opening a new chat | Rule |
| Key influencers | Sorted driver bars, described as association, not cause | Next release |
| Smart narrative | aiRA's finding title and prose | Rule |
| Anomaly detection | Anomaly markers on the line chart | This release |
| Slicers | Filter bar (on the design system's watchlist) | Watchlist |
| Q&A visual | The aiRA chat itself | Rule |
| Tooltips and report-page tooltips | Chart tooltip; one sparkline at most, anything bigger is "Ask about this" | Core release |
| Analytics pane: reference lines, forecast, error bars | Reference lines, error bars, forecast setting | This release |
| Visual calculations | Moving average and calculation rules | This release |
| Small multiples | Small multiples wrapper | Next release |
| Conditional formatting | Heatmap table, or the one allowed delta colour. No data bars in cells | This release |
| Themes | aiRA tokens; no per-report themes | Rule |
| Image, text box, shapes, buttons, navigators, Power Apps, paginated, R and Python | Not charts. aiRA generates dashboard scaffolding to each customer's request | Out of scope |
Proposed tokens
What this page needs that the design system does not have. On this page they are named --aira-proposed-*, so they can never collide with or redefine a real token.
| Proposed token | Light | Dark | Why |
|---|---|---|---|
--aira-proposed-viz-context | --aira-slate-300 | --aira-slate-600 | Context grey for highlight and grey. The site uses raw --aira-slate-300, which has no dark value |
--aira-proposed-div-neg-1 to -3 | #FBD5BE · #F08A4B · #C2410C | #4A2210 · #E0793A · #FB923C | Orange negative side of the diverging palette, replacing red. Starting values, built out from --aira-delta-negative |
| Stroke widths | 2 · 2.5 · 1.5px | same | Standard, emphasis and context lines (the site's current values) |
| Bar radius | 2px | same | Value end of bars (the site's current value) |
--aira-proposed-viz-dim | 0.35 | same | How far other marks fade while one is hovered or focused |
| Partial-period pattern | to design | to design | Marks the incomplete last period on live data |
Orange diverging ramp, as proposed
Divergences found
Places where the live design system disagrees with itself or with the rules on this page. Each needs a decision from the design system owner.
| # | What | Measured | Suggested fix |
|---|---|---|---|
| 1 | Dark-mode categorical neighbours look alike | viz-1 vs viz-2: 6.3 with normal vision (15 needed), 0.8 for red-green colour blindness · viz-5 vs viz-6: 13.0 · viz-2 vs viz-3: 4.0 for tritan | Re-step viz-2 and viz-6 in dark |
| 2 | Push and In-app channel colours look alike | cat-push vs cat-inapp: 6.9 light, 5.3 dark, with normal vision | Move In-app away from indigo |
| 3 | Four chart colours are below 3:1 on light surfaces | viz-2 2.57, viz-4 2.30, cat-whatsapp 2.35, cat-email 2.93 | Darken the light values; until then, label every mark |
| 4 | Diverging palette uses red for negatives | --aira-div-neg-* are red steps | Orange ramp above |
| 5 | Dark negative diverging steps out of order | neg-2 #F58B8B is lighter than neg-3 #F55959 | Fixed by the orange ramp |
| 6 | Chart labels below the type ramp | Site examples use 10px and 11px | Use --aira-t-caption (12px) |
| 7 | KPI example hard-codes currency | value="₹14.2L" | Pass the number; the formatter applies org locale |
| 9 | Title rule: the site says "the title is the takeaway" | aiRA titles name the chart ("Daily redemptions") | Deliberate aiRA override: the finding goes in aiRA's answer and annotations |
| 10 | Channel colours: the site locks a channel to its colour everywhere | aiRA draws a single series in indigo, even for one channel | Deliberate aiRA override: channel colours only when channels are compared |
| 8 | Web channel colour is a grey | cat-web reads as grey and competes with context grey | Give Web a hue, or reserve grey for context only |
Colour differences measured with a colour-blindness simulation validator (OKLab ΔE × 100); contrast is WCAG contrast against --aira-bg-surface.