@jbrowse/plugin-linear-genome-view 5.0.0-beta.2 → 5.0.0-beta.20
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.d.ts +22 -5
- package/esm/LinearGenomeView/afterAttach.js +129 -106
- package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
- package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
- package/esm/LinearGenomeView/components/CenterLine.js +10 -5
- package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +9 -4
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +45 -8
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +11 -1
- package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
- package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
- package/esm/LinearGenomeView/components/HeaderPanControls.js +6 -3
- package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +6 -35
- package/esm/LinearGenomeView/components/Highlight.js +27 -11
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +11 -2
- package/esm/LinearGenomeView/components/HighlightBand.js +32 -13
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -3
- package/esm/LinearGenomeView/components/HighlightChip.js +13 -8
- package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
- package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
- package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
- package/esm/LinearGenomeView/components/ImportForm.js +7 -9
- package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +13 -14
- package/esm/LinearGenomeView/components/MiniControls.js +11 -5
- package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +6 -5
- package/esm/LinearGenomeView/components/OverviewRubberband.js +25 -48
- package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
- package/esm/LinearGenomeView/components/PaddingBlocks.js +3 -8
- package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +74 -0
- package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
- package/esm/LinearGenomeView/components/Rubberband.js +3 -2
- package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +16 -5
- package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
- package/esm/LinearGenomeView/components/Scalebar.js +8 -5
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +51 -36
- package/esm/LinearGenomeView/components/SearchBox.js +10 -16
- package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
- package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -8
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +4 -7
- package/esm/LinearGenomeView/components/SearchResultsTable.js +25 -53
- package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
- package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
- package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
- package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
- package/esm/LinearGenomeView/components/TrackContainer.js +63 -10
- package/esm/LinearGenomeView/components/TrackLabel.js +14 -4
- package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +4 -5
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +11 -14
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +6 -4
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +12 -24
- package/esm/LinearGenomeView/components/TracksContainer.js +11 -23
- package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
- package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
- package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
- package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
- package/esm/LinearGenomeView/components/useRangeSelect.js +26 -61
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
- package/esm/LinearGenomeView/components/useSideScroll.d.ts +17 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +156 -102
- package/esm/LinearGenomeView/components/util.d.ts +4 -0
- package/esm/LinearGenomeView/components/util.js +4 -2
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +3 -1
- package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
- package/esm/LinearGenomeView/containingLgv.js +23 -0
- package/esm/LinearGenomeView/headerFit.d.ts +4 -2
- package/esm/LinearGenomeView/headerFit.js +20 -7
- package/esm/LinearGenomeView/index.d.ts +2 -7
- package/esm/LinearGenomeView/index.js +5 -7
- package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -7
- package/esm/LinearGenomeView/keyboardHandler.js +61 -23
- package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
- package/esm/LinearGenomeView/launchKeys.js +11 -6
- package/esm/LinearGenomeView/lazyDialogs.d.ts +16 -3
- package/esm/LinearGenomeView/lazyDialogs.js +1 -0
- package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
- package/esm/LinearGenomeView/linkedViewSync.js +27 -49
- package/esm/LinearGenomeView/menuItems.d.ts +16 -18
- package/esm/LinearGenomeView/menuItems.js +264 -164
- package/esm/LinearGenomeView/model.d.ts +1192 -560
- package/esm/LinearGenomeView/model.js +805 -590
- package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +17 -0
- package/esm/LinearGenomeView/rExport/exportLinearView.js +117 -0
- package/esm/LinearGenomeView/rExport/rScript.d.ts +110 -0
- package/esm/LinearGenomeView/rExport/rScript.js +524 -0
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
- package/esm/LinearGenomeView/showFiles.d.ts +2 -0
- package/esm/LinearGenomeView/showFiles.js +19 -0
- package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +13 -5
- package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
- package/esm/LinearGenomeView/stickyChrome.js +5 -9
- package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +9 -9
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +5 -5
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +13 -18
- package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
- package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
- package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +26 -0
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +21 -13
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -5
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +7 -6
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +2 -3
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +17 -3
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -20
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +74 -77
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +28 -12
- package/esm/LinearGenomeView/svgcomponents/util.js +41 -29
- package/esm/LinearGenomeView/types.d.ts +13 -10
- package/esm/LinearGenomeView/types.js +0 -1
- package/esm/LinearGenomeView/util.d.ts +103 -46
- package/esm/LinearGenomeView/util.js +222 -170
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +9 -6
- package/esm/MultiLevelRubberband/index.d.ts +2 -0
- package/esm/MultiLevelRubberband/index.js +2 -0
- package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
- package/esm/MultiLevelRubberband/menuItems.js +52 -0
- package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
- package/esm/MultiLevelRubberband/rowLabel.js +27 -0
- package/esm/MultiLevelRubberband/types.d.ts +1 -0
- package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
- package/esm/MultiLevelRubberband/useRangeSelect.js +45 -59
- package/esm/TrackModifiers/index.d.ts +83 -0
- package/esm/TrackModifiers/index.js +231 -0
- package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -68
- package/esm/displayKitTests/perRegionTestEnv.js +30 -26
- package/esm/index.d.ts +21 -99
- package/esm/index.js +17 -38
- package/esm/searchUtils.d.ts +26 -10
- package/esm/searchUtils.js +137 -137
- package/esm/shared/RubberbandSpan.d.ts +3 -2
- package/esm/shared/RubberbandSpan.js +6 -3
- package/esm/shared/coordLabels.d.ts +11 -4
- package/esm/shared/coordLabels.js +20 -5
- package/esm/shared/useWindowDrag.d.ts +18 -0
- package/esm/shared/useWindowDrag.js +47 -0
- package/package.json +12 -11
- package/esm/FeatureTrack/configSchema.d.ts +0 -124
- package/esm/FeatureTrack/configSchema.js +0 -54
- package/esm/FeatureTrack/index.d.ts +0 -2
- package/esm/FeatureTrack/index.js +0 -13
- package/esm/LinearGenomeView/initKeys.d.ts +0 -12
- package/esm/LinearGenomeView/initKeys.js +0 -68
- package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
- package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnknownRefNameError, assembleLocStrings, getTickDisplayStr, measureText, parseBpString, parseLocString, } from '@jbrowse/core/util';
|
|
1
|
+
import { UnknownRefNameError, assembleLocString, assembleLocStrings, getTickDisplayStr, measureText, parseBpString, parseLocString, } from '@jbrowse/core/util';
|
|
2
2
|
import { bpOffsetInRegion } from '@jbrowse/core/util/Base1DUtils';
|
|
3
3
|
import { chooseGridPitch } from '@jbrowse/core/util/chooseGridPitch';
|
|
4
4
|
import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
|
|
@@ -18,7 +18,7 @@ import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
|
|
|
18
18
|
* A caller building a view SNAPSHOT does: it has no model to call
|
|
19
19
|
* `fitAllRegions()` on, and naming `bpPerPx`/`offsetPx` instead is dropped
|
|
20
20
|
* whenever the snapshot also carries a `windowWidthBp` — which one copied off an
|
|
21
|
-
* existing view always does. plugin-
|
|
21
|
+
* existing view always does. plugin-features's collapsed-intron launch is that
|
|
22
22
|
* caller. A live view calls `fitAllRegions()`.
|
|
23
23
|
*
|
|
24
24
|
* `width` enters only through the zoom-in floor, which bites for a region set
|
|
@@ -89,7 +89,7 @@ export function makeOverviewTicks(start, end, bpPerPx, reversed = false) {
|
|
|
89
89
|
* `makeBlockTicks`. What the two do share is already shared: `chooseGridPitch`,
|
|
90
90
|
* and `base` as the 0-based coordinate that `getTickDisplayStr` labels `base+1`.
|
|
91
91
|
*/
|
|
92
|
-
export function makeTicks(start, end, bpPerPx,
|
|
92
|
+
export function makeTicks(start, end, bpPerPx, includeMinor = true) {
|
|
93
93
|
// Ask for the ~200px spacing this ruler actually wants, and mark majors at
|
|
94
94
|
// that pitch. This used to ask for 60px and then mark majors every *two*
|
|
95
95
|
// pitches, which spaced them the same but off chooseGridPitch's 1/2/5 ladder:
|
|
@@ -106,12 +106,12 @@ export function makeTicks(start, end, bpPerPx, emitMajor = true, emitMinor = tru
|
|
|
106
106
|
const iterPitch = minorPitch || majorPitch;
|
|
107
107
|
const ticks = [];
|
|
108
108
|
for (let base = Math.floor(minBase / iterPitch) * iterPitch; base < Math.ceil(maxBase / iterPitch) * iterPitch + 1; base += iterPitch) {
|
|
109
|
-
if (
|
|
110
|
-
ticks.push({ type: 'minor', base: base - 1 });
|
|
111
|
-
}
|
|
112
|
-
else if (emitMajor && !(base % majorPitch)) {
|
|
109
|
+
if (!(base % majorPitch)) {
|
|
113
110
|
ticks.push({ type: 'major', base: base - 1 });
|
|
114
111
|
}
|
|
112
|
+
else if (includeMinor) {
|
|
113
|
+
ticks.push({ type: 'minor', base: base - 1 });
|
|
114
|
+
}
|
|
115
115
|
}
|
|
116
116
|
return ticks;
|
|
117
117
|
}
|
|
@@ -177,13 +177,12 @@ function refNameRuns(blocks) {
|
|
|
177
177
|
return runs;
|
|
178
178
|
}
|
|
179
179
|
/**
|
|
180
|
-
* Index of the run carrying the "sticky" refName label pinned to the
|
|
181
|
-
*
|
|
182
|
-
*
|
|
180
|
+
* Index of the run carrying the "sticky" refName label pinned to the strip's
|
|
181
|
+
* left edge: the rightmost run starting left of `leftEdgePx` (block frame), or
|
|
182
|
+
* the first run when none does.
|
|
183
183
|
*/
|
|
184
|
-
function stickyRunIndex(runs,
|
|
185
|
-
|
|
186
|
-
return scrolledOff === -1 ? 0 : scrolledOff;
|
|
184
|
+
function stickyRunIndex(runs, leftEdgePx) {
|
|
185
|
+
return Math.max(0, runs.findLastIndex(run => run.offsetPx < leftEdgePx));
|
|
187
186
|
}
|
|
188
187
|
export function regionsOrientation(regions) {
|
|
189
188
|
const first = !!regions[0]?.reversed;
|
|
@@ -202,161 +201,79 @@ export function regionsOrientation(regions) {
|
|
|
202
201
|
* upstream is whole-or-nothing, and it would go on believing a glyph fits.
|
|
203
202
|
*/
|
|
204
203
|
const REV_MARKER = ' [rev]';
|
|
205
|
-
/** Clearance between the
|
|
206
|
-
const
|
|
207
|
-
/** Left inset of a non-sticky refName label from its region's left edge. */
|
|
208
|
-
const REF_NAME_LABEL_PADDING_PX = 7;
|
|
204
|
+
/** Clearance between the caption chip and the sticky label after it. */
|
|
205
|
+
const CAPTION_GAP = 4;
|
|
209
206
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* maxWidth is the width of the whole label box, paddingLeft included, so the
|
|
214
|
-
* text has `maxWidth - paddingLeft` to draw in — that is what both consumers
|
|
215
|
-
* clip to (a border-box max-width on the span, an SVG clip rect from the box's
|
|
216
|
-
* own left edge). The caller decides whether the name it wants to draw fits in
|
|
217
|
-
* that (see refNameLabelWidth); a name is drawn whole or not at all.
|
|
207
|
+
* Left inset of a non-sticky refName label from its region's left edge, clear
|
|
208
|
+
* of the 3px region divider drawn there.
|
|
218
209
|
*/
|
|
219
|
-
|
|
220
|
-
// left-edge x a sticky label may not start before, so a caption chip drawn
|
|
221
|
-
// there keeps the strip to itself. Zero wherever the caption folds into the
|
|
222
|
-
// label instead (the assembly prefix does)
|
|
223
|
-
minTransform = 0, }) {
|
|
224
|
-
const transform = sticky
|
|
225
|
-
? Math.max(minTransform, -offsetPx)
|
|
226
|
-
: run.offsetPx - offsetPx - 1;
|
|
227
|
-
// block-frame x where the label actually starts. Derived from `transform` (=
|
|
228
|
-
// transform + offsetPx) so the width-to-run-end clip stays in lockstep with
|
|
229
|
-
// where the label is drawn: a sticky label pins to the run's left edge, not
|
|
230
|
-
// the viewport's, whenever the view is left-overscrolled (offsetPx < 0) —
|
|
231
|
-
// reading offsetPx directly there over-counted the available width by
|
|
232
|
-
// |offsetPx|, letting the name bleed past its run's right edge.
|
|
233
|
-
const labelStartPx = transform + offsetPx;
|
|
234
|
-
// A non-sticky label's transform anchors at the same x as the region
|
|
235
|
-
// divider drawn just to its left (SVGRegionSeparators, a 3px bar spanning
|
|
236
|
-
// local [0,3] from this same run.offsetPx edge), so paddingLeft must clear
|
|
237
|
-
// that bar plus a few px of breathing room, else the text visually touches
|
|
238
|
-
// the divider. Sticky labels sit at the viewport's own left edge, no divider.
|
|
239
|
-
const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
|
|
240
|
-
return { transform, maxWidth: run.endPx - labelStartPx - 1, paddingLeft };
|
|
241
|
-
}
|
|
210
|
+
const REF_NAME_LABEL_PADDING_PX = 7;
|
|
242
211
|
/**
|
|
243
212
|
* Builds the refName labels drawn along the scalebar as plain data (no JSX): one
|
|
244
|
-
* label per run of same-refName regions
|
|
245
|
-
*
|
|
246
|
-
* refName under the viewport's left border.
|
|
213
|
+
* label per run of same-refName regions, the leftmost of them "sticky" — pinned
|
|
214
|
+
* so the run under the left edge stays named once its own left edge scrolls off.
|
|
247
215
|
*
|
|
248
|
-
* `caption` is the row-level chip at the viewport's left edge
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* label
|
|
252
|
-
*
|
|
216
|
+
* `caption` is the row-level chip at the viewport's left edge: which assembly
|
|
217
|
+
* the row is (`prefix`, synteny only) and whether the whole row is flipped. The
|
|
218
|
+
* chip takes `captionSpanPx` of the strip and the labels get the rest, so the
|
|
219
|
+
* sticky label pins to the chip's right edge ("hg38" then "chr1") and rides the
|
|
220
|
+
* run under THAT edge. A run the chip covers entirely goes unnamed, and the
|
|
221
|
+
* chip is not shortened to uncover it: a width fitted to the run under it
|
|
222
|
+
* changes with every run that pans past, and a fixed cap short enough to
|
|
223
|
+
* uncover a 55px chromosome cuts ordinary names. The assembly's `displayName`
|
|
224
|
+
* is where a long name gets shortened.
|
|
253
225
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* sticky label sits far enough right not to (a view scrolled left of its first
|
|
257
|
-
* region, so the row's data starts mid-viewport) or when there is no sticky
|
|
258
|
-
* label at all.
|
|
226
|
+
* `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
|
|
227
|
+
* it where it distinguishes, once where it does not.
|
|
259
228
|
*
|
|
260
|
-
* `
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* distinguishes, once where it does not.
|
|
265
|
-
*
|
|
266
|
-
* - `reversed`: the whole row is flipped, which is a fact about the row, so the
|
|
267
|
-
* caption carries it and NO name does. The caption cannot fold in this state
|
|
268
|
-
* — the sticky label is pushed clear of it instead — because a marker sitting
|
|
269
|
-
* on a chromosome name is how the mixed case spells a fact about that one
|
|
270
|
-
* region. `chr1 [rev] | chr2` would otherwise mean both "the row is flipped,
|
|
271
|
-
* said once" and "chr1 is flipped and chr2 is not", in identical pixels.
|
|
272
|
-
* - `mixed`: only some regions are flipped — reachable through the label menu's
|
|
273
|
-
* per-region "Reverse region" — so every reversed run is marked and the
|
|
274
|
-
* forward ones are left alone. Here the marker on a name is the only thing
|
|
275
|
-
* telling two labels apart.
|
|
229
|
+
* - `reversed`: the caption carries the marker and no name does. A marker on a
|
|
230
|
+
* chromosome name is how the mixed case flags that one region.
|
|
231
|
+
* - `mixed`: every reversed run is marked. A marked label that fails the fit
|
|
232
|
+
* test falls back to the bare name rather than dropping.
|
|
276
233
|
* - `forward`: no marker.
|
|
277
234
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* Under `mixed` the marker never costs a name: a suffixed label that fails the
|
|
282
|
-
* fit test falls back to the bare name rather than being dropped. Widening the
|
|
283
|
-
* text moves the region width a name needs from ~30px to ~56px at 11px bold,
|
|
284
|
-
* and a feature meant to surface information should not be deleting chromosome
|
|
285
|
-
* names in the narrow half of that gap.
|
|
235
|
+
* `maxWidth` is the whole label box, `paddingLeft` included — both consumers
|
|
236
|
+
* clip from the box's left edge. A name is drawn whole or not at all: clipped
|
|
237
|
+
* mid-glyph, "chr16" reads as "chr1".
|
|
286
238
|
*/
|
|
287
239
|
export function getScalebarRefNameLabels({ blocks, offsetPx, prefix, orientation = 'forward', }) {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
: prefix;
|
|
293
|
-
// the caption chip occupies the viewport's left edge, so a sticky label
|
|
294
|
-
// starting inside that span has to yield: absorb the assembly name, or, where
|
|
295
|
-
// the caption carries the row's orientation and so cannot fold, start after
|
|
296
|
-
// it. The gap is part of the span: a sticky label carries no padding of its
|
|
297
|
-
// own, so one starting at exactly the chip's right edge abuts it and the two
|
|
298
|
-
// read as a single word ("volvoxctgA") for the few px of scroll around the
|
|
299
|
-
// threshold
|
|
300
|
-
const captionSpanPx = captionText
|
|
301
|
-
? refNameLabelWidth(captionText) + PREFIX_GAP
|
|
302
|
-
: 0;
|
|
240
|
+
const caption = [prefix, orientation === 'reversed' ? REV_MARKER.trim() : undefined]
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.join(' ') || undefined;
|
|
243
|
+
const captionSpanPx = caption ? refNameLabelWidth(caption) + CAPTION_GAP : 0;
|
|
303
244
|
const runs = refNameRuns(blocks);
|
|
304
|
-
const stickyIdx = stickyRunIndex(runs, offsetPx);
|
|
245
|
+
const stickyIdx = stickyRunIndex(runs, offsetPx + captionSpanPx);
|
|
305
246
|
const labels = [];
|
|
306
|
-
let stickyHasPrefix = false;
|
|
307
247
|
for (const [i, run] of runs.entries()) {
|
|
308
248
|
const sticky = i === stickyIdx;
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
// every earlier run also *ends* left of the viewport. Its label would just
|
|
312
|
-
// repeat the name the sticky label already shows, drawn off-canvas —
|
|
313
|
-
// invisible on screen (overflow:clip) but bleeding into the margin of an
|
|
314
|
-
// SVG export, which has no such clip.
|
|
315
|
-
const runStart = run.offsetPx >= offsetPx && run.isLeftEndOfDisplayedRegion;
|
|
316
|
-
if (!(sticky || runStart)) {
|
|
249
|
+
// runs left of the sticky one end under the caption or off screen
|
|
250
|
+
if (i < stickyIdx || !(sticky || run.isLeftEndOfDisplayedRegion)) {
|
|
317
251
|
continue;
|
|
318
252
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const withPrefix = !rowIsReversed && sticky && hasPrefix && layout.transform < captionSpanPx;
|
|
326
|
-
const name = withPrefix ? `${prefix}:${run.refName}` : run.refName;
|
|
327
|
-
// draw the name whole or not at all: a name clipped mid-glyph reads as a
|
|
328
|
-
// different chromosome ("LG2" cut to "LG"), and measuring it means a short
|
|
329
|
-
// name gets its label in a region a fixed minimum width would have skipped.
|
|
330
|
-
// The marker is the one part that degrades instead of dropping the label
|
|
253
|
+
const runStartPx = run.offsetPx - offsetPx;
|
|
254
|
+
const transform = sticky
|
|
255
|
+
? Math.max(captionSpanPx, runStartPx)
|
|
256
|
+
: runStartPx - 1;
|
|
257
|
+
const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
|
|
258
|
+
const maxWidth = run.endPx - offsetPx - transform - 1;
|
|
331
259
|
const text = (orientation === 'mixed' && run.reversed
|
|
332
|
-
? [`${
|
|
333
|
-
: [
|
|
334
|
-
if (text
|
|
335
|
-
|
|
260
|
+
? [`${run.refName}${REV_MARKER}`, run.refName]
|
|
261
|
+
: [run.refName]).find(candidate => maxWidth >= paddingLeft + refNameLabelWidth(candidate));
|
|
262
|
+
if (text !== undefined) {
|
|
263
|
+
labels.push({
|
|
264
|
+
key: run.key,
|
|
265
|
+
refName: run.refName,
|
|
266
|
+
displayedRegionIndex: run.firstRegionIndex,
|
|
267
|
+
lastDisplayedRegionIndex: run.lastRegionIndex,
|
|
268
|
+
sticky,
|
|
269
|
+
transform,
|
|
270
|
+
maxWidth,
|
|
271
|
+
paddingLeft,
|
|
272
|
+
text,
|
|
273
|
+
});
|
|
336
274
|
}
|
|
337
|
-
stickyHasPrefix ||= withPrefix;
|
|
338
|
-
labels.push({
|
|
339
|
-
key: run.key,
|
|
340
|
-
refName: run.refName,
|
|
341
|
-
displayedRegionIndex: run.firstRegionIndex,
|
|
342
|
-
lastDisplayedRegionIndex: run.lastRegionIndex,
|
|
343
|
-
sticky,
|
|
344
|
-
transform: layout.transform,
|
|
345
|
-
maxWidth: layout.maxWidth,
|
|
346
|
-
paddingLeft: layout.paddingLeft,
|
|
347
|
-
text,
|
|
348
|
-
});
|
|
349
275
|
}
|
|
350
|
-
return {
|
|
351
|
-
labels,
|
|
352
|
-
// the chip to draw at the left edge, or undefined for none. A caption
|
|
353
|
-
// carrying the orientation always draws; one carrying only the assembly
|
|
354
|
-
// name draws when no sticky label folded it in
|
|
355
|
-
caption: rowIsReversed || (hasPrefix && !stickyHasPrefix)
|
|
356
|
-
? captionText
|
|
357
|
-
: undefined,
|
|
358
|
-
captionSpanPx,
|
|
359
|
-
};
|
|
276
|
+
return { labels, caption, captionSpanPx };
|
|
360
277
|
}
|
|
361
278
|
/**
|
|
362
279
|
* Screen-x span a refName label paints over: its padding and its glyphs, which
|
|
@@ -404,22 +321,74 @@ export function regionMoveActions(idx, numRegions) {
|
|
|
404
321
|
},
|
|
405
322
|
].flatMap(({ when, label, to }) => (when ? [{ label, to }] : []));
|
|
406
323
|
}
|
|
407
|
-
/**
|
|
408
|
-
* The region reorderings offered by the refName-label menu, as pure list
|
|
409
|
-
* transforms feeding setDisplayedRegions (which re-clamps bpPerPx/offsetPx for
|
|
410
|
-
* the new region set).
|
|
411
|
-
*/
|
|
412
324
|
export function withRegionMoved(regions, from, to) {
|
|
413
325
|
const out = [...regions];
|
|
414
326
|
const [moved] = out.splice(from, 1);
|
|
415
327
|
out.splice(to, 0, moved);
|
|
416
|
-
return
|
|
328
|
+
return {
|
|
329
|
+
regions: out,
|
|
330
|
+
newIndexOf: i => i === from
|
|
331
|
+
? to
|
|
332
|
+
: i - (i > from && i <= to ? 1 : 0) + (i >= to && i < from ? 1 : 0),
|
|
333
|
+
};
|
|
417
334
|
}
|
|
418
335
|
export function withRegionRemoved(regions, index) {
|
|
419
|
-
return
|
|
336
|
+
return {
|
|
337
|
+
regions: regions.filter((_, i) => i !== index),
|
|
338
|
+
newIndexOf: i => (i === index ? -1 : i > index ? i - 1 : i),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function withRegionsKept(regions, first, last) {
|
|
342
|
+
return {
|
|
343
|
+
regions: regions.slice(first, last + 1),
|
|
344
|
+
newIndexOf: i => (i >= first && i <= last ? i - first : -1),
|
|
345
|
+
};
|
|
420
346
|
}
|
|
421
347
|
export function withRegionReversed(regions, index) {
|
|
422
|
-
return
|
|
348
|
+
return {
|
|
349
|
+
regions: regions.map((region, i) => i === index ? { ...region, reversed: !region.reversed } : region),
|
|
350
|
+
newIndexOf: i => i,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Apply a region edit and keep the base under the middle of the viewport
|
|
355
|
+
* there, where the edit kept its region. `setDisplayedRegions` alone carries
|
|
356
|
+
* `offsetPx` into a coordinate space the edit redefined, which lands on
|
|
357
|
+
* whatever region now sits at that offset.
|
|
358
|
+
*/
|
|
359
|
+
export function setDisplayedRegionsKeepingCenter(model, { regions, newIndexOf }) {
|
|
360
|
+
const center = model.displayedRegions.length
|
|
361
|
+
? model.pxToBp(model.width / 2)
|
|
362
|
+
: undefined;
|
|
363
|
+
model.setDisplayedRegions(regions);
|
|
364
|
+
const index = center ? newIndexOf(center.index) : -1;
|
|
365
|
+
if (center && index !== -1) {
|
|
366
|
+
model.centerAt(center.coord0, center.refName, index);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* The full run of same-orientation regions on one refName that a scalebar
|
|
371
|
+
* label's `firstRegionIndex`..`lastRegionIndex` sits inside. Those come off the
|
|
372
|
+
* static blocks, which are clipped to the viewport and broken by an elided
|
|
373
|
+
* block, so a zoomed-in collapsed-introns view brackets only the exons on
|
|
374
|
+
* screen — which is the wrong answer for anything acting on the whole run.
|
|
375
|
+
*/
|
|
376
|
+
export function regionRunBounds(regions, firstIndex, lastIndex) {
|
|
377
|
+
const anchor = regions[firstIndex];
|
|
378
|
+
const inRun = (r) => r !== undefined &&
|
|
379
|
+
anchor !== undefined &&
|
|
380
|
+
r.refName === anchor.refName &&
|
|
381
|
+
r.assemblyName === anchor.assemblyName &&
|
|
382
|
+
!!r.reversed === !!anchor.reversed;
|
|
383
|
+
let first = firstIndex;
|
|
384
|
+
let last = lastIndex;
|
|
385
|
+
while (inRun(regions[first - 1])) {
|
|
386
|
+
first--;
|
|
387
|
+
}
|
|
388
|
+
while (inRun(regions[last + 1])) {
|
|
389
|
+
last++;
|
|
390
|
+
}
|
|
391
|
+
return { first, last };
|
|
423
392
|
}
|
|
424
393
|
/**
|
|
425
394
|
* Whether a label occupying [leftPx, leftPx + labelWidth] fits within a block
|
|
@@ -508,14 +477,16 @@ export function groupContiguousBlocks(blocks) {
|
|
|
508
477
|
current.end = Math.max(current.end, block.end);
|
|
509
478
|
}
|
|
510
479
|
else {
|
|
480
|
+
const reversed = !!block.reversed;
|
|
511
481
|
current = {
|
|
512
482
|
offsetPx: block.offsetPx,
|
|
513
483
|
widthPx: block.widthPx,
|
|
514
484
|
start: block.start,
|
|
515
485
|
end: block.end,
|
|
516
|
-
reversed
|
|
486
|
+
reversed,
|
|
517
487
|
refName: block.refName,
|
|
518
488
|
isLeftEndOfDisplayedRegion: !!block.isLeftEndOfDisplayedRegion,
|
|
489
|
+
labeled: current?.refName !== block.refName || current.reversed !== reversed,
|
|
519
490
|
};
|
|
520
491
|
currentRegionIndex = block.displayedRegionIndex;
|
|
521
492
|
runs.push(current);
|
|
@@ -553,8 +524,7 @@ export function groupContiguousBlocks(blocks) {
|
|
|
553
524
|
* this frame.
|
|
554
525
|
*/
|
|
555
526
|
export function runRefNameLabelPx(runs, orientation = 'forward') {
|
|
556
|
-
|
|
557
|
-
return runs.map((run, i) => isRunStart[i] && run.isLeftEndOfDisplayedRegion
|
|
527
|
+
return runs.map(run => run.labeled && run.isLeftEndOfDisplayedRegion
|
|
558
528
|
? REF_NAME_LABEL_PADDING_PX +
|
|
559
529
|
refNameLabelWidth(orientation === 'mixed' && run.reversed
|
|
560
530
|
? `${run.refName}${REV_MARKER}`
|
|
@@ -566,10 +536,10 @@ export function runRefNameLabelPx(runs, orientation = 'forward') {
|
|
|
566
536
|
* regions. Single source of the tick→px formula shared by gridlines, the
|
|
567
537
|
* scalebar coordinate labels, and SVG export so their positions can't drift.
|
|
568
538
|
*/
|
|
569
|
-
export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx,
|
|
570
|
-
return makeTicks(start, end, bpPerPx,
|
|
539
|
+
export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, includeMinor = true) {
|
|
540
|
+
return makeTicks(start, end, bpPerPx, includeMinor).map(tick => ({
|
|
571
541
|
...tick,
|
|
572
|
-
x: (
|
|
542
|
+
x: bpOffsetInRegion({ start, end, reversed }, tick.base) / bpPerPx,
|
|
573
543
|
}));
|
|
574
544
|
}
|
|
575
545
|
/**
|
|
@@ -588,6 +558,10 @@ export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, emitM
|
|
|
588
558
|
* navigating to a feature
|
|
589
559
|
*/
|
|
590
560
|
export async function generateLocations({ regions, assemblyManager, assemblyName, grow, }) {
|
|
561
|
+
const backwards = regions.find(({ start, end }) => start !== undefined && end !== undefined && start > end);
|
|
562
|
+
if (backwards) {
|
|
563
|
+
throw new Error(`found start greater than end in ${assembleLocString(backwards)}`);
|
|
564
|
+
}
|
|
591
565
|
return Promise.all(regions.map(async (region) => {
|
|
592
566
|
const asmName = region.assemblyName || assemblyName;
|
|
593
567
|
if (!asmName) {
|
|
@@ -639,14 +613,12 @@ export async function generateLocations({ regions, assemblyManager, assemblyName
|
|
|
639
613
|
* Used by navToLocString
|
|
640
614
|
*/
|
|
641
615
|
export function parseLocStrings(input, assemblyName, isValidRefName) {
|
|
642
|
-
const inputs = input
|
|
643
|
-
|
|
644
|
-
.map(f => f.trim())
|
|
645
|
-
.filter(f => !!f);
|
|
616
|
+
const inputs = input.split(/\s+/).filter(Boolean);
|
|
617
|
+
const isRef = (ref, asm) => isValidRefName(ref, asm ?? assemblyName);
|
|
646
618
|
// first try interpreting as a whitespace-separated sequence of
|
|
647
619
|
// multiple locstrings
|
|
648
620
|
try {
|
|
649
|
-
return inputs.map(loc => parseLocString(loc,
|
|
621
|
+
return inputs.map(loc => parseLocString(loc, isRef));
|
|
650
622
|
}
|
|
651
623
|
catch (e) {
|
|
652
624
|
// if this fails, try interpreting as a whitespace-separated refname,
|
|
@@ -656,15 +628,95 @@ export function parseLocStrings(input, assemblyName, isValidRefName) {
|
|
|
656
628
|
// in this form as in the colon form.
|
|
657
629
|
const [refName, start = '', end = ''] = inputs;
|
|
658
630
|
if (e instanceof UnknownRefNameError &&
|
|
631
|
+
inputs.length === 3 &&
|
|
659
632
|
parseBpString(start) !== undefined &&
|
|
660
633
|
parseBpString(end) !== undefined) {
|
|
661
|
-
return [
|
|
662
|
-
parseLocString(`${refName}:${start}..${end}`, ref => isValidRefName(ref, assemblyName)),
|
|
663
|
-
];
|
|
634
|
+
return [parseLocString(`${refName}:${start}..${end}`, isRef)];
|
|
664
635
|
}
|
|
665
636
|
throw e;
|
|
666
637
|
}
|
|
667
638
|
}
|
|
639
|
+
// Frozen because every consumer maps over it and a caller that sorted in place
|
|
640
|
+
// would do so for the whole session.
|
|
641
|
+
const NO_PADDING_SPANS = Object.freeze([]);
|
|
642
|
+
/**
|
|
643
|
+
* Tick positions in the staticBlocks frame, one pass per merged run so a tick
|
|
644
|
+
* on an internal ~800px chunk boundary is emitted once.
|
|
645
|
+
*/
|
|
646
|
+
export function getGridlineTicks({ blocks, firstBlockOffset, bpPerPx, }) {
|
|
647
|
+
const ticks = [];
|
|
648
|
+
for (const run of groupContiguousBlocks(blocks)) {
|
|
649
|
+
const runLeft = run.offsetPx - firstBlockOffset;
|
|
650
|
+
for (const { type, x } of makeBlockTicks(run, bpPerPx)) {
|
|
651
|
+
if (x >= 0 && x <= run.widthPx) {
|
|
652
|
+
ticks.push({ x: runLeft + x, major: type === 'major' });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return ticks;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Coordinate labels in the staticBlocks frame, off the same ticks as
|
|
660
|
+
* `getGridlineTicks`. A run that can hold too few labels to make a ruler goes
|
|
661
|
+
* unnumbered, and the bold refName label pinned at a run's left edge takes
|
|
662
|
+
* precedence over a number.
|
|
663
|
+
*/
|
|
664
|
+
export function getScalebarLabels({ blocks, firstBlockOffset, bpPerPx, orientation, }) {
|
|
665
|
+
const labels = [];
|
|
666
|
+
const runs = groupContiguousBlocks(blocks);
|
|
667
|
+
const refNameLabelPx = runRefNameLabelPx(runs, orientation);
|
|
668
|
+
for (const [i, run] of runs.entries()) {
|
|
669
|
+
const runLeft = run.offsetPx - firstBlockOffset;
|
|
670
|
+
const runLabels = [];
|
|
671
|
+
for (const { base, x } of makeBlockTicks(run, bpPerPx, false)) {
|
|
672
|
+
const label = getTickDisplayStr(base + 1, bpPerPx);
|
|
673
|
+
const w = tickLabelWidth(label);
|
|
674
|
+
if (x - w / 2 >= refNameLabelPx[i] &&
|
|
675
|
+
labelFitsInBlock(x - w / 2, w, run.widthPx)) {
|
|
676
|
+
runLabels.push({
|
|
677
|
+
x: runLeft + x,
|
|
678
|
+
label,
|
|
679
|
+
key: `${run.offsetPx}-${base}`,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (tickLabelsWorthDrawing(runLabels.length)) {
|
|
684
|
+
labels.push(...runLabels);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return labels;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Every span along the row that is not track data, in the staticBlocks frame.
|
|
691
|
+
* Nothing to draw returns one shared array, so the computed's value repeats and
|
|
692
|
+
* MobX's `===` stops the chain: a view inside one contig has no seam, elision
|
|
693
|
+
* or boundary, and a fresh `[]` each frame re-rendered every track's padding.
|
|
694
|
+
*/
|
|
695
|
+
export function getPaddingSpans(blocks, firstBlockOffset) {
|
|
696
|
+
const spans = [];
|
|
697
|
+
for (const block of blocks) {
|
|
698
|
+
const x = block.offsetPx - firstBlockOffset;
|
|
699
|
+
if (block.type === 'ContentBlock') {
|
|
700
|
+
if (block.isRightEndOfDisplayedRegion) {
|
|
701
|
+
spans.push({
|
|
702
|
+
key: `${block.key}-sep`,
|
|
703
|
+
x: x + block.widthPx - 1,
|
|
704
|
+
width: 3,
|
|
705
|
+
kind: 'seam',
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
spans.push({
|
|
711
|
+
key: block.key,
|
|
712
|
+
x,
|
|
713
|
+
width: block.widthPx,
|
|
714
|
+
kind: block.type === 'ElidedBlock' ? 'elided' : 'boundary',
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return spans.length > 0 ? spans : NO_PADDING_SPANS;
|
|
719
|
+
}
|
|
668
720
|
export function calculateVisibleLocStrings(contentBlocks) {
|
|
669
721
|
return assembleLocStrings(contentBlocks.map(block => ({
|
|
670
722
|
refName: block.refName,
|
|
@@ -5,7 +5,7 @@ import { getBpDisplayStr, stringify } from '@jbrowse/core/util';
|
|
|
5
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
6
|
import { observer } from 'mobx-react';
|
|
7
7
|
import RubberbandSpan from '../shared/RubberbandSpan.js';
|
|
8
|
-
import
|
|
8
|
+
import { VerticalGuideLine } from '../shared/coordLabels.js';
|
|
9
9
|
import { useRangeSelect } from './useRangeSelect.js';
|
|
10
10
|
const useStyles = makeStyles()({
|
|
11
11
|
rubberbandControl: {
|
|
@@ -19,11 +19,14 @@ const useStyles = makeStyles()({
|
|
|
19
19
|
const MultiLevelRubberband = observer(function MultiLevelRubberband({ model, ControlComponent = _jsx("div", {}), }) {
|
|
20
20
|
const ref = useRef(null);
|
|
21
21
|
const { classes } = useStyles();
|
|
22
|
-
const { guideX, rubberbandOn, leftBpOffset, rightBpOffset, numOfBpSelected, width, left, anchorPosition,
|
|
23
|
-
|
|
22
|
+
const { isClick, guideX, rubberbandOn, leftBpOffset, rightBpOffset, numOfBpSelected, width, left, anchorPosition, handleMenuItemClick, handleClose, mouseMove, mouseDown, mouseOut, } = useRangeSelect(ref, model);
|
|
23
|
+
const viewWidth = Math.min(...model.views.map(view => view.width));
|
|
24
|
+
return (_jsxs(_Fragment, { children: [guideX !== undefined ? (_jsx(VerticalGuideLine, { coordX: guideX, viewWidth: viewWidth, stickyTop: undefined, children: _jsx(PerLevelRows, { rows: model.views.map(view => stringify(view.pxToBp(guideX), true)) }) })) : rubberbandOn ? (_jsx(RubberbandSpan, { leftLabel: _jsx(PerLevelRows, { rows: leftBpOffset.map(l => stringify(l, true)) }), rightLabel: _jsx(PerLevelRows, { rows: rightBpOffset.map(r => stringify(r, true)) }), size: _jsx(PerLevelRows, { rows: numOfBpSelected.map(getBpDisplayStr) }), width: width, left: left, viewWidth: viewWidth, stickyTop: undefined })) : null, anchorPosition ? (_jsx(Menu, { anchorReference: "anchorPosition", anchorPosition: {
|
|
24
25
|
left: anchorPosition.clientX,
|
|
25
26
|
top: anchorPosition.clientY,
|
|
26
|
-
}, onMenuItemClick: handleMenuItemClick, open:
|
|
27
|
+
}, onMenuItemClick: handleMenuItemClick, open: true, onClose: handleClose, menuItems: isClick
|
|
28
|
+
? model.rubberbandClickMenuItemsAtPx(anchorPosition.offsetX)
|
|
29
|
+
: model.rubberBandMenuItems() })) : null, _jsx("div", { "data-testid": "rubberband_controls", className: classes.rubberbandControl, ref: ref, onMouseDown: mouseDown, onMouseMove: mouseMove,
|
|
27
30
|
// Leave, not out: mouseout bubbles up from every child, so moving across
|
|
28
31
|
// the ControlComponent's own children fired it and flickered the guide.
|
|
29
32
|
// Matches Rubberband/OverviewRubberband.
|
|
@@ -31,7 +34,7 @@ const MultiLevelRubberband = observer(function MultiLevelRubberband({ model, Con
|
|
|
31
34
|
});
|
|
32
35
|
// one row per rubberband level, in level order. values repeat across levels
|
|
33
36
|
// whenever the assemblies line up, so the index is the only stable key
|
|
34
|
-
function PerLevelRows({ rows }) {
|
|
37
|
+
const PerLevelRows = observer(function PerLevelRows({ rows, }) {
|
|
35
38
|
return rows.map((row, idx) => (_jsx("div", { children: row }, idx)));
|
|
36
|
-
}
|
|
39
|
+
});
|
|
37
40
|
export default MultiLevelRubberband;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { default as MultiLevelRubberband } from './MultiLevelRubberband.tsx';
|
|
2
|
+
export { multiLevelRowClickMenuItems, multiLevelRowMenuItems, } from './menuItems.ts';
|
|
3
|
+
export { rowLabels } from './rowLabel.ts';
|
|
2
4
|
export type { MultiLevelRubberbandModel } from './types.ts';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LinearGenomeViewModel } from '../LinearGenomeView/model.ts';
|
|
2
|
+
import type { MenuItem } from '@jbrowse/core/ui';
|
|
3
|
+
/**
|
|
4
|
+
* One row per genome row, each holding that row's own rubberband menu — Get
|
|
5
|
+
* sequence, Copy range, Highlight region, and whatever a plugin contributes
|
|
6
|
+
* under Launch.
|
|
7
|
+
*
|
|
8
|
+
* A drag on the multi-level strip commits offsets on EVERY row (see
|
|
9
|
+
* `useRangeSelect`), so each row's `rubberBandMenuItems()` already describes a
|
|
10
|
+
* real selection and was simply never offered: the stacked views' menu held
|
|
11
|
+
* "Zoom to region(s)" and nothing else, while the same drag in a plain linear
|
|
12
|
+
* genome view offered five actions. Per row rather than applied across rows
|
|
13
|
+
* because each row is its own assembly — one sequence to fetch, one range to
|
|
14
|
+
* copy, one highlight to place — so there is no "all rows" answer to give.
|
|
15
|
+
*
|
|
16
|
+
* Zoom is the exception and stays where each view already has it: zooming every
|
|
17
|
+
* row at once is what the gesture means, and the synteny view holds its follow
|
|
18
|
+
* anchor across the loop.
|
|
19
|
+
*/
|
|
20
|
+
export declare function multiLevelRowMenuItems(views: LinearGenomeViewModel[]): MenuItem[];
|
|
21
|
+
/**
|
|
22
|
+
* The same per-row rows for a bare CLICK on the strip rather than a drag — each
|
|
23
|
+
* row's Center view here, Zoom to base level and Copy coordinate, at the base
|
|
24
|
+
* that row paints under `px`.
|
|
25
|
+
*
|
|
26
|
+
* No all-rows row above them, unlike the drag's "Zoom to region(s)". A drag
|
|
27
|
+
* names a span in pixels and every row can act on it; a click names a
|
|
28
|
+
* COORDINATE, and the rows are different assemblies, so which genome's base the
|
|
29
|
+
* pointer is over is the question the menu has to ask rather than answer.
|
|
30
|
+
*
|
|
31
|
+
* `px` rather than a resolved offset: each row maps the pixel through its own
|
|
32
|
+
* `pxToBp`, so there is no one offset to pass.
|
|
33
|
+
*/
|
|
34
|
+
export declare function multiLevelRowClickMenuItems(views: LinearGenomeViewModel[], px: number): MenuItem[];
|