Data & analytics
BI Workspace
Dashboards built on the same catalog, prepared tables and metrics your agents use — so a chart and an agent answering about it cannot disagree.
Open Data → BI Workspace. Dashboards live in workspaces and folders; each is a grid of resizable widgets you drag into place.
Building a dashboard
- 1
Create a dashboard
Give it a home folder — folders are how you keep dev and production content apart. - 2
Add a visual
The right-hand builder pane takes your source (table, prepared table, or a governed metric), the fields to plot, and the chart type. - 3
Or describe it
The AI tab writes a whole dashboard, or a single visual, from a sentence. Read the generated query before trusting the chart. - 4
Arrange and publish
Drag and resize, then publish to make it visible to the people you've shared it with.
You can also send a chart straight from the SQL workbench with Add to dashboard once a query returns something worth keeping.
How a widget gets its data
Every chart widget stores the SQL it ran and a snapshot of the rows that came back. Which of those a viewer sees depends on the widget's data mode, and the difference decides freshness, cost, and — in one case worth reading carefully — whether the number is right.
| Mode | What happens at view time | Use it when |
|---|---|---|
| Import (default) | The stored snapshot renders. No query runs, nothing touches your warehouse, load is instant. | Almost always. Pair it with a refresh schedule. |
| Direct | The widget's SQL re-runs against the warehouse on every view, for the current truth. | A number that must be live, on a dashboard few people open. |
Published and embedded dashboards always render the snapshot
The row cap, and the totals it can quietly break
A snapshot holds at most 500 rows. That is fine for a chart of twelve months or twenty regions, and it is a problem for a chart built over raw rows — because the browser is what sums duplicate categories together.
A capped snapshot sums a subset and calls it the total
sum(amount) by region over a 50,000-row table and the snapshot keeps the first 500. The renderer then adds up the regions it can see, and draws a bar chart that looks entirely normal and is wrong — no error, just a smaller number. Widgets in this state carry a Partial badge, and the fix is the next section.Aggregate in SQL
Turning on Aggregate in SQL compiles the chart into a GROUP BY and makes the database do the adding. One row comes back per category instead of thousands of raw rows, so the totals are complete and the cap stops mattering:
-- what the widget stores
SELECT region, amount FROM sales
-- what actually runs with "Aggregate in SQL" on
SELECT region, SUM(amount) AS amount
FROM (SELECT region, amount FROM sales) AS _dq
GROUP BY regionSupported aggregates are sum (the default), avg, min, max, count and count_distinct. Wrapping SQL that already aggregates is harmless — grouping rows whose key is already unique returns them unchanged.
Why it isn't just switched on everywhere
Aggregating in SQL fixes the values, not the row count
VITE_BI_SNAPSHOT_ROWS_CAP.Incremental refresh
For a large table where only recent rows change, a widget can re-query just a recent window on a chosen date column and keep the previous snapshot's older rows. A refresh then reads a week instead of three years. Bind it in the builder: pick the date column, then Full re-query or a window of 7, 30, 90 or 365 days.
It assumes history does not change
avg and count_distinct correct, since those cannot be combined from partials. The trade you are accepting is the usual one: a late-arriving edit to a row outside the window will not appear until a full refresh. If your source back-dates corrections, either widen the window past your correction lag or leave incremental off.Chart types — all 26, with required fields
Each chart declares which fields it needs. The builder only offers types your selected columns can satisfy, so a chart missing a required field cannot be created.
Comparison and trend
| Type | Required fields | Optional | Use for |
|---|---|---|---|
bar | xField, yField | seriesField, stacked | Vertical columns; ranking and comparison |
hbar | xField, yField | — | Horizontal bars; long category names |
line | xField, yField | seriesField | Change over time |
area | xField, yField | seriesField | Trend with volume emphasis |
scolumn | xField, yField, seriesField | — | Stacked vertical columns |
shbar | xField, yField, seriesField | — | Stacked horizontal bars |
combo | xField, barField, lineField | — | Two measures at different scales (volume + rate) |
waterfall | xField, yField | — | How a total is built up from contributions |
Part of a whole
| Type | Required fields | Use for |
|---|---|---|
pie | nameField, valueField | Few slices — beyond ~6 it stops being readable |
treemap | nameField, valueField | Many parts, nested by size |
funnel | nameField, valueField | Stage-to-stage drop-off |
nightingale | nameField, valueField | Polar rose — cyclical categories |
sankey | xField (source), yField (target), valueField | Flow between nodes |
Single value
| Type | Required fields | Optional | Use for |
|---|---|---|---|
kpi | valueField | label, targetField | One headline number, optionally against a target |
gauge | valueField | label, targetField, max | Progress toward a ceiling |
Distribution and relationship
| Type | Required fields | Optional | Use for |
|---|---|---|---|
scatter | xField, yField | sizeField | Correlation between two measures |
heatmap | xField, yField, valueField | — | Density across two dimensions |
boxplot | xField, yField | — | Spread and outliers per category |
radar | xField, yField | seriesField | Several measures compared across entities |
Tables and geography
| Type | Required fields | Optional | Use for |
|---|---|---|---|
table | — | columnFormats | Exact figures read row by row |
matrix | rowField, colField, valueField | rowSubField, condFormat | Pivot. rowSubField makes rows expandable groups with subtotals; condFormat colours cells. |
map | locationField, valueField | — | Choropleth by region |
bubblemap | locationField, valueField | — | Magnitude at points |
Specialised
| Type | Required fields | Optional | Use for |
|---|---|---|---|
barrace | xField, yField, timeField | — | Animated ranking over time |
wordcloud | textField | valueField | Term frequency; weight by valueField when you have one |
ontology | spec | — | AI-built knowledge graph: subject–predicate–object triples across datasets, warehouses and KB knowledge graphs; nodes and edges are clickable |
Why it works this way
Number formatting
| Option | Values | Notes |
|---|---|---|
format | currency | percent | Omit for a plain number |
currency | ISO 4217 code | Defaults to USD when format is currency |
decimals | 0 – 4 | Fixed fraction digits. Leave undefined for automatic/compact (1.24M). |
columnFormats | per column | Table widgets only — format each column independently (number | currency | percent, with its own currency and decimals). |
Analytics options
Available on every chart spec; each renderer applies the ones it supports. This is where most of the analytical value lives, and it is the part people miss.
| Option | Values | Applies to | Effect |
|---|---|---|---|
drillFields | string[] | bar, hbar, pie | Drill hierarchy; level 0 is the configured field. Readers descend one level per click. |
dateGrain | auto | day | week | month | quarter | year | line, area | Default bucketing; viewers can toggle it. |
compare | prior_period | prior_year | line, area (single series) | Overlays the previous bucket or the same bucket last year. |
running | boolean | line, area (single series) | Cumulative running total. |
trend | boolean | line (single series) | Linear trend line. |
forecast | number of buckets | line (single series) | Projects ahead with a ±1.96σ confidence corridor. |
A forecast is a straight-line projection
Conditional formatting (matrix)
| Mode | Configuration |
|---|---|
scale | Continuous colour scale across the values, with an optional base colour. |
rules | An ordered list — first match wins. Each rule is an operator (gt, gte, lt, lte, eq, neq, between), a value (plus value2 for between, inclusive) and a colour. |
Reference lines
A horizontal reference line on cartesian charts, in one of two modes: avg draws the series average, or value draws a fixed number you supply. Both take an optional label — use it, because an unlabelled line invites the reader to guess what it means.
Making it interactive
- Global filters
- Dashboard-level controls — value pickers, numeric ranges and relative-date presets (last 7 days, this quarter). Save defaults so the dashboard opens on the right view.
- Cross-filtering
- Click a bar and every other widget filters to it. The fastest way to answer "what's driving that spike" without building anything.
- Drill hierarchies
- Define year → quarter → month, or region → country → city, and let readers descend a level at a time.
- Drill-through
- Open the underlying rows behind any data point — the answer to "is this number real?" and the fastest way to spot a broken join.
- Ask AI (embeds)
- On an EMBEDDED dashboard, readers can ask a follow-up question of its data in natural language, including a drill-down on what they clicked. Inside the app this is not a separate control — the AI analyst in the builder answers the same questions with more of the model behind it.
AI insights
Each visual can generate a written reading of what it shows — the notable movement, the outlier, the thing a person would say out loud. There is also a dashboard-level digest summarising the whole page.
Careful
Alerts and scheduled reports
- Alerts — watch a metric against a threshold and notify when it crosses. Delivered in-app and by email.
- Scheduled refresh — rebuild imported data on a cadence so the dashboard isn't stale.
- Incremental refresh — re-query only a recent window instead of the whole table. Covered in how a widget gets its data, along with the assumption it commits you to.
- Scheduled reports — email a digest of the dashboard on a schedule.
Aggregate in SQL — complete totals on any table size
GROUP BY compiled from the chart), so the warehouse returns complete grouped rows instead of raw ones. Existing widgets are not switched automatically — turning it on can change the number they display, which is the owner's call — they show a Partial badge when their snapshot hit the cap, with an "Aggregate in SQL" toggle in the widget menu.On a self-hosted deployment these need the scheduler running — see Install & deploy.
Sharing, export and embedding
| Method | Who can see it | Notes |
|---|---|---|
| Group share | Named users or IAM groups | Read-only; respects the underlying data grants |
| Public link | Anyone with the URL | No sign-in — treat the URL as the secret |
| Embed key | Any site you allow | Domain-restricted; see Web embedding |
| Export | Whoever you send the file to | PDF for the page, Excel/CSV for the data |
Check before publishing
Row filters and hidden columns on shares
A dashboard grant (Admin → IAM → Access) can carry a row filter (the grantee only sees rows where a column matches allowed values) and hidden columns (columns removed entirely). Both are enforced server-side: a grantee whose grant carries any restriction never reads stored widget data directly — the server applies the filter and drops masked columns before anything leaves it, on snapshots and on live direct queries alike. Semantics follow the rest of IAM: one unrestricted grant (directly or via any group) makes the whole dashboard visible, and a column is hidden only when every applicable grant hides it.
Versioning and promotion
- Version history — dashboards keep prior versions you can compare and restore.
- Dev → prod promotion — build in a development folder and promote a reviewed version into production, instead of editing what people are watching.
- Git export — export dashboard and model definitions as files for review in your own repository.
If a dashboard is slow
- Switch heavy widgets from direct query to imported snapshots with a refresh schedule. This is usually the whole fix.
- Turn on Aggregate in SQL. It returns one row per category instead of thousands, so it makes the dashboard faster and the totals correct at the same time.
- Use incremental refresh if the refresh itself is what's slow rather than the view.
- Aggregate in a prepared table rather than charting millions of raw rows.
- Reduce the number of widgets on one page — each is a query.
- Apply a default date filter so the dashboard doesn't open on all history.