@jbrowse/plugin-linear-genome-view 5.0.0-beta.7 → 5.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +140 -48
  2. package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
  3. package/esm/LinearGenomeView/afterAttach.js +18 -24
  4. package/esm/LinearGenomeView/closeUps.d.ts +83 -0
  5. package/esm/LinearGenomeView/closeUps.js +211 -0
  6. package/esm/LinearGenomeView/components/AddCloseUpDialog.d.ts +19 -0
  7. package/esm/LinearGenomeView/components/AddCloseUpDialog.js +29 -0
  8. package/esm/LinearGenomeView/components/CloseUps.d.ts +5 -0
  9. package/esm/LinearGenomeView/components/CloseUps.js +75 -0
  10. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +2 -1
  11. package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
  12. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  13. package/esm/LinearGenomeView/components/Header.js +6 -1
  14. package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
  15. package/esm/LinearGenomeView/components/Highlight.js +2 -4
  16. package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
  17. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  18. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  19. package/esm/LinearGenomeView/components/ImportForm.js +2 -2
  20. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
  21. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -2
  22. package/esm/LinearGenomeView/components/OverviewHighlight.js +1 -1
  23. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  24. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.d.ts +7 -1
  25. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +22 -7
  26. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  27. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +6 -21
  28. package/esm/LinearGenomeView/components/SearchBox.js +2 -2
  29. package/esm/LinearGenomeView/components/TrackContainer.js +12 -5
  30. package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
  31. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +3 -1
  32. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +3 -7
  33. package/esm/LinearGenomeView/components/TracksContainer.js +7 -15
  34. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  35. package/esm/LinearGenomeView/components/useSideScroll.js +1 -1
  36. package/esm/LinearGenomeView/consts.d.ts +2 -0
  37. package/esm/LinearGenomeView/consts.js +5 -0
  38. package/esm/LinearGenomeView/headerFit.d.ts +2 -1
  39. package/esm/LinearGenomeView/headerFit.js +7 -2
  40. package/esm/LinearGenomeView/index.d.ts +1 -1
  41. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  42. package/esm/LinearGenomeView/keyboardHandler.js +11 -0
  43. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  44. package/esm/LinearGenomeView/launchKeys.js +15 -5
  45. package/esm/LinearGenomeView/lazyDialogs.d.ts +7 -0
  46. package/esm/LinearGenomeView/lazyDialogs.js +2 -0
  47. package/esm/LinearGenomeView/menuItems.d.ts +28 -16
  48. package/esm/LinearGenomeView/menuItems.js +213 -85
  49. package/esm/LinearGenomeView/model.d.ts +367 -199
  50. package/esm/LinearGenomeView/model.js +302 -143
  51. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  52. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  53. package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.d.ts +5 -0
  54. package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.js +8 -0
  55. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +3 -4
  56. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  57. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +95 -24
  58. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +4 -5
  59. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +13 -4
  60. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +4 -4
  61. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  62. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +52 -63
  63. package/esm/LinearGenomeView/svgcomponents/util.d.ts +8 -6
  64. package/esm/LinearGenomeView/svgcomponents/util.js +37 -16
  65. package/esm/LinearGenomeView/types.d.ts +5 -4
  66. package/esm/LinearGenomeView/util.d.ts +20 -37
  67. package/esm/LinearGenomeView/util.js +58 -141
  68. package/esm/displayKitTests/perRegionTestEnv.d.ts +64 -45
  69. package/esm/displayKitTests/perRegionTestEnv.js +6 -6
  70. package/esm/index.d.ts +9 -67
  71. package/esm/index.js +6 -22
  72. package/esm/searchUtils.d.ts +2 -3
  73. package/esm/searchUtils.js +2 -2
  74. package/package.json +7 -7
  75. package/esm/lazyPluginExports.d.ts +0 -23
  76. package/esm/lazyPluginExports.js +0 -36
