@jbrowse/plugin-linear-genome-view 5.0.0-beta.11 → 5.0.0-beta.15

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 (69) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +2 -1
  2. package/esm/FeatureTrack/configSchema.js +2 -1
  3. package/esm/LinearGenomeView/afterAttach.js +1 -23
  4. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +6 -2
  5. package/esm/LinearGenomeView/components/ExportSvgDialog.js +31 -7
  6. package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
  7. package/esm/LinearGenomeView/components/HeaderPanControls.js +4 -2
  8. package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
  9. package/esm/LinearGenomeView/components/HighlightBand.d.ts +3 -2
  10. package/esm/LinearGenomeView/components/HighlightBand.js +4 -2
  11. package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -2
  12. package/esm/LinearGenomeView/components/HighlightChip.js +4 -2
  13. package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
  14. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +7 -9
  15. package/esm/LinearGenomeView/components/MiniControls.js +8 -3
  16. package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
  17. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +4 -2
  18. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
  19. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
  20. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +4 -4
  21. package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
  22. package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
  23. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -9
  24. package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  25. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +3 -8
  26. package/esm/LinearGenomeView/components/SearchResultsTable.js +4 -33
  27. package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
  28. package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
  29. package/esm/LinearGenomeView/components/TrackContainer.js +1 -1
  30. package/esm/LinearGenomeView/components/TrackLabel.js +4 -1
  31. package/esm/LinearGenomeView/components/TrackLabelMenu.js +8 -12
  32. package/esm/LinearGenomeView/components/useRangeSelect.js +3 -2
  33. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
  34. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
  35. package/esm/LinearGenomeView/components/useSideScroll.js +11 -12
  36. package/esm/LinearGenomeView/lazyDialogs.d.ts +17 -2
  37. package/esm/LinearGenomeView/menuItems.js +32 -24
  38. package/esm/LinearGenomeView/model.d.ts +121 -65
  39. package/esm/LinearGenomeView/model.js +143 -135
  40. package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +13 -0
  41. package/esm/LinearGenomeView/rExport/exportLinearView.js +98 -0
  42. package/esm/LinearGenomeView/rExport/rScript.d.ts +75 -0
  43. package/esm/LinearGenomeView/rExport/rScript.js +464 -0
  44. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +4 -7
  45. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +1 -2
  46. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +2 -2
  47. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +1 -2
  48. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +5 -10
  49. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +1 -2
  50. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +5 -5
  51. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -2
  52. package/esm/LinearGenomeView/svgcomponents/SVGView.js +3 -3
  53. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -21
  54. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
  55. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +32 -17
  56. package/esm/LinearGenomeView/svgcomponents/util.d.ts +16 -13
  57. package/esm/LinearGenomeView/svgcomponents/util.js +10 -19
  58. package/esm/LinearGenomeView/util.d.ts +42 -0
  59. package/esm/LinearGenomeView/util.js +81 -0
  60. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +2 -2
  61. package/esm/displayKitTests/perRegionTestEnv.d.ts +45 -12
  62. package/esm/index.d.ts +4 -2
  63. package/esm/index.js +3 -1
  64. package/esm/searchUtils.js +1 -0
  65. package/esm/shared/RubberbandSpan.d.ts +3 -2
  66. package/esm/shared/RubberbandSpan.js +4 -2
  67. package/esm/shared/coordLabels.d.ts +6 -6
  68. package/esm/shared/coordLabels.js +7 -6
  69. package/package.json +10 -10
@@ -1,4 +1,5 @@
1
1
  import type { ExportSvgOptions, TrackLabelMode } from '../types.ts';
2
+ import type { LgvSvgExportable } from '@jbrowse/display-kit/renderDisplaySvg';
2
3
  import type { ExportSvgDisplayOptions } from '@jbrowse/display-kit/types';
3
4
  import type { ThemeOptions } from '@mui/material';
4
5
  import type { ReactNode } from 'react';
