Docs / Build Workflow

Visualizations

What a visualization YAML is for

A visualization file binds one Malloy query to one chart or table. Every transformation belongs in the model query; the visualization YAML is presentation only — chart type, which fields go where, formatting, optional emphasis. For how to shape that Malloy layer (dimensions, measures, views, modular files), see Models.

For the per-type reference (every chart.* property each viz type accepts), see Visualization Types and the page for each type underneath.

Required top-level fields

  • id — workspace-unique identifier. Stable over time: dashboards reference it, so renaming breaks dashboards.
  • title — human-readable title shown in the dashboard.
  • type — viz type discriminator. Must be one of the supported types listed in the viz-types index; anything else is rejected at validation time.
  • query — Malloy query reference, exactly in the format path/to/file.malloy::query_name. The path is from the workspace root; both halves must exist or validation fails.

Other top-level fields

  • mapping — field-role assignments. Shape varies per viz type — see the per-type page.
  • chart — typed config block for ECharts-based types (bar, line, pie, scatter, heatmap, funnel, sankey). Closed surface: only the keys listed in the per-type reference are accepted.
  • kpi, grid, matrix — type-specific blocks for the DOM-based types (kpi, grid, report_matrix). Use these instead of chart.
  • body — the Markdown body of a text note (the one type where query is optional). See text.
  • pagination — for grid and report_matrix.
  • filters — list of user-facing controls. See Filters.
  • format — field-keyed number format patterns; field name → pattern string. (On the text type format is instead a scalar naming the body format — markdown.)
  • emphasis — declarative highlight rule (per supported type — see the per-type pages).
  • execution — execution mode override. The engine reads execution.mode: default (implicit) or dry_run; dry_run is honored on BigQuery only and ignored with a warning elsewhere.
  • tags — free-form labels for search.
  • published — boolean. Only true visualizations appear in dashboards.

The query reference format

Every visualization points to a Malloy query with this exact format:

query: "models/ec_revenue.malloy::by_category"

The part before :: is the path to the .malloy file from the workspace root. The part after :: is the view or query name defined inside that file. Both must match exactly.

Local validation (looky validate) checks that the file exists and the reference uses the :: separator. Server-side validation additionally compiles the model and dry-runs the query; if the view name doesn't exist, or the model fails to compile, you get a clear error before push.

Worked examples

Minimal KPI:

id: ec_revenue_kpi
title: Revenue
query: "models/ec_revenue.malloy::kpi"
type: kpi
mapping:
  value: revenue
  delta: revenue_delta_pct
format:
  revenue: "$#,##0a"
  revenue_delta_pct: "#,##0.00%"
published: true

Bar with one series — on bar, measures always go in mapping.series[]:

id: ec_revenue_by_category_bar
title: Revenue by Category
query: "models/ec_revenue.malloy::by_category"
type: bar
mapping:
  x: category
  series:
    - field: revenue
      label: Revenue
chart:
  show_value_labels: true
  value_label:
    position: top
format:
  revenue: "$#,##0.00"
published: true

Line with dual axis:

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"
  order_count: "#,##0"
published: true

For every other shape (grouped bars, percent-stacked, donut pie, scatter, heatmap, funnel, grid with pagination, report_matrix with totals) see the per-type pages under Visualization Types.

Cross-filtering at a glance

Inside a dashboard, clicking a chart can add a "pill" that narrows every other viz on the page. Per-type emit behavior:

  • Emit clicks: bar, line, pie, funnel, grid, report_matrix — plus scatter, heatmap, and sankey, which additionally require chart.cross_filter_emit naming what a click emits (label/x/series for scatter, x/y for heatmap, source/target for sankey).
  • Don't emit but consume pills: kpi and text (no categorical click target by structure).
  • Per-viz opt-out: chart.cross_filter: false for ECharts-based vizs (bar, line, pie, scatter, heatmap, funnel, sankey); grid.cross_filter: false for grid; matrix.cross_filter: false for report_matrix.

Full mechanism at Cross-filtering.

What validation actually checks (and what it does not)

Validation is two-pass — local then server-side. Per visualization, the checks are:

  • Local: YAML parses; id, title, type, query are present and non-empty; query uses the :: separator; the model file exists; the id is unique across the workspace; the chart block is schema-valid (every property is recognised, values are in their allowed range — error VZ020).
  • Server-side: the underlying Malloy model compiles, sources are reachable, and the query dry-runs against the runtime engine. The dry run also checks that every field named in mapping (and the other field-reference slots) exists in the query's result schema, and that every key under format is either a mapping slot or a result field. With --strict, each query is additionally verified against the live source (cost estimate on BigQuery, EXPLAIN on Postgres and MySQL).

Validation does not verify that the chart "looks right" — sizing, label collisions, color choices, and layout issues surface only at render time. Open the visualization detail page after push to catch them.

looky validate
looky list visualizations