@jbrowse/plugin-linear-genome-view 5.0.0-beta.8 → 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
@@ -3,7 +3,7 @@ import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
3
3
  * A container's zoom-out ceiling for the rows it stacks. Unanswered is its own
4
4
  * state rather than a zero: a row that has not been laid out yet cannot supply
5
5
  * a fit, and reading that as "no shared ceiling" is a different claim. See
6
- * `LinearComparativeView/sharedFit.ts`.
6
+ * `LinearSyntenyView/sharedFit.ts`.
7
7
  */
8
8
  export type SharedFit = {
9
9
  answered: false;
@@ -1,4 +1,5 @@
1
1
  import { getNotificationSink } from '@jbrowse/core/util';
2
+ import { transaction } from 'mobx';
2
3
  /**
3
4
  * Replace a view's displayed regions with a set someone asked for, frame them,
4
5
  * and offer an Undo that puts the view back exactly as it was.
@@ -32,7 +33,12 @@ export function showRegionsWithUndo({ view, regions, message, alsoUndo, }) {
32
33
  windowWidthBp: view.windowWidthBp,
33
34
  windowStartBp: view.windowStartBp,
34
35
  };
35
- view.showRegions(regions);
36
+ // two root actions in one transaction: `fitAllRegions` reads as the reader's
37
+ // own gesture to a synteny follow, which `showRegions` does not
38
+ transaction(() => {
39
+ view.setDisplayedRegions(regions);
40
+ view.fitAllRegions();
41
+ });
36
42
  getNotificationSink(view).notify(message, 'info', {
37
43
  name: 'Undo',
38
44
  onClick: () => {
@@ -0,0 +1,5 @@
1
+ export default function SVGCloseUpFrame({ x, width, height, }: {
2
+ x: number;
3
+ width: number;
4
+ height: number;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useTheme } from '@mui/material';
3
+ import { CLOSE_UP_FRAME_WIDTH, closeUpColor } from '../closeUps.js';
4
+ export default function SVGCloseUpFrame({ x, width, height, }) {
5
+ const theme = useTheme();
6
+ const inset = CLOSE_UP_FRAME_WIDTH / 2;
7
+ return (_jsx("rect", { x: x + inset, y: inset, width: width - CLOSE_UP_FRAME_WIDTH, height: height - CLOSE_UP_FRAME_WIDTH, fill: "none", stroke: closeUpColor(theme), strokeWidth: CLOSE_UP_FRAME_WIDTH }));
8
+ }
@@ -3,9 +3,8 @@ import { getSession } from '@jbrowse/core/util';
3
3
  import { useTheme } from '@mui/material';
4
4
  import SVGHighlightBand from '../components/SVGHighlightBand.js';
5
5
  import { getHighlightColor, highlightKey } from '../components/util.js';
6
- // Native LGV highlights (model.highlight) drawn as full-height bands over the
7
- // tracks area, with an optional label at the top. Bookmark highlights are added
8
- // separately via the LinearGenomeView-HighlightSVGComponent extension point.
6
+ // The view's highlights drawn as full-height bands over the tracks area, with
7
+ // an optional label at the top.
9
8
  //
10
9
  // Deliberately NOT an observer, and that is the whole of what keeps a live
11
10
  // figure coherent. `useViewSvgFigure` freezes a figure by memoizing it against
@@ -18,7 +17,7 @@ import { getHighlightColor, highlightKey } from '../components/util.js';
18
17
  export default function SVGHighlights({ model, height, }) {
19
18
  const theme = useTheme();
20
19
  return getSession(model).highlightsVisible
21
- ? model.highlight.map((h, idx) => {
20
+ ? model.highlights.map((h, idx) => {
22
21
  const coords = model.getHighlightCoords(h);
23
22
  return coords ? (_jsx(SVGHighlightBand, { coords: coords, height: height, color: getHighlightColor(h, theme).toRgbString(), label: model.labelsVisible ? h.label : undefined, labelColor: theme.palette.text.primary }, highlightKey(h, idx))) : null;
24
23
  })
@@ -4,9 +4,9 @@ import { svgNodeId } from '@jbrowse/core/svg/svgId';
4
4
  import { PluggableElements } from '@jbrowse/core/ui';
5
5
  import { getEnv } from '@jbrowse/core/util';
6
6
  import SVGHighlights from './SVGHighlights.js';
7
- // The highlight layer over one view's tracks area: native LGV highlights plus
8
- // whatever plugins contribute (bookmarks), clipped to the view width. Callers
9
- // translate this to the top-left of the tracks area.
7
+ // The highlight layer over one view's tracks area: the view's highlights plus
8
+ // whatever plugins contribute, clipped to the view width. Callers translate
9
+ // this to the top-left of the tracks area.
10
10
  export default function SVGHighlightsOverlay({ model, tracksHeight, }) {
11
11
  const { pluginManager } = getEnv(model);
12
12
  return (_jsxs(SvgClipRect, { id: `highlight-clip-${svgNodeId(model)}`, width: model.width, height: tracksHeight, children: [_jsx(SVGHighlights, { model: model, height: tracksHeight }), _jsx(PluggableElements, { pluginManager: pluginManager, name: "LinearGenomeView-HighlightSVGComponent", props: { model, height: tracksHeight } })] }));
@@ -1,13 +1,17 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { exportMargin } from '@jbrowse/core/svg/constants';
3
- import { awaitViewInitialized } from '@jbrowse/core/svg/svgReady';
3
+ import { awaitSvgRenders, awaitViewInitialized, } from '@jbrowse/core/svg/svgReady';
4
4
  import { notifySkippedSvgTracks } from '@jbrowse/core/svg/trackNames';
5
5
  import { wrapSvgExport } from '@jbrowse/core/svg/wrapSvgExport';
6
6
  import { getSession } from '@jbrowse/core/util';
7
+ import { closeUpStackRows } from '../closeUps.js';
8
+ import OverviewScalebarPolygon from '../components/OverviewScalebarPolygon.js';
9
+ import SVGCloseUpFrame from './SVGCloseUpFrame.js';
7
10
  import SVGHeader from './SVGHeader.js';
11
+ import SVGRowHeader from './SVGRowHeader.js';
8
12
  import SVGView from './SVGView.js';
9
13
  import { renderViewTracks } from './renderViewTracks.js';
10
- import { defaultTextHeight, getHeaderLayout, trackLabelLeftOffset, } from './util.js';
14
+ import { defaultTextHeight, getHeaderLayout, getRowHeaderLayout, trackLabelLeftOffset, } from './util.js';
11
15
  export async function renderToSvg(model, opts) {
12
16
  await awaitViewInitialized(model);
13
17
  // `initialized` only answers "have the assemblies loaded" — navigating is the
@@ -24,7 +28,7 @@ export async function renderToSvg(model, opts) {
24
28
  const { fontSize = 13,
25
29
  // the label band scales with the font it holds; destructured after
26
30
  // fontSize so the default can read it
27
- textHeight = defaultTextHeight(fontSize), rulerHeight = 34, trackLabels = 'offset', themeName = 'default', fontFamily, showGridlines = false, Wrapper, } = opts;
31
+ textHeight = defaultTextHeight(fontSize), rulerHeight = 34, trackLabels = 'offset', themeName, fontFamily, showGridlines = false, Wrapper, } = opts;
28
32
  const session = getSession(model);
29
33
  const theme = session.getActiveThemeOptions?.(themeName);
30
34
  // owns the two orderings this used to spell out by hand: legendWidth before
@@ -32,17 +36,32 @@ export async function renderToSvg(model, opts) {
32
36
  // own readiness wait — an LGV display through `renderDisplaySvg`'s
33
37
  // `awaitSvgReady`, a non-LGV one (dotplot, synteny, circular) by calling that
34
38
  // itself.
35
- const { tracks, displayResults, tracksHeight, legendWidth, skippedTracks } = await renderViewTracks({
36
- view: model,
37
- opts,
38
- theme,
39
- textHeight,
40
- trackLabels,
41
- // the standalone export is the one with room to give: it widens its
42
- // canvas below so a legend sits beside the plot rather than over it
43
- reserveLegendWidth: true,
44
- });
45
- notifySkippedSvgTracks(session, skippedTracks);
39
+ const closeUps = model.closeUpViews;
40
+ const stack = closeUpStackRows(model, closeUps);
41
+ const [{ tracks, displayResults, tracksHeight, legendWidth, skippedTracks }, closeUpTracks,] = await awaitSvgRenders([
42
+ renderViewTracks({
43
+ view: model,
44
+ opts,
45
+ theme,
46
+ textHeight,
47
+ trackLabels,
48
+ // the standalone export is the one with room to give: it widens its
49
+ // canvas below so a legend sits beside the plot rather than over it
50
+ reserveLegendWidth: true,
51
+ }),
52
+ // a close-up is a stacked row, like a synteny row: no room for a legend
53
+ awaitSvgRenders(closeUps.map(closeUp => renderViewTracks({
54
+ view: closeUp,
55
+ opts,
56
+ theme,
57
+ textHeight,
58
+ trackLabels,
59
+ }))),
60
+ ]);
61
+ notifySkippedSvgTracks(session, [
62
+ ...skippedTracks,
63
+ ...closeUpTracks.flatMap(r => r.skippedTracks),
64
+ ]);
46
65
  // The view geometry is read *after* the displays' waits, never before —
47
66
  // SVGHeader re-reads both of these when it renders (later still, inside
48
67
  // wrapSvgExport) and lays itself out with the same getHeaderLayout call, so a
@@ -55,27 +74,79 @@ export async function renderToSvg(model, opts) {
55
74
  showCytobands: effectiveShowCytobands,
56
75
  rulerHeight,
57
76
  });
58
- const height = tracksHeight + tracksTop + exportMargin;
77
+ // one gutter for the whole export, wide enough for the widest label in any
78
+ // close-up, so the close-ups stay aligned with the view
59
79
  const trackLabelOffset = trackLabelLeftOffset({
60
- tracks,
80
+ tracks: [...tracks, ...closeUpTracks.flatMap(r => r.tracks)],
61
81
  trackLabels,
62
82
  fontSize,
63
83
  fontFamily,
64
84
  session,
65
85
  });
66
86
  const w = width + trackLabelOffset + legendWidth;
87
+ // The view under its full header, then each close-up under the trapezoid
88
+ // joining it to the row above, in the order `closeUpStackRows` gives — which
89
+ // is the order the screen draws too.
90
+ //
91
+ // A close-up's row header is a scalebar and no assembly name — the opposite
92
+ // of a synteny row's. Every close-up is the host's own assembly, named once
93
+ // in the host's own header, while the span each close-up covers is the whole
94
+ // point of the stack and is the one thing a ruler at figure size cannot be
95
+ // read for.
96
+ const rowTopGap = 6;
97
+ const { bandHeight } = getRowHeaderLayout({
98
+ fontSize,
99
+ showScalebar: true,
100
+ reserveAssemblyName: false,
101
+ });
102
+ const rendered = new Map(closeUps.map((closeUp, i) => [closeUp.id, closeUpTracks[i]]));
103
+ const closeUpRow = (closeUp) => {
104
+ const { tracksHeight, displayResults } = rendered.get(closeUp.id);
105
+ const rowTop = rowTopGap + bandHeight;
106
+ const height = rowTop + rulerHeight + tracksHeight;
107
+ return {
108
+ key: closeUp.id,
109
+ height,
110
+ node: (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(${exportMargin} ${rowTop})`, children: _jsx(SVGView, { view: closeUp, displayResults: displayResults, header: _jsx(SVGRowHeader, { view: closeUp, fontSize: fontSize, rulerHeight: rulerHeight, showAssemblyName: false, showScalebar: true }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: rulerHeight, tracksHeight: tracksHeight, showGridlines: showGridlines, leftBuffer: exportMargin }) }), _jsx(SVGCloseUpFrame, { x: exportMargin + trackLabelOffset, width: closeUp.width, height: height })] })),
111
+ };
112
+ };
113
+ const connectorRow = (closeUp, context) => ({
114
+ key: `connector-${closeUp.id}`,
115
+ // the band the reader set by dragging one of them, since how steep the
116
+ // connectors read is the whole of what that drag is for
117
+ height: model.closeUpConnectorHeight,
118
+ node: (_jsx("g", { transform: `translate(${exportMargin + trackLabelOffset} 0)`, children: _jsx(OverviewScalebarPolygon, { model: closeUp, overview: context, overviewOffsetPx: -context.offsetPx, height: model.closeUpConnectorHeight, gradient: true }) })),
119
+ });
120
+ const hostRow = {
121
+ key: model.id,
122
+ height: tracksTop + tracksHeight,
123
+ node: (_jsx("g", { transform: `translate(${exportMargin} 0)`, children: _jsx(SVGView, { view: model, displayResults: displayResults,
124
+ // the standalone export is the one with room above the tracks, so its
125
+ // header is the full one: cytoband overview and total-bp scalebar as
126
+ // well as the assembly name and ruler. `tracksTop` is the height
127
+ // getHeaderLayout gave it.
128
+ header: _jsx(SVGHeader, { model: model, fontSize: fontSize, rulerHeight: rulerHeight }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: tracksTop, tracksHeight: tracksHeight, showGridlines: showGridlines, leftBuffer: exportMargin, legendWidth: legendWidth }) })),
129
+ };
130
+ const rows = [
131
+ hostRow,
132
+ ...stack.flatMap(({ closeUp, context }) => [
133
+ connectorRow(closeUp, context),
134
+ closeUpRow(closeUp),
135
+ ]),
136
+ ];
137
+ let y = 0;
138
+ const children = rows.map(row => {
139
+ const top = y;
140
+ y += row.height;
141
+ return (_jsx("g", { transform: `translate(0 ${top})`, children: row.node }, row.key));
142
+ });
67
143
  // the xlink namespace is used for rendering <image> tag
68
144
  return wrapSvgExport({
69
145
  theme,
70
146
  width: w,
71
- height,
147
+ height: y + exportMargin,
72
148
  fontFamily,
73
149
  Wrapper,
74
- children: (_jsx("g", { transform: `translate(${exportMargin} 0)`, children: _jsx(SVGView, { view: model, displayResults: displayResults,
75
- // the standalone export is the one with room above the tracks, so its
76
- // header is the full one: cytoband overview and total-bp scalebar as
77
- // well as the assembly name and ruler. `tracksTop` is the height
78
- // getHeaderLayout gave it.
79
- header: _jsx(SVGHeader, { model: model, fontSize: fontSize, rulerHeight: rulerHeight }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: tracksTop, tracksHeight: tracksHeight, showGridlines: showGridlines, leftBuffer: exportMargin, legendWidth: legendWidth }) })),
150
+ children,
80
151
  });
81
152
  }
@@ -3,9 +3,9 @@ import { SvgClipRect } from '@jbrowse/core/svg/SvgExport';
3
3
  import { svgNodeId } from '@jbrowse/core/svg/svgId';
4
4
  import { stripAlpha } from '@jbrowse/core/util';
5
5
  import { useTheme } from '@mui/material';
6
- import { REF_NAME_LABEL_FONT_SIZE, getScalebarRefNameLabels, labelFitsInBlock, refNameLabelFitsInView, tickLabelWidth, } from '../util.js';
6
+ import { REF_NAME_LABEL_FONT_SIZE, TICK_LABEL_FONT_SIZE, getScalebarRefNameLabels, labelFitsInBlock, refNameLabelFitsInView, tickLabelWidth, } from '../util.js';
7
7
  import SVGRegionSeparators from './SVGRegionSeparators.js';
8
- import { RULER_MAJOR_TICK, RULER_MINOR_TICK, RULER_TICK_FONT_SIZE, getRulerLayout, gridlineTickXs, refNameLabelBaselineY, refNameLabelBoxHeight, vlinePath, } from './util.js';
8
+ import { RULER_MAJOR_TICK, RULER_MINOR_TICK, getRulerLayout, gridlineTickXs, refNameLabelBaselineY, refNameLabelBoxHeight, vlinePath, } from './util.js';
9
9
  // Tick marks and their coordinate labels, read from the same model getters the
10
10
  // on-screen Gridlines/ScalebarCoordinateLabels use (gridlineTicks /
11
11
  // scalebarLabels), so tick pitch, label text and label placement can't drift
@@ -26,15 +26,14 @@ function Ruler({ model, tickTopY, numbersBaselineY, }) {
26
26
  const w = tickLabelWidth(label);
27
27
  return labelFitsInBlock(dx + x - w / 2, w, width);
28
28
  })
29
- .map(({ x, label, key }) => (_jsx("text", { x: dx + x, y: numbersBaselineY, textAnchor: "middle", fontSize: RULER_TICK_FONT_SIZE, fill: color, children: label }, key)))] }));
29
+ .map(({ x, label, key }) => (_jsx("text", { x: dx + x, y: numbersBaselineY, textAnchor: "middle", fontSize: TICK_LABEL_FONT_SIZE, fill: color, children: label }, key)))] }));
30
30
  }
31
31
  // Chromosome/refName labels along the ruler. Reuses the on-screen scalebar's
32
32
  // sticky-label logic (getScalebarRefNameLabels) so the exported name stays
33
33
  // pinned to the viewport's left edge when a region has scrolled off — otherwise
34
34
  // the name renders off-canvas whenever you export a view zoomed into a
35
35
  // chromosome interior. No assembly-name prefix here (unlike the on-screen
36
- // scalebar): the SVG export already draws a standalone assembly-name label
37
- // above the ruler, so folding it into this one too is redundant.
36
+ // scalebar): the SVG export already draws the assembly name above the ruler.
38
37
  //
39
38
  // `orientation` is not redundant, and this is the surface that needed it most.
40
39
  // A stacked export gives each row SVGRowHeader — assembly name, ruler, refName
@@ -5,12 +5,12 @@ import { TRACK_LABEL_GAP, insetLabelBaselineY, offsetLabelBaselineY, } from './u
5
5
  // Where the label sits, per mode. 'left' right-aligns in the gutter
6
6
  // trackLabelLeftOffset reserved (hence the shared TRACK_LABEL_GAP); the other
7
7
  // modes hang off the leftmost visible content at `x`, either just above the
8
- // track body ('offset') or inset over it ('overlay').
8
+ // track body ('offset') or inset over it ('overlapping').
9
9
  //
10
10
  // Every mode is placed on its alphabetic baseline (the SVG default, so no
11
11
  // dominantBaseline is emitted at all): 'offset' so its descenders land a known
12
12
  // distance above the track body — the band it sits in is `textHeight` tall, and
13
- // offsetLabelBaselineY owns that arithmetic — and 'left'/'overlay' so their
13
+ // offsetLabelBaselineY owns that arithmetic — and 'left'/'overlapping' so their
14
14
  // ascenders stay inside the track box they align with. 'left' used to be pinned
15
15
  // at a hardcoded y=20 on a hanging baseline, which neither scaled with fontSize
16
16
  // nor stayed inside a track shorter than ~35px.
@@ -33,7 +33,7 @@ function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x,
33
33
  }
34
34
  export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, }) {
35
35
  const theme = useTheme();
36
- if (trackLabels === 'none') {
36
+ if (trackLabels === 'hidden') {
37
37
  return null;
38
38
  }
39
39
  const pos = labelPosition({
@@ -43,5 +43,14 @@ export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHe
43
43
  fontSize,
44
44
  x,
45
45
  });
46
- return (_jsx("text", { x: pos.x, y: pos.y, textAnchor: pos.textAnchor, fontSize: fontSize, fill: stripAlpha(theme.palette.text.primary), children: trackName }));
46
+ // 'overlapping' prints over the track's own data, which the on-screen label
47
+ // clears with a paper box, so it carries the halo text over a plot needs
48
+ const halo = trackLabels === 'overlapping'
49
+ ? {
50
+ stroke: stripAlpha(theme.palette.background.paper),
51
+ strokeWidth: 3,
52
+ paintOrder: 'stroke',
53
+ }
54
+ : {};
55
+ return (_jsx("text", { x: pos.x, y: pos.y, textAnchor: pos.textAnchor, fontSize: fontSize, fill: stripAlpha(theme.palette.text.primary), ...halo, children: trackName }));
47
56
  }
@@ -5,17 +5,17 @@ import { svgTrackName } from '@jbrowse/core/svg/trackNames';
5
5
  import { getSession } from '@jbrowse/core/util';
6
6
  import SVGRegionSeparators from './SVGRegionSeparators.js';
7
7
  import SVGTrackLabel from './SVGTrackLabel.js';
8
- import { labelOffset, trackBoxOffsets } from './util.js';
8
+ import { labelOffset, trackBoxOffsets, trackLabelMode } from './util.js';
9
9
  export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels = 'offset', trackLabelOffset = 0, leftBuffer = 0, legendWidth = 0, }) {
10
10
  const session = getSession(model);
11
- const textOffset = labelOffset(trackLabels, textHeight);
12
11
  const x = Math.max(-model.offsetPx, 0);
13
- const offsets = trackBoxOffsets(displayResults.map(r => r.track), textOffset);
12
+ const offsets = trackBoxOffsets(displayResults.map(r => r.track), trackLabels, textHeight);
14
13
  return (_jsx(_Fragment, { children: displayResults.map(({ track, result }, i) => {
15
14
  const conf = track.configuration;
16
15
  const trackName = svgTrackName(track, session);
17
16
  const display = track.displays[0];
18
17
  const currentOffset = offsets[i];
19
- return (_jsxs("g", { transform: `translate(0 ${currentOffset})`, children: [_jsx(SvgClipRect, { id: `track-clip-${svgNodeId(model)}-${conf.trackId}`, x: -leftBuffer, y: textOffset, width: model.width + trackLabelOffset + leftBuffer + legendWidth, height: display.height, children: _jsxs("g", { transform: `translate(${trackLabelOffset} ${textOffset})`, children: [result, _jsx(SVGRegionSeparators, { model: model, height: display.height })] }) }), _jsx(SVGTrackLabel, { trackName: trackName, fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, x: x })] }, conf.trackId));
18
+ const textOffset = labelOffset(track, trackLabels, textHeight);
19
+ return (_jsxs("g", { transform: `translate(0 ${currentOffset})`, children: [_jsx(SvgClipRect, { id: `track-clip-${svgNodeId(model)}-${conf.trackId}`, x: -leftBuffer, y: textOffset, width: model.width + trackLabelOffset + leftBuffer + legendWidth, height: display.height, children: _jsxs("g", { transform: `translate(${trackLabelOffset} ${textOffset})`, children: [result, _jsx(SVGRegionSeparators, { model: model, height: display.height })] }) }), _jsx(SVGTrackLabel, { trackName: trackName, fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabelMode(track, trackLabels), trackLabelOffset: trackLabelOffset, x: x })] }, conf.trackId));
20
20
  }) }));
21
21
  }
@@ -59,7 +59,7 @@ export async function renderViewTracks({ view, opts, theme, textHeight, trackLab
59
59
  return {
60
60
  tracks,
61
61
  displayResults,
62
- tracksHeight: totalHeight(tracks, textHeight, trackLabels),
62
+ tracksHeight: totalHeight(tracks, trackLabels, textHeight),
63
63
  legendWidth,
64
64
  skippedTracks,
65
65
  };
@@ -1,14 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { memo, useCallback, useEffect, useRef, useSyncExternalStore, } from 'react';
3
+ import { SvgThemeProviders } from '@jbrowse/core/svg/SvgThemeProviders';
3
4
  import { exportMargin } from '@jbrowse/core/svg/constants';
5
+ import { svgNodeId } from '@jbrowse/core/svg/svgId';
4
6
  import { svgTrackName } from '@jbrowse/core/svg/trackNames';
5
- import { StyleThemeProvider } from '@jbrowse/core/ui/PaletteContext';
6
- import { resolveStyleTheme } from '@jbrowse/core/ui/styleTheme';
7
- import { createJBrowseTheme } from '@jbrowse/core/ui/theme';
8
7
  import { getSession } from '@jbrowse/core/util';
9
8
  import { useFetch } from '@jbrowse/core/util/useFetch';
10
- import { isAlive } from '@jbrowse/mobx-state-tree';
11
- import { ThemeProvider } from '@mui/material';
9
+ import { getSnapshot, isAlive } from '@jbrowse/mobx-state-tree';
12
10
  import { reaction } from 'mobx';
13
11
  import SVGRowHeader from './SVGRowHeader.js';
14
12
  import SVGView from './SVGView.js';
@@ -87,17 +85,16 @@ async function renderFigure(view, opts) {
87
85
  * render. Where it matters, this line is the only thing holding the two halves
88
86
  * together.
89
87
  *
90
- * Both providers, as `wrapSvgExport` mounts for the file path, and for the same
91
- * reason: the chrome takes its colors from the Material UI theme and a
92
- * `makeStyles` component would take its own from the style theme, so a figure
93
- * mounted in a host that installs neither would otherwise draw itself in
94
- * JBrowse's *default light* theme whatever the session is set to.
88
+ * The export's theme providers, as the file path mounts them: a host that
89
+ * installs neither would otherwise get JBrowse's default light theme whatever
90
+ * the session is set to.
95
91
  *
96
92
  * No background rect, unlike a file — that has nothing behind it, and a figure
97
- * in a page has the page. No `resetSvgClipIds` either, and that is the opposite
98
- * decision from the file path on purpose: the counter is document-global, so
99
- * resetting it here would renumber ids that a figure already on the page is
100
- * pointing at. Letting it run is what makes several live figures unique.
93
+ * in a page has the page. No `withFreshSvgClipIds` either, and that is the
94
+ * opposite decision from the file path on purpose: the counter is
95
+ * document-global, so restarting it here would reuse ids that a figure already
96
+ * on the page is pointing at. Letting it run is what makes several live figures
97
+ * unique.
101
98
  */
102
99
  const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
103
100
  const { width, height, margin, theme } = snapshot;
@@ -105,20 +102,20 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
105
102
  // INSIDE the tree, which the memo above is not between
106
103
  const ref = useRef(null);
107
104
  useFrozenFigureContract(ref, snapshot);
108
- return (_jsx(ThemeProvider, { theme: createJBrowseTheme(theme), children: _jsx(StyleThemeProvider, { theme: resolveStyleTheme({ configTheme: theme }), children: _jsx("svg", { ref: ref, width: width, height: height, viewBox: `0 0 ${width} ${height}`,
109
- // It is a vector, so a narrower container scales it rather than
110
- // clipping it; the height attribute stays, so a box reserved for it
111
- // does not change size when that happens. Deliberately not a prop:
112
- // anything passed per-render would have to reach past the memo above,
113
- // which is the one thing holding the figure's two halves together. A
114
- // host that wants other sizing wraps this in a box of its own.
115
- style: { display: 'block', maxWidth: '100%' }, children: _jsx("g", { transform: `translate(${margin} ${snapshot.bandHeight})`, children: _jsx(SVGView, { view: view, displayResults: snapshot.displayResults, header: _jsx(SVGRowHeader, { view: view, fontSize: snapshot.fontSize, rulerHeight: snapshot.rulerHeight, showScalebar: snapshot.showScalebar }), fontSize: snapshot.fontSize, textHeight: snapshot.textHeight, trackLabels: snapshot.trackLabels, trackLabelOffset: snapshot.trackLabelOffset,
116
- // the row header is exactly the ruler, so the bodies start at its
117
- // height
118
- contentTop: snapshot.rulerHeight, tracksHeight: snapshot.tracksHeight, showGridlines: snapshot.showGridlines,
119
- // the left gutter the per-track clip may bleed into, so content
120
- // drawn left of zero — a wiggle's y-axis — survives
121
- leftBuffer: snapshot.margin, legendWidth: snapshot.legendWidth }) }) }) }) }));
105
+ return (_jsx(SvgThemeProviders, { theme: theme, children: _jsx("svg", { ref: ref, width: width, height: height, viewBox: `0 0 ${width} ${height}`,
106
+ // It is a vector, so a narrower container scales it rather than
107
+ // clipping it; the height attribute stays, so a box reserved for it
108
+ // does not change size when that happens. Deliberately not a prop:
109
+ // anything passed per-render would have to reach past the memo above,
110
+ // which is the one thing holding the figure's two halves together. A
111
+ // host that wants other sizing wraps this in a box of its own.
112
+ style: { display: 'block', maxWidth: '100%' }, children: _jsx("g", { transform: `translate(${margin} ${snapshot.bandHeight})`, children: _jsx(SVGView, { view: view, displayResults: snapshot.displayResults, header: _jsx(SVGRowHeader, { view: view, fontSize: snapshot.fontSize, rulerHeight: snapshot.rulerHeight, showScalebar: snapshot.showScalebar }), fontSize: snapshot.fontSize, textHeight: snapshot.textHeight, trackLabels: snapshot.trackLabels, trackLabelOffset: snapshot.trackLabelOffset,
113
+ // the row header is exactly the ruler, so the bodies start at its
114
+ // height
115
+ contentTop: snapshot.rulerHeight, tracksHeight: snapshot.tracksHeight, showGridlines: snapshot.showGridlines,
116
+ // the left gutter the per-track clip may bleed into, so content
117
+ // drawn left of zero — a wiggle's y-axis — survives
118
+ leftBuffer: snapshot.margin, legendWidth: snapshot.legendWidth }) }) }) }));
122
119
  });
