Widgets
Widgets are the atoms of every Emban dashboard. Each widget is a saved configuration that says what to query, how to render it, and where it lives on the 12-column grid. The builder exposes 19 chart variants across 6 top-level kinds, and most of the work of building a dashboard is picking the right kind for the question you're answering.
kind (which family of visualization) and a chart (the specific variant). Kind decides which query shape is allowed; chart decides how that shape is drawn. You can swap chart without re-authoring the query.The six kinds
stat (big number + delta), progress (value vs. target as a bar), gauge (arc with optional zone bands).
line, area, bar, stacked_bar (needs a group-by), dual_axis (two metrics on separate y-axes), waterfall (cumulative delta).
horizontal_bar, donut, list, bar, funnel, treemap, radar, scatter (two aggregates + optional size dimension).
table (ranked rows with share and change), pivot (2D matrix from two group-bys).
heatmap.
text.
The query model
Every widget (except text) has a query block that describes the data to fetch. The fields that matter vary by kind:
| Field | Used by | Meaning |
|---|---|---|
event_name | All | Which event to aggregate (e.g. api.call). Required. |
metric | All | How to aggregate: count, count_unique, count_unique_approx, sum, avg, p95, p99. |
numeric_prop | sum / avg / p95 / p99 | Which numeric prop to aggregate. Required when metric isn't count. |
group_by | breakdown, table, pivot, stacked_bar | String prop to split on. Required for breakdown kinds. |
period | All | Lookback window. Default 30d. Accepts 1h, 24h, 7d, 30d, etc. |
granularity | timeseries | Bucket size. hour, day, week, month. |
compare | kpi, breakdown, table | previous_period or previous_year — attaches previous_value + delta_pct for scalar widgets. |
limit | breakdown, table | Cap on rows returned. Default varies by chart; typical 10–20. |
second_metric, second_numeric_prop | dual_axis | Second aggregate drawn on the right-hand y-axis. |
y_metric, y_numeric_prop, size_metric, size_numeric_prop | scatter | Second (y-axis) and optional third (size) aggregates. The primary metric is x. |
group_by2 | pivot | Second group-by for the matrix columns. |
dimension_filters | All | Map of {string_prop: exact_value} baked into this widget (in addition to dashboard-level filters). |
The viz model
The viz block decides rendering. The same query can be shown as multiple chart variants by swapping viz.chart:
| Field | Meaning |
|---|---|
chart | Specific variant within the kind (e.g. line within timeseries). |
format | Number formatting: number, compact_number (1.2M / 42k), percent. |
unit | Suffix label: ms, cents, usd, percent, tokens, bytes. |
color | Accent color for the primary series (hex). Defaults to the dashboard theme's accent. |
target | Target value for progress and gauge (also drawn as a reference line on timeseries). |
reference_lines | Array of {value, label?, color?, style?} drawn as horizontal markers on line/area/bar/stacked_bar. Style is solid, dashed (default), or dotted. |
zones | Array of {min, max, color} coloring bands on a gauge's arc (e.g. 0–0.6 green, 0.6–0.85 amber, 0.85+ red). Values are fractions of target. |
markdown | Raw markdown body for kind=text widgets. Supports headings, bold/italic, lists, links. |
Two widget-level flags affect both query and viz:
invert_delta— flip the up/down sign convention. Latency, error rate, cost — rising is bad, so a delta of +12% should render red. Set this totrueon those widgets.hidden/locked— embed-side permissions.hiddenremoves the widget from customer-facing renders entirely;lockedrenders it but disables drill/filter interactions. See Permissions.
The layout grid
Dashboards use a 12-column responsive grid with 72px row height and 12px gutters. Every widget's layout is {x, y, w, h} in grid units:
{
"id": "w_api_volume",
"kind": "timeseries",
"title": "API calls per day",
"query": {"event_name": "api.call", "metric": "count", "granularity": "day", "period": "30d"},
"viz": {"chart": "area", "color": "#6d9edb", "reference_lines": [{"value": 50000, "label": "SLO floor"}]},
"layout": {"x": 0, "y": 0, "w": 6, "h": 4}
}
The draft canvas lets you drag and resize interactively — the inspector panel writes these coordinates as you move things. Widgets can overlap while you arrange, but collisions are resolved at save time so the stored layout is stable.
The inspector
The right-hand pane on every widget edit screen is the inspector. It has four tabs, each editing a subset of the widget config:
chart, choose format and unit, pin a color, add reference lines or gauge zones. The preview re-renders on every change.
target for progress/gauge widgets. Flip invert_delta if rising-is-bad. For dual-axis, pick the second metric.
hidden or locked on customer embeds, and attach filter_bindings so the widget updates on cross-filter clicks. Requires Growth plan or above.
Design patterns
invert_delta=true keeps the ↑ red / ↓ green convention intuitive. The single most common source of "my dashboard looks wrong" tickets.
limit explicitly and add an "Other" rollup if the long tail matters. Donut charts especially — a donut with 40 slices is a pie-chart crime.
reference_line on the trend chart. The line labeled "SLO 500 ms" does more work than any KPI card alone — it puts the value in context.
A full widget config
{
"id": "w_model_breakdown",
"kind": "breakdown",
"title": "Tokens by model (30d)",
"invert_delta": false,
"query": {
"event_name": "api.call",
"metric": "sum",
"numeric_prop": "tokens",
"group_by": "model",
"period": "30d",
"compare": "previous_period",
"limit": 10,
"dimension_filters": {"status": "success"}
},
"viz": {
"chart": "donut",
"format": "compact_number",
"unit": "tokens",
"color": "#6d9edb"
},
"layout": {"x": 6, "y": 4, "w": 6, "h": 6}
}
hidden/locked/filter_bindings on customer-facing embeds, and Query Builder to cross-check a widget's numbers against raw SQL.