@@ -80,7 +80,7 @@ export function defaultTextHeight(fontSize) {
80
80
  export function offsetLabelBaselineY(textHeight, fontSize) {
81
81
  return textHeight - LABEL_PAD_BOTTOM - labelDescent(fontSize);
82
82
  }
83
- // Baseline for a label inset into the top of the box it draws over ('overlay'
83
+ // Baseline for a label inset into the top of the box it draws over ('overlapping'
84
84
  // mode), measured down from that top edge so the ascenders stay inside it.
85
85
  export function insetLabelBaselineY(fontSize) {
86
86
  return labelBaselineFromTop(LABEL_PAD_TOP, fontSize);
@@ -99,7 +99,14 @@ export function insetLabelBaselineY(fontSize) {
99
99
  // scale in each row the only thing saying whether two rows are at the same zoom
100
100
  // is their ruler coordinates, which are unreadable at the size a figure is
101
101
  // published at. The standalone LGV export has drawn this bar all along.
102
- export function getRowHeaderLayout({ fontSize, showScalebar, }) {
102
+ //
103
+ // `reserveAssemblyName` keeps the name's band whether or not the row draws one,
104
+ // which is what a stack of one genome at several loci wants: a breakpoint split
105
+ // view names its first row only, and the rest stay the same height as it. The
106
+ // close-ups export is the case that says false — every row there is the
107
+ // host's own assembly, so no row names it and the band is space no glyph is
108
+ // drawn in.
109
+ export function getRowHeaderLayout({ fontSize, showScalebar, reserveAssemblyName = true, }) {
103
110
  const label = labelInkHeight(fontSize);
104
111
  // the bp label hangs a cap below the bar's line and its own ink box ends a gap
105
112
  // above the ruler at y=0
@@ -114,6 +121,14 @@ export function getRowHeaderLayout({ fontSize, showScalebar, }) {
114
121
  const assemblyInkTop = scalebarLineY === undefined
115
122
  ? -label
116
123
  : scalebarLineY - SVG_SCALEBAR_CAP - ROW_GAP - label;
124
+ // up from the ruler to the topmost ink the row draws above it: the assembly
125
+ // label's box where there is one to reserve, else the scalebar's upper cap,
126
+ // else nothing
127
+ const bandHeight = reserveAssemblyName
128
+ ? -assemblyInkTop
129
+ : scalebarLineY === undefined
130
+ ? 0
131
+ : SVG_SCALEBAR_CAP - scalebarLineY;
117
132
  return {
118
133
  // undefined rather than an unused number: a row without a scalebar has no
119
134
  // line to place, and the caller draws nothing
@@ -123,9 +138,7 @@ export function getRowHeaderLayout({ fontSize, showScalebar, }) {
123
138
  assemblyLabelBaselineY: showScalebar
124
139
  ? labelBaselineFromTop(assemblyInkTop, fontSize)
125
140
  : 0,
126
- // up from the ruler to the top of the topmost ink box — the assembly
127
- // label's, in both cases
128
- bandHeight: -assemblyInkTop,
141
+ bandHeight,
129
142
  };
130
143
  }
131
144
  // Compact vertical layout for the exported header: rows (assembly name,
@@ -161,7 +174,6 @@ export function getHeaderLayout({ fontSize, showCytobands, rulerHeight, }) {
161
174
  // Ruler tick geometry, shared by the tick marks and their number labels.
162
175
  export const RULER_MAJOR_TICK = 5;
163
176
  export const RULER_MINOR_TICK = 3;
164
- export const RULER_TICK_FONT_SIZE = 11;
165
177
  // clearance between the tick-number baseline and the marks below it
166
178
  const RULER_TICK_LABEL_GAP = 2;
167
179
  // Vertical positions within a ruler of the given budget: tick marks hang just
@@ -180,9 +192,17 @@ export function getRulerLayout(rulerHeight) {
180
192
  // 13, and both ends of the glyphs at any smaller export font.
181
193
  export const refNameLabelBoxHeight = labelInkHeight(REF_NAME_LABEL_FONT_SIZE);
182
194
  export const refNameLabelBaselineY = labelBaselineFromTop(0, REF_NAME_LABEL_FONT_SIZE);
183
- // space the label pushes a track down by; only 'offset' mode does
184
- export function labelOffset(trackLabels, textHeight) {
185
- return trackLabels === 'offset' ? textHeight : 0;
195
+ // The mode one track's label is drawn in. 'overlapping' yields to 'offset' for a
196
+ // display that `prefersOffset`, as TrackContainer does on screen: its left edge
197
+ // carries content — an axis, a sample's genotypes — the label would print over.
198
+ export function trackLabelMode(track, trackLabels) {
199
+ return trackLabels === 'overlapping' && track.displays[0]?.prefersOffset
200
+ ? 'offset'
201
+ : trackLabels;
202
+ }
203
+ // space the label pushes a track's body down by; only 'offset' mode does
204
+ export function labelOffset(track, trackLabels, textHeight) {
205
+ return trackLabelMode(track, trackLabels) === 'offset' ? textHeight : 0;
186
206
  }
187
207
  // Gap between a 'left' track label's right edge and the track body.
188
208
  // SVGTrackLabel right-aligns its text at `trackLabelOffset - TRACK_LABEL_GAP`,
@@ -216,23 +236,24 @@ export function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily
216
236
  }
217
237
  // vertical box a single track occupies. Shared by totalHeight (sum) and
218
238
  // trackBoxOffsets (prefix-sum) so the two can't drift.
219
- export function trackBoxHeight(track, textOffset) {
220
- return track.displays[0].height + textOffset + trackSpacing;
239
+ export function trackBoxHeight(track, trackLabels, textHeight) {
240
+ return (track.displays[0].height +
241
+ labelOffset(track, trackLabels, textHeight) +
242
+ trackSpacing);
221
243
  }
222
244
  // Top y of each track's box within a stack of them. SVGTracks lays the bodies
223
245
  // out with it and the breakpoint-split export anchors its overlay ribbons with
224
246
  // it, so one implementation rather than two prefix-sums that must agree —
225
247
  // they drifted by trackSpacing per track when they didn't.
226
- export function trackBoxOffsets(tracks, textOffset) {
248
+ export function trackBoxOffsets(tracks, trackLabels, textHeight) {
227
249
  const offsets = [];
228
250
  let total = 0;
229
251
  for (const track of tracks) {
230
252
  offsets.push(total);
231
- total += trackBoxHeight(track, textOffset);
253
+ total += trackBoxHeight(track, trackLabels, textHeight);
232
254
  }
233
255
  return offsets;
234
256
  }
235
- export function totalHeight(tracks, textHeight, trackLabels) {
236
- const textOffset = labelOffset(trackLabels, textHeight);
237
- return sum(tracks.map(t => trackBoxHeight(t, textOffset)));
257
+ export function totalHeight(tracks, trackLabels, textHeight) {
258
+ return sum(tracks.map(t => trackBoxHeight(t, trackLabels, textHeight)));
238
259
  }
@@ -1,5 +1,5 @@
1
1
  import type { LinearGenomeViewStateModel } from './model.ts';
2
- import type { HighlightType } from '@jbrowse/core/util/highlights';
2
+ import type { HighlightInput, HighlightType } from '@jbrowse/core/util/highlights';
3
3
  import type { TrackInit } from '@jbrowse/core/util/tracks';
4
4
  import type { SnapshotIn } from '@jbrowse/mobx-state-tree';
5
5
  export interface BpOffset {
@@ -13,8 +13,8 @@ export interface BpOffset {
13
13
  assemblyName?: string;
14
14
  oob?: boolean;
15
15
  }
16
- export type { ExportSvgOptions, TrackLabelMode, } from '@jbrowse/display-kit/types';
17
- export type { HighlightType };
16
+ export type { ExportSvgOptions, TrackLabelMode, ViewTrackLabelMode, } from '@jbrowse/display-kit/types';
17
+ export type { HighlightInput, HighlightType };
18
18
  export interface NavLocation {
19
19
  refName: string;
20
20
  start?: number;
@@ -34,11 +34,12 @@ export interface InitState {
34
34
  */
35
35
  loc?: string;
36
36
  grow?: number;
37
+ showHitTrack?: boolean;
37
38
  assembly: string;
38
39
  displayedRegionNames?: string[];
39
40
  tracks?: TrackInit[];
40
41
  tracklist?: boolean;
41
42
  nav?: boolean;
42
- highlight?: (string | HighlightType)[];
43
+ highlight?: (string | HighlightInput)[];
43
44
  }
44
45
  export type LinearGenomeViewLaunchProps = Partial<Omit<SnapshotIn<LinearGenomeViewStateModel>, keyof InitState | 'id' | 'type' | 'launch'>>;
@@ -112,48 +112,31 @@ export interface ScalebarRefNameLabel {
112
112
  }
113
113
  /**
114
114
  * Builds the refName labels drawn along the scalebar as plain data (no JSX): one
115
- * label per run of same-refName regions (deduped so collapsed introns don't
116
- * repeat the name) plus a "sticky" label pinned to the left edge naming the
117
- * refName under the viewport's left border.
115
+ * label per run of same-refName regions, the leftmost of them "sticky" — pinned
116
+ * so the run under the left edge stays named once its own left edge scrolls off.
118
117
  *
119
- * `caption` is the row-level chip at the viewport's left edge, and it says two
120
- * things: which assembly this row is (`prefix`, an assembly name, synteny only)
121
- * and whether the row is flipped. Without it a row of stacked genomes would
122
- * label some rows with the assembly name and some without, depending only on
123
- * how wide their leftmost chromosome happens to be.
118
+ * `caption` is the row-level chip at the viewport's left edge: which assembly
119
+ * the row is (`prefix`, synteny only) and whether the whole row is flipped. The
120
+ * chip takes `captionSpanPx` of the strip and the labels get the rest, so the
121
+ * sticky label pins to the chip's right edge ("hg38" then "chr1") and rides the
122
+ * run under THAT edge. A run the chip covers entirely goes unnamed, and the
123
+ * chip is not shortened to uncover it: a width fitted to the run under it
124
+ * changes with every run that pans past, and a fixed cap short enough to
125
+ * uncover a 55px chromosome cuts ordinary names. The assembly's `displayName`
126
+ * is where a long name gets shortened.
124
127
  *
125
- * The assembly name alone folds into the sticky label instead — "prefix:refName"
126
- * — whenever the two would collide at the left edge, and stands alone when the
127
- * sticky label sits far enough right not to (a view scrolled left of its first
128
- * region, so the row's data starts mid-viewport) or when there is no sticky
129
- * label at all.
128
+ * `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
129
+ * it where it distinguishes, once where it does not.
130
130
  *
131
- * `orientation` decides the ` [rev]` marker, and it is the row's
132
- * (`displayedRegionsOrientation`) rather than anything read off the blocks —
133
- * blocks cover the window, and a row is flipped or not whatever part of it you
134
- * are looking at. The rule is `assembleLocStrings`': say it where it
135
- * distinguishes, once where it does not.
136
- *
137
- * - `reversed`: the whole row is flipped, which is a fact about the row, so the
138
- * caption carries it and NO name does. The caption cannot fold in this state
139
- * — the sticky label is pushed clear of it instead — because a marker sitting
140
- * on a chromosome name is how the mixed case spells a fact about that one
141
- * region. `chr1 [rev] | chr2` would otherwise mean both "the row is flipped,
142
- * said once" and "chr1 is flipped and chr2 is not", in identical pixels.
143
- * - `mixed`: only some regions are flipped — reachable through the label menu's
144
- * per-region "Reverse region" — so every reversed run is marked and the
145
- * forward ones are left alone. Here the marker on a name is the only thing
146
- * telling two labels apart.
131
+ * - `reversed`: the caption carries the marker and no name does. A marker on a
132
+ * chromosome name is how the mixed case flags that one region.
133
+ * - `mixed`: every reversed run is marked. A marked label that fails the fit
134
+ * test falls back to the bare name rather than dropping.
147
135
  * - `forward`: no marker.
148
136
  *
149
- * Saying it once is the point: a flipped whole-genome row would otherwise
150
- * repeat `[rev]` after all 24 chromosomes to say a single thing about the row.
151
- *
152
- * Under `mixed` the marker never costs a name: a suffixed label that fails the
153
- * fit test falls back to the bare name rather than being dropped. Widening the
154
- * text moves the region width a name needs from ~30px to ~56px at 11px bold,
155
- * and a feature meant to surface information should not be deleting chromosome
156
- * names in the narrow half of that gap.
137
+ * `maxWidth` is the whole label box, `paddingLeft` included — both consumers
138
+ * clip from the box's left edge. A name is drawn whole or not at all: clipped
139
+ * mid-glyph, "chr16" reads as "chr1".
157
140
  */
158
141
  export declare function getScalebarRefNameLabels({ blocks, offsetPx, prefix, orientation, }: {
159
142
  blocks: BaseBlock[];
@@ -177,13 +177,12 @@ function refNameRuns(blocks) {
177
177
  return runs;
178
178
  }
179
179
  /**
180
- * Index of the run carrying the "sticky" refName label pinned to the left edge:
181
- * the rightmost run whose left edge has scrolled off the left of the viewport,
182
- * or the first run when none have.
180
+ * Index of the run carrying the "sticky" refName label pinned to the strip's
181
+ * left edge: the rightmost run starting left of `leftEdgePx` (block frame), or
182
+ * the first run when none does.
183
183
  */
184
- function stickyRunIndex(runs, offsetPx) {
185
- const scrolledOff = runs.findLastIndex(run => run.offsetPx < offsetPx);
186
- return scrolledOff === -1 ? 0 : scrolledOff;
184
+ function stickyRunIndex(runs, leftEdgePx) {
185
+ return Math.max(0, runs.findLastIndex(run => run.offsetPx < leftEdgePx));
187
186
  }
188
187
  export function regionsOrientation(regions) {
189
188
  const first = !!regions[0]?.reversed;
@@ -202,161 +201,79 @@ export function regionsOrientation(regions) {
202
201
  * upstream is whole-or-nothing, and it would go on believing a glyph fits.
203
202
  */
204
203
  const REV_MARKER = ' [rev]';
205
- /** Clearance between the standalone assembly-name chip and a sticky label. */
206
- const PREFIX_GAP = 4;
207
- /** Left inset of a non-sticky refName label from its region's left edge. */
208
- const REF_NAME_LABEL_PADDING_PX = 7;
204
+ /** Clearance between the caption chip and the sticky label after it. */
205
+ const CAPTION_GAP = 4;
209
206
  /**
210
- * translateX, maxWidth and paddingLeft for one refName label. Sticky labels
211
- * start at the viewport's left edge; others start at their run's left edge.
212
- *
213
- * maxWidth is the width of the whole label box, paddingLeft included, so the
214
- * text has `maxWidth - paddingLeft` to draw in — that is what both consumers
215
- * clip to (a border-box max-width on the span, an SVG clip rect from the box's
216
- * own left edge). The caller decides whether the name it wants to draw fits in
217
- * that (see refNameLabelWidth); a name is drawn whole or not at all.
207
+ * Left inset of a non-sticky refName label from its region's left edge, clear
208
+ * of the 3px region divider drawn there.
218
209
  */
219
- function refLabelLayout({ run, offsetPx, sticky,
220
- // left-edge x a sticky label may not start before, so a caption chip drawn
221
- // there keeps the strip to itself. Zero wherever the caption folds into the
222
- // label instead (the assembly prefix does)
223
- minTransform = 0, }) {
224
- const transform = sticky
225
- ? Math.max(minTransform, -offsetPx)
226
- : run.offsetPx - offsetPx - 1;
227
- // block-frame x where the label actually starts. Derived from `transform` (=
228
- // transform + offsetPx) so the width-to-run-end clip stays in lockstep with
229
- // where the label is drawn: a sticky label pins to the run's left edge, not
230
- // the viewport's, whenever the view is left-overscrolled (offsetPx < 0) —
231
- // reading offsetPx directly there over-counted the available width by
232
- // |offsetPx|, letting the name bleed past its run's right edge.
233
- const labelStartPx = transform + offsetPx;
234
- // A non-sticky label's transform anchors at the same x as the region
235
- // divider drawn just to its left (SVGRegionSeparators, a 3px bar spanning
236
- // local [0,3] from this same run.offsetPx edge), so paddingLeft must clear
237
- // that bar plus a few px of breathing room, else the text visually touches
238
- // the divider. Sticky labels sit at the viewport's own left edge, no divider.
239
- const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
240
- return { transform, maxWidth: run.endPx - labelStartPx - 1, paddingLeft };
241
- }
210
+ const REF_NAME_LABEL_PADDING_PX = 7;
242
211
  /**
243
212
  * Builds the refName labels drawn along the scalebar as plain data (no JSX): one
244
- * label per run of same-refName regions (deduped so collapsed introns don't
245
- * repeat the name) plus a "sticky" label pinned to the left edge naming the
246
- * refName under the viewport's left border.
247
- *
248
- * `caption` is the row-level chip at the viewport's left edge, and it says two
249
- * things: which assembly this row is (`prefix`, an assembly name, synteny only)
250
- * and whether the row is flipped. Without it a row of stacked genomes would
251
- * label some rows with the assembly name and some without, depending only on
252
- * how wide their leftmost chromosome happens to be.
213
+ * label per run of same-refName regions, the leftmost of them "sticky" — pinned
214
+ * so the run under the left edge stays named once its own left edge scrolls off.
253
215
  *
254
- * The assembly name alone folds into the sticky label instead — "prefix:refName"
255
- * — whenever the two would collide at the left edge, and stands alone when the
256
- * sticky label sits far enough right not to (a view scrolled left of its first
257
- * region, so the row's data starts mid-viewport) or when there is no sticky
258
- * label at all.
216
+ * `caption` is the row-level chip at the viewport's left edge: which assembly
217
+ * the row is (`prefix`, synteny only) and whether the whole row is flipped. The
218
+ * chip takes `captionSpanPx` of the strip and the labels get the rest, so the
219
+ * sticky label pins to the chip's right edge ("hg38" then "chr1") and rides the
220
+ * run under THAT edge. A run the chip covers entirely goes unnamed, and the
221
+ * chip is not shortened to uncover it: a width fitted to the run under it
222
+ * changes with every run that pans past, and a fixed cap short enough to
223
+ * uncover a 55px chromosome cuts ordinary names. The assembly's `displayName`
224
+ * is where a long name gets shortened.
259
225
  *
260
- * `orientation` decides the ` [rev]` marker, and it is the row's
261
- * (`displayedRegionsOrientation`) rather than anything read off the blocks —
262
- * blocks cover the window, and a row is flipped or not whatever part of it you
263
- * are looking at. The rule is `assembleLocStrings`': say it where it
264
- * distinguishes, once where it does not.
226
+ * `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
227
+ * it where it distinguishes, once where it does not.
265
228
  *
266
- * - `reversed`: the whole row is flipped, which is a fact about the row, so the
267
- * caption carries it and NO name does. The caption cannot fold in this state
268
- * — the sticky label is pushed clear of it instead — because a marker sitting
269
- * on a chromosome name is how the mixed case spells a fact about that one
270
- * region. `chr1 [rev] | chr2` would otherwise mean both "the row is flipped,
271
- * said once" and "chr1 is flipped and chr2 is not", in identical pixels.
272
- * - `mixed`: only some regions are flipped — reachable through the label menu's
273
- * per-region "Reverse region" — so every reversed run is marked and the
274
- * forward ones are left alone. Here the marker on a name is the only thing
275
- * telling two labels apart.
229
+ * - `reversed`: the caption carries the marker and no name does. A marker on a
230
+ * chromosome name is how the mixed case flags that one region.
231
+ * - `mixed`: every reversed run is marked. A marked label that fails the fit
232
+ * test falls back to the bare name rather than dropping.
276
233
  * - `forward`: no marker.
277
234
  *
278
- * Saying it once is the point: a flipped whole-genome row would otherwise
279
- * repeat `[rev]` after all 24 chromosomes to say a single thing about the row.
280
- *
281
- * Under `mixed` the marker never costs a name: a suffixed label that fails the
282
- * fit test falls back to the bare name rather than being dropped. Widening the
283
- * text moves the region width a name needs from ~30px to ~56px at 11px bold,
284
- * and a feature meant to surface information should not be deleting chromosome
285
- * names in the narrow half of that gap.
235
+ * `maxWidth` is the whole label box, `paddingLeft` included — both consumers
236
+ * clip from the box's left edge. A name is drawn whole or not at all: clipped
237
+ * mid-glyph, "chr16" reads as "chr1".
286
238
  */
287
239
  export function getScalebarRefNameLabels({ blocks, offsetPx, prefix, orientation = 'forward', }) {
288
- const hasPrefix = prefix !== undefined && prefix !== '';
289
- const rowIsReversed = orientation === 'reversed';
290
- const captionText = rowIsReversed
291
- ? `${hasPrefix ? prefix : ''}${REV_MARKER}`.trimStart()
292
- : prefix;
293
- // the caption chip occupies the viewport's left edge, so a sticky label
294
- // starting inside that span has to yield: absorb the assembly name, or, where
295
- // the caption carries the row's orientation and so cannot fold, start after
296
- // it. The gap is part of the span: a sticky label carries no padding of its
297
- // own, so one starting at exactly the chip's right edge abuts it and the two
298
- // read as a single word ("volvoxctgA") for the few px of scroll around the
299
- // threshold
300
- const captionSpanPx = captionText
301
- ? refNameLabelWidth(captionText) + PREFIX_GAP
302
- : 0;
240
+ const caption = [prefix, orientation === 'reversed' ? REV_MARKER.trim() : undefined]
241
+ .filter(Boolean)
242
+ .join(' ') || undefined;
243
+ const captionSpanPx = caption ? refNameLabelWidth(caption) + CAPTION_GAP : 0;
303
244
  const runs = refNameRuns(blocks);
304
- const stickyIdx = stickyRunIndex(runs, offsetPx);
245
+ const stickyIdx = stickyRunIndex(runs, offsetPx + captionSpanPx);
305
246
  const labels = [];
306
- let stickyHasPrefix = false;
307
247
  for (const [i, run] of runs.entries()) {
308
248
  const sticky = i === stickyIdx;
309
- // A non-sticky run starting left of the viewport is entirely off-screen:
310
- // the sticky run is by definition the rightmost one starting there, so
311
- // every earlier run also *ends* left of the viewport. Its label would just
312
- // repeat the name the sticky label already shows, drawn off-canvas —
313
- // invisible on screen (overflow:clip) but bleeding into the margin of an
314
- // SVG export, which has no such clip.
315
- const runStart = run.offsetPx >= offsetPx && run.isLeftEndOfDisplayedRegion;
316
- if (!(sticky || runStart)) {
249
+ // runs left of the sticky one end under the caption or off screen
250
+ if (i < stickyIdx || !(sticky || run.isLeftEndOfDisplayedRegion)) {
317
251
  continue;
318
252
  }
319
- const layout = refLabelLayout({
320
- run,
321
- offsetPx,
322
- sticky,
323
- minTransform: rowIsReversed ? captionSpanPx : 0,
324
- });
325
- const withPrefix = !rowIsReversed && sticky && hasPrefix && layout.transform < captionSpanPx;
326
- const name = withPrefix ? `${prefix}:${run.refName}` : run.refName;
327
- // draw the name whole or not at all: a name clipped mid-glyph reads as a
328
- // different chromosome ("LG2" cut to "LG"), and measuring it means a short
329
- // name gets its label in a region a fixed minimum width would have skipped.
330
- // The marker is the one part that degrades instead of dropping the label
253
+ const runStartPx = run.offsetPx - offsetPx;
254
+ const transform = sticky
255
+ ? Math.max(captionSpanPx, runStartPx)
256
+ : runStartPx - 1;
257
+ const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
258
+ const maxWidth = run.endPx - offsetPx - transform - 1;
331
259
  const text = (orientation === 'mixed' && run.reversed
332
- ? [`${name}${REV_MARKER}`, name]
333
- : [name]).find(candidate => layout.maxWidth >= layout.paddingLeft + refNameLabelWidth(candidate));
334
- if (text === undefined) {
335
- continue;
260
+ ? [`${run.refName}${REV_MARKER}`, run.refName]
261
+ : [run.refName]).find(candidate => maxWidth >= paddingLeft + refNameLabelWidth(candidate));
262
+ if (text !== undefined) {
263
+ labels.push({
264
+ key: run.key,
265
+ refName: run.refName,
266
+ displayedRegionIndex: run.firstRegionIndex,
267
+ lastDisplayedRegionIndex: run.lastRegionIndex,
268
+ sticky,
269
+ transform,
270
+ maxWidth,
271
+ paddingLeft,
272
+ text,
273
+ });
336
274
  }
337
- stickyHasPrefix ||= withPrefix;
338
- labels.push({
339
- key: run.key,
340
- refName: run.refName,
341
- displayedRegionIndex: run.firstRegionIndex,
342
- lastDisplayedRegionIndex: run.lastRegionIndex,
343
- sticky,
344
- transform: layout.transform,
345
- maxWidth: layout.maxWidth,
346
- paddingLeft: layout.paddingLeft,
347
- text,
348
- });
349
275
  }
350
- return {
351
- labels,
352
- // the chip to draw at the left edge, or undefined for none. A caption
353
- // carrying the orientation always draws; one carrying only the assembly
354
- // name draws when no sticky label folded it in
355
- caption: rowIsReversed || (hasPrefix && !stickyHasPrefix)
356
- ? captionText
357
- : undefined,
358
- captionSpanPx,
359
- };
276
+ return { labels, caption, captionSpanPx };
360
277
  }
361
278
  /**
362
279
  * Screen-x span a refName label paints over: its padding and its glyphs, which