Skip to content

Enhancements: Z-order issue#223

Merged
tsenoner merged 13 commits into
mainfrom
fix/enhancements
May 18, 2026
Merged

Enhancements: Z-order issue#223
tsenoner merged 13 commits into
mainfrom
fix/enhancements

Conversation

@peymanvahidi
Copy link
Copy Markdown
Collaborator

@peymanvahidi peymanvahidi commented Apr 7, 2026

This PR fixes the issues below:

Notes

#101 status. Duplicate badges are now grouped per the current projection: two points stack iff they share exact coords in the displayed view. PCA collapses identical embeddings to the same coordinates, so badges appear there. UMAP/t-SNE generally don't co-locate them, so badges don't appear under those projections.

html { font-size: clamp(0.75rem, 0.87vw, 1.125rem) } in app/src/index.css. Fluid base font size, 12px to 18px between ~1379–2069px viewport width. Intent: improve readability on wider displays. Tested on standard desktop viewports (~1440–1920px, MacBook 14"/16" + external monitor).

ESC cascade. ESC priority is now: (1) close open duplicate-badge spider, (2) clear selection, (3) toggle off selection mode.

New renderer API. WebGLRenderer.invalidateDepthOrder() lets callers signal a depth-sort invalidation without invalidating the position cache. Used by scatter-plot's z-order/color-mapping handlers.

@peymanvahidi peymanvahidi marked this pull request as draft April 7, 2026 06:30
… consistent badge rendering

- Added a new private property to track duplicate groups across different projections.
- Implemented a method to compute and unify duplicate groups based on exact coordinates across projections.
- Updated relevant methods to utilize the new grouping for rendering and interaction, ensuring consistent badge display for duplicates.
@peymanvahidi
Copy link
Copy Markdown
Collaborator Author

next: add docs for the changes (especially badges)

@peymanvahidi peymanvahidi marked this pull request as ready for review May 11, 2026 09:30
@peymanvahidi
Copy link
Copy Markdown
Collaborator Author

@tsenoner ready to merge

@peymanvahidi
Copy link
Copy Markdown
Collaborator Author

check the docs page, Using the Explore Page -> Navigating the Scatterplot -> Duplicate Points for the gif

@tsenoner
Copy link
Copy Markdown
Owner

Hi @peymanvahidi,
I noticed some odd behavior when clicking a duplicate badge.

Bug: Only the top-most point of the duplicate group renders at the expected position — the other duplicate points appear elsewhere on the plot. This may be a UMAP-specific issue where overlapping/duplicate points aren't rendered at the same coordinates. Could you investigate and propose a fix? See the screen recording below:

Screen.Recording.2026-05-12.at.19.28.46.mov

Additional request: ESC should also close an open duplicate badge, mirroring the behavior we have for selections. Proposed precedence:

  1. First ESC → close the open duplicate badge (if any)
  2. Next ESC → clear the selection

@tsenoner tsenoner marked this pull request as draft May 12, 2026 17:55
@tsenoner
Copy link
Copy Markdown
Owner

A few things still missing before this is ready:

  • ESC handler (per my earlier comment): close badge → then clear selection.
  • Unit test for the duplicate-stack logic — at minimum a Vitest covering legend hide and projection switch, so [BUG] duplication badges lagging behind #121 doesn't regress.
  • PR title typo: "Ehnacements" → "Enhancements".
  • html { font-size: clamp(...) } in app/src/index.css — please add a one-liner to the PR description explaining the intent and the viewport range you tested, since it affects every rem-sized element in the app.

Comment thread packages/core/src/components/scatter-plot/scatter-plot.ts Outdated
Comment thread packages/core/src/components/scatter-plot/scatter-plot.ts Outdated
Comment thread packages/core/src/components/scatter-plot/scatter-plot.ts
@peymanvahidi peymanvahidi changed the title Ehnacements: Z-order issue Enhancements: Z-order issue May 13, 2026
Drop the union-find that grouped points sharing coords in ANY projection.
Under UMAP and t-SNE, members of a cross-projection group can render at
distinct canvas coordinates, so the spider would open at one anchor while
the other group members remained visible as scattered dots elsewhere.

Now keyed by exact coords in the current projection only: identical
embeddings still stack under PCA (where they collapse to the same point),
but UMAP/t-SNE no longer fabricate stacks for points that are physically
apart on screen. Extracted into duplicate-stack-helpers so the key
contract can be unit-tested.

Preserves the _cancelDuplicateStackCompute guard and the
_scheduleDuplicateOverlayUpdate(true) call after quadtree rebuild —
those fix the original #121 lag and are independent of this change.
_toggleSpiderfy previously wrote the click anchor directly onto the
current stack object's x/y/px/py. _duplicateStackByKey rebuilds with
fresh objects on every viewport recompute, so any pan or zoom while
the spider was open snapped the anchor back to whichever group member
the iterator happened to encounter first.

