Forum Discussion
Custom Visual Highlighting/Selection stops working
- 9 months ago
Hi v-sshirivolu ,
I have issued the ticket - When I will receive any solution, certainly I will share it here 🙂
Hi ,
Custom Visual: highlights/selection stop working when Table/Matrix gets big
You’re not imagining it. What you’re seeing is a mix of (a) how highlights are delivered to custom visuals, (b) data reduction/windowing, and (c) rows that don’t carry real identities (nulls/totals/rolled-up rows).
What’s actually happening
Highlights only arrive when your visual opted in
With "supportsHighlight": true, the host may send categorical.values[i].highlights (same length as values; non-selected rows are null). That array is only guaranteed for cross-visual highlight scenarios, not for your own selection, and not in all table/matrix states. Microsoft Learn
Your visual probably isn’t receiving the selected row because of data reduction
By default, custom visuals receive at most ~1000 rows per query window (top reduction). If the user selects a row that’s outside the window your visual requested, the host can’t line up the selected index → the highlights array is omitted (or doesn’t mark any of your current window rows). Use a bigger dataReductionAlgorithm count and/or implement fetchMoreData to page until the selected row falls into your window. Microsoft Learn+2GitHub+2
Some rows don’t have real identities
Table/Matrix can surface totals, blanks, or grouped rows. Those can show placeholders (or no identity at all), so your fallback on categorical.categories[0].identity[] won’t map back to a real selection. (This is why things work on “small/simple” tables but fail once you add columns, nulls, or groupings.) There isn’t a single doc line that calls it “placeholders,” but identity issues with table mapping and selection are a well-trodden pain point in the SDK/GitHub threads. coacervo+1
Practical fixes (what I’d change)
Raise your row window + implement paging
In capabilities.json, bump the window:
"dataViewMappings": [{
"categorical": {
"dataReductionAlgorithm": { "top": { "count": 30000 } }
}
}]
Handle fetchMoreData() when a selection occurs but your current window shows no highlighted rows. Keep appending until you either see the selected row or the host returns “no more data.” Microsoft Learn
Add a stable surrogate key and bind it in both visuals
Put a not-null RowKey (e.g., surrogate id) in the Table/Matrix and also bind the same field into your custom visual’s categories. This gives the engine a clean join path for highlights/identities even when the model grows, and avoids null/total rows breaking identity mapping. (This is the most reliable long-term pattern.)
Treat highlights as cross-visual only; own your selection via Selection Manager
Keep your “read highlights” path only when highlights exists.
For your own clicks, store/restore ISelectionIds with ISelectionManager so a re-update() doesn’t wipe your state. (Highlights are not the echo channel for your own selection.) Microsoft Learn+1
Defend against identity-less rows
If a selected Table/Matrix row has no identity (blank/total), show a friendly “cannot comment on totals/blank rows” message or require the RowKey field.
Optionally filter the user’s Table to exclude totals/blank GTM/keys to guarantee identities.
Consider table mapping if you truly work row-by-row
If your visual is logically “row” oriented, table dataViewMapping can be simpler to reason about than categorical when you start paging. (Still doesn’t fix identity for totals/nulls, but it makes row scanning straightforward.) coacervo
// On click in Table/Matrix (cross-visual): your visual receives update()
const hasHighlights = values?.some(v => Array.isArray((v as any).highlights));
if (!hasHighlights) {
// Selected row might be outside our window → try to page
if (await this.host.fetchMoreData()) return; // host will call update() again
}
// When we *do* have highlights, compute selected indices
const selectedIdx = rows.filter((r,i) => values.some(v => v.highlights?.[i] != null));
// If still none: either totals/blank identity or selection not in our data
// → fall back to RowKey (if you bound it) or show a friendly message.
Did I answer your question? Mark my post as a solution! Appreciate your Kudos !!
Hi johnbasha33
Thank you very much for the detailed breakdown.
Let me summarize your proposed solution to verify that I fully understand the mechanics behind it:
My understanding of your explanation:
Highlights are not guaranteed
They are only reliably sent in cross-visual scenarios, and only when the selected row is inside the current data window delivered to the custom visual.
If the selected row is outside the window, or if Power BI decides to reduce the payload, highlights[] will be missing or truncated.Data reduction / windowing is likely the root cause
My custom visual receives only a window of ~1000 rows by default.
When the user selects a row outside that window, Power BI cannot align indices → the highlight array is dropped.
To fix this, I should increase the dataReductionAlgorithm.top.count and implement fetchMoreData() when I detect no highlights.Identity issues occur when the table surface includes totals/null/grouped rows
In these cases, the Table/Matrix does not send real identities, only placeholders, which explains why my fallback identity logic stops working when the model becomes wide or contains blanks.A stable surrogate RowKey is recommended
Adding a guaranteed non-null RowKey (bound both in the Table/Matrix and in my custom visual) gives Power BI a consistent join path that reduces identity mismatches and avoids the null/total cases breaking selection propagation.Highlights should be treated as cross-visual only; not as a mechanism for my own internal selection
For my own state I should use ISelectionManager, because highlights are not meant to be the "echo" channel for my visual’s own selection state.Paging logic should fire when I detect “no highlights”
If highlights[] is missing, it may simply mean the selected row is not yet in the window → so the correct next step is host.fetchMoreData() until the selection becomes visible or I exhaust all pages.
My questions to confirm whether I got it right:
Am I correct that adding a RowKey does not itself guarantee that highlights will always be sent, but instead reduces cases where Power BI collapses identities or drops highlights due to null/total rows?
Regarding data reduction:
Is the main expectation that once I set a larger top.count (e.g., 30,000) and implement fetchMoreData(), highlight disappearance should no longer occur unless the dataset exceeds that expanded window?For the RowKey:
Should I treat this RowKey as the only meaningful category for selection detection inside my custom visual, and ignore all other category columns?If the user selects a row that has no real identity (totals, nulls, rolled-up rows), is the recommended behavior to show a message like "Cannot comment on total/blank rows" rather than trying to derive fallback selection?
Finally, is it correct that none of this guarantees "unlimited" reliability for Table/Matrix selection, but implementing:
a surrogate RowKey,
higher data reduction window,
fetchMoreData paging,
highlight-as-cross-visual-only,
yields the most stable approach possible within the current Power BI custom visual model?
Thank you again for sharing these insights!