@jbrowse/plugin-linear-genome-view 5.0.0-beta.10 → 5.0.0-beta.13
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.
- package/esm/LinearGenomeView/afterAttach.js +2 -3
- package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
- package/esm/LinearGenomeView/components/Highlight.js +1 -1
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +9 -1
- package/esm/LinearGenomeView/components/HighlightBand.js +13 -1
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
- package/esm/LinearGenomeView/components/MiniControls.js +8 -3
- package/esm/LinearGenomeView/components/SearchResultsTable.js +1 -2
- package/esm/LinearGenomeView/components/TrackLabel.js +4 -1
- package/esm/LinearGenomeView/model.d.ts +18 -0
- package/esm/LinearGenomeView/model.js +15 -0
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +8 -9
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +2 -0
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +26 -6
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +18 -8
- package/esm/LinearGenomeView/svgcomponents/util.js +17 -10
- package/esm/displayKitTests/perRegionTestEnv.d.ts +30 -0
- package/package.json +8 -8
|
@@ -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
|
|
238
|
-
//
|
|
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
|
|
@@ -13,8 +13,11 @@ const useStyles = makeStyles()(theme => ({
|
|
|
13
13
|
display: 'flex',
|
|
14
14
|
alignItems: 'center',
|
|
15
15
|
gap: theme.spacing(0.5),
|
|
16
|
+
minWidth: 0,
|
|
16
17
|
},
|
|
17
18
|
}));
|
|
19
|
+
const MIN_BOX_WIDTH = 100;
|
|
20
|
+
const boxStyle = { margin: 0, minWidth: MIN_BOX_WIDTH };
|
|
18
21
|
// One row's locate control in a header that stacks several linear genome views:
|
|
19
22
|
// the compact search box, then the assembly name and the span on screen. The
|
|
20
23
|
// comparative view and the breakpoint split view each draw one per row, and each
|
|
@@ -23,6 +26,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
23
26
|
const HeaderSearchBoxes = observer(function HeaderSearchBoxes({ view, }) {
|
|
24
27
|
const { classes } = useStyles();
|
|
25
28
|
const { assemblyDisplayNames, coarseTotalBp } = view;
|
|
26
|
-
return (_jsxs("span", { className: classes.searchBox, children: [_jsx(SearchBox, { model: view, showHelp: false, maxWidth: 250, minWidth:
|
|
29
|
+
return (_jsxs("span", { className: classes.searchBox, children: [_jsx(SearchBox, { model: view, showHelp: false, maxWidth: 250, minWidth: MIN_BOX_WIDTH, style: boxStyle }), _jsxs(Typography, { variant: "body2", color: "text.secondary", className: classes.bp, children: [assemblyDisplayNames.join(','), " ", getBpDisplayStr(coarseTotalBp)] })] }));
|
|
27
30
|
});
|
|
28
31
|
export default HeaderSearchBoxes;
|
|
@@ -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
|
|
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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
3
|
import { VIEW_HEADER_HEIGHT } from '@jbrowse/core/ui';
|
|
4
4
|
import { getSession } from '@jbrowse/core/util';
|
|
5
5
|
import { LGV_HEADER_HEIGHT_VAR, SCROLL_PORT_HEIGHT_VAR, VIEW_HEADER_HEIGHT_VAR, useChromeHeightVar, useFocusOnInteraction, } from '@jbrowse/core/util/hooks';
|
|
@@ -11,12 +11,11 @@ import { stickyChromeTops } from '../stickyChrome.js';
|
|
|
11
11
|
import Header from './Header.js';
|
|
12
12
|
import MiniControls from './MiniControls.js';
|
|
13
13
|
import NavigationAnnouncer from './NavigationAnnouncer.js';
|
|
14
|
+
import NoTracksActiveButton from './NoTracksActiveButton.js';
|
|
14
15
|
import ResizeAllTracksHandle from './ResizeAllTracksHandle.js';
|
|
15
16
|
import Rubberband from './Rubberband.js';
|
|
16
17
|
import TrackContainer from './TrackContainer.js';
|
|
17
18
|
import TracksContainer from './TracksContainer.js';
|
|
18
|
-
// lazies
|
|
19
|
-
const NoTracksActiveButton = lazy(() => import('./NoTracksActiveButton.js'));
|
|
20
19
|
const useStyles = makeStyles()(theme => ({
|
|
21
20
|
header: {
|
|
22
21
|
background: theme.palette.background.paper,
|
|
@@ -104,7 +103,7 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
|
|
|
104
103
|
.map(d => d.hoveredFeature)
|
|
105
104
|
.find(Boolean);
|
|
106
105
|
session.setHovered({ hoverPosition, hoverFeature });
|
|
107
|
-
}, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, "data-testid": `lgv-header-${model.id}`, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(
|
|
106
|
+
}, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, "data-testid": `lgv-header-${model.id}`, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(NoTracksActiveButton, { model: model })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 2, "data-testid": "pinned-tracks", className: classes.pinnedTracks, style: {
|
|
108
107
|
top: stickyTops.pinnedTracks,
|
|
109
108
|
// the scroll port this sticks in, not the window: it is
|
|
110
109
|
// under the app bar in the classic stack, a dockview
|
|
@@ -14,19 +14,24 @@ const useStyles = makeStyles()(theme => ({
|
|
|
14
14
|
background: {
|
|
15
15
|
position: 'absolute',
|
|
16
16
|
right: 0,
|
|
17
|
-
background: theme.palette.background.paper,
|
|
18
17
|
// needed when sticky header is off in lgv, e.g. in breakpoint split view
|
|
19
18
|
zIndex: 2,
|
|
20
19
|
},
|
|
21
20
|
focusedBackground: {
|
|
22
|
-
|
|
21
|
+
backgroundColor: alpha(theme.palette.secondary.light, 0.2),
|
|
23
22
|
},
|
|
24
23
|
}));
|
|
25
24
|
const MiniControls = observer(function MiniControls({ model, }) {
|
|
26
25
|
const { classes } = useStyles();
|
|
27
26
|
const { id, scalebarOnly } = model;
|
|
28
27
|
const { focusedViewId } = getSession(model);
|
|
29
|
-
|
|
28
|
+
// A ruler-only row is shorter than these controls, so they rise from the
|
|
29
|
+
// ruler's bottom edge rather than hang from its top: a nested row always has
|
|
30
|
+
// chrome above it, while the last row of a stack has only the view's frame
|
|
31
|
+
// below.
|
|
32
|
+
return (_jsx(Paper, { "data-testid": "lgv-mini-controls", className: classes.background, style: scalebarOnly
|
|
33
|
+
? { top: model.scalebarHeight, transform: 'translateY(-100%)' }
|
|
34
|
+
: undefined, children: _jsxs(Paper, { className: focusedViewId === id ? classes.focusedBackground : undefined, children: [_jsx(Tooltip, { title: "Open track selector", children: _jsx(IconButton, { size: "small", onClick: () => {
|
|
30
35
|
model.activateTrackSelector();
|
|
31
36
|
}, children: _jsx(TrackSelectorIcon, { fontSize: "small" }) }) }), _jsx(CascadingMenuButton, { menuItems: () => model.menuItems(), children: _jsx(MoreVertIcon, { fontSize: "small" }) }), _jsx(Tooltip, { title: scalebarOnly ? 'Show tracks' : 'Show ruler only', children: _jsx(IconButton, { size: "small", onClick: () => {
|
|
32
37
|
model.setScalebarOnly(!scalebarOnly);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
|
|
3
|
-
import { readConfObject } from '@jbrowse/core/configuration';
|
|
4
3
|
import { SanitizedHTML } from '@jbrowse/core/ui';
|
|
5
4
|
import { getSession } from '@jbrowse/core/util';
|
|
6
5
|
import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
|
|
@@ -12,7 +11,7 @@ const SearchResultsTable = observer(function SearchResultsTable({ searchResults,
|
|
|
12
11
|
const assembly = assemblyManager.get(assemblyName);
|
|
13
12
|
function getTrackName(trackId) {
|
|
14
13
|
const conf = trackId !== undefined ? session.getTrackById(trackId) : undefined;
|
|
15
|
-
return conf
|
|
14
|
+
return conf?.name ?? '';
|
|
16
15
|
}
|
|
17
16
|
// A location the assembly can't resolve is listed as-is: Go navigates
|
|
18
17
|
// through navToOption, which reports its own failure
|
|
@@ -13,7 +13,10 @@ const useStyles = makeStyles()(theme => ({
|
|
|
13
13
|
root: {
|
|
14
14
|
// above breakpoint split view
|
|
15
15
|
zIndex: 200,
|
|
16
|
-
|
|
16
|
+
...(theme.palette.mode === 'dark' && {
|
|
17
|
+
outline: `1px solid ${theme.palette.divider}`,
|
|
18
|
+
outlineOffset: -1,
|
|
19
|
+
}),
|
|
17
20
|
display: 'inline-flex',
|
|
18
21
|
alignItems: 'center',
|
|
19
22
|
maxWidth: 'calc(100% - 16px)',
|
|
@@ -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
|
|
@@ -34,7 +34,7 @@ export async function renderToSvg(model, opts) {
|
|
|
34
34
|
// itself.
|
|
35
35
|
const { tracks, displayResults, tracksHeight, legendWidth, skippedTracks } = await renderViewTracks({
|
|
36
36
|
view: model,
|
|
37
|
-
opts,
|
|
37
|
+
opts: { ...opts, fontSize },
|
|
38
38
|
theme,
|
|
39
39
|
textHeight,
|
|
40
40
|
trackLabels,
|
|
@@ -3,17 +3,16 @@ 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)
|
|
7
|
-
// modes hang off the leftmost visible content
|
|
8
|
-
// track body ('offset') or inset over it
|
|
6
|
+
// trackLabelLeftOffset reserved (hence the shared TRACK_LABEL_GAP), beside the
|
|
7
|
+
// first row. The other modes hang off the leftmost visible content
|
|
8
|
+
// at `x`, either just above the track body ('offset') or inset over it
|
|
9
|
+
// ('overlapping').
|
|
9
10
|
//
|
|
10
11
|
// 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
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// at a hardcoded y=20 on a hanging baseline, which neither scaled with fontSize
|
|
16
|
-
// nor stayed inside a track shorter than ~35px.
|
|
12
|
+
// dominantBaseline is emitted at all): 'offset' so its descenders land a known distance above the track body — the band is
|
|
13
|
+
// `textHeight` tall, and offsetLabelBaselineY owns that arithmetic — and
|
|
14
|
+
// 'left'/'overlapping' so their ascenders stay inside the track box they align
|
|
15
|
+
// with.
|
|
17
16
|
function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x, }) {
|
|
18
17
|
if (trackLabels === 'left') {
|
|
19
18
|
return {
|
|
@@ -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, x: x + trackLabelOffset })] }, conf.trackId));
|
|
21
21
|
}) }));
|
|
22
22
|
}
|
|
@@ -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';
|
|
@@ -7,6 +8,7 @@ export interface SvgExportTrack {
|
|
|
7
8
|
displays: {
|
|
8
9
|
height: number;
|
|
9
10
|
svgLegendWidth?: () => number;
|
|
11
|
+
svgSidebarWidth?: LgvSvgExportable['svgSidebarWidth'];
|
|
10
12
|
/**
|
|
11
13
|
* Optional, and the option is the point: SVG export is a substantial extra
|
|
12
14
|
* implementation for a display type, and a third-party plugin that has not
|
|
@@ -125,7 +125,15 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
|
|
|
125
125
|
* question about regions and is true before a track is instantiated (the first
|
|
126
126
|
* figure then draws a header over nothing, and nothing else ever changes to
|
|
127
127
|
* correct it); **their heights and minimized flags**, since the geometry is
|
|
128
|
-
* derived from them; **
|
|
128
|
+
* derived from them; **their configs**, since a display's settings are slots on
|
|
129
|
+
* the track config and a shown track's snapshot spells that config as its id;
|
|
130
|
+
* **the legend sections a reader dismissed** and **the per-group state**
|
|
131
|
+
* (hidden sections, collapses, height overrides: `groupStateKey`), which are
|
|
132
|
+
* volatile and in no snapshot; **the reload counter**, since a Retry or a Force
|
|
133
|
+
* load leaves every snapshot as it was while the figure has an error or a
|
|
134
|
+
* too-large note to replace; **the view's coloring toggles** (`colorByCDS`,
|
|
135
|
+
* `showAminoAcids`), named one by one because the view's own snapshot moves
|
|
136
|
+
* every pan frame; **the width**, since a resize can widen the canvas without
|
|
129
137
|
* moving the locus; **the theme**, since each display bakes colors into its own
|
|
130
138
|
* bodies, so a figure from the other mode is not stale but unreadable; and **the
|
|
131
139
|
* highlight bands**, which are drawn in the figure and are the one thing in it a
|
|
@@ -133,8 +141,10 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
|
|
|
133
141
|
*
|
|
134
142
|
* A plugin's own overlay, through `LinearGenomeView-HighlightSVGComponent`, is
|
|
135
143
|
* state this cannot see, so a change to it shows up at the figure's next
|
|
136
|
-
* redraw rather than at once.
|
|
137
|
-
*
|
|
144
|
+
* redraw rather than at once. So does a track's vertical scroll, which moves
|
|
145
|
+
* every wheel frame and has no settled copy to key on. Subscribing from inside
|
|
146
|
+
* the figure is the thing that must not happen — see the note at the top of
|
|
147
|
+
* the file.
|
|
138
148
|
*/
|
|
139
149
|
function figureKey(view, themeName) {
|
|
140
150
|
// A closed view is not an error, it is a host doing an ordinary thing in an
|
|
@@ -156,9 +166,19 @@ function figureKey(view, themeName) {
|
|
|
156
166
|
view.coarseVisibleLocStrings,
|
|
157
167
|
view.coarseBpPerPx,
|
|
158
168
|
view.width,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
view.tracks.map(track => {
|
|
170
|
+
const display = track.displays[0];
|
|
171
|
+
return [
|
|
172
|
+
getSnapshot(track),
|
|
173
|
+
getSnapshot(track.configuration),
|
|
174
|
+
display?.height,
|
|
175
|
+
display?.dismissedLegendSections,
|
|
176
|
+
display?.groupStateKey,
|
|
177
|
+
display?.reloadCounter,
|
|
178
|
+
];
|
|
179
|
+
}),
|
|
180
|
+
view.colorByCDS,
|
|
181
|
+
view.showAminoAcids,
|
|
162
182
|
session.getActiveThemeOptions?.(themeName),
|
|
163
183
|
session.highlightsVisible ? view.highlights : false,
|
|
164
184
|
session.highlightLabelsVisible,
|
|
@@ -1,21 +1,24 @@
|
|
|
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
|
-
}[];
|
|
12
|
+
} & SidebarDisplay)[];
|
|
10
13
|
}
|
|
11
14
|
export interface SvgDisplayResult {
|
|
12
15
|
track: {
|
|
13
16
|
configuration: AnyConfigurationModel;
|
|
14
|
-
displays: {
|
|
17
|
+
displays: ({
|
|
15
18
|
height: number;
|
|
16
19
|
regionTooLarge?: boolean;
|
|
17
20
|
drawsWhenTooLarge?: boolean;
|
|
18
|
-
}[];
|
|
21
|
+
} & SidebarDisplay)[];
|
|
19
22
|
};
|
|
20
23
|
result: ReactNode;
|
|
21
24
|
}
|
|
@@ -81,18 +84,25 @@ export declare function getRulerLayout(rulerHeight: number): {
|
|
|
81
84
|
};
|
|
82
85
|
export declare const refNameLabelBoxHeight: number;
|
|
83
86
|
export declare const refNameLabelBaselineY: number;
|
|
84
|
-
export declare function trackLabelMode(track:
|
|
87
|
+
export declare function trackLabelMode(track: {
|
|
88
|
+
displays: ({
|
|
89
|
+
prefersOffset?: boolean;
|
|
90
|
+
} & SidebarDisplay)[];
|
|
91
|
+
}, trackLabels: TrackLabelMode): TrackLabelMode;
|
|
85
92
|
export declare function labelOffset(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
|
|
93
|
+
interface SidebarTrack {
|
|
94
|
+
configuration: AnyConfigurationModel;
|
|
95
|
+
displays: SidebarDisplay[];
|
|
96
|
+
}
|
|
86
97
|
export declare const TRACK_LABEL_GAP = 40;
|
|
87
98
|
export declare function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }: {
|
|
88
|
-
tracks:
|
|
89
|
-
configuration: AnyConfigurationModel;
|
|
90
|
-
}[];
|
|
99
|
+
tracks: SidebarTrack[];
|
|
91
100
|
trackLabels: TrackLabelMode;
|
|
92
101
|
fontSize: number;
|
|
93
102
|
fontFamily?: string;
|
|
94
103
|
session: TrackCatalog;
|
|
95
104
|
}): number;
|
|
105
|
+
export declare function trackSidebarWidth(track: Pick<SidebarTrack, 'displays'>, text?: ExportTextStyle): number;
|
|
96
106
|
export declare function trackBoxHeight(track: TrackHeights, trackLabels: TrackLabelMode, textHeight: number): number;
|
|
97
107
|
export declare function trackBoxOffsets(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number[];
|
|
98
108
|
export declare function totalHeight(tracks: TrackHeights[], trackLabels: TrackLabelMode, textHeight: number): number;
|
|
@@ -179,12 +179,15 @@ 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
|
-
// space
|
|
190
|
+
// space an 'offset' label pushes a track's body down by
|
|
188
191
|
export function labelOffset(track, trackLabels, textHeight) {
|
|
189
192
|
return trackLabelMode(track, trackLabels) === 'offset' ? textHeight : 0;
|
|
190
193
|
}
|
|
@@ -193,9 +196,10 @@ export function labelOffset(track, trackLabels, textHeight) {
|
|
|
193
196
|
// so it and trackLabelLeftOffset must agree or the widest name overflows the
|
|
194
197
|
// gutter it was measured for.
|
|
195
198
|
export const TRACK_LABEL_GAP = 40;
|
|
196
|
-
// Horizontal gutter
|
|
197
|
-
//
|
|
198
|
-
// the
|
|
199
|
+
// Horizontal gutter left of the track bodies: the widest of each track's
|
|
200
|
+
// sidebar and of each name drawn 'left', plus TRACK_LABEL_GAP. Takes an
|
|
201
|
+
// already-minimized-filtered track list, so the reserved width matches the
|
|
202
|
+
// labels that actually get drawn.
|
|
199
203
|
//
|
|
200
204
|
// `fontFamily` is the export's own font option, and it has to reach the ruler
|
|
201
205
|
// here: the labels are drawn in it (wrapSvgExport puts it on the root <svg>,
|
|
@@ -212,11 +216,14 @@ export const TRACK_LABEL_GAP = 40;
|
|
|
212
216
|
// `getScalebarRefNameLabels`, an on-screen model getter, so making them
|
|
213
217
|
// family-aware means threading an export option through screen state.
|
|
214
218
|
export function trackLabelLeftOffset({ tracks, trackLabels, fontSize, fontFamily, session, }) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
return max(tracks.map(t => Math.max(trackSidebarWidth(t, { fontSize, fontFamily }), trackLabelMode(t, trackLabels) === 'left'
|
|
220
|
+
? measureText(svgTrackName(t, session), fontSize, fontFamily) +
|
|
221
|
+
TRACK_LABEL_GAP
|
|
222
|
+
: 0)), 0);
|
|
223
|
+
}
|
|
224
|
+
// Px of tree and row labels a track's display draws left of its body.
|
|
225
|
+
export function trackSidebarWidth(track, text) {
|
|
226
|
+
return track.displays[0]?.svgSidebarWidth?.(text) ?? 0;
|
|
220
227
|
}
|
|
221
228
|
// vertical box a single track occupies. Shared by totalHeight (sum) and
|
|
222
229
|
// 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.
|
|
3
|
+
"version": "5.0.0-beta.13",
|
|
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.
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.7.0",
|
|
25
25
|
"@mui/icons-material": "^9.4.0",
|
|
26
26
|
"@mui/material": "^9.4.0",
|
|
27
|
-
"mobx": "^7.0.
|
|
27
|
+
"mobx": "^7.0.6",
|
|
28
28
|
"mobx-react": "^10.0.2",
|
|
29
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
30
|
-
"@jbrowse/
|
|
31
|
-
"@jbrowse/
|
|
32
|
-
"@jbrowse/display-ui": "^5.0.0-beta.
|
|
29
|
+
"@jbrowse/core": "^5.0.0-beta.13",
|
|
30
|
+
"@jbrowse/render-core": "^5.0.0-beta.13",
|
|
31
|
+
"@jbrowse/display-kit": "^5.0.0-beta.13",
|
|
32
|
+
"@jbrowse/display-ui": "^5.0.0-beta.13"
|
|
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.
|
|
43
|
+
"@jbrowse/display-test-utils": "^5.0.0-beta.13"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"prebuild": "pnpm clean",
|