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

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.
@@ -234,9 +234,8 @@ export function setupInitAutorun(self) {
234
234
  name: 'LGVInit',
235
235
  // `init` is what makes hasSomethingToShow / awaitingInitNavigation report
236
236
  // "loading"
237
- // until navigation populates displayedRegions, so the alternative gate
238
- // (clearing `init` up front, the way SpreadsheetView does) would flash the
239
- // import form mid-load.
237
+ // until navigation populates displayedRegions, so clearing `init` up front
238
+ // would flash the import form mid-load.
240
239
  ready: () => self.initialized,
241
240
  // No pre-materialization phase to protect: the view is its own single row,
242
241
  // and `error` already derives a failed init assembly declaratively (via
@@ -17,7 +17,7 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
17
17
  const label = getSession(model).highlightLabelsVisible
18
18
  ? highlight.label
19
19
  : undefined;
20
- return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, children: coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
20
+ return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, gaps: model.nonGenomicTrackBands(), children: coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
21
21
  {
22
22
  label: 'Dismiss highlight',
23
23
  icon: CloseIcon,
@@ -1,9 +1,17 @@
1
- export default function HighlightBand({ coords, background, label, children, }: {
1
+ export declare function gapMask(gaps: {
2
+ top: number;
3
+ height: number;
4
+ }[]): string;
5
+ export default function HighlightBand({ coords, background, label, gaps, children, }: {
2
6
  coords: {
3
7
  left: number;
4
8
  width: number;
5
9
  };
6
10
  background: string;
7
11
  label?: string;
12
+ gaps?: {
13
+ top: number;
14
+ height: number;
15
+ }[];
8
16
  children?: React.ReactNode;
9
17
  }): import("react").JSX.Element;
@@ -24,11 +24,20 @@ const useStyles = makeStyles()(theme => ({
24
24
  color: theme.palette.text.primary,
25
25
  },
26
26
  }));
27
+ export function gapMask(gaps) {
28
+ const stops = gaps.flatMap(g => [
29
+ `#000 ${g.top}px`,
30
+ `transparent ${g.top}px`,
31
+ `transparent ${g.top + g.height}px`,
32
+ `#000 ${g.top + g.height}px`,
33
+ ]);
34
+ return `linear-gradient(to bottom, ${stops.join(', ')})`;
35
+ }
27
36
  // OverviewHighlight stays separate (different bpToPx API). A non-interactive
28
37
  // colored band with an
29
38
  // optional top label; when `children` (the highlight chip) is passed it renders
30
39
  // that instead of the plain label. Visibility is toggled from the view menu
31
- export default function HighlightBand({ coords, background, label, children, }) {
40
+ export default function HighlightBand({ coords, background, label, gaps, children, }) {
32
41
  const { classes } = useStyles();
33
42
  const content = children ??
34
43
  (label ? (_jsx(Typography, { variant: "caption", className: classes.label, noWrap: true, children: label })) : null);
@@ -36,5 +45,8 @@ export default function HighlightBand({ coords, background, label, children, })
36
45
  transform: `translateX(${coords.left}px)`,
37
46
  width: coords.width,
38
47
  background,
48
+ ...(gaps?.length
49
+ ? { maskImage: gapMask(gaps), WebkitMaskImage: gapMask(gaps) }
50
+ : undefined),
39
51
  }, children: coords.width > 3 && content ? (_jsx("div", { className: classes.overlay, children: content })) : null }));
40
52
  }
@@ -1698,6 +1698,15 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
1698
1698
  width: number;
1699
1699
  left: number;
1700
1700
  } | undefined;
