Fix heatmap hover pick raising IndexError in the kernel handler#106
Merged
Conversation
pick() assumed point-like traces where len(t.x) == n_points, but heatmap traces store only the two outer grid edges while counting rows*cols points, so every hover (and click) on a heatmap cell indexed a 2-element array with a cell index and crashed the comm handler, spamming notebook output with tracebacks. Grid traces now take a dedicated branch that returns the bin readout of dossier §17: row/col plus the exact f64 z from the canonical grid (omitted for NaN cells). x/y are deliberately left out — the client's local row already shows the cell center with categorical axis labels, and kernel-provided numerics would override those labels in the tooltip merge. Returning None instead was not an option: the client hides the tooltip on a null pick_result row.
Merging this PR will not alter performance
Comparing Footnotes
|
Alek99
added a commit
that referenced
this pull request
Jul 21, 2026
The move in this PR promoted docs/engineering/ to spec/ and CLAUDE.md declares that tree the source of truth. An audit of all 24 documents against the code found the tree had drifted at PR #96: five behavior PRs (#103, #105, #106, #108, #115) had landed with no spec update, and ~100 claims contradicted shipped code. Accuracy: correct claims that no longer match the implementation, including the Rust ABI version (v31 -> v36), the "zero-crate cdylib" claim (png is a real dependency), the pyramid ABI function names, the panic-shield status, the R2/VAO claim in the renderer doc, the WASM/Web-Worker architecture in dossier §3/§8 (superseded by §32's C-ABI cdylib), the count-only tier rule, the matplotlib shim overhead figures (the cited guardrail says +60% at 10k, not +9%), and the Plotly Scattergl 10M figure that appeared as two different numbers from the same run. Coverage: add spec/api/export.md, spec/api/interaction.md and spec/design/wire-protocol.md for three surfaces that had no coverage at all, plus the config.py threshold table, full JS and Rust module inventories, and the hexbin centers-only wire contract that three renderers must expand identically. Structure: group 15 top-level files into api/, design/, matplotlib/, benchmarks/ and process/, and rewrite spec/README.md as a real index (it previously listed only the two SVG assets, leaving 21 of 22 docs unreachable). All 150 consumer references repointed, including the CI --emit-md target and the compat-matrix generator. Guard: verify_sdist.py pinned only 5 top-level spec files, so the sdist could silently lose spec/design/ or spec/assets/ and still pass; it now asserts markdown under every subdirectory and both SVGs.
Alek99
added a commit
that referenced
this pull request
Jul 21, 2026
…roup the tree (#122) * Move engineering docs to root spec directory * Resync spec/ with the implementation and group the tree The move in this PR promoted docs/engineering/ to spec/ and CLAUDE.md declares that tree the source of truth. An audit of all 24 documents against the code found the tree had drifted at PR #96: five behavior PRs (#103, #105, #106, #108, #115) had landed with no spec update, and ~100 claims contradicted shipped code. Accuracy: correct claims that no longer match the implementation, including the Rust ABI version (v31 -> v36), the "zero-crate cdylib" claim (png is a real dependency), the pyramid ABI function names, the panic-shield status, the R2/VAO claim in the renderer doc, the WASM/Web-Worker architecture in dossier §3/§8 (superseded by §32's C-ABI cdylib), the count-only tier rule, the matplotlib shim overhead figures (the cited guardrail says +60% at 10k, not +9%), and the Plotly Scattergl 10M figure that appeared as two different numbers from the same run. Coverage: add spec/api/export.md, spec/api/interaction.md and spec/design/wire-protocol.md for three surfaces that had no coverage at all, plus the config.py threshold table, full JS and Rust module inventories, and the hexbin centers-only wire contract that three renderers must expand identically. Structure: group 15 top-level files into api/, design/, matplotlib/, benchmarks/ and process/, and rewrite spec/README.md as a real index (it previously listed only the two SVG assets, leaving 21 of 22 docs unreachable). All 150 consumer references repointed, including the CI --emit-md target and the compat-matrix generator. Guard: verify_sdist.py pinned only 5 top-level spec files, so the sdist could silently lose spec/design/ or spec/assets/ and still pass; it now asserts markdown under every subdirectory and both SVGs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hovering (or clicking) a heatmap in a notebook crashed the Python-side pick handler with an
IndexErroron every mouse move, spamming the cell output with tracebacks via ipywidgets'CallbackDispatcher. The chart kept rendering — only the hover readout died.Root cause
pick()(python/xy/interaction.py) assumed the point-like invariantlen(t.x.values) == t.n_points. Heatmap traces violate it: they store only the two outer grid edges as x/y whilen_pointsisrows * cols, so the bounds guard passed andt.x.values[cell_index]indexed a 2-element array. The resultingIndexErrorisn't caught bychannel.py'sexcept (TypeError, ValueError), so it escaped the comm handler.Fix
Grid traces (
t.grid_shape is not None) now take a dedicated branch inpick()that returns the bin readout per dossier §17:trace,index,row,col, andcolor_value— the exact f64 z from the canonical grid (honoring §16's f64-hover rule; omitted for NaN cells). Out-of-range indices returnNone. The same branch fixes the identical crash in theclickpath.Two deliberate choices:
x/yare omitted from the row. The client merge lets kernel fields override the local row, and heatmap axes can be categorical (labels live figure-side while trace columns are numeric) — a kernel numericxwould replace category labels with raw numbers in the tooltip. The client's local row already shows the correct cell center; the kernel's genuine contribution is the exact z value. A test pins the omission.row: None. The client hides the tooltip on a nullpick_resultrow, which would have erased the working local readout.Testing
test_pick_heatmap_cell_returns_grid_readout,test_pick_heatmap_out_of_range_returns_none,test_pick_heatmap_nan_cell_omits_value(tests/test_scatter.py) andtest_heatmap_pick_message_replies_without_raising(tests/test_channel.py, mirrors the heatmap+contour repro throughhandle_message).uv run pytest tests/test_scatter.py tests/test_channel.py -q— 97 passeduv run pytest tests/ -q -k "pick or heatmap"— 57 passed, 1 skippedpre-commit run --all-files,ruff check,ruff format --check— cleanCloses #89