@@ -6,8 +7,8 @@ export interface SvgExportTrack {
6
7
  minimized: boolean;
7
8
  displays: {
8
9
  height: number;
9
- svgLegendWidth?: () => number;
10
- svgSidebarWidth?: () => number;
10
+ regionTooLarge?: boolean;
11
+ svgSidebarWidth?: LgvSvgExportable['svgSidebarWidth'];
11
12
  /**
12
13
  * Optional, and the option is the point: SVG export is a substantial extra
13
14
  * implementation for a display type, and a third-party plugin that has not
@@ -22,13 +23,18 @@ export interface SvgExportTrack {
22
23
  export interface ViewTracksSvg<T> {
23
24
  /** the tracks that were rendered, in the order they were laid out */
24
25
  tracks: T[];
26
+ /**
27
+ * `legendHeight` is the room a track's key has in the gutter, absent for a
28
+ * track with no key to draw
29
+ */
25
30
  displayResults: {
26
31
  track: T;
27
32
  result: ReactNode;
33
+ legendHeight?: number;
28
34
  }[];
29
35
  /** total height of the stack, valid only because the renders above resolved */
30
36
  tracksHeight: number;
31
- /** 0 unless `reserveLegendWidth` was asked for */
37
+ /** px the caller widens its canvas by for the keys right of the plot */
32
38
  legendWidth: number;
33
39
  /**
34
40
  * Visible tracks left out because their display implements no `renderSvg`.
@@ -41,30 +47,28 @@ export interface ViewTracksSvg<T> {
41
47
  * One view's worth of exported track bodies: which tracks are in, their
42
48
  * rendered SVG, and how tall the stack ends up.
43
49
  *
44
- * It exists to own the two orderings that each of the three view exports got
45
- * wrong at least once, because neither is visible at a call site that merely
46
- * lists the same statements in some order:
50
+ * It exists to own an ordering that each of the three view exports got wrong at
51
+ * least once, because it is not visible at a call site that merely lists the
52
+ * same statements in some order: the geometry is measured **after** the
53
+ * awaits.
47
54
  *
48
- * - `legendWidth` is measured **before** the awaits, because it is an input to
49
- * them: a display draws its legend beside its body or floating over it
50
- * depending on whether the container reserved room. That is why
51
- * `svgLegendWidth` is specified as a function of the *settings* and not of
52
- * the loaded data — see LinearHicDisplay's implementation.
53
- * - `tracksHeight` is measured **after** them. A display whose height follows
54
- * its data (grow mode; `LinearMultiRowFeatureDisplay` is `nrow *
55
- * effectiveRowHeight`) only reaches its final height once `renderSvg`'s
56
- * readiness wait resolves, and `SVGTracks` re-reads `displays[0].height`
57
- * later still. Measuring up front sized the canvas for the pre-fetch height
58
- * and the taller bodies ran off the bottom of the export — and in the
59
- * breakpoint-split export it also desynced the overlay anchors, resolved
60
- * after, from the view tops, which weren't.
55
+ * - `tracksHeight`: a display whose height follows its data (grow mode;
56
+ * `LinearMultiRowFeatureDisplay` is `nrow * effectiveRowHeight`) only reaches
57
+ * its final height once `renderSvg`'s readiness wait resolves, and
58
+ * `SVGTracks` re-reads `displays[0].height` later still. Measuring up front
59
+ * sized the canvas for the pre-fetch height and the taller bodies ran off the
60
+ * bottom of the export — and in the breakpoint-split export it also desynced
61
+ * the overlay anchors, resolved after, from the view tops, which weren't.
62
+ * - `legendWidth`: most keys list what was loaded, and on a headless export
63
+ * (jbrowse-img) nothing has loaded before the waits. Measured after, a track
64
+ * with no key reserves nothing and the figure has no empty strip.
61
65
  *
62
66
  * The minimized filter and the track order are here for the same reason: the
63
67
  * reserved height, the rendered bodies, the label gutter and (breakpoint-split)
64
68
  * the ribbon anchors must all be derived from one list, and pinned tracks are
65
69
  * drawn at the top of a view on screen.
66
70
  */
67
- export declare function renderViewTracks<T extends SvgExportTrack>({ view, opts, theme, textHeight, trackLabels, reserveLegendWidth, }: {
71
+ export declare function renderViewTracks<T extends SvgExportTrack>({ view, opts, theme, textHeight, trackLabels, }: {
68
72
  view: {
69
73
  pinnedTracks: T[];
70
74
  unpinnedTracks: T[];
@@ -73,5 +77,4 @@ export declare function renderViewTracks<T extends SvgExportTrack>({ view, opts,
73
77
  theme: ThemeOptions | undefined;
74
78
  textHeight: number;
75
79
  trackLabels: TrackLabelMode;
76
- reserveLegendWidth?: boolean;
77
80
  }): Promise<ViewTracksSvg<T>>;
@@ -1,34 +1,33 @@
1
1
  import { awaitSvgRenders } from '@jbrowse/core/svg/svgReady';
2
2
  import { max } from '@jbrowse/core/util';
3
- import { totalHeight } from './util.js';
3
+ import { svgLegendGutterWidth } from '@jbrowse/display-kit/renderDisplaySvg';
4
+ import { labelOffset, totalHeight, trackBoxOffsets } from './util.js';
4
5
  /**
5
6
  * One view's worth of exported track bodies: which tracks are in, their
6
7
  * rendered SVG, and how tall the stack ends up.
7
8
  *
8
- * It exists to own the two orderings that each of the three view exports got
9
- * wrong at least once, because neither is visible at a call site that merely
10
- * lists the same statements in some order:
9
+ * It exists to own an ordering that each of the three view exports got wrong at
10
+ * least once, because it is not visible at a call site that merely lists the
11
+ * same statements in some order: the geometry is measured **after** the
12
+ * awaits.
11
13
  *
12
- * - `legendWidth` is measured **before** the awaits, because it is an input to
13
- * them: a display draws its legend beside its body or floating over it
14
- * depending on whether the container reserved room. That is why
15
- * `svgLegendWidth` is specified as a function of the *settings* and not of
16
- * the loaded data — see LinearHicDisplay's implementation.
17
- * - `tracksHeight` is measured **after** them. A display whose height follows
18
- * its data (grow mode; `LinearMultiRowFeatureDisplay` is `nrow *
19
- * effectiveRowHeight`) only reaches its final height once `renderSvg`'s
20
- * readiness wait resolves, and `SVGTracks` re-reads `displays[0].height`
21
- * later still. Measuring up front sized the canvas for the pre-fetch height
22
- * and the taller bodies ran off the bottom of the export — and in the
23
- * breakpoint-split export it also desynced the overlay anchors, resolved
24
- * after, from the view tops, which weren't.
14
+ * - `tracksHeight`: a display whose height follows its data (grow mode;
15
+ * `LinearMultiRowFeatureDisplay` is `nrow * effectiveRowHeight`) only reaches
16
+ * its final height once `renderSvg`'s readiness wait resolves, and
17
+ * `SVGTracks` re-reads `displays[0].height` later still. Measuring up front
18
+ * sized the canvas for the pre-fetch height and the taller bodies ran off the
19
+ * bottom of the export — and in the breakpoint-split export it also desynced
20
+ * the overlay anchors, resolved after, from the view tops, which weren't.
21
+ * - `legendWidth`: most keys list what was loaded, and on a headless export
22
+ * (jbrowse-img) nothing has loaded before the waits. Measured after, a track
23
+ * with no key reserves nothing and the figure has no empty strip.
25
24
  *
26
25
  * The minimized filter and the track order are here for the same reason: the
27
26
  * reserved height, the rendered bodies, the label gutter and (breakpoint-split)
28
27
  * the ribbon anchors must all be derived from one list, and pinned tracks are
29
28
  * drawn at the top of a view on screen.
30
29
  */
31
- export async function renderViewTracks({ view, opts, theme, textHeight, trackLabels, reserveLegendWidth = false, }) {
30
+ export async function renderViewTracks({ view, opts, theme, textHeight, trackLabels, }) {
32
31
  // Partitioned before anything is measured, so a skipped track reserves no
33
32
  // height and takes no place in the label gutter — the alternative is a figure
34
33
  // with a labelled empty band where the track would have been. One pass, so
@@ -42,25 +41,39 @@ export async function renderViewTracks({ view, opts, theme, textHeight, trackLab
42
41
  (track.displays[0]?.renderSvg ? tracks : skippedTracks).push(track);
43
42
  }
44
43
  }
45
- const legendWidth = reserveLegendWidth
46
- ? max(tracks.map(track => track.displays[0].svgLegendWidth?.() ?? 0), 0)
47
- : 0;
48
44
  // `awaitSvgRenders` over `Promise.all`: a track whose data won't load fails
49
45
  // the export, and a view with three broken tracks should say so once rather
50
46
  // than name whichever rejected first
51
47
  const displayResults = await awaitSvgRenders(tracks.map(async (track) => ({
52
48
  track,
53
- result: await track.displays[0].renderSvg({
54
- ...opts,
55
- theme,
56
- legendWidth,
57
- }),
49
+ result: await track.displays[0].renderSvg({ ...opts, theme }),
58
50
  })));
51
+ const tracksHeight = totalHeight(tracks, trackLabels, textHeight);
52
+ const keyed = tracks.map(track => !track.displays[0].regionTooLarge &&
53
+ svgLegendGutterWidth(track.displays[0], theme, Infinity) > 0);
54
+ const bodyTops = trackBoxOffsets(tracks, trackLabels, textHeight).map((top, i) => top + labelOffset(tracks[i], trackLabels, textHeight));
55
+ // A key runs from its body's top to the next keyed body's, or to the foot of
56
+ // the stack, so a long key beside a short track carries on past the unkeyed
57
+ // tracks below it before it cuts to "+N more".
58
+ const legendHeights = keyed.map((isKeyed, i) => {
59
+ const next = keyed.findIndex((k, j) => k && j > i);
60
+ return isKeyed
61
+ ? (next === -1 ? tracksHeight : bodyTops[next]) - bodyTops[i]
62
+ : undefined;
63
+ });
59
64
  return {
60
65
  tracks,
61
- displayResults,
62
- tracksHeight: totalHeight(tracks, trackLabels, textHeight),
63
- legendWidth,
66
+ displayResults: displayResults.map((r, i) => ({
67
+ ...r,
68
+ legendHeight: legendHeights[i],
69
+ })),
70
+ tracksHeight,
71
+ legendWidth: max(tracks.map((track, i) => {
72
+ const height = legendHeights[i];
73
+ return height === undefined
74
+ ? 0
75
+ : svgLegendGutterWidth(track.displays[0], theme, height);
76
+ }), 0),
64
77
  skippedTracks,
65
78
  };
66
79
  }
@@ -16,12 +16,11 @@ import { defaultTextHeight, getRowHeaderLayout, trackLabelLeftOffset, } from './
16
16
  * One figure: every visible track rendered to SVG elements, and the geometry
17
17
  * that lays them out.
18
18
  *
19
- * `renderViewTracks` owns the two orderings this cannot state for itself — the
20
- * legend gutter measured before the renders (a display draws its key beside the
21
- * plot or floating over it depending on whether the container reserved room),
22
- * the stack height after them (a display whose height follows its data only
23
- * reaches its final height once its readiness wait resolves). Everything read
24
- * below is read after the awaits for the same reason.
19
+ * `renderViewTracks` owns the ordering this cannot state for itself: the stack
20
+ * height and the legend gutter are measured after the renders, since a
21
+ * display's height and its key follow data that lands only once its readiness
22
+ * wait resolves. Everything read below is read after the awaits for the same
23
+ * reason.
25
24
  */
26
25
  async function renderFigure(view, opts) {
27
26
  const { fontSize, rulerHeight, trackLabels, rasterizeLayers, showScalebar } = opts;
@@ -39,9 +38,6 @@ async function renderFigure(view, opts) {
39
38
  theme,
40
39
  textHeight,
41
40
  trackLabels,
42
- // an inline figure has room to give: a legend sits beside the plot rather
43
- // than over it
44
- reserveLegendWidth: true,
45
41
  });
46
42
  // the room the row header needs *above* its own origin — it draws the assembly
47
43
  // name and the scalebar at negative y, so the caller reserves the band and
@@ -61,7 +57,6 @@ async function renderFigure(view, opts) {
61
57
  height: bandHeight + rulerHeight + tracksHeight + opts.margin,
62
58
  bandHeight,
63
59
  tracksHeight,
64
- legendWidth,
65
60
  trackLabelOffset,
66
61
  textHeight,
67
62
  locstring: view.visibleLocStrings,
@@ -108,7 +103,7 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
108
103
  // anything passed per-render would have to reach past the memo above,
109
104
  // which is the one thing holding the figure's two halves together. A
110
105
  // host that wants other sizing wraps this in a box of its own.
111
- style: { display: 'block', maxWidth: '100%' }, children: _jsx(SVGStackedRow, { view: view, rendered: snapshot, top: snapshot.bandHeight, margin: margin, fontSize: snapshot.fontSize, textHeight: snapshot.textHeight, rulerHeight: snapshot.rulerHeight, trackLabels: snapshot.trackLabels, trackLabelOffset: snapshot.trackLabelOffset, showGridlines: snapshot.showGridlines, legendWidth: snapshot.legendWidth, showScalebar: snapshot.showScalebar }) }) }));
106
+ style: { display: 'block', maxWidth: '100%' }, children: _jsx(SVGStackedRow, { view: view, rendered: snapshot, top: snapshot.bandHeight, margin: margin, fontSize: snapshot.fontSize, textHeight: snapshot.textHeight, rulerHeight: snapshot.rulerHeight, trackLabels: snapshot.trackLabels, trackLabelOffset: snapshot.trackLabelOffset, showGridlines: snapshot.showGridlines, showScalebar: snapshot.showScalebar }) }) }));
112
107
  });
