Docs / Build Workflow

Visualization — funnel

When to use funnel

Funnel is the right choice for conversion flows, fulfillment pipelines, or any process where order between steps and drop-off matters — site visitor → product view → cart → checkout → purchase, or order created → packed → shipped → delivered. Each row of the query is one stage.

Use bar instead when the categories are unordered or when you do not care about cumulative drop-off. Use grid when the audience needs the exact step counts and conversion rates side by side.

Mapping

  • mapping.stage — required. String field whose values become the per-stage labels.
  • mapping.value — required. Numeric field for the per-stage size.
mapping:
  stage: stage
  value: order_count

The order of stages in the chart follows the order of rows in the query result. Sort in the underlying Malloy query to control sequence (typically by order_index or by descending value).

chart shortcuts

The chart block is typed and closed.

  • chart.percent_mode"first" (default), "total", or "none". Picks the base for the percent shown next to each stage value:
    • first — relative to the first stage (conversion cascade: 100% → 80% → 60% …).
    • total — relative to the sum of all stages (share of the funnel).
    • none — no percentages, raw values only.
  • chart.show_value_labels — boolean. Turns stage labels on. Style them with chart.label below.
  • chart.cross_filter — boolean, default true. Set to false to suppress click emission for this viz (it still consumes pills set elsewhere).
  • chart.height — pixel height of the viz container.

Pass-through layout fields:

  • chart.left / chart.top / chart.bottom — distance from the corresponding edge of the chart container.
  • chart.width — funnel horizontal width (string percent or pixel number).
  • chart.gap — pixels between stages.
  • chart.min_size — minimum stage width (e.g. "10%"). The narrowest stage will not get smaller than this even if its value is tiny.
  • chart.max_size — maximum stage width.
  • chart.sort — stage sort order. Default lets row order win; set to a known value if you want the renderer to reorder.

Stage labels

chart.label styles each stage's label when chart.show_value_labels is on:

  • position"inside" (label sits on top of the stage), "left", "right", "top", "bottom".
  • rotate, color, font_size, font_weight.
  • formatter — string template. Use {b} for the stage name, {c} for the value, {d} for the percent (when percent_mode is "first" or "total").
  • distance, align, vertical_align, clip.

Legend & tooltip

chart.legend and chart.tooltip share the same shape as on bar. For a single-funnel viz the legend is usually redundant; set chart.legend.show: false.

format

  • format.y or format[<value_field_name>] — pattern for stage value labels and tooltip values.
  • format at the root — fallback.

Cross-filter behavior

Funnel participates fully in cross-filtering. Inside a dashboard:

  • Emits — clicking a stage adds a pill { field: <the stage field>, value: <clicked stage name> }, provided chart.cross_filter !== false AND the field is declared as a parameter in at least one model on the dashboard.
  • Consumes — pills set elsewhere, and dashboard-level filters, become parameters on the next run; the funnel's underlying query re-runs and the stages recompute accordingly.
  • Opt-out — set chart.cross_filter: false to suppress click emission while still consuming pills.

Worked examples

Standard conversion funnel with first-stage percent:

id: ec_fulfillment_funnel
title: Fulfillment Funnel
query: "models/ec_fulfillment.malloy::funnel"
type: funnel
mapping:
  stage: stage
  value: order_count
chart:
  height: 360
  width: "80%"
  show_value_labels: true
  label:
    position: inside
    formatter: "{b}: {c} ({d}%)"
  percent_mode: first
  legend:
    show: false
format:
  order_count: "#,##0"
published: true

Total-share funnel (each stage shown as % of the sum):

chart:
  percent_mode: total
  show_value_labels: true

Compact funnel embedded in a dashboard row alongside KPIs:

chart:
  height: 240
  width: "100%"
  gap: 6
  min_size: "20%"
  max_size: "100%"
  show_value_labels: true
  label:
    position: right
    formatter: "{b}: {c}"
  percent_mode: none

Common pitfalls

  • Stages are in the wrong order. The funnel respects query-result order. Sort in the Malloy query to control sequence.
  • Tiny last stages disappear. Set chart.min_size to enforce a minimum visible width.
  • Percent mode produces unexpected numbers. Pick deliberately: "first" for conversion cascades (each stage ÷ first stage), "total" for share of total (each stage ÷ sum), "none" for raw counts.
  • Stage labels overlap the bars. Move the labels outside (chart.label.position: "right") or reduce font_size.
  • Clicking a stage does nothing. Funnel emits cross-filter pills only inside a dashboard, and only if (a) chart.cross_filter is not false, and (b) the stage field is declared as a parameter in at least one model on the dashboard. Add the parameter declaration in the underlying Malloy model to enable the pill.
  • Multi-funnel comparison. Not supported in one viz; place two side-by-side funnels in a dashboard for A/B comparison.