What cross-filtering is in Looky
Inside a dashboard, clicking a data point in one visualization narrows every other visualization on the page. The clicked field/value pair becomes a removable "pill" the user sees at the top of the dashboard, and Looky applies that field/value to every other viz as a parameter on its next run.
Cross-filtering is a runtime behavior, not a YAML feature. There is no dashboard-YAML cross_filter block — the wiring is automatic, driven by which fields the underlying models declare as parameters.
Where cross-filtering works
- In dashboards. Any chart click anywhere inside a dashboard adds (or removes) a pill.
- Not on the standalone visualization page. Clicking a chart that is open as its own page does not produce a pill.
If you want cross-filter behavior, the user must be looking at the chart through a dashboard.
Which viz types emit clicks
- bar — clicking a bar emits the x-axis category.
- line — clicking a point emits the x value (single / dual-axis) or the series name (multi-series).
- pie — clicking a slice emits the slice label.
- scatter — clicking a point emits its label / series / x value.
- funnel — clicking a stage emits the stage label.
- heatmap — emits only when the viz has
chart.cross_filter_emitset to"x"or"y". - grid — clicking a cell in a column configured as clickable emits the column name and the cell value.
- report_matrix — clicking a body cell emits the column name and the cell value, same shape as grid. Totals rows, trend cells, and section headers are not clickable; document-mode rendering suppresses all click wiring.
- kpi — does not emit clicks (no categorical click target by structure), but consumes pills: its query re-runs with the active params just like every other viz.
Pills
Pills are the visual representation of active cross-filters at the top of a dashboard. The lifecycle:
- User clicks a chart — the field / value pair becomes a pill.
- Pills are additive — clicking on a second chart adds another pill, and both apply to every viz on the next run.
- Clicking a pill's remove button drops that filter and re-runs the dashboard without it.
- Pills last for the dashboard session — reloading the page clears them.
A click is only kept as a pill if the clicked field is also a parameter declared by at least one model on the dashboard. Clicks on fields no model accepts are silently ignored — there is no error, just no pill. To make a column cross-filterable, declare a parameter for it in the model that powers the affected charts (see Malloy support).
How cross-filter values reach the underlying queries
Active pill values are merged with any dashboard-level filter values and passed as parameters to every viz's query on the next run. From the model's point of view, a cross-filter pill is indistinguishable from a value the user typed in a filter control — the same parameter binding applies.
Pills override declared filter values when both set the same parameter. So a click on a chart that emits {country: "MX"} wins over the dashboard's "country" select that was set to "all". Removing the pill restores the declared value.
Practical consequence: every model used by a dashboard should declare a Malloy parameter for any field you want users to be able to cross-filter on. Use a sensible default (typically a sentinel like "all", an empty string, or the broadest valid value) so the query still runs when the parameter is unset.
# in a Malloy model
##! experimental.parameters
source: orders(
p_country::string is "all",
p_brand::string is "all"
) is bigquery.table('...') extend {
view: by_category is {
where:
(p_country = "all" or country = p_country)
and (p_brand = "all" or brand = p_brand)
group_by: category
aggregate:
revenue is sum(sale_price)
}
}
With the parameters above, clicking a country bar adds a {country: "MX"} pill, the dashboard re-runs with p_country = "MX", and every chart using this view narrows accordingly.
Per-viz opt-out
Suppress click emission for a specific viz by setting the cross-filter flag to false in its YAML. The flag lives in the viz's primary config block — chart for ECharts-based vizs, the type-named block for DOM-based vizs (because they are not "charts" in the ECharts sense).
- bar, line, pie, scatter, heatmap, funnel →
chart.cross_filter: false - grid →
grid.cross_filter: false - report_matrix →
matrix.cross_filter: false - kpi → no flag; KPI does not emit by structure (no categorical click target).
Use opt-out for "headline" charts that should always show the unfiltered total — a top-line revenue chart that should not change when the user clicks elsewhere, for instance.
Adapter differences
Cross-filter routing itself is the same on all three adapters. Pill values become query parameters; from that point the same adapter caveats apply as for filter values — see Source adapter differences. Numeric and string pills are unaffected; date / timestamp pills follow the Postgres / MySQL pattern.
Patterns
One drill path per dashboard
Decide which fields users should be able to drill on (typically the dimensions in your group-by clauses) and declare parameters for those in every model used by the dashboard. Skip parameters for fields that should not drive cross-filter — clicks on them silently fall through.
Headline + drill grid
Use a KPI at the top showing the total (KPI does not react to pills, by design). Below it, a bar chart that emits clicks. Below the bar, a grid that consumes the cross-filter and shows the underlying rows. Each click on the bar narrows the grid to the matching subset.
Cross-filter + declared filters together
Mix a date filter at the top of the dashboard (e.g. date_range_preset) with implicit cross-filtering on the chart layout. Both end up as parameters on the same queries.
Common pitfalls
- Click does nothing. The clicked field must be declared as a parameter in at least one model on the dashboard. If no model declares it, the click is silently ignored.
- Pills disappear after reload. Pills do not persist across page reloads. If you need shareable filter state, use a declared filter (Filters) — those are reflected in the URL.
- One viz reacts to the pill, another does not. Both viz must use models that declare the same parameter. Audit each viz's underlying model.
- The "headline" KPI changes when it should not. KPI reacts to pills like every other viz. If you want a KPI that ignores them, write the underlying model so the parameter doesn't influence the where-clause; or use a non-KPI viz with
chart.cross_filter: falseas the headline. - Clicking a kpi does not add a pill. KPI is a consumer-only viz (no categorical click target by structure). Its query re-runs with active params but it cannot emit. report_matrix and grid both emit on body-cell clicks.
- Funnel clicks ARE pills. Funnel emits like bar / line / pie / scatter. If you want a funnel that does not emit, set
chart.cross_filter: falseon it. - Two pills should be combined as OR but combine as AND. The current behavior is AND across all pills. To support OR semantics, encode it in the underlying model parameter (e.g. accept a list, default to
"all").