113
108
  /**
114
109
  * What "the figure is out of date" means, as a string.
@@ -125,7 +120,15 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
125
120
  * question about regions and is true before a track is instantiated (the first
126
121
  * figure then draws a header over nothing, and nothing else ever changes to
127
122
  * correct it); **their heights and minimized flags**, since the geometry is
128
- * derived from them; **the width**, since a resize can widen the canvas without
123
+ * derived from them; **their configs**, since a display's settings are slots on
124
+ * the track config and a shown track's snapshot spells that config as its id;
125
+ * **the legend sections a reader dismissed** and **the per-group state**
126
+ * (hidden sections, collapses, height overrides: `groupStateKey`), which are
127
+ * volatile and in no snapshot; **the reload counter**, since a Retry or a Force
128
+ * load leaves every snapshot as it was while the figure has an error or a
129
+ * too-large note to replace; **the view's coloring toggles** (`colorByCDS`,
130
+ * `showAminoAcids`), named one by one because the view's own snapshot moves
131
+ * every pan frame; **the width**, since a resize can widen the canvas without
129
132
  * moving the locus; **the theme**, since each display bakes colors into its own
130
133
  * bodies, so a figure from the other mode is not stale but unreadable; and **the
131
134
  * highlight bands**, which are drawn in the figure and are the one thing in it a
@@ -133,8 +136,10 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
133
136
  *
134
137
  * A plugin's own overlay, through `LinearGenomeView-HighlightSVGComponent`, is
135
138
  * state this cannot see, so a change to it shows up at the figure's next
136
- * redraw rather than at once. Subscribing from inside the figure is the thing
137
- * that must not happen — see the note at the top of the file.
139
+ * redraw rather than at once. So does a track's vertical scroll, which moves
140
+ * every wheel frame and has no settled copy to key on. Subscribing from inside
141
+ * the figure is the thing that must not happen — see the note at the top of
142
+ * the file.
138
143
  */
