Repository navigation
Plots: cases as points, the portal's sidebar layout, sample as the unit, point layers - #47
Merged
Merged
Conversation
A plot now draws its cases as points the way cBioPortal's Plots tab does, without shipping a quarter of a million of them. At or under POINTS_CAP (5,000) every case is a point, on a scatter as well as a box. Past the cap the bulk stays density and the outliers are points: - box: the values beyond each group's Tukey whiskers, drawn over a violin that now stops at the whiskers (plotPointsSql's `outside`, whiskers passed as literals) - scatter: the cases in sparse grid cells, i.e. every cell holding at most k cases, where k is the largest value up to OUTLIER_CELL_MAX (3) whose points fit under the cap (sparseCellMax). They are binned with the same expression and extent as the density (binIndex), so those cells are left unshaded and no case is drawn twice. If a subset is itself over the cap, none of it is drawn, never the first 5,000. PlotData.pointSet records the rule, how many points were drawn, and why there are none when there are none; the caption says the same in words. Only PatientLevelResolver returns points (plotPoints, plus the new plotScatterPoints). An aggregate-only participant gets density, and the caption names it. Box axis: points beyond the whiskers used to spill outside the frame. The axis now covers the points, but extends at most one whisker span past the whiskers, so one TMB of 570 can't flatten every box. Points further out are drawn as triangles at the frame edge and counted in the caption. Explore's 48x32 density passes points:false. docs/plot-case-level.md is rewritten to this model (density for the bulk, a chosen subset as points) with the later phases; the DSL reference and docs/dsl.md are regenerated. Verified on msk_chord_2024: - TP53-mut scatter (13,087 samples): 1,224 sparse-cell points, equal to the bins' own count of cases in cells <=3, and none lands outside a sparse cell when re-binned client-side. - TP53-mut box: 77 outliers, matching an independent count over all 13,087 values. - KRAS G12C (1,116): every case drawn. - /lab renders all four with no console errors. Gate: build, unit, db, portal counts, cohort logic, method scripts, Python, and e2e all pass except "the upload example matches the explored study in full", which fails the same way on master's own CI run (job 115375037223) and touches no plot code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L
Each layer used to size its points from its own count: box points from 1.3 to 4.5 px, scatter points from 1.4 to 3.4, highlights from 1.6 to 4.2. So the 77 outliers over a violin were drawn at 4.5 px, nearly twice the size of the 1,224 scatter points beside them. PLOT_POINT_R (2.5) is now the radius of every plotted case: scatter or box, plotted cohort or highlight, whatever the count. A ring no longer enlarges its dot; it is a stroke on the same circle. Legend swatches are not cases and keep their size. A test asserts that a scatter with 2 points, one with 4,000, and a box plot with a ringed point all use the same radius. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L
The plot card's controls move from one toolbar row into the Plots tab's layout. A left column holds a "Horizontal axis" block and a "Vertical axis" block, each titled down its side, with "Swap axes" between them. Each block's fields are named and stacked: Data type, then what that source needs (Clinical attribute; Molecular profile and Gene; Group cases by and Gene(s) or OQL query), then Log scale. Below the blocks are "Each point is a" (patient or sample) and "Highlight cohorts". On the right, a line says how many cases have data on both axes, then the box plot's "colour samples by" fill/ring row, then the figure. Under a narrow card (container query, 48rem) the sidebar sits above the figure. The layout is PlotControls (app/lab/plot-controls.tsx), which now takes the figure as its child, so the lab's plot card and the Results page's Plots tab share it. AxisPicker and HighlightPicker take `stacked` for the sidebar (field names, full width); the inline form is unchanged for the Cox and logistic cards. A box is always drawn with its categories across, so a box whose categories are in the document's y says that under the horizontal block, and Swap makes the two agree. For a box already drawn as the document says, Swap is disabled and says why. Verified against cbioportal.org's Plots tab (BRCA PanCancer, TP53 mutation type vs FGA) by screenshot, at 1500 and 760 px, with no console errors. A new e2e check asserts both blocks, the field names, the figure beside the sidebar, one control height, and that Swap rewrites x and y. Gate: all stages pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L
…les by a stated rule
A plot's point is now a sample unless the view says unit:"patient", as on cBioPortal's
Plots tab, which has no patient mode at all. PlotView's own comment already said the
default was "sample"; the runner disagreed.
Patient mode used to read a sample-level clinical attribute with argMax(v, pref) over
rows that all had the same pref, so a patient with two samples got one of them, chosen
by ClickHouse. Now a patient's own row still wins, and otherwise a number is the mean
of the patient's samples and a category is their common value, or "Multiple" where they
differ. That matches what molecular axes (mean) and mutation type ("Multiple") already
did. The coverage query counts the cohort's patients with more than one sample
(`multi`), and the caption says how many there are and how each kind of axis combined
them. Composition views read the same clinical-axis query, so they get the same rule.
On msk_chord_2024, a patient-mode CANCER_TYPE x FGA plot has 90 multi-sample patients.
All 90 land in "Multiple": they are patients with two different primary cancers, one
sample each, e.g. P-0020057 (NSCLC and breast). The old query gave each of them one of
the two at random.
Gallery examples that describe patients set unit:"patient", so their text and pinned
counts are unchanged. The unit choice offers sample first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L
…the plotted cohort
`points` replaces both the old outlier rule and `highlight`. It is an ordered list of
at most four layers, each a subset of the plotted cohort's cases drawn in its own
colour:
- {rule:"outliers"}: the default. Where the cohort is density, a box's values beyond
its whiskers, and a single study's scatter's cases in sparse cells.
- {rule:"extremes", axis, n}: the n highest and lowest values of a numeric axis, per
group on a box.
- {rule:"trend", n}: on a scatter, the n cases furthest from the fitted line.
- {cohort}: what a highlight was.
[] draws none. `allPoints` (default true) is the separate switch for drawing every case
at or under 5,000; Explore's small scatter turns it off.
Rules every layer follows:
- One POINTS_CAP budget is shared by all layers.
- A case two layers share is drawn by the first, and the caption counts what each layer
left to an earlier one.
- A layer is never cut to its first cases. A cohort layer past the budget becomes
density cells or a box per group; a rule layer is not drawn; the caption says why.
- Extremes and trend are exact across studies: each study returns its own top n and
the runner keeps the overall top n. The caption says ties at the cut are broken by
case id.
- With a box's cases coloured by a category, a layer is a ring round the case.
- A participant that may not show cases draws no rule layer, and its cohort layers are
aggregates. A peer that cannot answer a cohort layer leaves that layer undrawn with
the reason, rather than failing the view.
- An old `highlight` is refused by name, saying what replaced it.
The new id-returning query, plotRankedPointsSql / PatientLevelResolver.plotRankedPoints,
lives only on the patient-level resolver; boundary tests assert it, as for the other
point methods. lib/plot-highlight.ts becomes lib/plot-layers.ts: the cap, colours,
defaults and labels, browser-safe. The figure draws `layers`. The sidebar's "Highlight
cohorts" becomes a Points section: the every-case checkbox, one coloured chip per layer
(an N field for ranked layers, move up, remove) and an add menu offering the rules and
cohorts, up to four. The DSL reference, the three highlight examples (now point-layer
examples, counts unchanged), the figure stress document and docs/plot-case-level.md are
rewritten to match.
Verified on msk_chord_2024 TP53-mutant samples (13,087):
- scatter with [outliers, extremes y 20, trend 20, KRAS G12C]: 1,224 sparse-cell
outliers (the same as before); the extremes' and trend's overlaps with the outliers
are counted; KRAS G12C is 480 samples, 68 of them already drawn.
- box: 77 outliers (as before); extremes 3 per group across 6 groups = 36.
- CANCER_TYPE x TMB with extremes n=2: every group's top and bottom two equal a direct
ClickHouse query.
- In the browser, adding, editing n, moving and removing a layer each rewrite the
document and redraw, with no page errors.
A new e2e check adds a layer from the sidebar and finds it in the figure's key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
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.
The plot view, moving toward cBioPortal's Plots tab: individual cases drawn as points, the portal's sidebar layout, sample as the unit, and point layers.
What changes
Cases as points, without shipping every case. At or under 5,000 cases every case is a point, on scatters and box plots. Over that, the bulk is a density, and chosen subsets are drawn as points. Nothing is cut to its first N, and the caption says what was drawn and why. Every point is the same size.
The portal's layout. Each axis is defined in its own block in a left sidebar ("Horizontal axis", "Vertical axis"), with "↑ Swap axes ↓" between them and the figure beside it under a "Showing N samples with data on both axes" line. It's one component,
PlotControls, shared by the lab's plot card and the Results page's Plots tab. On a narrow card the sidebar stacks above the figure.A point is a sample by default, as on the portal, which has no patient mode.
unit: "patient"now combines a patient's samples by a stated rule: numbers are averaged, a category is the samples' shared value or "Multiple", and an alteration counts if any sample has it. The old patient mode took an arbitrary sample's value for sample-level clinical attributes. On msk_chord, 90 patients have two different primary cancers, and the old query gave each of them one of the two at random.Point layers.
pointsis an ordered list of up to 4 subsets, each drawn in its own colour, and it replaceshighlight:outliers(the default): past the cap, a box's values beyond its whiskers, or a scatter's cases in sparse cells.extremes: the highest and lowest n on a numeric axis, per group on a box.trend: the n cases furthest from the fitted line.cohort: whathighlightwas.Layers share one point budget. A case in two layers is drawn by the first, and the caption counts the overlap. A layer that doesn't fit becomes an aggregate or is not drawn, saying why. The sidebar's Points section lists the layers as coloured chips, with an add menu. An old
highlightis refused by name, with an error saying what replaced it.Data boundary
The new case-returning query (
plotRankedPoints/plotRankedPointsSql) exists only onPatientLevelResolver, and the boundary tests assert it the same way as the other point methods. An aggregate-only participant gets density only, and the caption says why.Verified
On msk_chord_2024:
Gate: the full gate run failed on one gallery example that still used the old
points: false. It was fixed in this branch, and the three stages that read the examples were rerun and passed:docs:check,check:figures(387 figures, 0 issues) andcheck:methods. CI on this PR runs the whole gate.Known, fixed in the follow-up
An OQL axis still counts a sample whose gene panel omits the gene as "Unaltered" (pre-existing). The next PR from
session/plotsmakes those samples "Not profiled". It also adds one group per OQL statement, commas between statements, a 10,000-point cap set from a measurement, and showing one layer alone.🤖 Generated with Claude Code
https://claude.ai/code/session_01BhP3GAdWXr2EtfBDHvRS1L