Participant profile — a shared drill-down module

One participant profile, every chart

A collaborator hit the exact seam between the shipped hep-explorer and the original SafetyGraphics eDISH:

“Is there a way to drill down to patient level details with the composite plots? Hovering the cursor brings up some details, but it’s not like the original eDISH that opens up multiple graphs below when you click on a patient point.”
— collaborator feedback on the safety.viz composite view, July 2026

They are right, and the gap is bigger than the composite view. The original eDISH answered a fourth question that none of our population views currently answer well: after which arm?, how severe?, and which participants? comes “show me this one person’s whole story.” This report proposes carving that answer out as its own module — a participant profile that any safety.viz chart can open — and lays out four options (A–D) for how it gets surfaced, with a recommendation. Nothing here is built yet; this is the decision artifact.

1 · What clicking a participant does today

Every selectable renderer already speaks the same event (participantsSelected, payload {detail:{data: ids}}), but what the user sees after a click is different in every chart — and only one view in the whole library plots labs over time for the clicked participant:

SurfaceClick todayPatient-level detail?
hep-explorer · scatter (eDISH)Selects one participant: visit-path trace + axis styling on the chart, sidebar select syncs, and a single lab-over-time line chart renders below (drawDetail)partial one chart, no header, no measure table
hep-explorer · compositeMulti-select toggle: point restyles across all panels, header count updates, event fires. No drill-down renders. Hover tooltip shows peak values + quadrant shift onlynone — the collaborator’s gap
outlier-explorer / shift-plotSelection overlay / brush → linked record listing below the chartpartial raw records, no graphs
ae-timelinesSwaps the whole view for a per-participant AE timeline + listing, with a Back buttonyes (AE domain only)
delta-deltaHighlights the point, writes “Participant X selected.”, draws a measure table. No event dispatchedpartial
histogram / results-over-time / qt-explorer / ae-explorerNo participant selectionnone

