Cross-filtering
Connect a selection in one view to the queries that should respond
A cross-filter connects a selectable view to the queries that should respond to it. When a reader clicks a row or bar in one view, the mapped queries are filtered by that selection.
Mapping a cross-filter
When a reader clicks a region in the breakdown, the page selects that row and executes the app. The cross-filter applies the selection to trend:
const byRegion = app.createQuery('byRegion', {
dataset: 'sales',
aql: `explore {
dimensions { region: users.region }
measures { revenue: revenue }
}`,
})
app.mapCrossFilter(byRegion, trend)
regionChart.on('click', (params) => {
const row = byRegion.result.rows[params.dataIndex]
byRegion.select([row])
app.execute()
})Your subscription can then redraw both views from byRegion.result and trend.result.
The page decides how the views look and how they handle clicks. anfra runs the queries and applies the mapped filters through the semantic model, so the page contains no filter logic.
Add a second cross-filter in the other direction if both views should drive each other:
app.mapCrossFilter(byRegion, trend)
app.mapCrossFilter(trend, byRegion)Selecting several rows
query.select(rows) replaces the selection with the full array, so pass all selected rows together. To let a click toggle a row in or out:
regionChart.on('click', (params) => {
const row = byRegion.result.rows[params.dataIndex]
const selected = byRegion.selectedRows
byRegion.select(selected.includes(row)
? selected.filter((picked) => picked !== row)
: [...selected, row])
app.execute()
})query.select([]) clears the selection. app.clearSelection() does the same for the whole app:
app.clearSelection()
app.execute()Use query.selectedRows to dim the marks that are not selected.
How a selection becomes a filter
Conditions are ANDed within each row and ORed between rows: (A and B) or (C and D).
- Selecting
(APAC, Aug 2025)and(EMEA, Oct 2025)keeps those pairs. It does not match every combination of the two regions and months. - Selecting Aug 2025 and Oct 2025 on a monthly chart includes those two months and leaves September out.
The SDK builds the conditions from the selected rows and the applied date grain.
Choosing the columns
By default every dimension column of the row becomes a condition. Pass fields, a list of result column aliases, to use only some of them:
trendByRegion.select([row], { fields: ['region'] }) // filter by region, not by periodfields takes dimension aliases only. Naming a measure, a query-local expression or an alias that is missing from the last result throws a ValidationError.
Rules
- Pass row objects from
query.result.rows, never rebuilt objects. The SDK reads the dimension values off them. - Only dimension columns become conditions. Measures and query-local expressions are skipped. When an expression column is dropped,
app.selection.lossyis set and a warning is logged. - There is one selection per app. Selecting in another query replaces it.
- The source query is never filtered by its own selection.
- Both queries must use the same dataset, and a query can't be mapped to itself.
Related
- Controls: filters and date drills, the other way to change a query.
- Results: the rows you pass to
select. - Example app: a bar chart that cross-filters a trend line.