139
144
  function figureKey(view, themeName) {
140
145
  // A closed view is not an error, it is a host doing an ordinary thing in an
@@ -156,9 +161,19 @@ function figureKey(view, themeName) {
156
161
  view.coarseVisibleLocStrings,
157
162
  view.coarseBpPerPx,
158
163
  view.width,
159
- // the snapshot carries pinned, minimized and the display settings a body
160
- // bakes in; the effective height is a getter, so it is read beside it
161
- view.tracks.map(track => [getSnapshot(track), track.displays[0]?.height]),
164
+ view.tracks.map(track => {
165
+ const display = track.displays[0];
166
+ return [
167
+ getSnapshot(track),
168
+ getSnapshot(track.configuration),
169
+ display?.height,
170
+ display?.dismissedLegendSections,
171
+ display?.groupStateKey,
172
+ display?.reloadCounter,
173
+ ];
174
+ }),
175
+ view.colorByCDS,
176
+ view.showAminoAcids,
162
177
  session.getActiveThemeOptions?.(themeName),
163
178
  session.highlightsVisible ? view.highlights : false,
164
179
  session.highlightLabelsVisible,
@@ -1,25 +1,27 @@
1
1
  import type { TrackLabelMode } from '../types.ts';
2
2
  import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
3
3
  import type { TrackCatalog } from '@jbrowse/core/util';
4
+ import type { LgvSvgExportable } from '@jbrowse/display-kit/renderDisplaySvg';
5
+ import type { ExportTextStyle } from '@jbrowse/display-kit/types';
4
6
  import type { ReactNode } from 'react';
7
+ type SidebarDisplay = Pick<LgvSvgExportable, 'svgSidebarWidth'>;
5
8
  interface TrackHeights {
6
- displays: {
9
+ displays: ({
7
10
  height: number;
8
11
  prefersOffset?: boolean;
9
- svgSidebarWidth?: () => number;
10
- }[];
12
+ } & SidebarDisplay)[];
11
13
  }
12
14
  export interface SvgDisplayResult {
13
15
  track: {
14
16
  configuration: AnyConfigurationModel;
15
- displays: {
17
+ displays: ({
16
18
  height: number;
17
19
  regionTooLarge?: boolean;
18
- drawsWhenTooLarge?: boolean;
19
- svgSidebarWidth?: () => number;
20
- }[];
20
+ } & SidebarDisplay)[];
21
21
  };
22
22
  result: ReactNode;
23
+ /** the room its key has in the export's legend gutter, absent for no key */
24
+ legendHeight?: number;
23
25
  }
24
26
  export declare const trackSpacing = 2;
25
27
  export declare function gridlineTickXs(model: {
@@ -83,14 +85,15 @@ export declare function getRulerLayout(rulerHeight: number): {
83
85
  };
84
86
  export declare const refNameLabelBoxHeight: number;
85
87
  export declare const refNameLabelBaselineY: number;
86
- export declare function trackLabelMode(track: TrackHeights, trackLabels: TrackLabelMode): TrackLabelMode;
87
- export declare function leftLabelRaised(track: Pick<TrackHeights, 'displays'>, trackLabels: TrackLabelMode): boolean;
88
+ export declare function trackLabelMode(track: {
89
+ displays: ({
90
+ prefersOffset?: boolean;
91
+ } & SidebarDisplay)[];
92
+ }, trackLabels: TrackLabelMode): TrackLabelMode;
88
93
  export declare function labelOffset(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
89
94
  interface SidebarTrack {
90
95
  configuration: AnyConfigurationModel;
91
- displays: {
92
- svgSidebarWidth?: () => number;
93
- }[];
96
+ displays: SidebarDisplay[];
94
97
  }
95
98
  export declare const TRACK_LABEL_GAP = 40;
96
99
  export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }: {
@@ -100,7 +103,7 @@ export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fo
100
103
  fontFamily?: string;
101
104
  session: TrackCatalog;
102
105
  }): number;
103
- export declare function trackSidebarWidth(track: Pick<SidebarTrack, 'displays'>): number;
106
+ export declare function trackSidebarWidth(track: Pick<SidebarTrack, 'displays'>, text?: ExportTextStyle): number;
104
107
  export declare function trackBoxHeight(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
105
108
  export declare function trackBoxOffsets(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number[];
106
109
  export declare function totalHeight(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number;
@@ -179,24 +179,17 @@ export const refNameLabelBaselineY = labelBaselineFromTop(0, REF_NAME_LABEL_FONT
179
179
  // The mode one track's label is drawn in. 'overlapping' yields to 'offset' for a
180
180
  // display that `prefersOffset`, as TrackContainer does on screen: its left edge
181
181
  // carries content — an axis, a sample's genotypes — the label would print over.
182
+ // 'left' yields to 'offset' for a track whose sidebar holds the gutter beside
183
+ // its rows.
182
184
  export function trackLabelMode(track, trackLabels) {
183
- return trackLabels === 'overlapping' && track.displays[0]?.prefersOffset
185
+ return (trackLabels === 'overlapping' && track.displays[0]?.prefersOffset) ||
186
+ (trackLabels === 'left' && trackSidebarWidth(track) > 0)
184
187
  ? 'offset'
185
188
  : trackLabels;
186
189
  }
187
- // Whether a 'left' name sits in a band above its track's rows rather than
188
- // beside the first of them: the gutter beside the rows holds the track's own
189
- // sidebar, so the name heads it instead of standing off to its left.
190
- export function leftLabelRaised(track, trackLabels) {
191
- return trackLabels === 'left' && trackSidebarWidth(track) > 0;
192
- }
193
- // space the label pushes a track's body down by: an 'offset' label, or a raised
194
- // 'left' one
190
+ // space an 'offset' label pushes a track's body down by
195
191
  export function labelOffset(track, trackLabels, textHeight) {
196
- return trackLabelMode(track, trackLabels) === 'offset' ||
197
- leftLabelRaised(track, trackLabels)
198
- ? textHeight
199
- : 0;
192
+ return trackLabelMode(track, trackLabels) === 'offset' ? textHeight : 0;
200
193
  }
201
194
  // Gap between a 'left' track label's right edge and the track body.
202
195
  // SVGTrackLabel right-aligns its text at `trackLabelOffset - TRACK_LABEL_GAP`,
@@ -204,9 +197,7 @@ export function labelOffset(track, trackLabels, textHeight) {
204
197
  // gutter it was measured for.
205
198
  export const TRACK_LABEL_GAP = 40;
206
199
  // Horizontal gutter left of the track bodies: the widest of each track's
207
- // sidebar and, in 'left' mode, its name plus TRACK_LABEL_GAP. A sidebar and a
208
- // name share the column because the name rises above the sidebar
209
- // (leftLabelRaised). Takes an
200
+ // sidebar and of each name drawn 'left', plus TRACK_LABEL_GAP. Takes an
210
201
  // already-minimized-filtered track list, so the reserved width matches the
211
202
  // labels that actually get drawn.
212
203
  //
@@ -225,14 +216,14 @@ export const TRACK_LABEL_GAP = 40;
225
216
  // `getScalebarRefNameLabels`, an on-screen model getter, so making them
226
217
  // family-aware means threading an export option through screen state.
227
218
  export function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }) {
228
- return max(tracks.map(t => Math.max(trackSidebarWidth(t), trackLabels === 'left'
219
+ return max(tracks.map(t => Math.max(trackSidebarWidth(t, { fontSize, fontFamily }), trackLabelMode(t, trackLabels) === 'left'
229
220
  ? measureText(svgTrackName(t, session), fontSize, fontFamily) +
230
221
  TRACK_LABEL_GAP
231
222
  : 0)), 0);
232
223
  }
233
224
  // Px of tree and row labels a track's display draws left of its body.
234
- export function trackSidebarWidth(track) {
235
- return track.displays[0]?.svgSidebarWidth?.() ?? 0;
225
+ export function trackSidebarWidth(track, text) {
226
+ return track.displays[0]?.svgSidebarWidth?.(text) ?? 0;
236
227
  }
237
228
  // vertical box a single track occupies. Shared by totalHeight (sum) and
238
229
  // trackBoxOffsets (prefix-sum) so the two can't drift.
@@ -361,5 +361,47 @@ export declare function generateLocations({ regions, assemblyManager, assemblyNa
361
361
  * Used by navToLocString
362
362
  */
363
363
  export declare function parseLocStrings(input: string, assemblyName: string, isValidRefName: (str: string, assemblyName: string) => boolean): ParsedLocString[];
364
+ /** One span of the row that is not track data — see `paddingSpans`. */
365
+ export interface PaddingSpan {
366
+ key: string;
367
+ x: number;
368
+ width: number;
369
+ kind: 'seam' | 'elided' | 'boundary';
370
+ }
371
+ /**
372
+ * Tick positions in the staticBlocks frame, one pass per merged run so a tick
373
+ * on an internal ~800px chunk boundary is emitted once.
374
+ */
375
+ export declare function getGridlineTicks({ blocks, firstBlockOffset, bpPerPx, }: {
376
+ blocks: BaseBlock[];
377
+ firstBlockOffset: number;
378
+ bpPerPx: number;
379
+ }): {
380
+ x: number;
381
+ major: boolean;
382
+ }[];
383
+ /**
384
+ * Coordinate labels in the staticBlocks frame, off the same ticks as
385
+ * `getGridlineTicks`. A run that can hold too few labels to make a ruler goes
386
+ * unnumbered, and the bold refName label pinned at a run's left edge takes
387
+ * precedence over a number.
388
+ */
389
+ export declare function getScalebarLabels({ blocks, firstBlockOffset, bpPerPx, orientation, }: {
390
+ blocks: BaseBlock[];
391
+ firstBlockOffset: number;
392
+ bpPerPx: number;
393
+ orientation: RegionsOrientation;
394
+ }): {
395
+ x: number;
396
+ label: string;
397
+ key: string;
398
+ }[];
399
+ /**
400
+ * Every span along the row that is not track data, in the staticBlocks frame.
401
+ * Nothing to draw returns one shared array, so the computed's value repeats and
402
+ * MobX's `===` stops the chain: a view inside one contig has no seam, elision
403
+ * or boundary, and a fresh `[]` each frame re-rendered every track's padding.
404
+ */
405
+ export declare function getPaddingSpans(blocks: BaseBlock[], firstBlockOffset: number): readonly PaddingSpan[];
364
406
  export declare function calculateVisibleLocStrings(contentBlocks: ContentBlock[]): string;
365
407
  export {};
@@ -636,6 +636,87 @@ export function parseLocStrings(input, assemblyName, isValidRefName) {
636
636
  throw e;
637
637
  }
638
638
  }
639
+ // Frozen because every consumer maps over it and a caller that sorted in place
640
+ // would do so for the whole session.
641
+ const NO_PADDING_SPANS = Object.freeze([]);
642
+ /**
643
+ * Tick positions in the staticBlocks frame, one pass per merged run so a tick
644
+ * on an internal ~800px chunk boundary is emitted once.
645
+ */
646
+ export function getGridlineTicks({ blocks, firstBlockOffset, bpPerPx, }) {
647
+ const ticks = [];
648
+ for (const run of groupContiguousBlocks(blocks)) {
649
+ const runLeft = run.offsetPx - firstBlockOffset;
650
+ for (const { type, x } of makeBlockTicks(run, bpPerPx)) {
651
+ if (x >= 0 && x <= run.widthPx) {
652
+ ticks.push({ x: runLeft + x, major: type === 'major' });
653
+ }
654
+ }
655
+ }
656
+ return ticks;
657
+ }
658
+ /**
659
+ * Coordinate labels in the staticBlocks frame, off the same ticks as
660
+ * `getGridlineTicks`. A run that can hold too few labels to make a ruler goes
661
+ * unnumbered, and the bold refName label pinned at a run's left edge takes
662
+ * precedence over a number.
663
+ */
664
+ export function getScalebarLabels({ blocks, firstBlockOffset, bpPerPx, orientation, }) {
665
+ const labels = [];
666
+ const runs = groupContiguousBlocks(blocks);
667
+ const refNameLabelPx = runRefNameLabelPx(runs, orientation);
668
+ for (const [i, run] of runs.entries()) {
669
+ const runLeft = run.offsetPx - firstBlockOffset;
670
+ const runLabels = [];
671
+ for (const { base, x } of makeBlockTicks(run, bpPerPx, false)) {
672
+ const label = getTickDisplayStr(base + 1, bpPerPx);
673
+ const w = tickLabelWidth(label);
674
+ if (x - w / 2 >= refNameLabelPx[i] &&
675
+ labelFitsInBlock(x - w / 2, w, run.widthPx)) {
676
+ runLabels.push({
677
+ x: runLeft + x,
678
+ label,
679
+ key: `${run.offsetPx}-${base}`,
680
+ });
681
+ }
682
+ }
683
+ if (tickLabelsWorthDrawing(runLabels.length)) {
684
+ labels.push(...runLabels);
685
+ }
686
+ }
687
+ return labels;
688
+ }
689
+ /**
690
+ * Every span along the row that is not track data, in the staticBlocks frame.
691
+ * Nothing to draw returns one shared array, so the computed's value repeats and
692
+ * MobX's `===` stops the chain: a view inside one contig has no seam, elision
693
+ * or boundary, and a fresh `[]` each frame re-rendered every track's padding.
694
+ */
695
+ export function getPaddingSpans(blocks, firstBlockOffset) {
696
+ const spans = [];
697
+ for (const block of blocks) {
698
+ const x = block.offsetPx - firstBlockOffset;
699
+ if (block.type === 'ContentBlock') {
700
+ if (block.isRightEndOfDisplayedRegion) {
701
+ spans.push({
702
+ key: `${block.key}-sep`,
703
+ x: x + block.widthPx - 1,
704
+ width: 3,
705
+ kind: 'seam',
706
+ });
707
+ }
708
+ }
709
+ else {
710
+ spans.push({
711
+ key: block.key,
712
+ x,
713
+ width: block.widthPx,
714
+ kind: block.type === 'ElidedBlock' ? 'elided' : 'boundary',
715
+ });
716
+ }
717
+ }
718
+ return spans.length > 0 ? spans : NO_PADDING_SPANS;
719
+ }
639
720
  export function calculateVisibleLocStrings(contentBlocks) {
640
721
  return assembleLocStrings(contentBlocks.map(block => ({
641
722
  refName: block.refName,
@@ -34,7 +34,7 @@ const MultiLevelRubberband = observer(function MultiLevelRubberband({ model, Con
34
34
  });
35
35
  // one row per rubberband level, in level order. values repeat across levels
36
36
  // whenever the assemblies line up, so the index is the only stable key
37
- function PerLevelRows({ rows }) {
37
+ const PerLevelRows = observer(function PerLevelRows({ rows, }) {
38
38
  return rows.map((row, idx) => (_jsx("div", { children: row }, idx)));
39
- }
39
+ });
40
40
  export default MultiLevelRubberband;