Planned surfaces raise the stakes: the migration Sankey (sv#92) deliberately ends in “Review these N in the composite plot” — a hand-off that today lands the reviewer in the one view with no individual drill-down. The waterfall (sv#93) is one bar per participant; a bar click has nowhere to go either.

2 · What the original eDISH profile actually contained

The original’s click-to-drill-down was a four-part composition, mounted below the main chart with the chart still visible (verified in the hep-explorer source, src/callbacks/onResize/customizePoints/):

PieceContentssafety.viz today
Participant headerDemographics from config.details, computed R Ratio (and P_ALT), a Clear button, optional “Full Participant Profile” link-outMissing (the link-out is sv#53)
Spaghetti plot“Standardized Lab Values by Study Day” — one line per key lab, ×ULN / ×baseline toggle, per-measure cut lines on hover, filled points above the cut, optional exposure stripScatter view has a single-participant version (drawDetail); no toggle, no cuts, no exposure
Measure summary tableAll lab measures (key ones first, extras behind a toggle): N / min / median / max + an inline sparkline per measure; clicking a sparkline expands an inset line chart with the normal-range band and population-extent guidesMissing entirely
On-chart traceVisit path + axis rugs drawn on the eDISH scatter itself; other points dimmedPorted (scatter view)

Two useful limits of the original worth not reproducing: it was strictly single-participant (each click overwrote the last), and the profile code was welded into the eDISH renderer — which is why none of the other seven safetyGraphics charts ever got it. That welding is the thing to fix.

3 · The module (common to every option below)

One new shared module — working name participant-profile — that owns the drill-down regardless of which chart opens it. The four options in the next section only disagree about where it appears; the module itself is the same:

Contents (v1)

Header (id, demographics from config.details, computed stats, Clear, templated profile link-out — absorbing sv#53) · labs-over-time spaghetti (×ULN / ×baseline toggle, per-measure reference cuts) · measure summary table with inline sparklines · optional record listing reusing the shared listing renderer. Sections are individually toggleable per chart config.

Why it is cheap now

The hard parts already exist as pure functions inside hep-explorer’s data layer: participantMeasureSeries (day-indexed series per measure), measureSummary (per-measure N/min/median/max), and the retained cleaned rows every renderer keeps after render. The planned hep-core extraction (sv#91) moves exactly these reducers into a shared layer — the profile module is their second consumer, ready-made.

Wiring

The module listens for the existing participantsSelected event on the chart’s outer container (which catches all current dispatch targets) and renders when the selection resolves to one focused participant. It needs no new chart APIs — though the rollout piggybacks on sv#88’s event-parity work (every selectable chart dispatching the same payload, including on clear). Because seven of the nine renderers ingest the same long measure-per-visit records, the same module serves the whole lab family — hep-explorer, histogram, outlier-explorer, shift-plot, results-over-time, qt-explorer, delta-delta — not just eDISH.

Option A

The profile dock

“Faithful to the original: click a point, the story opens below, the chart stays put.”

The shell grows a sv-profile slot under the chart card (above the record listing). Any renderer that opts in gets the full profile there when a single participant is focused. Chart and profile are visible together, so the on-chart trace and the labs-over-time read as one linked view — the interplay reviewers liked in the original.

What changes

  • One new shell slot + profile: true (or per-section config) per renderer
  • hep scatter’s drawDetail is deleted in favor of the module
  • Composite: focusing one participant (click with one selected, or via the selector) opens the dock

Trade-offs

Long pages on small screens — profile pushes the listing down. Multi-panel views (composite) already scroll; the dock adds more. Needs the stepper (§5) for multi-select.

Choose when

Parity with the original eDISH experience is the point, and charts are reviewed on desktop reports — which is our reviewers’ reality.

A · COMPOSITE VIEW + PROFILE DOCK BELOW
Controls
Group byTreatment Arm
ParticipantsSearch ids…CLD-2013 ✕
Baseline eDISH
Peak on-treatment
×BLN ALT
×BLN TB
migration
by arm
Participant CLD-2013Hy’s Law · R Ratio 6.2Clear ✕
Arm ActiveAge 61Sex FBaseline quadrant Temple’sFull profile ↗
standardized labs by study day · ×ULN ▾ · cut line 3×
MeasureNMax
ALT128.1×
TB123.4×
AST125.0×
ALP111.2×
+ 14 additional measures ▸
Option B

The drawer

“The chart is the workspace; the profile slides in beside it and slides away.”

The profile opens as a right-hand slide-over panel on top of the chart card, dismissed with ✕ or Escape. The population view stays in place at reduced width; the reviewer flicks between individuals without the page ever reflowing.

What changes

  • Same module, mounted in a shell-owned overlay panel with focus management
  • Charts need no layout awareness — nothing below them moves
  • On narrow screens the drawer becomes a bottom sheet

Trade-offs

Covers part of the chart it annotates — the dimmed-points + trace choreography loses half its audience. Sparkline-to-inset expansion gets cramped at drawer width. Overlay a11y (focus trap, scroll lock) is real new surface for the library.

Choose when

Screen economy beats chart–profile adjacency, or profiles are opened dozens of times in quick succession (triage workflows).

B · PROFILE AS SLIDE-OVER DRAWER
Composite · peak on-treatment
CLD-2013
Arm ActiveR 6.2
labs by study day
ALT8.1×
TB3.4×
Chart never reflows — the drawer overlays the right half and Escape dismisses it.
Option C

The profile view

“Profile is a destination: a full view in the view selector, like Scatter and Composite.”

The profile becomes another entry in each chart family’s view list (the shared view selector of hub#41). Clicking a participant — or picking one in the participant selector (sv#87) — switches to a full-canvas Profile view with a Back affordance, the ae-timelines pattern generalized.

What changes

  • Full width for the spaghetti, measure table and listing at once — the richest single-participant canvas
  • View state, so profiles are addressable (deep-linkable) alongside scatter/composite
  • ae-timelines’ bespoke detail swap retires into the shared pattern

Trade-offs

The population chart disappears while reading the profile — click, teleport, Back, re-orient. Exactly the context-break the original avoided by rendering below. Weakest match for compare-against-the-population review.

Choose when

The profile grows past what a dock can hold (AE track, exposure, conmeds) and becomes a report of its own.

C · PROFILE AS A FULL VIEW
View
ScatterCompositeMigrationProfile · CLD-2013
ParticipantCLD-2013 ▾
Participant CLD-2013Active · 61 F · Hy’s Law← Back to Composite
full-width labs by study day · exposure strip · measure table · record listing below
measures
exposure
listing
notes
Option D

Host-composed only

“The profile is a tenth renderer; apps wire it next to any chart with one event listener.”

Ship SafetyViz.participantProfile(el, data, config) as a standalone renderer and change the charts not at all. Host pages — the gallery demos, gsm.safety widgets in Shiny, open.gismo — mount it wherever they want and pipe participantsSelected into it. The library’s first linked-charts demo makes it discoverable.

What changes

  • New renderer with the full done-gate (gallery card, demo, guide, evidence, API ref)
  • Zero changes inside existing charts; the R side gets Widget_ParticipantProfile with crosstalk-style wiring for free
  • A composable primitive: one profile can serve several charts on the same page

Trade-offs

A bare chart embed still has no drill-down — the collaborator’s question stays unanswered out of the box, deferred to whoever builds the page. Two data ingests (chart + profile) unless the host shares one cleaned dataset.

Choose when

You believe composition in the host app (open.gismo) is where linked views belong, and single-chart embeds are a secondary case.

D · STANDALONE RENDERER, WIRED BY THE HOST
SafetyViz.hepExplorer host page
SafetyViz.participantProfile
CLD-2013Active · 61 F
chart.addEventListener('participantsSelected', e ⇒ profile.show(e.detail.data))
Any pairing works — the same profile instance can sit beside the composite, the Sankey, the waterfall, or all three.

★ Recommendation: one module, two mounts (D’s packaging, A’s default)

Build the profile once, as Option D’s standalone module — its own factory, data contract, evidence page, and event wiring, so gsm.safety widgets and open.gismo can compose it freely. Then give every lab-family chart Option A’s dock as the built-in default: a profile config (on by default for the hep family) that mounts that same module into a new shell slot below the chart and feeds it the chart’s own cleaned rows — no second ingest, no host wiring. A bare hepExplorer(el, data) embed then answers the collaborator’s question out of the box, exactly the way the original did, while the standalone form stays available for linked-page composition.

The drawer (B) remains cheap to add later as profile: {mode: 'drawer'} if triage-style review demands it — same module, different mount. The full view (C) is the one I’d pass on for now: it breaks chart–profile adjacency, and its one real payoff (a bigger canvas) matters only after the profile grows AE/conmed/exposure tracks in v2.

Multi-select and cohorts (§5 below): the dock renders the full profile when exactly one participant is focused. With N selected, it collapses to a one-line stepper — 3 of 12 · CLD-2013 — stepping through the cohort in ranked order (worst quadrant first). That makes the Sankey hand-off (sv#92) a complete chain for the first time: ribbon → cohort in composite → step through individuals → record listing. Population to single lab value in four clicks.

4 · Where each chart picks it up

SurfaceEntry gestureNotes
hep-explorer · scatterPoint click (today’s gesture)Replaces drawDetail; on-chart trace + rugs stay
hep-explorer · compositeClick with one focused; stepper when N selectedCloses the collaborator’s gap. Points exist in baseline + peak cards and ×BLN panels — dock highlights the focused id in all of them
migration Sankey (sv#92)Via the composite hand-off (“Review these N…”) → stepperProfile is the last mile of the paper’s two-step review
hep-waterfall (sv#93)Bar clickOne bar = one participant — the most natural fit in the family
outlier-explorer / shift-plot / histogram / results-over-time / qt-explorer / delta-deltaExisting selection gestures + the shared participant selector (sv#87)Same long-lab contract, so the dock is config-on; rolls out with sv#88’s batches
ae-timelines / ae-explorerDeferredAE-domain profile section is v2; ae-timelines keeps its detail swap until then

5 · Sequencing against the issues already filed

Yesterday’s filings put every prerequisite on the board; the profile module slots in without reshuffling any of them:

StepWorkStatus
1sv#91 hep-core split — moves participantMeasureSeries / measureSummary / cleaned-row layer into the shared core the profile consumesFiled, first PR of the hub#43 stack
2sv#87 shared participant selector — the non-click way to focus a participant; chips become the stepper’s siblingsFiled
3Profile module + dock (this report) — new requirement, lands first in hep-explorer composite + scatter; absorbs sv#53 (link-out lives in the profile header, templated by id rather than the original’s static URL)Awaiting direction (D1–D4 below)
4sv#88 selection-note rollout — its event-parity work (SELN-4) is exactly the wiring the profile listens to; dock adoption rides the same per-renderer batchesFiled, depends on sv#87
5sv#92 migration view + sv#93 waterfall pick the dock up on arrivalFiled, depend on sv#91

6 · Decisions for @jwildfire

D1 — Surfacing default

Dock below the chart (A), drawer (B), full view (C), or host-composed only (D)?

Recommend: the hybrid — standalone module (D packaging) with the dock (A) as the built-in default; drawer later if wanted; skip C.
D2 — v1 contents

Header + spaghetti + measure table with sparklines + optional listing is proposed v1. The original’s sparkline-click inset chart, exposure strip, and AE/conmed tracks would be v2.

Recommend: v1 as proposed — it already exceeds the original’s three blocks minus the inset expansion.
D3 — Multi-select behavior

When a cohort is selected (composite multi-select, Sankey ribbon, shift-plot brush): stepper strip that pages through the cohort, or profile only when exactly one is selected?

Recommend: the stepper — it is what makes the Sankey→composite hand-off end in individuals, per the paper’s review model.
D4 — Filing and naming

File as a new hub requirement (with safety.viz sub-issues, after sv#91 lands) or fold into an existing issue? And: “participant profile” (house/CDISC style, matches participantsSelected) vs the collaborator’s “patient profile”?

Recommend: new hub requirement — it is cross-renderer scope like the selector (hub#41); name it participant-profile.
Meanwhile — an answer you can send the collaborator today

“Not yet in the composite view — click currently selects participants (and feeds the linked views) but doesn’t open the drill-down; the scatter view has a first slice of it (a labs-over-time chart below the plot on click). You’ve hit exactly the gap we’re closing next: we’re carving the original eDISH click-profile out as a shared module — header, standardized-labs-over-time, and the measure table with sparklines — that opens below every chart in the family, composite included, and steps through a selected cohort one participant at a time. Design options are drafted and it’s queued right behind the current hep-explorer refactor.”