One type for one purpose
Each visualization type answers a specific kind of question. Picking the right type is not about aesthetics — it is about making the answer legible at a glance. The field reference below shows what each type requires and a working YAML example taken from the ecommerce-showcase workspace.
All visualizations share the same base fields. Only type, mapping, and chart differ per type.
kpi — single metric with optional comparison
Use when the answer is one number. Best for revenue totals, order counts, rates, and any metric where trend direction matters as much as the value itself.
id: ec_revenue_kpi
title: Revenue
query: "models/ec_revenue.malloy::kpi"
type: kpi
mapping:
value: revenue
delta: return_rate_pct
format:
revenue: "$#0,0a"
return_rate_pct: "#0,00%"
published: true
Key fields:
mapping.value: the main metric field from the query output.mapping.delta: optional secondary metric shown as a comparison indicator.format: per-field format strings.$#0,0aproduces abbreviated currency (e.g. $1.2M).#0,00%produces a percentage.
bar — category comparison
Use when comparing a measure across discrete categories. Use variant: donut when you want part-of-whole composition instead of absolute comparison.
id: ec_revenue_by_category_bar
title: Revenue by Category
query: "models/ec_revenue.malloy::by_category"
type: bar
mapping:
x: category
y: revenue
format:
revenue: "$#0,00"
published: true
Donut variant for part-of-whole:
id: ec_orders_by_status_bar
title: Orders by Status
query: "models/ec_revenue.malloy::by_status"
type: bar
mapping:
x: status
y: order_count
chart:
variant: donut
show_value_labels: true
published: true
Cross-filtering is enabled by default on bar charts. When a user clicks a bar, it filters all other charts in the dashboard. Disable it explicitly if this view should not participate:
chart:
cross_filter: false
line — time series with optional dual axis
Use for trends over time. Supports a second y-axis (y2) when two measures have incompatible scales but belong on the same time canvas.
id: ec_revenue_over_time_line
title: Revenue Over Time
query: "models/ec_revenue.malloy::over_time"
type: line
mapping:
x: order_month
y: revenue
y2: order_count
series_label: Revenue
series_label_2: Orders
format:
revenue: "$#0,00"
order_count: "#0"
published: true
When using y2, each axis renders its own scale. The series labels appear in the legend.
funnel — sequential pipeline stages
Use for conversion flows, fulfillment pipelines, or any process where order and drop-off between steps matter.
id: ec_fulfillment_funnel
title: Fulfillment Funnel
query: "models/ec_fulfillment.malloy::funnel"
type: funnel
mapping:
stage: stage
value: order_count
chart:
height: 560
show_value_labels: true
published: true
Use a smaller height (e.g. 420) when embedding the funnel inside a fluid grid dashboard alongside other visualizations.
grid — row-level data table
Use when the audience needs to read individual records, export data, or verify detail behind a summary. Grids are also the right type for hierarchical data with nested rows.
id: ec_orders_detail_grid
title: Order Detail
query: "models/ec_fulfillment.malloy::detail"
type: grid
mapping:
columns:
- order_date
- category
- brand
- country
- status
- item_count
- revenue
- avg_order_value
format:
revenue: "$#0,00"
avg_order_value: "$#0,00"
published: true
The order of fields in mapping.columns controls column order in the rendered table.
scatter — correlation between two measures
Use when you want to see whether two continuous measures move together across a population. Each row in the query becomes a point.
id: brand_margin_vs_revenue
title: Margin vs Revenue by Brand
query: "models/ec_revenue.malloy::by_brand"
type: scatter
mapping:
x: revenue
y: margin_pct
series_label: brand
format:
revenue: "$#0,00"
margin_pct: "#0,00%"
published: true
heatmap — two-dimensional intensity
Use when you want to see a measure distributed across two categorical or temporal dimensions at once. Common use: day-of-week vs hour-of-day for activity patterns.
id: orders_by_day_hour
title: Orders by Day and Hour
query: "models/ec_orders.malloy::by_day_hour"
type: heatmap
mapping:
x: hour_of_day
y: day_of_week
value: order_count
format:
order_count: "#0"
published: true
report_matrix — hierarchical report with totals
Use when the audience needs a structured report with grouped rows, nested sections, subtotals, and group totals. Designed for document mode and PDF export.
id: category_brand_matrix
title: Category and Brand Breakdown
query: "models/ec_performance.malloy::category_breakdown"
type: report_matrix
mapping:
columns:
- field: category
label: Category
- field: revenue
label: Revenue
format: "$#0,00"
- field: order_count
label: Orders
- field: margin_pct
label: Margin
format: "#0,00%"
nested:
field: top_brands
columns:
- field: brand
label: Brand
- field: revenue
label: Revenue
format: "$#0,00"
chart:
pdf_expand_all: true
published: true
pdf_expand_all: true ensures all nested groups are expanded when exported to PDF. Without it, collapsed groups are invisible in the exported document.
Format string reference
Format strings follow a numeric pattern with optional prefix/suffix:
$#0,0a— abbreviated currency: $1.2M, $340K$#0,00— full currency with two decimals: $1,234.56#0— integer: 1234#.##0,0— number with one decimal: 1,234.5#0,00%— percentage with two decimals: 12.34%#.##0,00%— percentage with more precision: 12.345%