123
120
  /**
124
121
  * What "the figure is out of date" means, as a string.
@@ -141,11 +138,10 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
141
138
  * highlight bands**, which are drawn in the figure and are the one thing in it a
142
139
  * reader adds without navigating anywhere.
143
140
  *
144
- * A plugin's own overlay (grid-bookmark's bookmarks, through
145
- * `LinearGenomeView-HighlightSVGComponent`) is state this cannot see, so a
146
- * bookmark added under a drawn figure shows up at its next redraw rather than at
147
- * once. Subscribing from inside the figure is the thing that must not happen —
148
- * see the note at the top of the file.
141
+ * A plugin's own overlay, through `LinearGenomeView-HighlightSVGComponent`, is
142
+ * state this cannot see, so a change to it shows up at the figure's next
143
+ * redraw rather than at once. Subscribing from inside the figure is the thing
144
+ * that must not happen — see the note at the top of the file.
149
145
  */
150
146
  function figureKey(view, themeName) {
151
147
  // A closed view is not an error, it is a host doing an ordinary thing in an
@@ -156,10 +152,10 @@ function figureKey(view, themeName) {
156
152
  // throw a TypeError inside the reaction, where MobX swallows it. No key means
157
153
  // no figure, which is what a host that closed the view wanted anyway.
158
154
  //
159
- // `view.width` throws by design before the view has been measured, and
160
- // `ready` is the gate that says it has been — and that there are regions to
161
- // draw, which is the second async step `initialized` does not cover
162
- if (!isAlive(view) || !view.ready) {
155
+ // `view.width` throws by design before the view has been measured. `status`
156
+ // is ready only once it has been AND there are regions to draw — `ready` is
157
+ // also true for a view nothing has told where to look, measured or not
158
+ if (!isAlive(view) || view.status.type !== 'ready') {
163
159
  return '';
164
160
  }
165
161
  const session = getSession(view);
@@ -167,13 +163,11 @@ function figureKey(view, themeName) {
167
163
  view.coarseVisibleLocStrings,
168
164
  view.coarseBpPerPx,
169
165
  view.width,
170
- view.tracks.map(track => [
171
- track.configuration.trackId,
172
- track.minimized,
173
- track.displays[0]?.height,
174
- ]),
166
+ // the snapshot carries pinned, minimized and the display settings a body
167
+ // bakes in; the effective height is a getter, so it is read beside it
168
+ view.tracks.map(track => [getSnapshot(track), track.displays[0]?.height]),
175
169
  session.getActiveThemeOptions?.(themeName),
176
- session.highlightsVisible ? view.highlight : false,
170
+ session.highlightsVisible ? view.highlights : false,
177
171
  view.labelsVisible,
178
172
  ]);
179
173
  }
@@ -212,25 +206,7 @@ function useFigureKey(view, themeName) {
212
206
  export function useViewSvgFigure(view, { fontSize = 12, rulerHeight = 30, trackLabels = 'offset', showGridlines = true, showScalebar = true, rasterizeLayers = false, margin = exportMargin, themeName, } = {}) {
213
207
  const key = useFigureKey(view, themeName);
214
208
  useOneFigurePerView(view);
215
- const { data: snapshot, error, isLoading, } = useFetch(
216
- // Every option is in the key by value, so an inline options object costs
217
- // nothing and a changed option redraws. A null key is `useFetch`'s own "not
218
- // yet", which is what an unready view reports.
219
- key
220
- ? [
221
- 'lgv-svg-figure',
222
- key,
223
- JSON.stringify([
224
- fontSize,
225
- rulerHeight,
226
- trackLabels,
227
- showGridlines,
228
- showScalebar,
229
- rasterizeLayers,
230
- margin,
231
- ]),
232
- ]
233
- : null, () => renderFigure(view, {
209
+ const opts = {
234
210
  fontSize,
235
211
  rulerHeight,
236
212
  trackLabels,
@@ -239,7 +215,20 @@ export function useViewSvgFigure(view, { fontSize = 12, rulerHeight = 30, trackL
239
215
  rasterizeLayers,
240
216
  margin,
241
217
  themeName,
242
- }));
218
+ };
219
+ const { data: snapshot, error, isLoading, } = useFetch(
220
+ // Every option is in the key by value, so an inline options object costs
221
+ // nothing and a changed option redraws. The view's id too: two views can
222
+ // share every other term. A null key is `useFetch`'s own "not yet", which
223
+ // is what an unready view reports.
224
+ key
225
+ ? [
226
+ 'lgv-svg-figure',
227
+ svgNodeId(view),
228
+ key,
229
+ JSON.stringify(opts),
230
+ ]
231
+ : null, () => renderFigure(view, opts));
243
232
  // Written in an effect and read during render, which is the order that makes
244
233
  // it work: a key change clears `data` on the render where the ref still holds
245
234
  // the previous commit's size, so the host's box keeps the height it had rather
@@ -5,6 +5,7 @@ import type { ReactNode } from 'react';
5
5
  interface TrackHeights {
6
6
  displays: {
7
7
  height: number;
8
+ prefersOffset?: boolean;
8
9
  }[];
9
10
  }
10
11
  export interface SvgDisplayResult {
@@ -50,9 +51,10 @@ export declare function labelBaselineFromTop(topY: number, fontSize: number): nu
50
51
  export declare function defaultTextHeight(fontSize: number): number;
51
52
  export declare function offsetLabelBaselineY(textHeight: number, fontSize: number): number;
52
53
  export declare function insetLabelBaselineY(fontSize: number): number;
53
- export declare function getRowHeaderLayout({ fontSize, showScalebar, }: {
54
+ export declare function getRowHeaderLayout({ fontSize, showScalebar, reserveAssemblyName, }: {
54
55
  fontSize: number;
55
56
  showScalebar: boolean;
57
+ reserveAssemblyName?: boolean;
56
58
  }): {
57
59
  scalebarLineY: number | undefined;
58
60
  assemblyLabelBaselineY: number;
@@ -71,14 +73,14 @@ export declare function getHeaderLayout({ fontSize, showCytobands, rulerHeight,
71
73
  };
72
74
  export declare const RULER_MAJOR_TICK = 5;
73
75
  export declare const RULER_MINOR_TICK = 3;
74
- export declare const RULER_TICK_FONT_SIZE = 11;
75
76
  export declare function getRulerLayout(rulerHeight: number): {
76
77
  tickTopY: number;
77
78
  numbersBaselineY: number;
78
79
  };
79
80
  export declare const refNameLabelBoxHeight: number;
80
81
  export declare const refNameLabelBaselineY: number;
81
- export declare function labelOffset(trackLabels: TrackLabelMode, textHeight: number): number;
82
+ export declare function trackLabelMode(track: TrackHeights, trackLabels: TrackLabelMode): TrackLabelMode;
83
+ export declare function labelOffset(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
82
84
  export declare const TRACK_LABEL_GAP = 40;
83
85
  export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }: {
84
86
  tracks: {
@@ -89,7 +91,7 @@ export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fo
89
91
  fontFamily?: string;
90
92
  session: TrackCatalog;
91
93
  }): number;
92
- export declare function trackBoxHeight(track: TrackHeights, textOffset: number): number;
93
- export declare function trackBoxOffsets(tracks: TrackHeights[], textOffset: number): number[];
94
- export declare function totalHeight(tracks: TrackHeights[], textHeight: number, trackLabels: TrackLabelMode): number;
94
+ export declare function trackBoxHeight(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
95
+ export declare function trackBoxOffsets(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number[];
96
+ export declare function totalHeight(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number;
95
97
  export {};