Skip to content

Add annotation overlay for the OncoTree (+ migrate frontend to pnpm)#342

Open
inodb wants to merge 26 commits into
cBioPortal:masterfrom
inodb:oncotree-pulse
Open

Add annotation overlay for the OncoTree (+ migrate frontend to pnpm)#342
inodb wants to merge 26 commits into
cBioPortal:masterfrom
inodb:oncotree-pulse

Conversation

@inodb

@inodb inodb commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Adds an annotation overlay to the OncoTree frontend: users can map OncoTree codes to custom values and see them visualized on the tree. Useful for overlaying sample counts per cancer type, gene lists from tools like Oncotree-to-Genes-LLM, or any code → value data.

This branch contains two independent commits:

  1. Migrate the frontend package manager from npm to pnpmpackage-lock.jsonpnpm-lock.yaml (versions preserved via pnpm import), packageManager pinned to pnpm@10.33.0, esbuild's build script whitelisted. CI workflows, Dockerfile, the husky hook, and the READMEs are switched to pnpm.
  2. Add the annotation overlay feature.

Feature details

Input (top-right Annotations panel):

  • Paste a JSON object mapping codes to values, or a CSV/TSV with the code in the first column.
  • Upload a .json/.csv/.tsv file, or load built-in sample data.
  • Share a view via ?annotations=<base64-json> in the URL.

Accepted value shapes per code: a number, a string label, a gene list, or { "value": n, "label": "…", "genes": [...] }. Example:

{
  "LUAD": {"label": "1204 samples", "value": 1204},
  "GB": {"genes": ["EGFR", "PTEN", "TP53"]}
}

Visualization:

  • Numeric values → color-coded halo (sequential scale) + value badge.
  • Gene lists / text → accent halo + "N genes" / label badge.
  • Hover any annotated node for a tooltip with the full detail.
  • Unknown codes (not in the current OncoTree version) are flagged but still kept.

Implementation note: the @oncokb/oncotree renderer is a compiled D3 black box that re-styles its node circles on every expand/collapse, so overlays are appended as extra SVG children of each node and re-applied via a debounced MutationObserver. No changes to the tree library or the Go backend.

Testing

  • pnpm build, pnpm lint, and tsc -b pass.
  • Verified end-to-end with headless Chrome: manual paste, file-shaped input, numeric color scale + value badges, gene-list badges, hover tooltips, overlay re-application after expanding the tree, and ?annotations= URL loading. No console errors.

🤖 Generated with Claude Code

inodb and others added 26 commits June 19, 2026 17:10
Replace package-lock.json with pnpm-lock.yaml (versions preserved via
pnpm import), pin pnpm@10.33.0 via the packageManager field, and whitelist
esbuild's build script. Switch CI workflows, Dockerfile, the husky
pre-commit hook, and the READMEs to pnpm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Let users overlay custom annotations on the tree by mapping OncoTree codes
to values. Annotations can be pasted (JSON or CSV), uploaded as a file, or
shared via a ?annotations=<base64> URL.

- annotations.ts: parse/normalize JSON and CSV/TSV into values, labels, or
  gene lists; validate codes against the tree; build a color scale; encode
  and decode the URL parameter.
- AnnotationOverlay: decorate each rendered SVG node with a color-coded halo
  and a value/gene badge plus a hover tooltip. Re-applied on every tree
  re-render via a debounced MutationObserver since the tree library restyles
  its nodes on expand/collapse.
- AnnotationPanel: paste/upload/sample/clear controls, share-link button,
  validation feedback, and a legend.
- Wire annotation state and URL loading into App and Home.
- vite: make the dev API proxy target overridable via ONCOTREE_API_PROXY.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
ts-jest defaults to an ES2015 target whose lib lacks Object.values; this
surfaced as a TS2550 compile error under the pnpm node_modules layout
(the npm layout happened to resolve a higher lib). Set an explicit ES2020
target/lib for the test transform, matching tsconfig.app.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Use a neutral amber ring to mark annotated nodes instead of a data-colored
halo, so each node keeps its own OncoTree color (e.g. HCC stays
MediumSeaGreen). Numeric magnitude moves to the badge fill (value color
scale); gene/text badges use a neutral slate. Legend updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
decodeAnnotations now accepts either raw (URL-encoded) JSON
(e.g. ?annotations={"LUAD":1204}) or base64/base64url-encoded JSON, and
normalizes every value shape so bare numbers, strings, and gene arrays all
render. Share both behind a shared normalizeAnnotationMap helper reused by
parseAnnotations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
A collapsed node now aggregates its hidden subtree: numeric values are
summed (badge shows "Σ <total>") and gene lists are unioned, with the
tooltip reporting how many descendants contributed. Expanding a node splits
the data back to each child, so every annotated code is counted exactly once
at any expansion state. Clamp the color scale so summed values stay in range.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
A collapsed node already implies its badge is a rollup, so show the plain
total. The tooltip still labels it "Sum".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
When annotations arrive from the ?annotations= URL parameter, populate the
panel textarea with their JSON so they are visible and editable. Skips the
sync when the user is already typing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
When embedded in an iframe, the app announces { type: "oncotree-ready" } to
its parent and listens for { type: "oncotree-annotations", annotations } to
overlay data programmatically (object, JSON string, or null to clear). Lets
tools push large payloads without a backend or URL-length limits. Document
all annotation input methods in the README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Add a script that builds the frontend and publishes it to a fork's gh-pages
branch, served from a subpath against a public OncoTree API. Supporting
config, all no-ops at their defaults:

