Fix heatmap axis labels not updating when toggling clustering#3487
Conversation
|
/review |
|
@ewels could you have a look at this? Or maybe tell Claude to check? It's annoying me more and more :( |
|
Ah well spotted! Not sure why your request didn't work: /review PR looks simple enough though - will make sure it gets into the next release 👍🏻 Thanks! |
|
This PR fixes a real bug where clustering a heatmap leaves axis tick labels stale (still in unclustered order) after toggling views. The JS fix is minimal and well-targeted, and the PR description clearly explains the tradeoff between a JS-side and Python-side fix. DetailsCode ReviewJS Fix (
|
| Assessment | |
|---|---|
| Bug validity | Confirmed — ticktext was never updated on clustering toggle |
| JS fix correctness | Correct — runs before recalculateTicks, covers the right code path |
| Python test coverage | Validates data structure, but not the JS tick-sync logic |
| Risk of regression | Low — the fix only mutates layout when tickmode === "array", leaving other code paths unchanged |
Suggested improvement: A comment in buildTraces() explaining why the tick update only applies when tickmode === "array" (large heatmaps use auto-ticks derived from trace data, which are already correct) would help future readers.
Attention: The changes, as well as the description below have been heavily LLM-generated. To me the changes seem valid, but I am not familiar with the MultiQC codebase.
Summary
Problem
When a heatmap supports clustering, toggling to the "Clustered" view reorders the data matrix (
rows,xcats,ycats) correctly, but the axis tick labels remain in the original unclustered order. This causes the wrong label to appear next to the wrong row/column.The root cause is in
heatmap.py(HeatmapPlot.create), where the Plotly layout'stickmodeis set to"array"withticktextfixed to the original category order. When the JavaScript-side clustering toggle swaps the trace data to the clustered versions, the layout'sticktextis never updated to match, so Plotly renders stale labels at each position.Fix
In
buildTraces()(in bothdefaultandoriginaltemplate heatmap JS), sync the layout'sticktextandtickvalsto the current category order before rendering. This runs beforerecalculateTicks, so downstream tick subsampling and highlighting still operate on the correct labels.Alternative considered
A more "root-cause" fix would be to remove the
tickmode: "array"/ticktextoverride from the Python side entirely, and rely on Plotly'stype: "category"axis to derive labels automatically from the trace'sx/yvalues. This would eliminate the coupling between layout and data ordering altogether.We chose the JS-side fix instead because:
tickmode: "array"path is also used byrecalculateTicks()to control tick density on large heatmaps, and by the toolbox for sample highlighting. Removing it could introduce regressions in those features.prepData()already resolves the current category order (clustered or unclustered, with toolbox filtering applied). The fix simply ensures the layout stays in sync with that resolved state on every render.