1701
+ /**
1702
+ * #method
1703
+ * y ranges, from the top of the highlight bands, of tracks whose display
1704
+ * sets `drawsGenomicCoordinates` to false; the bands skip them
1705
+ */
1706
+ nonGenomicTrackBands(): {
1707
+ top: number;
1708
+ height: any;
1709
+ }[];
1701
1710
  /**
1702
1711
  * #method
1703
1712
  * like getHighlightCoords but laid out against the overview scalebar and
@@ -3403,6 +3412,15 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
3403
3412
  width: number;
3404
3413
  left: number;
3405
3414
  } | undefined;
3415
+ /**
3416
+ * #method
3417
+ * y ranges, from the top of the highlight bands, of tracks whose display
3418
+ * sets `drawsGenomicCoordinates` to false; the bands skip them
3419
+ */
3420
+ nonGenomicTrackBands(): {
3421
+ top: number;
3422
+ height: any;
3423
+ }[];
3406
3424
  /**
3407
3425
  * #method
3408
3426
  * like getHighlightCoords but laid out against the overview scalebar and
@@ -2657,6 +2657,21 @@ export function stateModelFactory(pluginManager) {
2657
2657
  getHighlightCoords(region) {
2658
2658
  return getLayoutHighlightCoords(self, canonicalHighlight(self, region));
2659
2659
  },
2660
+ /**
2661
+ * #method
2662
+ * y ranges, from the top of the highlight bands, of tracks whose display
2663
+ * sets `drawsGenomicCoordinates` to false; the bands skip them
2664
+ */
2665
+ nonGenomicTrackBands() {
2666
+ const y0 = self.headerHeight + self.scalebarHeight;
2667
+ return [...self.pinnedTracks, ...self.unpinnedTracks].flatMap(track => {
2668
+ const display = track.activeDisplay;
2669
+ const top = self.getTrackYOffset(track.configuration.trackId);
2670
+ return display.drawsGenomicCoordinates === false && top !== undefined
2671
+ ? [{ top: top - y0, height: self.trackHeight(track) }]
2672
+ : [];
2673
+ });
2674
+ },
2660
2675
  /**
2661
2676
  * #method
2662
2677
  * like getHighlightCoords but laid out against the overview scalebar and
@@ -1,9 +1,10 @@
1
1
  import type { TrackLabelMode } from '../types.ts';
2
- export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, }: {
2
+ export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, raised, }: {
3
3
  trackName: string;
4
4
  trackLabels: TrackLabelMode;
5
5
  fontSize: number;
6
6
  textHeight: number;
7
7
  trackLabelOffset: number;
8
8
  x: number;
9
+ raised?: boolean;
9
10
  }): import("react").JSX.Element | null;
@@ -3,22 +3,25 @@ import { usePalette } from '@jbrowse/core/ui/PaletteContext';
3
3
  import { stripAlpha } from '@jbrowse/core/util';
4
4
  import { TRACK_LABEL_GAP, insetLabelBaselineY, offsetLabelBaselineY, } from './util.js';
5
5
  // Where the label sits, per mode. 'left' right-aligns in the gutter
6
- // trackLabelLeftOffset reserved (hence the shared TRACK_LABEL_GAP); the other
7
- // modes hang off the leftmost visible content at `x`, either just above the
8
- // track body ('offset') or inset over it ('overlapping').
6
+ // trackLabelLeftOffset reserved (hence the shared TRACK_LABEL_GAP), beside the
7
+ // first row or, `raised`, in the band above a track whose sidebar holds the
8
+ // gutter beside its rows. The other modes hang off the leftmost visible content
9
+ // at `x`, either just above the track body ('offset') or inset over it
10
+ // ('overlapping').
9
11
  //
10
12
  // Every mode is placed on its alphabetic baseline (the SVG default, so no
11
- // dominantBaseline is emitted at all): 'offset' so its descenders land a known
12
- // distance above the track body — the band it sits in is `textHeight` tall, and
13
- // offsetLabelBaselineY owns that arithmetic — and 'left'/'overlapping' so their
14
- // ascenders stay inside the track box they align with. 'left' used to be pinned
15
- // at a hardcoded y=20 on a hanging baseline, which neither scaled with fontSize
16
- // nor stayed inside a track shorter than ~35px.
17
- function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x, }) {
13
+ // dominantBaseline is emitted at all): 'offset' and a raised 'left' so their
14
+ // descenders land a known distance above the track body — the band is
15
+ // `textHeight` tall, and offsetLabelBaselineY owns that arithmetic — and
16
+ // 'left'/'overlapping' so their ascenders stay inside the track box they align
17
+ // with.
18
+ function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x, raised, }) {
18
19
  if (trackLabels === 'left') {
19
20
  return {
20
21
  x: trackLabelOffset - TRACK_LABEL_GAP,
21
- y: insetLabelBaselineY(fontSize),
22
+ y: raised
23
+ ? offsetLabelBaselineY(textHeight, fontSize)
24
+ : insetLabelBaselineY(fontSize),
22
25
  textAnchor: 'end',
23
26
  };
24
27
  }
@@ -31,7 +34,7 @@ function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x,
31
34
  // stay inside it (at y=0 they rose into the track above)
32
35
  return { x: x + 5, y: insetLabelBaselineY(fontSize) };
33
36
  }
34
- export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, }) {
37
+ export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, raised = false, }) {
35
38
  const palette = usePalette();
36
39
  if (trackLabels === 'hidden') {
37
40
  return null;
@@ -42,6 +45,7 @@ export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHe
42
45
  textHeight,
43
46
  fontSize,
44
47
  x,
48
+ raised,
45
49
  });
46
50
  // 'overlapping' prints over the track's own data, which the on-screen label
47
51
  // clears with a paper box, so it carries the halo text over a plot needs
@@ -6,7 +6,7 @@ import { getSession } from '@jbrowse/core/util';
6
6
  import { exportsTooLargeNote } from '@jbrowse/display-kit/renderDisplaySvg';
7
7
  import SVGRegionSeparators from './SVGRegionSeparators.js';
8
8
  import SVGTrackLabel from './SVGTrackLabel.js';
9
- import { labelOffset, trackBoxOffsets, trackLabelMode } from './util.js';
9
+ import { labelOffset, leftLabelRaised, trackBoxOffsets, trackLabelMode, } from './util.js';
10
10
  export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels, trackLabelOffset, leftBuffer, legendWidth, }) {
11
11
  const session = getSession(model);
12
12
  const x = Math.max(-model.offsetPx, 0);
@@ -17,6 +17,6 @@ export default function SVGTracks({ displayResults, model, textHeight, fontSize,
17
17
  const display = track.displays[0];
18
18
  const currentOffset = offsets[i];
19
19
  const textOffset = labelOffset(track, trackLabels, textHeight);
20
- 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, exportsTooLargeNote(display) ? null : (_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
+ 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, exportsTooLargeNote(display) ? null : (_jsx(SVGRegionSeparators, { model: model, height: display.height }))] }) }), _jsx(SVGTrackLabel, { trackName: trackName, fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabelMode(track, trackLabels), trackLabelOffset: trackLabelOffset, raised: leftLabelRaised(track, trackLabels), x: x + trackLabelOffset })] }, conf.trackId));
21
21
  }) }));
22
22
  }
@@ -7,6 +7,7 @@ export interface SvgExportTrack {
7
7
  displays: {
8
8
  height: number;
9
9
  svgLegendWidth?: () => number;
10
+ svgSidebarWidth?: () => number;
10
11
  /**
11
12
  * Optional, and the option is the point: SVG export is a substantial extra
12
13
  * implementation for a display type, and a third-party plugin that has not
@@ -6,6 +6,7 @@ interface TrackHeights {
6
6
  displays: {
7
7
  height: number;
8
8
  prefersOffset?: boolean;
9
+ svgSidebarWidth?: () => number;
9
10
  }[];
10
11
  }
11
12
  export interface SvgDisplayResult {
@@ -15,6 +16,7 @@ export interface SvgDisplayResult {
15
16
  height: number;
16
17
  regionTooLarge?: boolean;
17
18
  drawsWhenTooLarge?: boolean;
19
+ svgSidebarWidth?: () => number;
18
20
  }[];
19
21
  };
20
22
  result: ReactNode;
@@ -82,17 +84,23 @@ export declare function getRulerLayout(rulerHeight: number): {
82
84
  export declare const refNameLabelBoxHeight: number;
83
85
  export declare const refNameLabelBaselineY: number;
84
86
  export declare function trackLabelMode(track: TrackHeights, trackLabels: TrackLabelMode): TrackLabelMode;
87
+ export declare function leftLabelRaised(track: Pick<TrackHeights, 'displays'>, trackLabels: TrackLabelMode): boolean;
85
88
  export declare function labelOffset(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
89
+ interface SidebarTrack {
90
+ configuration: AnyConfigurationModel;
91
+ displays: {
92
+ svgSidebarWidth?: () => number;
93
+ }[];
94
+ }
86
95
  export declare const TRACK_LABEL_GAP = 40;
87
96
  export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }: {
88
- tracks: {
89
- configuration: AnyConfigurationModel;
90
- }[];
97
+ tracks: SidebarTrack[];
91
98
  trackLabels: TrackLabelMode;
92
99
  fontSize: number;
93
100
  fontFamily?: string;
94
101
  session: TrackCatalog;
95
102
  }): number;
103
+ export declare function trackSidebarWidth(track: Pick<SidebarTrack, 'displays'>): number;
96
104
  export declare function trackBoxHeight(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
97
105
  export declare function trackBoxOffsets(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number[];
98
106
  export declare function totalHeight(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number;
@@ -184,18 +184,31 @@ export function trackLabelMode(track, trackLabels) {
184
184
  ? 'offset'
185
185
  : trackLabels;
186
186
  }
187
- // space the label pushes a track's body down by; only 'offset' mode does
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
188
195
  export function labelOffset(track, trackLabels, textHeight) {
189
- return trackLabelMode(track, trackLabels) === 'offset' ? textHeight : 0;
196
+ return trackLabelMode(track, trackLabels) === 'offset' ||
197
+ leftLabelRaised(track, trackLabels)
198
+ ? textHeight
199
+ : 0;
190
200
  }
191
201
  // Gap between a 'left' track label's right edge and the track body.
192
202
  // SVGTrackLabel right-aligns its text at `trackLabelOffset - TRACK_LABEL_GAP`,
193
203
  // so it and trackLabelLeftOffset must agree or the widest name overflows the
194
204
  // gutter it was measured for.
195
205
  export const TRACK_LABEL_GAP = 40;
196
- // Horizontal gutter reserved for 'left' track labels (0 in every other mode).
197
- // Takes an already-minimized-filtered track list, so the reserved width matches
198
- // the labels that actually get drawn.
206
+ // 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
210
+ // already-minimized-filtered track list, so the reserved width matches the
211
+ // labels that actually get drawn.
199
212
  //
200
213
  // `fontFamily` is the export's own font option, and it has to reach the ruler
201
214
  // here: the labels are drawn in it (wrapSvgExport puts it on the root <svg>,
@@ -212,11 +225,14 @@ export const TRACK_LABEL_GAP = 40;
212
225
  // `getScalebarRefNameLabels`, an on-screen model getter, so making them
213
226
  // family-aware means threading an export option through screen state.
214
227
  export function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }) {
215
- // no labels means no gutter, and that includes having no tracks to label: the
216
- // gap alone would push the whole figure right of a margin nothing is drawn in
217
- return trackLabels === 'left' && tracks.length > 0
218
- ? max(tracks.map(t => measureText(svgTrackName(t, session), fontSize, fontFamily)), 0) + TRACK_LABEL_GAP
219
- : 0;
228
+ return max(tracks.map(t => Math.max(trackSidebarWidth(t), trackLabels === 'left'
229
+ ? measureText(svgTrackName(t, session), fontSize, fontFamily) +
230
+ TRACK_LABEL_GAP
231
+ : 0)), 0);
232
+ }
233
+ // 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;
220
236
  }
221
237
  // vertical box a single track occupies. Shared by totalHeight (sum) and
222
238
  // trackBoxOffsets (prefix-sum) so the two can't drift.
@@ -260,6 +260,16 @@ declare function makeStateModel(control: PerRegionTestControl, gate: GateOptIns,
260
260
  error: string;
261
261
  }[];
262
262
  };
263
+ } & {
264
+ readonly plotKeys: string[];
265
+ readonly plot: import("@jbrowse/core/configuration").Plot;
266
+ readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
267
+ readonly configDocsUrl: string;
268
+ liftPlot(draft: import("@jbrowse/core/configuration").Plot): import("@jbrowse/core/configuration").AnyConfigurationModel;
269
+ plotProblems(draft: import("@jbrowse/core/configuration").Plot): string[];
270
+ plotWrites(draft: import("@jbrowse/core/configuration").Plot): Record<string, unknown>;
271
+ } & {
272
+ applyPlot(draft: import("@jbrowse/core/configuration").Plot): void;
263
273
  } & {
264
274
  scrollTop: number;
265
275
  } & {
@@ -777,6 +787,16 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
777
787
  error: string;
778
788
  }[];
779
789
  };
790
+ } & {
791
+ readonly plotKeys: string[];
792
+ readonly plot: import("@jbrowse/core/configuration").Plot;
793
+ readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
794
+ readonly configDocsUrl: string;
795
+ liftPlot(draft: import("@jbrowse/core/configuration").Plot): import("@jbrowse/core/configuration").AnyConfigurationModel;
796
+ plotProblems(draft: import("@jbrowse/core/configuration").Plot): string[];
797
+ plotWrites(draft: import("@jbrowse/core/configuration").Plot): Record<string, unknown>;
798
+ } & {
799
+ applyPlot(draft: import("@jbrowse/core/configuration").Plot): void;
780
800
  } & {
781
801
  scrollTop: number;
782
802
  } & {
@@ -1183,6 +1203,16 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
1183
1203
  error: string;
1184
1204
  }[];
1185
1205
  };
1206
+ } & {
1207
+ readonly plotKeys: string[];
1208
+ readonly plot: import("@jbrowse/core/configuration").Plot;
1209
+ readonly plotExamples: readonly import("@jbrowse/core/configuration").PlotExample[];
1210
+ readonly configDocsUrl: string;
1211
+ liftPlot(draft: import("@jbrowse/core/configuration").Plot): import("@jbrowse/core/configuration").AnyConfigurationModel;
1212
+ plotProblems(draft: import("@jbrowse/core/configuration").Plot): string[];
1213
+ plotWrites(draft: import("@jbrowse/core/configuration").Plot): Record<string, unknown>;
1214
+ } & {
1215
+ applyPlot(draft: import("@jbrowse/core/configuration").Plot): void;
1186
1216
  } & {
1187
1217
  scrollTop: number;
1188
1218
  } & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "5.0.0-beta.10",
3
+ "version": "5.0.0-beta.11",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -21,15 +21,15 @@
21
21
  "esm"
22
22
  ],
23
23
  "dependencies": {
24
- "@jbrowse/mobx-state-tree": "^6.6.1",
24
+ "@jbrowse/mobx-state-tree": "^6.7.0",
25
25
  "@mui/icons-material": "^9.4.0",
26
26
  "@mui/material": "^9.4.0",
27
27
  "mobx": "^7.0.5",
28
28
  "mobx-react": "^10.0.2",
29
- "@jbrowse/core": "^5.0.0-beta.10",
30
- "@jbrowse/display-kit": "^5.0.0-beta.10",
31
- "@jbrowse/render-core": "^5.0.0-beta.10",
32
- "@jbrowse/display-ui": "^5.0.0-beta.10"
29
+ "@jbrowse/display-kit": "^5.0.0-beta.11",
30
+ "@jbrowse/core": "^5.0.0-beta.11",
31
+ "@jbrowse/render-core": "^5.0.0-beta.11",
32
+ "@jbrowse/display-ui": "^5.0.0-beta.11"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": ">=19.0.0",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "sideEffects": false,
42
42
  "devDependencies": {
43
- "@jbrowse/display-test-utils": "^5.0.0-beta.10"
43
+ "@jbrowse/display-test-utils": "^5.0.0-beta.11"
44
44
  },
45
45
  "scripts": {
46
46
  "prebuild": "pnpm clean",