- vite `base` from DEPLOY_BASE (default "/")
- router basename from import.meta.env.BASE_URL (so routing works on a subpath)
- ONCOTREE_BASE_URL from VITE_ONCOTREE_BASE_URL (default window.location.origin)
- fetch the tree from the canonical no-slash route (avoids a 301 redirect; the
  Gin route is registered without a trailing slash)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
An embedding page can post { type: "oncotree-search", query } to filter the
tree to nodes matching a code, name, or annotation (gene/label/value); the
tree collapses and expands the path to the matches and focuses the first.
An empty query (or { clear: true }) resets. The app posts back
{ type: "oncotree-search-result", query, count }.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
The demo fetched the tree from oncotree.mskcc.org at runtime, which fails CORS
/ Private Network Access when the page is embedded (the host can resolve to a
private IP). Snapshot the API at build time and serve it from the deploy's own
subpath so the running page only makes same-origin requests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
For iframe embedding, `?embed` (or ?embed=1) renders only the tree (with the
expand/collapse and annotation controls) at full height, hiding the site
header and footer. The host drives annotations and search via postMessage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
In ?embed mode the host supplies annotations via postMessage, so the input
panel/toggle is unnecessary; hide it (the data overlays on the tree still
render). Add a hideAnnotationPanel prop on Home, set from the embed flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Rework the overlay for legibility:
- Fold annotation detail (value, gene union) into the tree library's own node
  tooltip, shown on hovering the node name, instead of a separate tooltip on
  the hard-to-hit marker.
- Drop the marker ring; the badge itself marks an annotated node.
- Show the value/gene-count in a white pill with a dark bold number (fixes the
  low-contrast white-on-light-blue badges) and place it on the node's own row.
- Remove the value color scale (de-emphasized) for a consistent neutral badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
An expanded node's badge shows its own value only (no double-counting), which
is confusing when a child's value exceeds the parent's. The tooltip now adds a
"Subtree total: N across M nodes (collapse to combine)" line on expanded nodes
that have annotated descendants, so the relationship is explicit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
Replace the faint default expand/collapse icons with labelled, high-contrast
buttons (white pill, tooltip) and add a full-screen toggle that expands the
tree area. Expand/collapse now call the OncoTree public API directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
- Replace the chevron icons with custom glyphs: expand shows a node forking
  into two children, collapse a node with its subtree tucked into one line.
- Give the content area its own light background so fullscreen shows the gray
  grid instead of the browser's black fullscreen backdrop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
requestFullscreen rejects when embedded without allow="fullscreen"; catch it
and show a hint instead of an unhandled rejection. Document the iframe
allow="fullscreen" requirement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
- Node labels show a pointer cursor and underline on hover, and clicking a
  label now toggles the node (the library only wired the circle).
- The annotation badge is interactive: pointer cursor, a hover state (border
  darkens), it shows the node tooltip on hover (previously only the label did),
  and clicking it toggles the node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
When embedded, clicking a leaf node's annotation badge now posts
{ type: "oncotree-node-click", code, label } to the parent window, so a host
page can react (e.g. filter to that cancer type). Parent badges keep toggling
expand/collapse.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add an "oncotree-selection" embed message: the host posts the set of selected
codes, and their annotation badges render with a checkmark and a blue highlight
so it's clear which cancer types are selected. Two-way with oncotree-node-click.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Clicking a badge selects the cancer type(s) it represents (leaf = that code;
  collapsed parent = its whole subtree) and never expands/collapses.
- Clicking a node name/dot expands/collapses; expanding a collapsed parent also
  selects every annotated cancer type in its subtree (mode:"add").
- Badge shows a 3-way selection state: none, partial (some of a collapsed
  parent's subtree, "–"), or all ("✓").

🤖 Generated with [Claude Code](https://claude.com/claude-code)
A badge now represents its node's full annotated subtree regardless of expand
state, so clicking an expanded parent's badge deselects (or selects) the whole
branch and keeps it expanded, instead of only toggling the parent's own code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
The library's caret is positioned at left:10% of the tooltip width (via a
stray-space selector that also applies it to every child), so it rarely points
at the node. Hide it; the tooltip already appears next to the cursor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sjAMWhXEZQt1Mqxdb4EBE
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.

1 participant