@jbrowse/plugin-linear-genome-view 5.0.0-beta.19 → 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 +17 -0
- package/esm/LinearGenomeView/afterAttach.js +71 -43
- package/esm/LinearGenomeView/components/CenterLine.js +6 -3
- package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +12 -3
- package/esm/LinearGenomeView/components/Header.js +1 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +3 -2
- package/esm/LinearGenomeView/components/HighlightBand.js +7 -7
- package/esm/LinearGenomeView/components/ImportForm.js +3 -3
- package/esm/LinearGenomeView/components/MiniControls.js +1 -1
- package/esm/LinearGenomeView/components/OverviewRubberband.js +5 -3
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +7 -4
- package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +15 -3
- package/esm/LinearGenomeView/components/SearchBox.js +3 -2
- package/esm/LinearGenomeView/components/SearchResultsTable.js +2 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +1 -1
- package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -3
- package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
- package/esm/LinearGenomeView/components/useRangeSelect.js +5 -2
- package/esm/LinearGenomeView/menuItems.js +5 -5
- package/esm/LinearGenomeView/model.d.ts +52 -40
- package/esm/LinearGenomeView/model.js +71 -50
- package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +4 -0
- package/esm/LinearGenomeView/rExport/exportLinearView.js +26 -6
- package/esm/LinearGenomeView/rExport/rScript.d.ts +19 -9
- package/esm/LinearGenomeView/rExport/rScript.js +91 -53
- package/esm/LinearGenomeView/showRegionsWithUndo.js +5 -3
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +2 -2
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +2 -2
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +15 -1
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +1 -0
- package/esm/LinearGenomeView/types.d.ts +6 -0
- package/esm/LinearGenomeView/types.js +0 -1
- package/esm/LinearGenomeView/util.js +9 -9
- package/esm/MultiLevelRubberband/useRangeSelect.js +14 -12
- package/esm/TrackModifiers/index.d.ts +4 -1
- package/esm/TrackModifiers/index.js +26 -7
- package/esm/displayKitTests/perRegionTestEnv.d.ts +2 -0
- package/esm/index.d.ts +2 -2
- package/esm/index.js +1 -1
- package/esm/searchUtils.d.ts +1 -3
- package/esm/searchUtils.js +33 -44
- package/esm/shared/useWindowDrag.js +11 -9
- package/package.json +7 -7
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
import { expandAssemblySequence, expandSidecarShorthand, refNameAliasAdapterType, } from '@jbrowse/core/assemblyManager/expandAssemblyConfigShorthand';
|
|
2
2
|
import { resolveUri } from '@jbrowse/core/util';
|
|
3
|
+
const VCF_ADAPTERS = {
|
|
4
|
+
VcfTabixAdapter: { slot: 'vcfGzLocation', ext: /\.vcf\.gz$/, index: '.tbi' },
|
|
5
|
+
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* Each display ggjbrowse reads, by the reader that reads it, and the adapters
|
|
5
|
-
* it opens
|
|
6
|
-
*
|
|
9
|
+
* it opens: the slot holding the file, the extension the reader knows it by,
|
|
10
|
+
* and the suffix of the index the reader looks for beside it.
|
|
7
11
|
*/
|
|
8
12
|
export const READERS = {
|
|
9
13
|
LinearBasicDisplay: {
|
|
10
14
|
call: 'jbrowse_features',
|
|
11
15
|
adapters: {
|
|
12
|
-
Gff3TabixAdapter: {
|
|
16
|
+
Gff3TabixAdapter: {
|
|
17
|
+
slot: 'gffGzLocation',
|
|
18
|
+
ext: /\.gff3?\.gz$/,
|
|
19
|
+
index: '.tbi',
|
|
20
|
+
},
|
|
13
21
|
Gff3Adapter: { slot: 'gffLocation', ext: /\.gff3?$/ },
|
|
14
|
-
GtfTabixAdapter: {
|
|
22
|
+
GtfTabixAdapter: {
|
|
23
|
+
slot: 'gtfGzLocation',
|
|
24
|
+
ext: /\.gtf\.gz$/,
|
|
25
|
+
index: '.tbi',
|
|
26
|
+
},
|
|
15
27
|
GtfAdapter: { slot: 'gtfLocation', ext: /\.gtf$/ },
|
|
16
|
-
BedTabixAdapter: {
|
|
28
|
+
BedTabixAdapter: {
|
|
29
|
+
slot: 'bedGzLocation',
|
|
30
|
+
ext: /\.bed\.gz$/,
|
|
31
|
+
index: '.tbi',
|
|
32
|
+
},
|
|
17
33
|
BedAdapter: { slot: 'bedLocation', ext: /\.bed$/ },
|
|
18
34
|
BigBedAdapter: { slot: 'bigBedLocation', ext: /\.(bb|bigbed)$/ },
|
|
19
35
|
},
|
|
20
36
|
},
|
|
21
37
|
LinearVariantDisplay: {
|
|
22
|
-
call: 'jbrowse_features',
|
|
23
|
-
adapters: {
|
|
24
|
-
VcfTabixAdapter: { slot: 'vcfGzLocation', ext: /\.vcf\.gz$/ },
|
|
25
|
-
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
LinearMultiSampleVariantDisplay: {
|
|
29
38
|
call: 'jbrowse_variants',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
33
|
-
},
|
|
39
|
+
recordsOnlyCall: 'jbrowse_features',
|
|
40
|
+
adapters: VCF_ADAPTERS,
|
|
34
41
|
},
|
|
35
42
|
LinearWiggleDisplay: {
|
|
36
43
|
call: 'jbrowse_wiggle',
|
|
@@ -42,14 +49,17 @@ export const READERS = {
|
|
|
42
49
|
LinearAlignmentsDisplay: {
|
|
43
50
|
call: 'jbrowse_alignments',
|
|
44
51
|
adapters: {
|
|
45
|
-
BamAdapter: { slot: 'bamLocation', ext: /(?<!\.cram)
|
|
46
|
-
CramAdapter: { slot: 'cramLocation', ext: /\.cram
|
|
52
|
+
BamAdapter: { slot: 'bamLocation', ext: /(?<!\.cram)$/, index: '.bai' },
|
|
53
|
+
CramAdapter: { slot: 'cramLocation', ext: /\.cram$/, index: '.crai' },
|
|
47
54
|
},
|
|
48
55
|
},
|
|
49
56
|
};
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The ggjbrowse release the script is tested against: its floor, and the tag
|
|
59
|
+
* its install line pins. The R export workflow installs this tag on a push.
|
|
60
|
+
*/
|
|
61
|
+
export const GGJBROWSE_VERSION = '0.11.0';
|
|
62
|
+
const INSTALL = `remotes::install_github("GMOD/ggjbrowse@v${GGJBROWSE_VERSION}")`;
|
|
53
63
|
function isSet(value) {
|
|
54
64
|
return !(value === undefined ||
|
|
55
65
|
value === null ||
|
|
@@ -189,20 +199,10 @@ function fileArgument(adapters, adapter) {
|
|
|
189
199
|
skipped: 'it reads a file opened in the browser, which R cannot reach',
|
|
190
200
|
};
|
|
191
201
|
}
|
|
192
|
-
const file = known.slot
|
|
193
|
-
? (locationPath(adapter[known.slot]) ??
|
|
194
|
-
(typeof adapter.uri === 'string' ? adapter.uri : undefined))
|
|
195
|
-
: undefined;
|
|
202
|
+
const file = known.slot ? locationPath(adapter[known.slot]) : undefined;
|
|
196
203
|
const index = adapter.index;
|
|
197
204
|
const indexPath = locationPath(index?.location) ?? locationPath(adapter.craiLocation);
|
|
198
|
-
const
|
|
199
|
-
const plainSlots = new Set([
|
|
200
|
-
'type',
|
|
201
|
-
'uri',
|
|
202
|
-
'index',
|
|
203
|
-
'craiLocation',
|
|
204
|
-
known.slot,
|
|
205
|
-
]);
|
|
205
|
+
const plainSlots = new Set(['type', 'index', 'craiLocation', known.slot]);
|
|
206
206
|
const plain = file !== undefined &&
|
|
207
207
|
known.ext?.test(file.replace(/\?.*$/, '').toLowerCase()) &&
|
|
208
208
|
Object.keys(located).every(k => plainSlots.has(k)) &&
|
|
@@ -211,7 +211,7 @@ function fileArgument(adapters, adapter) {
|
|
|
211
211
|
return plain
|
|
212
212
|
? {
|
|
213
213
|
file: rStr(file),
|
|
214
|
-
index: indexPath && indexPath !== `${file}${
|
|
214
|
+
index: indexPath && indexPath !== `${file}${known.index}`
|
|
215
215
|
? indexPath
|
|
216
216
|
: undefined,
|
|
217
217
|
uris,
|
|
@@ -345,6 +345,44 @@ function stackArgument(title, name) {
|
|
|
345
345
|
: `${isIdentifier(title) ? title : rStr(title)} = ${name}`;
|
|
346
346
|
}
|
|
347
347
|
/** What the figure cannot show of a track whose settings ggjbrowse reads. */
|
|
348
|
+
// The variant display's record settings, under `records`, as the feature
|
|
349
|
+
// track's top-level spelling `jbrowse_features` reads; the genotype rows'
|
|
350
|
+
// settings stay behind.
|
|
351
|
+
const RECORD_SETTINGS = [
|
|
352
|
+
'displayMode',
|
|
353
|
+
'mark',
|
|
354
|
+
'labels',
|
|
355
|
+
'facet',
|
|
356
|
+
'maxFeatureScreenDensity',
|
|
357
|
+
];
|
|
358
|
+
const SHARED_SETTINGS = [
|
|
359
|
+
'filter',
|
|
360
|
+
'encoding',
|
|
361
|
+
'legend',
|
|
362
|
+
'heightMode',
|
|
363
|
+
'growMaxHeight',
|
|
364
|
+
];
|
|
365
|
+
function isRecord(x) {
|
|
366
|
+
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
367
|
+
}
|
|
368
|
+
function setEntries(source, keys) {
|
|
369
|
+
return keys.flatMap(k => {
|
|
370
|
+
const entry = [k, source[k]];
|
|
371
|
+
return entry[1] === undefined ? [] : [entry];
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function recordsAsFeatureDisplay(display) {
|
|
375
|
+
const records = isRecord(display.records) ? display.records : {};
|
|
376
|
+
const tooltip = {
|
|
377
|
+
...(isRecord(display.tooltip) ? display.tooltip : {}),
|
|
378
|
+
...(isRecord(records.tooltip) ? records.tooltip : {}),
|
|
379
|
+
};
|
|
380
|
+
return Object.fromEntries([
|
|
381
|
+
...setEntries(display, SHARED_SETTINGS),
|
|
382
|
+
...setEntries(records, RECORD_SETTINGS),
|
|
383
|
+
...setEntries({ tooltip }, Object.keys(tooltip).length > 0 ? ['tooltip'] : []),
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
348
386
|
function settingNotes(track) {
|
|
349
387
|
const { color } = track.display.encoding ?? {};
|
|
350
388
|
return [
|
|
@@ -371,46 +409,50 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
|
|
|
371
409
|
const assemblyValue = assembly && 'value' in assembly ? assembly.value : undefined;
|
|
372
410
|
const loci = regions.map(r => locusOf(r.refName, r.start, r.end));
|
|
373
411
|
const totalBp = regions.reduce((sum, r) => sum + r.end - r.start, 0);
|
|
374
|
-
const viewPx =
|
|
375
|
-
const widthReaders = [];
|
|
412
|
+
const viewPx = Math.round(totalBp / bpPerPx);
|
|
376
413
|
const names = trackNames(tracks);
|
|
377
414
|
const reads = [];
|
|
378
415
|
const drawn = [];
|
|
379
416
|
const bands = [];
|
|
380
417
|
let alignments;
|
|
381
418
|
tracks.forEach((track, i) => {
|
|
382
|
-
const
|
|
383
|
-
if (!
|
|
419
|
+
const known = READERS[track.displayType];
|
|
420
|
+
if (!known) {
|
|
384
421
|
notes.push(`${track.trackName}: ${track.displayType} has no R reader in ggjbrowse yet`);
|
|
385
422
|
return;
|
|
386
423
|
}
|
|
424
|
+
const recordsCall = track.recordsOnly ? known.recordsOnlyCall : undefined;
|
|
425
|
+
const asRecords = recordsCall !== undefined;
|
|
426
|
+
const reader = {
|
|
427
|
+
adapters: known.adapters,
|
|
428
|
+
call: recordsCall ?? known.call,
|
|
429
|
+
};
|
|
387
430
|
const source = fileArgument(reader.adapters, track.adapter);
|
|
388
431
|
if ('skipped' in source) {
|
|
389
432
|
notes.push(`${track.trackName}: ${source.skipped}`);
|
|
390
433
|
return;
|
|
391
434
|
}
|
|
392
435
|
const { heightMode, height } = track;
|
|
436
|
+
const written = asRecords
|
|
437
|
+
? recordsAsFeatureDisplay(track.display)
|
|
438
|
+
: track.display;
|
|
393
439
|
const display = reader.call === 'jbrowse_features'
|
|
394
440
|
? {
|
|
395
|
-
...
|
|
441
|
+
...written,
|
|
396
442
|
...(heightMode === undefined ? {} : { heightMode }),
|
|
397
443
|
...(height === undefined ? {} : { height }),
|
|
398
444
|
}
|
|
399
|
-
:
|
|
400
|
-
const width = viewPx !== undefined;
|
|
445
|
+
: written;
|
|
401
446
|
const geneTrack = track.displayType === 'LinearBasicDisplay';
|
|
402
447
|
const args = [
|
|
403
448
|
...(Object.keys(display).length ? [`display = ${rValue(display)}`] : []),
|
|
404
|
-
|
|
449
|
+
'width = width',
|
|
405
450
|
...(assemblyValue ? ['assembly = assembly'] : []),
|
|
406
451
|
...(geneTrack && colorByFrame ? ['color_by_cds = TRUE'] : []),
|
|
407
452
|
...(geneTrack && !showAminoAcids ? ['amino_acids = FALSE'] : []),
|
|
408
453
|
...(source.index ? [`index = ${rStr(source.index)}`] : []),
|
|
409
454
|
];
|
|
410
455
|
const name = names[i];
|
|
411
|
-
if (width) {
|
|
412
|
-
widthReaders.push(name);
|
|
413
|
-
}
|
|
414
456
|
reads.push([
|
|
415
457
|
`# ${track.trackName}`,
|
|
416
458
|
`${name} <- ${reader.call}(${source.file},`,
|
|
@@ -466,14 +508,10 @@ export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, b
|
|
|
466
508
|
`assembly <- ${assemblyValue}`,
|
|
467
509
|
]
|
|
468
510
|
: []),
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
'# fewer labels at the same size.',
|
|
474
|
-
`width <- ${viewPx}`,
|
|
475
|
-
]
|
|
476
|
-
: []),
|
|
511
|
+
"# The view's pixels across its regions. A smaller number lays the tracks",
|
|
512
|
+
'# out as a narrower browser window would, for a narrower figure with',
|
|
513
|
+
'# fewer labels at the same size.',
|
|
514
|
+
`width <- ${viewPx}`,
|
|
477
515
|
'',
|
|
478
516
|
reads.join('\n\n'),
|
|
479
517
|
'',
|
|
@@ -42,9 +42,11 @@ export function showRegionsWithUndo({ view, regions, message, alsoUndo, }) {
|
|
|
42
42
|
getNotificationSink(view).notify(message, 'info', {
|
|
43
43
|
name: 'Undo',
|
|
44
44
|
onClick: () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
transaction(() => {
|
|
46
|
+
view.setDisplayedRegions(previous.displayedRegions);
|
|
47
|
+
view.setWindow(previous.windowWidthBp, previous.windowStartBp);
|
|
48
|
+
alsoUndo?.();
|
|
49
|
+
});
|
|
48
50
|
},
|
|
49
51
|
});
|
|
50
52
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { BandGap } from '../components/SVGHighlightBand.tsx';
|
|
1
2
|
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
|
-
export default function SVGHighlights({ model, height, }: {
|
|
3
|
+
export default function SVGHighlights({ model, height, gaps, }: {
|
|
3
4
|
model: LinearGenomeViewModel;
|
|
4
5
|
height: number;
|
|
6
|
+
gaps: BandGap[];
|
|
5
7
|
}): (import("react").JSX.Element | null)[] | null;
|
|
@@ -14,13 +14,13 @@ import { getHighlightColor, highlightKey } from '../components/util.js';
|
|
|
14
14
|
// bands across track bodies that were still drawn where the snapshot left them.
|
|
15
15
|
// The file export renders the whole document in one synchronous pass, so it
|
|
16
16
|
// never needed the subscription either.
|
|
17
|
-
export default function SVGHighlights({ model, height, }) {
|
|
17
|
+
export default function SVGHighlights({ model, height, gaps, }) {
|
|
18
18
|
const theme = useStyleTheme();
|
|
19
19
|
const session = getSession(model);
|
|
20
20
|
return session.highlightsVisible
|
|
21
21
|
? model.highlights.map((h, idx) => {
|
|
22
22
|
const coords = model.getHighlightCoords(h);
|
|
23
|
-
return coords ? (_jsx(SVGHighlightBand, { coords: coords, height: height, color: getHighlightColor(h, theme).toRgbString(), label: session.highlightLabelsVisible ? h.label : undefined, labelColor: theme.palette.text.primary }, highlightKey(h, idx))) : null;
|
|
23
|
+
return coords ? (_jsx(SVGHighlightBand, { coords: coords, height: height, gaps: gaps, color: getHighlightColor(h, theme).toRgbString(), label: session.highlightLabelsVisible ? h.label : undefined, labelColor: theme.palette.text.primary }, highlightKey(h, idx))) : null;
|
|
24
24
|
})
|
|
25
25
|
: null;
|
|
26
26
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { BandGap } from '../components/SVGHighlightBand.tsx';
|
|
1
2
|
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
|
-
export default function SVGHighlightsOverlay({ model, tracksHeight, }: {
|
|
3
|
+
export default function SVGHighlightsOverlay({ model, tracksHeight, gaps, }: {
|
|
3
4
|
model: LinearGenomeViewModel;
|
|
4
5
|
tracksHeight: number;
|
|
6
|
+
gaps: BandGap[];
|
|
5
7
|
}): import("react").JSX.Element;
|
|
@@ -7,7 +7,7 @@ import SVGHighlights from './SVGHighlights.js';
|
|
|
7
7
|
// The highlight layer over one view's tracks area: the view's highlights plus
|
|
8
8
|
// whatever plugins contribute, clipped to the view width. Callers translate
|
|
9
9
|
// this to the top-left of the tracks area.
|
|
10
|
-
export default function SVGHighlightsOverlay({ model, tracksHeight, }) {
|
|
10
|
+
export default function SVGHighlightsOverlay({ model, tracksHeight, gaps, }) {
|
|
11
11
|
const { pluginManager } = getEnv(model);
|
|
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 } })] }));
|
|
12
|
+
return (_jsxs(SvgClipRect, { id: `highlight-clip-${svgNodeId(model)}`, width: model.width, height: tracksHeight, children: [_jsx(SVGHighlights, { model: model, height: tracksHeight, gaps: gaps }), _jsx(PluggableElements, { pluginManager: pluginManager, name: "LinearGenomeView-HighlightSVGComponent", props: { model, height: tracksHeight } })] }));
|
|
13
13
|
}
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import SVGGridlines from './SVGGridlines.js';
|
|
3
3
|
import SVGHighlightsOverlay from './SVGHighlightsOverlay.js';
|
|
4
4
|
import SVGTracks from './SVGTracks.js';
|
|
5
|
+
import { labelOffset, trackBoxOffsets } from './util.js';
|
|
5
6
|
// One LGV's worth of exported SVG: a caller-supplied header on top, then
|
|
6
7
|
// optional gridlines, the track bodies, and the highlight layer over them.
|
|
7
8
|
// Shared by all three exports that stack an LGV — the standalone one, and the
|
|
@@ -9,5 +10,18 @@ import SVGTracks from './SVGTracks.js';
|
|
|
9
10
|
// drift. It did drift while the standalone export kept its own copy of these
|
|
10
11
|
// four groups.
|
|
11
12
|
export default function SVGView({ view, displayResults, header, fontSize, textHeight, trackLabels, trackLabelOffset, contentTop, tracksHeight, showGridlines, leftBuffer, }) {
|
|
12
|
-
|
|
13
|
+
const tracks = displayResults.map(r => r.track);
|
|
14
|
+
const offsets = trackBoxOffsets(tracks, trackLabels, textHeight);
|
|
15
|
+
const nonGenomicBands = tracks.flatMap((track, i) => {
|
|
16
|
+
const display = track.displays[0];
|
|
17
|
+
return display.drawsGenomicCoordinates === false
|
|
18
|
+
? [
|
|
19
|
+
{
|
|
20
|
+
top: offsets[i] + labelOffset(track, trackLabels, textHeight),
|
|
21
|
+
height: display.height,
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
: [];
|
|
25
|
+
});
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(${trackLabelOffset})`, children: header }), showGridlines ? (_jsx("g", { transform: `translate(${trackLabelOffset} ${contentTop})`, children: _jsx(SVGGridlines, { model: view, height: tracksHeight }) })) : null, _jsx("g", { transform: `translate(0 ${contentTop})`, children: _jsx(SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: view, displayResults: displayResults, trackLabelOffset: trackLabelOffset, leftBuffer: leftBuffer }) }), _jsx("g", { transform: `translate(${trackLabelOffset} ${contentTop})`, children: _jsx(SVGHighlightsOverlay, { model: view, tracksHeight: tracksHeight, gaps: nonGenomicBands }) })] }));
|
|
13
27
|
}
|
|
@@ -39,3 +39,9 @@ export interface InitState {
|
|
|
39
39
|
highlight?: (string | HighlightInput)[];
|
|
40
40
|
}
|
|
41
41
|
export type LinearGenomeViewLaunchProps = Partial<Omit<SnapshotIn<LinearGenomeViewStateModel>, keyof InitState | 'id' | 'type' | 'launch'>>;
|
|
42
|
+
/**
|
|
43
|
+
* One LinearGenomeView panel inside another view (a breakpoint split view's
|
|
44
|
+
* stack, a synteny view's rows): the LGV's launch keys and props, less `nav`
|
|
45
|
+
* and `tracklist`, the header and the track selector a panel does not show.
|
|
46
|
+
*/
|
|
47
|
+
export type LinearGenomeViewPanelInit = Omit<InitState, 'nav' | 'tracklist'> & LinearGenomeViewLaunchProps;
|
|
@@ -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';
|
|
@@ -558,6 +558,10 @@ export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, inclu
|
|
|
558
558
|
* navigating to a feature
|
|
559
559
|
*/
|
|
560
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
|
+
}
|
|
561
565
|
return Promise.all(regions.map(async (region) => {
|
|
562
566
|
const asmName = region.assemblyName || assemblyName;
|
|
563
567
|
if (!asmName) {
|
|
@@ -609,14 +613,12 @@ export async function generateLocations({ regions, assemblyManager, assemblyName
|
|
|
609
613
|
* Used by navToLocString
|
|
610
614
|
*/
|
|
611
615
|
export function parseLocStrings(input, assemblyName, isValidRefName) {
|
|
612
|
-
const inputs = input
|
|
613
|
-
|
|
614
|
-
.map(f => f.trim())
|
|
615
|
-
.filter(f => !!f);
|
|
616
|
+
const inputs = input.split(/\s+/).filter(Boolean);
|
|
617
|
+
const isRef = (ref, asm) => isValidRefName(ref, asm ?? assemblyName);
|
|
616
618
|
// first try interpreting as a whitespace-separated sequence of
|
|
617
619
|
// multiple locstrings
|
|
618
620
|
try {
|
|
619
|
-
return inputs.map(loc => parseLocString(loc,
|
|
621
|
+
return inputs.map(loc => parseLocString(loc, isRef));
|
|
620
622
|
}
|
|
621
623
|
catch (e) {
|
|
622
624
|
// if this fails, try interpreting as a whitespace-separated refname,
|
|
@@ -629,9 +631,7 @@ export function parseLocStrings(input, assemblyName, isValidRefName) {
|
|
|
629
631
|
inputs.length === 3 &&
|
|
630
632
|
parseBpString(start) !== undefined &&
|
|
631
633
|
parseBpString(end) !== undefined) {
|
|
632
|
-
return [
|
|
633
|
-
parseLocString(`${refName}:${start}..${end}`, ref => isValidRefName(ref, assemblyName)),
|
|
634
|
-
];
|
|
634
|
+
return [parseLocString(`${refName}:${start}..${end}`, isRef)];
|
|
635
635
|
}
|
|
636
636
|
throw e;
|
|
637
637
|
}
|
|
@@ -57,18 +57,20 @@ export function useRangeSelect(ref, model) {
|
|
|
57
57
|
onCancel: handleClose,
|
|
58
58
|
});
|
|
59
59
|
function mouseDown(event) {
|
|
60
|
-
event.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
if (event.button === 0) {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
// clear any leftover menu/selection so a fresh drag isn't blocked by a
|
|
64
|
+
// stale anchorPosition keeping mouseDragging false
|
|
65
|
+
setAnchorPosition(undefined);
|
|
66
|
+
// the guide is what the pointer was showing up to now, and mouseMove
|
|
67
|
+
// won't touch it during the drag — leaving it set keeps the selection
|
|
68
|
+
// span from ever rendering, since the guide wins that either/or
|
|
69
|
+
setGuideX(undefined);
|
|
70
|
+
const relativeX = getRelativeX(event, ref.current);
|
|
71
|
+
setStartX(relativeX);
|
|
72
|
+
setCurrentX(relativeX);
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
75
|
function mouseMove(event) {
|
|
74
76
|
// Don't update guideX while a menu is open — the guide is what the click
|
|
@@ -74,7 +74,10 @@ export declare function launchTrackInit(view: ModifierView & IAnyStateTreeNode,
|
|
|
74
74
|
* after every other modifier, so a member write keeps the rest of the setting
|
|
75
75
|
* whatever order the command line gives them in. A display replaces a color
|
|
76
76
|
* or facet object whole, which is why these cannot ride in on the launch
|
|
77
|
-
* snapshot.
|
|
77
|
+
* snapshot. A keyed object (`rows`, ADR-257) seeds only its field and the
|
|
78
|
+
* objects written whole that the writes reach, such as `rows.color`: seeded
|
|
79
|
+
* whole it would restate the arrangement a field write must drop, and
|
|
80
|
+
* unseeded a spec holding the patch would lose the field and the channel.
|
|
78
81
|
*/
|
|
79
82
|
export declare function writeMembers(view: Pick<ModifierView, 'tracks' | 'showTrack'>, trackId: string, members: string[]): Record<string, unknown>;
|
|
80
83
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mergeConfSettings } from '@jbrowse/core/configuration';
|
|
1
|
+
import { keyedWritesAt, mergeConfSettings, writesWholeAt, } from '@jbrowse/core/configuration';
|
|
2
2
|
import { getEnv, getSession } from '@jbrowse/core/util';
|
|
3
3
|
import { basePaintedAt } from '@jbrowse/core/util/Base1DUtils';
|
|
4
4
|
import { predicatesOf, testsSomething, withFields, } from '@jbrowse/core/util/fieldPredicate';
|
|
@@ -181,12 +181,24 @@ export async function launchTrackInit(view, t) {
|
|
|
181
181
|
session.notify(notes.join('; '), 'warning');
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
// the named members of a keyed object as configured, undefined when it
|
|
185
|
+
// states none of them
|
|
186
|
+
function keyedSeed(member, names) {
|
|
187
|
+
const configured = isStateTreeNode(member)
|
|
188
|
+
? getSnapshot(member)
|
|
189
|
+
: {};
|
|
190
|
+
const seed = Object.fromEntries(names.flatMap(name => configured[name] === undefined ? [] : [[name, configured[name]]]));
|
|
191
|
+
return Object.keys(seed).length > 0 ? seed : undefined;
|
|
192
|
+
}
|
|
184
193
|
/**
|
|
185
194
|
* Write a track's `memberWrites` onto what its display in `view` already has,
|
|
186
195
|
* after every other modifier, so a member write keeps the rest of the setting
|
|
187
196
|
* whatever order the command line gives them in. A display replaces a color
|
|
188
197
|
* or facet object whole, which is why these cannot ride in on the launch
|
|
189
|
-
* snapshot.
|
|
198
|
+
* snapshot. A keyed object (`rows`, ADR-257) seeds only its field and the
|
|
199
|
+
* objects written whole that the writes reach, such as `rows.color`: seeded
|
|
200
|
+
* whole it would restate the arrangement a field write must drop, and
|
|
201
|
+
* unseeded a spec holding the patch would lose the field and the channel.
|
|
190
202
|
*/
|
|
191
203
|
export function writeMembers(view, trackId, members) {
|
|
192
204
|
if (members.length === 0) {
|
|
@@ -198,14 +210,21 @@ export function writeMembers(view, trackId, members) {
|
|
|
198
210
|
}
|
|
199
211
|
const { configuration } = track.activeDisplay;
|
|
200
212
|
const configured = getSnapshot(configuration);
|
|
201
|
-
const
|
|
213
|
+
const type = getType(configuration);
|
|
214
|
+
const patch = memberPatch(members, (root, reached) => {
|
|
202
215
|
// a list at a default the snapshot strips, Manhattan's marks, still seeds
|
|
203
216
|
// the write, so `marks.0.mark=bar` edits the mark drawn
|
|
204
217
|
const member = configuration[root];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
const keyed = keyedWritesAt(type, [root]);
|
|
219
|
+
return keyed
|
|
220
|
+
? keyedSeed(member, [
|
|
221
|
+
keyed.keyedBy,
|
|
222
|
+
...reached.filter(m => writesWholeAt(type, [root, m])),
|
|
223
|
+
])
|
|
224
|
+
: (configured[root] ??
|
|
225
|
+
(isStateTreeNode(member) && isArrayType(getType(member))
|
|
226
|
+
? getSnapshot(member)
|
|
227
|
+
: undefined));
|
|
209
228
|
});
|
|
210
229
|
view.showTrack(trackId, {}, patch);
|
|
211
230
|
return patch;
|
|
@@ -537,6 +537,7 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
|
|
|
537
537
|
notifyError(message: string): void;
|
|
538
538
|
queueDialog(cb: (handleClose: () => void) => import("@jbrowse/display-test-utils").QueuedDialog): void;
|
|
539
539
|
setSelection(thing: unknown): void;
|
|
540
|
+
clearSelection(): void;
|
|
540
541
|
addWidget(type: string, id: string, initialState?: Record<string, unknown>): {
|
|
541
542
|
type: string;
|
|
542
543
|
id: string;
|
|
@@ -578,6 +579,7 @@ export declare function createPerRegionTestEnvironment({ measuresBytes, gate, es
|
|
|
578
579
|
notifyError(message: string): void;
|
|
579
580
|
queueDialog(cb: (handleClose: () => void) => import("@jbrowse/display-test-utils").QueuedDialog): void;
|
|
580
581
|
setSelection(thing: unknown): void;
|
|
582
|
+
clearSelection(): void;
|
|
581
583
|
addWidget(type: string, id: string, initialState?: Record<string, unknown>): {
|
|
582
584
|
type: string;
|
|
583
585
|
id: string;
|
package/esm/index.d.ts
CHANGED
|
@@ -28,13 +28,13 @@ export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/Hea
|
|
|
28
28
|
export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
|
|
29
29
|
export type { SearchBoxPrefs } from './LinearGenomeView/components/useSearchBoxPrefs.ts';
|
|
30
30
|
export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
|
|
31
|
-
export { applyInitHighlights } from './LinearGenomeView/afterAttach.ts';
|
|
31
|
+
export { applyInitHighlights, navToInitLoc, showInitTracks, } from './LinearGenomeView/afterAttach.ts';
|
|
32
32
|
export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.ts';
|
|
33
33
|
export type { ModifierView } from './TrackModifiers/index.ts';
|
|
34
34
|
export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.ts';
|
|
35
35
|
export { fetchResults, SearchResultsNotFoundError } from './searchUtils.ts';
|
|
36
36
|
export type { LaunchLinearGenomeViewArgs } from './LaunchLinearGenomeView/index.ts';
|
|
37
|
-
export type { BpOffset, ExportSvgOptions, HighlightInput, HighlightType, InitState, LinearGenomeViewLaunchProps, NavLocation, TrackLabelMode, ViewTrackLabelMode, VolatileGuide, } from './LinearGenomeView/types.ts';
|
|
37
|
+
export type { BpOffset, ExportSvgOptions, HighlightInput, HighlightType, InitState, LinearGenomeViewLaunchProps, LinearGenomeViewPanelInit, NavLocation, TrackLabelMode, ViewTrackLabelMode, VolatileGuide, } from './LinearGenomeView/types.ts';
|
|
38
38
|
export type { ScalebarRefNameLabel } from './LinearGenomeView/util.ts';
|
|
39
39
|
export type { HighlightRegion, SearchHits, SearchPickerRow, SearchPickerState, } from './LinearGenomeView/model.ts';
|
|
40
40
|
export type { RegionsOrientation } from './LinearGenomeView/util.ts';
|
package/esm/index.js
CHANGED
|
@@ -68,7 +68,7 @@ export { default as SearchBox } from './LinearGenomeView/components/SearchBox.js
|
|
|
68
68
|
export { default as HeaderSearchBoxRow } from './LinearGenomeView/components/HeaderSearchBoxRow.js';
|
|
69
69
|
export { searchBoxMenuItems, useSearchBoxPrefs, } from './LinearGenomeView/components/useSearchBoxPrefs.js';
|
|
70
70
|
export { normalizeTrackInit } from '@jbrowse/core/util/tracks';
|
|
71
|
-
export { applyInitHighlights } from './LinearGenomeView/afterAttach.js';
|
|
71
|
+
export { applyInitHighlights, navToInitLoc, showInitTracks, } from './LinearGenomeView/afterAttach.js';
|
|
72
72
|
export { launchTrackInit, launchTrackWithModifiers, openedDisplayType, writeFilter, writeMembers, writeReadFilter, } from './TrackModifiers/index.js';
|
|
73
73
|
export { MultiLevelRubberband, multiLevelRowClickMenuItems, multiLevelRowMenuItems, rowLabels, } from './MultiLevelRubberband/index.js';
|
|
74
74
|
export { fetchResults, SearchResultsNotFoundError } from './searchUtils.js';
|
package/esm/searchUtils.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare function navigateToSelectedOption({ option, model, assemblyName,
|
|
|
23
23
|
option: BaseResult;
|
|
24
24
|
model: LinearGenomeViewModel;
|
|
25
25
|
assemblyName: string;
|
|
26
|
-
}): Promise<
|
|
26
|
+
}): Promise<boolean>;
|
|
27
27
|
export declare function isOpenInView(result: BaseResult, model: LinearGenomeViewModel): boolean;
|
|
28
28
|
export declare function distinctDestinations({ results, model, session, assembly, }: {
|
|
29
29
|
results: BaseResult[];
|
|
@@ -58,7 +58,6 @@ export declare function handleSelectedRegion({ input, model, assemblyName, grow,
|
|
|
58
58
|
grow?: number;
|
|
59
59
|
showHitTrack?: boolean;
|
|
60
60
|
}): Promise<boolean>;
|
|
61
|
-
export declare function checkRef(str: string, isRef: (name: string) => boolean): boolean;
|
|
62
61
|
interface SearchArgs {
|
|
63
62
|
queryString: string;
|
|
64
63
|
assemblyName: string;
|
|
@@ -84,5 +83,4 @@ export declare function nameNotFound(input: string, assemblyName: string, { inde
|
|
|
84
83
|
indexCount: number;
|
|
85
84
|
failures: unknown[];
|
|
86
85
|
}): Error;
|
|
87
|
-
export declare function splitLast(str: string, split: string): [string, string];
|
|
88
86
|
export {};
|