Docs / Build Workflow

Visualization Types

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,0a produces 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%