Store the anchor separately as { stackKey, x, y } on the component
and reapply it inside _ensureDuplicateStacksForViewport after each
rebuild. The reapply is guarded by stackKey match, so stale anchors
from a previously-closed stack stay inert.
…'t lie about positions

WebGLRenderer's sort gate was tied to positionsDirty plus a sample-based
depth-changed check. The check compared this.depths[i] (sorted-order
value) against getDepth(points[i]) (input-order value), so it could not
reliably detect category-level z-order swaps. The scatter-plot used to
work around this by calling invalidatePositionCache() on z-order change,
which forced a re-sort by claiming the position buffer was stale.

Add a separate depthOrderDirty flag and a public invalidateDepthOrder()
method on WebGLRenderer. populateBuffers honors the flag and clears it
after re-sorting. scatter-plot's _handleZOrderChange and the !colorOnly
path of _handleColorMappingChange now call invalidateDepthOrder() —
positions remain valid, but the painter-sort is rebuilt.
…ring selection

Mirror the selection ESC cascade: pressing Escape with a duplicate-badge
spider open now collapses the spider first, leaving the rest of the
selection state untouched. A subsequent Escape clears selections as
before, and a third toggles off selection mode.

Expose hasExpandedDuplicateStack() and closeExpandedDuplicateStack()
from the scatter-plot, extend the control-bar's ScatterplotElementLike
interface accordingly, and prepend the badge-close branch to the
existing handleDocumentKeydown cascade.
…switch regressions

Extend duplicate-stack-helpers with buildDuplicateStacks (a sync helper
that mirrors the chunked production algorithm) and the DuplicateStackPoint
type, then add Vitest coverage for the two failure modes from #121:

  - legend-hide: stacks must shrink, then disappear, as visible points
    are filtered out — proving the duplicate pass respects opacity-based
    filtering applied upstream in _buildQuadtree.
  - projection-switch: the same proteins under different projection
    coords must produce different stack sets — no leakage from the
    previous projection's groupings.

Also pins the UMAP-jitter case explicitly: two points with identical
embeddings but different UMAP coords must NOT form a stack, which is
the failure mode the cross-projection union-find used to produce.
@peymanvahidi
Copy link
Copy Markdown
Collaborator Author

Addressed all items from your review:

  1. UMAP duplicate-badge misbehavior and union-find revert: see thread reply on scatter-plot.ts:637. Reverted to per-projection grouping in 2aca991.
  2. ESC handler: implemented in 0961fd5. ESC now closes an open duplicate-badge spider first; the next ESC clears the selection.
  3. Spider anchor persistence on pan/zoom: see thread reply on scatter-plot.ts:1825. Stored separately in 2c50aef.
  4. Renderer depth-sampling workaround: see thread reply on scatter-plot.ts:376. Replaced with a proper WebGLRenderer.invalidateDepthOrder() API in 6be0b82.
  5. Unit test for duplicate-stack logic: added in 308099b covering legend-hide and projection-switch (locks [BUG] duplication badges lagging behind #121 against regression). 15 new tests, total now 824.
  6. PR title typo: fixed (Ehnacements to Enhancements).
  7. font-size: clamp(...) rationale: added to the PR description.

#101 note: per-projection grouping means UMAP/t-SNE generally don't show duplicate badges (they don't co-locate identical embeddings). PCA still does. Happy to revisit if you'd prefer "badges by embedding identity regardless of projection," but that's the behavior that produced the visual misbehavior you recorded.

@peymanvahidi peymanvahidi marked this pull request as ready for review May 14, 2026 09:43
@tsenoner
Copy link
Copy Markdown
Owner

tsenoner commented May 14, 2026

Why are the same sequences in UMAP not overlapping as they do in PCA?
e.g.: P0C8L1 and P0C8L1 have the exact same sequences and overlap in PCA but not on UMAP. There is even another entry that is not the same that is in between the two: P0C185.
Is that a limitation/property of UMAP?

@peymanvahidi
Copy link
Copy Markdown
Collaborator Author

I think because it's stochastic.

@tsenoner
Copy link
Copy Markdown
Owner

tsenoner commented May 14, 2026

Yeah, came to the same conclusion. So we just have to keep in mind that, for the UMAP, duplicated badges might not serve any purpose. Which is fine.

Untested below the verified 1440-1920px range, the clamp() shrank
1rem to 12px on smaller viewports (incl. the 1280x720 Playwright
config), affecting all rem-based sizing across the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tsenoner tsenoner merged commit fb9bda3 into main May 18, 2026
4 checks passed
@tsenoner tsenoner deleted the fix/enhancements branch May 18, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Colorization behaves unexpected when changing ordering (decreasing and increasing) [BUG] duplication badges lagging behind

2 participants