@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.
- package/esm/FeatureTrack/configSchema.d.ts +140 -48
- package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
- package/esm/LinearGenomeView/afterAttach.js +18 -24
- package/esm/LinearGenomeView/closeUps.d.ts +83 -0
- package/esm/LinearGenomeView/closeUps.js +211 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.d.ts +19 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.js +29 -0
- package/esm/LinearGenomeView/components/CloseUps.d.ts +5 -0
- package/esm/LinearGenomeView/components/CloseUps.js +75 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +2 -1
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +6 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
- package/esm/LinearGenomeView/components/Highlight.js +2 -4
- package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
- package/esm/LinearGenomeView/components/ImportForm.js +2 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlight.js +1 -1
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.d.ts +7 -1
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +22 -7
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +6 -21
- package/esm/LinearGenomeView/components/SearchBox.js +2 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +12 -5
- package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +3 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +3 -7
- package/esm/LinearGenomeView/components/TracksContainer.js +7 -15
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +1 -1
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +5 -0
- package/esm/LinearGenomeView/headerFit.d.ts +2 -1
- package/esm/LinearGenomeView/headerFit.js +7 -2
- package/esm/LinearGenomeView/index.d.ts +1 -1
- package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
- package/esm/LinearGenomeView/keyboardHandler.js +11 -0
- package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
- package/esm/LinearGenomeView/launchKeys.js +15 -5
- package/esm/LinearGenomeView/lazyDialogs.d.ts +7 -0
- package/esm/LinearGenomeView/lazyDialogs.js +2 -0
- package/esm/LinearGenomeView/menuItems.d.ts +28 -16
- package/esm/LinearGenomeView/menuItems.js +213 -85
- package/esm/LinearGenomeView/model.d.ts +367 -199
- package/esm/LinearGenomeView/model.js +302 -143
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.d.ts +5 -0
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.js +8 -0
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +3 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +95 -24
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +4 -5
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +13 -4
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +52 -63
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +8 -6
- package/esm/LinearGenomeView/svgcomponents/util.js +37 -16
- package/esm/LinearGenomeView/types.d.ts +5 -4
- package/esm/LinearGenomeView/util.d.ts +20 -37
- package/esm/LinearGenomeView/util.js +58 -141
- package/esm/displayKitTests/perRegionTestEnv.d.ts +64 -45
- package/esm/displayKitTests/perRegionTestEnv.js +6 -6
- package/esm/index.d.ts +9 -67
- package/esm/index.js +6 -22
- package/esm/searchUtils.d.ts +2 -3
- package/esm/searchUtils.js +2 -2
- package/package.json +7 -7
- package/esm/lazyPluginExports.d.ts +0 -23
- 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
|
-
* `
|
|
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
|
-
|
|
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,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
|
-
//
|
|
7
|
-
//
|
|
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.
|
|
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:
|
|
8
|
-
// whatever plugins contribute
|
|
9
|
-
//
|
|
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
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
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 ('
|
|
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'/'
|
|
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 === '
|
|
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
|
-
|
|
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),
|
|
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
|
-
|
|
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,
|
|
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
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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 `
|
|
98
|
-
* decision from the file path on purpose: the counter is
|
|
99
|
-
*
|
|
100
|
-
* pointing at. Letting it run is what makes several live figures
|
|
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(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
if (!isAlive(view) ||
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
93
|
-
export declare function trackBoxOffsets(tracks: TrackHeights[],
|
|
94
|
-
export declare function totalHeight(tracks: TrackHeights[],
|
|
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 {};
|