react-msaview 8.3.0 → 8.4.0
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/bundle/index.js +77 -287
- package/bundle/index.js.map +4 -4
- package/dist/blockFasta.d.ts +6 -0
- package/dist/blockFasta.js +12 -0
- package/dist/blockFasta.js.map +1 -0
- package/dist/colorSchemes.d.ts +7 -0
- package/dist/colorSchemes.js +15 -0
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/MSAView.js +15 -9
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +28 -7
- package/dist/components/MSAViewer.js +61 -22
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/MetadataTable.d.ts +5 -0
- package/dist/components/MetadataTable.js +29 -0
- package/dist/components/MetadataTable.js.map +1 -0
- package/dist/components/Track.js +11 -2
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/MetadataDialog.js +5 -6
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
- package/dist/components/header/ColorSchemeMenu.js +23 -5
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/{RowSearch.d.ts → GoToBox.d.ts} +2 -2
- package/dist/components/header/GoToBox.js +21 -0
- package/dist/components/header/GoToBox.js.map +1 -0
- package/dist/components/header/Header.js +4 -2
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/SelectionMenu.d.ts +12 -0
- package/dist/components/header/SelectionMenu.js +72 -0
- package/dist/components/header/SelectionMenu.js.map +1 -0
- package/dist/components/header/goTo.d.ts +20 -0
- package/dist/components/header/goTo.js +69 -0
- package/dist/components/header/goTo.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +13 -2
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +5 -1
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.js +6 -0
- package/dist/components/msa/msaKeys.js.map +1 -1
- package/dist/components/msa/msaRaster.js +1 -0
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +14 -3
- package/dist/components/msa/renderHighlights.js +29 -4
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/tileColor.js +9 -8
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +1 -1
- package/dist/components/msa/useMsaBlockMouse.js +18 -11
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +3 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/selectionDrag.d.ts +17 -0
- package/dist/components/selectionDrag.js +30 -0
- package/dist/components/selectionDrag.js.map +1 -0
- package/dist/components/tracks/useTrackHover.d.ts +3 -1
- package/dist/components/tracks/useTrackHover.js +26 -6
- package/dist/components/tracks/useTrackHover.js.map +1 -1
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +9 -8
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +6 -6
- package/dist/constants.js.map +1 -1
- package/dist/element.d.ts +7 -3
- package/dist/element.js +32 -4
- package/dist/element.js.map +1 -1
- package/dist/featuresLayer.d.ts +8 -0
- package/dist/featuresLayer.js +42 -0
- package/dist/featuresLayer.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/model/DataModel.d.ts +1 -1
- package/dist/model/DataModel.js +3 -3
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +14 -1
- package/dist/model/msaModel.js +17 -1
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model.d.ts +122 -11
- package/dist/model.js +260 -41
- package/dist/model.js.map +1 -1
- package/dist/svgTestUtil.d.ts +68 -8
- package/dist/types.d.ts +50 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/blockFasta.ts +18 -0
- package/src/colorSchemes.ts +20 -0
- package/src/components/MSAView.tsx +16 -9
- package/src/components/MSAViewer.tsx +95 -26
- package/src/components/MetadataTable.tsx +55 -0
- package/src/components/Track.tsx +14 -1
- package/src/components/dialogs/MetadataDialog.tsx +9 -8
- package/src/components/header/ColorSchemeMenu.tsx +31 -12
- package/src/components/header/GoToBox.tsx +50 -0
- package/src/components/header/Header.tsx +6 -2
- package/src/components/header/SelectionMenu.tsx +117 -0
- package/src/components/header/goTo.ts +75 -0
- package/src/components/msa/MSACanvas.tsx +12 -2
- package/src/components/msa/MSACanvasBlock.tsx +5 -1
- package/src/components/msa/msaKeys.ts +6 -0
- package/src/components/msa/msaRaster.ts +1 -0
- package/src/components/msa/renderHighlights.ts +46 -4
- package/src/components/msa/tileColor.ts +9 -8
- package/src/components/msa/useMsaBlockMouse.ts +24 -27
- package/src/components/overlayColors.ts +4 -0
- package/src/components/selectionDrag.ts +42 -0
- package/src/components/tracks/useTrackHover.ts +28 -6
- package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +14 -16
- package/src/constants.ts +6 -6
- package/src/element.ts +45 -5
- package/src/featuresLayer.ts +48 -0
- package/src/index.ts +5 -0
- package/src/model/DataModel.ts +3 -3
- package/src/model/msaModel.ts +19 -1
- package/src/model.ts +309 -51
- package/src/types.ts +58 -0
- package/src/version.ts +1 -1
- package/dist/components/header/RowSearch.js +0 -15
- package/dist/components/header/RowSearch.js.map +0 -1
- package/dist/components/header/revealRow.d.ts +0 -3
- package/dist/components/header/revealRow.js +0 -15
- package/dist/components/header/revealRow.js.map +0 -1
- package/src/components/header/RowSearch.tsx +0 -34
- package/src/components/header/revealRow.ts +0 -17
package/dist/model.js
CHANGED
|
@@ -4,9 +4,10 @@ import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst';
|
|
|
4
4
|
import { addDisposer, applySnapshot, cast, getSnapshot, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
5
5
|
import { autorun, compareStructural, computed, reaction, transaction, } from 'mobx';
|
|
6
6
|
import { generateNodeIds, gffToAnnotations, interProScanToAnnotations, parseEmfTree, parseGFF, parseMSA, parseNewick, } from 'msa-parsers';
|
|
7
|
+
import { blockFasta } from './blockFasta.js';
|
|
7
8
|
import { calculateBlocks } from './calculateBlocks.js';
|
|
8
9
|
import { clustalXColumnColors } from './clustalX.js';
|
|
9
|
-
import colorSchemes from './colorSchemes.js';
|
|
10
|
+
import colorSchemes, { letterColorTable } from './colorSchemes.js';
|
|
10
11
|
import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.js';
|
|
11
12
|
import { columnStats } from './columnStats.js';
|
|
12
13
|
import { packDomainLanes } from './components/msa/packDomainLanes.js';
|
|
@@ -14,10 +15,11 @@ import { visibleColRange } from './components/msa/visibleColRange.js';
|
|
|
14
15
|
import TrackBlocks from './components/tracks/TrackBlocks.js';
|
|
15
16
|
import { cladeGutterWidth } from './components/tree/cladeBrackets.js';
|
|
16
17
|
import { scaleBarLength } from './components/tree/scaleBar.js';
|
|
17
|
-
import { cladeHighlightAlpha, cladeHighlightColor, defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultFeaturePanelWidth, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, defaultTreeWidth, labelReferenceFontSize, maxCellSize, maxInlineSnapshotBytes, maxNeighborJoiningRows, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, rowPanelHeaderHeight, rowTintAlpha, scrollZoomAxes, segmentFeatureTypes, segmentShades, treeScaleBarHeight, } from './constants.js';
|
|
18
|
+
import { cladeHighlightAlpha, cladeHighlightColor, defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultFeaturePanelWidth, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, defaultTreeWidth, labelReferenceFontSize, maxCellSize, maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes, maxNeighborJoiningRows, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, rowPanelHeaderHeight, rowTintAlpha, scrollZoomAxes, segmentFeatureTypes, segmentShades, treeScaleBarHeight, } from './constants.js';
|
|
18
19
|
import { createPaletteMap } from './createPaletteMap.js';
|
|
19
20
|
import { exportFileName } from './exportFileName.js';
|
|
20
21
|
import { featureField, featureName } from './featureFields.js';
|
|
22
|
+
import { featuresToAnnotations } from './featuresLayer.js';
|
|
21
23
|
import { fetchTextWithProgress, isAbortError } from './fetchUtils.js';
|
|
22
24
|
import { flatToTree } from './flatToTree.js';
|
|
23
25
|
import { calcDepthToLeaf, clusterLayout, collapse, find, forEachDescendant, hierarchy, layoutTree, leafIndex, leaves, maxLength, mrca, nodeCoveringRows, setBrLength, sort, sum as hierarchySum, } from './hierarchy.js';
|
|
@@ -82,6 +84,23 @@ function rowPanelWidth(panel, view) {
|
|
|
82
84
|
return (panel.width ??
|
|
83
85
|
(panel.kind === 'features' ? defaultFeaturePanelWidth : view.rowHeight));
|
|
84
86
|
}
|
|
87
|
+
// one row of the row table as the channels read it. A number or a boolean
|
|
88
|
+
// reads as its string, the way a GFF value does, and null or an object drops
|
|
89
|
+
function stringFields(row) {
|
|
90
|
+
if (typeof row !== 'object' || row === null) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const fields = {};
|
|
94
|
+
for (const [field, value] of Object.entries(row)) {
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
fields[field] = value;
|
|
97
|
+
}
|
|
98
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
99
|
+
fields[field] = String(value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return fields;
|
|
103
|
+
}
|
|
85
104
|
// the color each row takes from a field of the row table under a scale
|
|
86
105
|
function colorByRow(rowData, field, colorOf) {
|
|
87
106
|
const colors = new Map();
|
|
@@ -218,11 +237,17 @@ function featurePanelSpans({ panel, width, colWidth, domainBands, annotationsByR
|
|
|
218
237
|
})),
|
|
219
238
|
]));
|
|
220
239
|
}
|
|
221
|
-
// a scale over the values a field takes across
|
|
222
|
-
|
|
240
|
+
// a scale over the values a field takes across every feature loaded, so hiding
|
|
241
|
+
// a type in the filter dialog leaves the rest in their colors, and a legend of
|
|
242
|
+
// the values the features drawn carry
|
|
243
|
+
function resolveFeatureScale(field, scale, annotations, drawn) {
|
|
244
|
+
const valuesOf = (list) => list.map(a => featureField(a, field)).filter(notEmpty);
|
|
245
|
+
const resolved = resolveScale(scale, valuesOf(annotations));
|
|
246
|
+
const shown = new Set(valuesOf(drawn));
|
|
223
247
|
return {
|
|
224
248
|
field,
|
|
225
|
-
...
|
|
249
|
+
...resolved,
|
|
250
|
+
legend: resolved.legend.filter(entry => shown.has(entry.id)),
|
|
226
251
|
};
|
|
227
252
|
}
|
|
228
253
|
const columnTrackBytes = new WeakMap();
|
|
@@ -239,7 +264,7 @@ function columnTrackSize(track) {
|
|
|
239
264
|
function columnTrackSizes(tracks) {
|
|
240
265
|
return (tracks ?? []).map(columnTrackSize);
|
|
241
266
|
}
|
|
242
|
-
function smallColumnTracks(tracks) {
|
|
267
|
+
function smallColumnTracks(tracks, maxInlineSnapshotBytes) {
|
|
243
268
|
const sizes = columnTrackSizes(tracks);
|
|
244
269
|
const kept = tracks?.filter((_t, i) => sizes[i] <= maxInlineSnapshotBytes);
|
|
245
270
|
return kept?.length ? { columnTracks: kept } : {};
|
|
@@ -265,6 +290,7 @@ export const preservedOnReset = new Set([
|
|
|
265
290
|
'scrollZoomAxis',
|
|
266
291
|
'bgColor',
|
|
267
292
|
'colorSchemeName',
|
|
293
|
+
'customColorScheme',
|
|
268
294
|
'showColumnStats',
|
|
269
295
|
'drawLabels',
|
|
270
296
|
'labelsAlignRight',
|
|
@@ -474,6 +500,20 @@ function overflows(content, viewport) {
|
|
|
474
500
|
function inRanges(ranges, position) {
|
|
475
501
|
return !!ranges?.some(([start, end]) => position >= start && position <= end);
|
|
476
502
|
}
|
|
503
|
+
// sorted integers as inclusive [first, last] runs of consecutive values
|
|
504
|
+
function consecutiveRuns(sorted) {
|
|
505
|
+
const runs = [];
|
|
506
|
+
for (const n of sorted) {
|
|
507
|
+
const last = runs.at(-1);
|
|
508
|
+
if (last && n <= last[1] + 1) {
|
|
509
|
+
last[1] = Math.max(last[1], n);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
runs.push([n, n]);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return runs;
|
|
516
|
+
}
|
|
477
517
|
// the value every one of a node's children reports, or undefined where one of
|
|
478
518
|
// them has no value or they disagree
|
|
479
519
|
function sharedValue(values) {
|
|
@@ -514,8 +554,13 @@ function preorder(tree) {
|
|
|
514
554
|
* const root = RootModel.create({})
|
|
515
555
|
* root.view.setData({ msa: '>seq1\nACGT\n>seq2\nACGT' })
|
|
516
556
|
* ```
|
|
557
|
+
*
|
|
558
|
+
* `maxInlineSnapshotBytes` is the largest pasted or local document, and the
|
|
559
|
+
* largest data track, the snapshot keeps; a larger one stays in the live model
|
|
560
|
+
* and `unshareableData` names it. The standalone app, whose links carry the
|
|
561
|
+
* snapshot in the URL fragment, raises it.
|
|
517
562
|
*/
|
|
518
|
-
function stateModelFactory() {
|
|
563
|
+
function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBytes, } = {}) {
|
|
519
564
|
return types
|
|
520
565
|
.compose(DialogQueueSessionMixin(), TreeModelF(), MSAModelF(), types.model('MsaView', {
|
|
521
566
|
/**
|
|
@@ -676,7 +721,7 @@ function stateModelFactory() {
|
|
|
676
721
|
* data from the loaded tree/msa/treeMetadata, generally loaded by
|
|
677
722
|
* autorun
|
|
678
723
|
*/
|
|
679
|
-
data: types.optional(DataModelF(), {}),
|
|
724
|
+
data: types.optional(DataModelF(maxInlineSnapshotBytes), {}),
|
|
680
725
|
/**
|
|
681
726
|
* #property
|
|
682
727
|
* the user's explicit hide choices per annotation accession, keyed by
|
|
@@ -706,6 +751,21 @@ function stateModelFactory() {
|
|
|
706
751
|
* Persists in the snapshot and the URL.
|
|
707
752
|
*/
|
|
708
753
|
highlights: stripDefault(types.array(types.frozen()), []),
|
|
754
|
+
/**
|
|
755
|
+
* #property
|
|
756
|
+
* features on the rows as data, `{row, start, end, name, ...}` in
|
|
757
|
+
* residues of each row, drawn with the GFF's annotations. Persists in
|
|
758
|
+
* the snapshot and the URL.
|
|
759
|
+
*/
|
|
760
|
+
features: stripDefault(types.array(types.frozen()), []),
|
|
761
|
+
/**
|
|
762
|
+
* #property
|
|
763
|
+
* the block the reader selected: `{start, end}` columns of the file,
|
|
764
|
+
* 1-based inclusive like a column highlight, and `rows` by name, every
|
|
765
|
+
* row where absent. Undefined until something is selected, so a view
|
|
766
|
+
* without one adds nothing to the shared URL.
|
|
767
|
+
*/
|
|
768
|
+
selection: types.frozen(),
|
|
709
769
|
/**
|
|
710
770
|
* #property
|
|
711
771
|
* where the view opens, in `highlights` coordinates: `{row, start,
|
|
@@ -980,6 +1040,33 @@ function stateModelFactory() {
|
|
|
980
1040
|
setHighlights(highlights) {
|
|
981
1041
|
self.highlights.replace(highlights);
|
|
982
1042
|
},
|
|
1043
|
+
/**
|
|
1044
|
+
* #action
|
|
1045
|
+
*/
|
|
1046
|
+
setFeatures(features) {
|
|
1047
|
+
self.features.replace(features);
|
|
1048
|
+
},
|
|
1049
|
+
/**
|
|
1050
|
+
* #action
|
|
1051
|
+
* select a block, in `selection` coordinates. `start` and `end` may come
|
|
1052
|
+
* in either order.
|
|
1053
|
+
*/
|
|
1054
|
+
setSelection(selection) {
|
|
1055
|
+
const next = selection && {
|
|
1056
|
+
start: Math.min(selection.start, selection.end),
|
|
1057
|
+
end: Math.max(selection.start, selection.end),
|
|
1058
|
+
...(selection.rows ? { rows: selection.rows } : {}),
|
|
1059
|
+
};
|
|
1060
|
+
if (!compareStructural(next, self.selection)) {
|
|
1061
|
+
self.selection = next;
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
/**
|
|
1065
|
+
* #action
|
|
1066
|
+
*/
|
|
1067
|
+
clearSelection() {
|
|
1068
|
+
this.setSelection(undefined);
|
|
1069
|
+
},
|
|
983
1070
|
/**
|
|
984
1071
|
* #action
|
|
985
1072
|
* replace the clades the viewer marks (see docs/layers.md)
|
|
@@ -1180,7 +1267,7 @@ function stateModelFactory() {
|
|
|
1180
1267
|
* store the GFF text in the snapshot like the alignment and tree. The
|
|
1181
1268
|
* parsed annotations are volatile and a blob filehandle is cleared once
|
|
1182
1269
|
* read, so the text is the only persisted copy. An autorun parses it
|
|
1183
|
-
* into annotations.
|
|
1270
|
+
* into annotations, and clears the ones it drew when the text goes.
|
|
1184
1271
|
*/
|
|
1185
1272
|
setGFF(result) {
|
|
1186
1273
|
self.data.setGFF(result);
|
|
@@ -1201,11 +1288,21 @@ function stateModelFactory() {
|
|
|
1201
1288
|
get realAllowedGappyness() {
|
|
1202
1289
|
return this.hideGapsEffective ? self.allowedGappyness : 100;
|
|
1203
1290
|
},
|
|
1291
|
+
/**
|
|
1292
|
+
* #getter
|
|
1293
|
+
* the annotations the overlay, the legend and the encodings draw: the
|
|
1294
|
+
* GFF's, then the `features` layer's
|
|
1295
|
+
*/
|
|
1296
|
+
get allAnnotations() {
|
|
1297
|
+
return self.features.length > 0
|
|
1298
|
+
? [...self.annotations, ...featuresToAnnotations(self.features)]
|
|
1299
|
+
: self.annotations;
|
|
1300
|
+
},
|
|
1204
1301
|
/**
|
|
1205
1302
|
* #getter
|
|
1206
1303
|
*/
|
|
1207
1304
|
get actuallyShowDomains() {
|
|
1208
|
-
return self.showDomains &&
|
|
1305
|
+
return self.showDomains && this.allAnnotations.length > 0;
|
|
1209
1306
|
},
|
|
1210
1307
|
/**
|
|
1211
1308
|
* #getter
|
|
@@ -1239,10 +1336,25 @@ function stateModelFactory() {
|
|
|
1239
1336
|
* #getter
|
|
1240
1337
|
*/
|
|
1241
1338
|
get colorScheme() {
|
|
1339
|
+
const { customColorScheme, colorSchemeName } = self;
|
|
1242
1340
|
// colorSchemeName is a free string (menus, snapshots, URL params), so a
|
|
1243
1341
|
// stale name falls back to the default
|
|
1244
|
-
return
|
|
1245
|
-
|
|
1342
|
+
return customColorScheme
|
|
1343
|
+
? letterColorTable(customColorScheme)
|
|
1344
|
+
: (colorSchemes[colorSchemeName] ??
|
|
1345
|
+
colorSchemes[defaultColorSchemeName]);
|
|
1346
|
+
},
|
|
1347
|
+
/**
|
|
1348
|
+
* #getter
|
|
1349
|
+
* the scheme coloring each cell from its column's statistics, or
|
|
1350
|
+
* undefined while the cells take a fixed color per letter, from the
|
|
1351
|
+
* built-in table or from `customColorScheme`
|
|
1352
|
+
*/
|
|
1353
|
+
get dynamicColorSchemeName() {
|
|
1354
|
+
const { customColorScheme, colorSchemeName } = self;
|
|
1355
|
+
return !customColorScheme && colorSchemeName.includes('dynamic')
|
|
1356
|
+
? colorSchemeName
|
|
1357
|
+
: undefined;
|
|
1246
1358
|
},
|
|
1247
1359
|
/**
|
|
1248
1360
|
* #getter
|
|
@@ -1263,7 +1375,7 @@ function stateModelFactory() {
|
|
|
1263
1375
|
return !!this.tree.noTree;
|
|
1264
1376
|
},
|
|
1265
1377
|
get noDomains() {
|
|
1266
|
-
return self.
|
|
1378
|
+
return self.allAnnotations.length === 0;
|
|
1267
1379
|
},
|
|
1268
1380
|
/**
|
|
1269
1381
|
* #getter
|
|
@@ -1271,9 +1383,9 @@ function stateModelFactory() {
|
|
|
1271
1383
|
* from disk or pasted in becomes inline text, and DataModel drops an
|
|
1272
1384
|
* inline document past `maxInlineSnapshotBytes`.
|
|
1273
1385
|
*
|
|
1274
|
-
* The header lists these, and the standalone app drops
|
|
1386
|
+
* The header lists these, and the standalone app drops `#data=` from
|
|
1275
1387
|
* the address bar while the list is non-empty or the encoded link would
|
|
1276
|
-
* pass
|
|
1388
|
+
* pass its `maxLinkLength`, so a copied link never opens an empty viewer
|
|
1277
1389
|
* unannounced. A document fetched from a URL never appears here, since
|
|
1278
1390
|
* the snapshot keeps its filehandle.
|
|
1279
1391
|
*
|
|
@@ -1310,7 +1422,8 @@ function stateModelFactory() {
|
|
|
1310
1422
|
* `data.treeMetadata`, the name that travels in existing links, so the
|
|
1311
1423
|
* inline size limit and `treeMetadataFilehandle` cover it. labelWidthMap
|
|
1312
1424
|
* reads it on every layout, so a malformed user-supplied file returns {}
|
|
1313
|
-
* instead of throwing out of rendering
|
|
1425
|
+
* instead of throwing out of rendering, and every field is a string by
|
|
1426
|
+
* the time a scale sorts it.
|
|
1314
1427
|
*/
|
|
1315
1428
|
get rowData() {
|
|
1316
1429
|
const text = self.data.treeMetadata;
|
|
@@ -1320,7 +1433,10 @@ function stateModelFactory() {
|
|
|
1320
1433
|
try {
|
|
1321
1434
|
const parsed = JSON.parse(text);
|
|
1322
1435
|
return typeof parsed === 'object' && parsed !== null
|
|
1323
|
-
? parsed
|
|
1436
|
+
? Object.fromEntries(Object.entries(parsed).map(([name, row]) => [
|
|
1437
|
+
name,
|
|
1438
|
+
stringFields(row),
|
|
1439
|
+
]))
|
|
1324
1440
|
: {};
|
|
1325
1441
|
}
|
|
1326
1442
|
catch (e) {
|
|
@@ -2586,7 +2702,7 @@ function stateModelFactory() {
|
|
|
2586
2702
|
// first occurrence wins; only name, description and segment start are
|
|
2587
2703
|
// read from it
|
|
2588
2704
|
const types = new Map();
|
|
2589
|
-
for (const annot of self.
|
|
2705
|
+
for (const annot of self.allAnnotations) {
|
|
2590
2706
|
if (!types.has(annot.accession)) {
|
|
2591
2707
|
types.set(annot.accession, annot);
|
|
2592
2708
|
}
|
|
@@ -2594,7 +2710,7 @@ function stateModelFactory() {
|
|
|
2594
2710
|
return types;
|
|
2595
2711
|
},
|
|
2596
2712
|
get filteredAnnotations() {
|
|
2597
|
-
return self.
|
|
2713
|
+
return self.allAnnotations.filter(r => !self.turnedOffFeatures.get(r.accession));
|
|
2598
2714
|
},
|
|
2599
2715
|
get annotationsByRow() {
|
|
2600
2716
|
return groupBy(this.filteredAnnotations, r => r.id);
|
|
@@ -3306,18 +3422,7 @@ function stateModelFactory() {
|
|
|
3306
3422
|
* on every mouse move.
|
|
3307
3423
|
*/
|
|
3308
3424
|
get highlightedColumnRuns() {
|
|
3309
|
-
|
|
3310
|
-
const runs = [];
|
|
3311
|
-
for (const col of [...(highlightedColumns ?? [])].sort((a, b) => a - b)) {
|
|
3312
|
-
const last = runs.at(-1);
|
|
3313
|
-
if (last && col === last.end + 1) {
|
|
3314
|
-
last.end = col;
|
|
3315
|
-
}
|
|
3316
|
-
else {
|
|
3317
|
-
runs.push({ start: col, end: col });
|
|
3318
|
-
}
|
|
3319
|
-
}
|
|
3320
|
-
return runs;
|
|
3425
|
+
return consecutiveRuns([...(self.highlightedColumns ?? [])].sort((a, b) => a - b)).map(([start, end]) => ({ start, end }));
|
|
3321
3426
|
},
|
|
3322
3427
|
/**
|
|
3323
3428
|
* #method
|
|
@@ -3368,6 +3473,78 @@ function stateModelFactory() {
|
|
|
3368
3473
|
return span ? [{ ...base, ...span, rowIndices: [] }] : [];
|
|
3369
3474
|
});
|
|
3370
3475
|
},
|
|
3476
|
+
/**
|
|
3477
|
+
* #getter
|
|
3478
|
+
* `selection` projected onto what is on screen, the way
|
|
3479
|
+
* `resolvedHighlights` projects a column span and a row set. A
|
|
3480
|
+
* selection whose columns are all hidden, or whose rows are all
|
|
3481
|
+
* collapsed away or unknown, resolves to undefined.
|
|
3482
|
+
*/
|
|
3483
|
+
get resolvedSelection() {
|
|
3484
|
+
const { selection, rowNamesSet, numRows } = self;
|
|
3485
|
+
const span = selection &&
|
|
3486
|
+
self.visibleSpan({ start: selection.start, end: selection.end });
|
|
3487
|
+
if (!selection || !span) {
|
|
3488
|
+
return undefined;
|
|
3489
|
+
}
|
|
3490
|
+
const rowRuns = selection.rows
|
|
3491
|
+
? consecutiveRuns(selection.rows
|
|
3492
|
+
.map(name => rowNamesSet.get(name))
|
|
3493
|
+
.filter(notEmpty)
|
|
3494
|
+
.sort((a, b) => a - b))
|
|
3495
|
+
: numRows > 0
|
|
3496
|
+
? [[0, numRows - 1]]
|
|
3497
|
+
: [];
|
|
3498
|
+
return rowRuns.length > 0 ? { ...span, rowRuns } : undefined;
|
|
3499
|
+
},
|
|
3500
|
+
/**
|
|
3501
|
+
* #getter
|
|
3502
|
+
* the columns and rows the selection covers, for the header's readout.
|
|
3503
|
+
* Columns count the file's columns from `start` to `end`, hidden ones
|
|
3504
|
+
* included, as `selectionFasta` copies them.
|
|
3505
|
+
*/
|
|
3506
|
+
get selectionSize() {
|
|
3507
|
+
const resolved = this.resolvedSelection;
|
|
3508
|
+
const span = this.selectionFileSpan;
|
|
3509
|
+
return resolved && span
|
|
3510
|
+
? {
|
|
3511
|
+
columns: span.end - span.start + 1,
|
|
3512
|
+
rows: sum(resolved.rowRuns.map(([a, b]) => b - a + 1)),
|
|
3513
|
+
}
|
|
3514
|
+
: undefined;
|
|
3515
|
+
},
|
|
3516
|
+
/**
|
|
3517
|
+
* #getter
|
|
3518
|
+
* `selection`'s columns clamped to the file's
|
|
3519
|
+
*/
|
|
3520
|
+
get selectionFileSpan() {
|
|
3521
|
+
const { selection } = self;
|
|
3522
|
+
const width = self.MSA?.getWidth() ?? 0;
|
|
3523
|
+
const start = Math.max(1, Math.floor(selection?.start ?? 0));
|
|
3524
|
+
const end = Math.min(width, Math.ceil(selection?.end ?? 0));
|
|
3525
|
+
return selection && start <= end ? { start, end } : undefined;
|
|
3526
|
+
},
|
|
3527
|
+
/**
|
|
3528
|
+
* #getter
|
|
3529
|
+
* the selected block as FASTA: the selected rows on screen, top to
|
|
3530
|
+
* bottom, each with its letters across the file's columns from `start`
|
|
3531
|
+
* to `end`, gaps included
|
|
3532
|
+
*/
|
|
3533
|
+
get selectionFasta() {
|
|
3534
|
+
const { rowNames, rowMap } = self;
|
|
3535
|
+
const resolved = this.resolvedSelection;
|
|
3536
|
+
const span = this.selectionFileSpan;
|
|
3537
|
+
if (!resolved || !span) {
|
|
3538
|
+
return '';
|
|
3539
|
+
}
|
|
3540
|
+
const rows = resolved.rowRuns
|
|
3541
|
+
.flatMap(([first, last]) => rowNames.slice(first, last + 1))
|
|
3542
|
+
.flatMap(name => {
|
|
3543
|
+
const seq = rowMap.get(name);
|
|
3544
|
+
return seq === undefined ? [] : [[name, seq]];
|
|
3545
|
+
});
|
|
3546
|
+
return blockFasta(rows, span.start, span.end);
|
|
3547
|
+
},
|
|
3371
3548
|
/**
|
|
3372
3549
|
* #method
|
|
3373
3550
|
* per-column summary statistics: consensus residue and its identity
|
|
@@ -3396,20 +3573,17 @@ function stateModelFactory() {
|
|
|
3396
3573
|
/**
|
|
3397
3574
|
* #getter
|
|
3398
3575
|
* each encoding with its scale resolved against the values its field
|
|
3399
|
-
* takes: a feature channel reads them across
|
|
3576
|
+
* takes: a feature channel reads them across every feature loaded, every
|
|
3400
3577
|
* other channel across the row table. Resolved once per change of that
|
|
3401
3578
|
* table or the encodings, never per row per frame.
|
|
3402
3579
|
*/
|
|
3403
3580
|
get resolvedEncodings() {
|
|
3404
3581
|
const rows = Object.values(self.rowData);
|
|
3405
|
-
const features = self.filteredAnnotations;
|
|
3406
3582
|
return self.encodings.map(encoding => ({
|
|
3407
3583
|
...encoding,
|
|
3408
|
-
...
|
|
3409
|
-
?
|
|
3410
|
-
|
|
3411
|
-
.filter(notEmpty)
|
|
3412
|
-
: rows.map(row => row?.[encoding.field]).filter(notEmpty)),
|
|
3584
|
+
...(featureChannels.has(encoding.channel)
|
|
3585
|
+
? resolveFeatureScale(encoding.field, encoding.scale, self.allAnnotations, self.filteredAnnotations)
|
|
3586
|
+
: resolveScale(encoding.scale, rows.map(row => row?.[encoding.field]).filter(notEmpty))),
|
|
3413
3587
|
}));
|
|
3414
3588
|
},
|
|
3415
3589
|
/**
|
|
@@ -3426,7 +3600,7 @@ function stateModelFactory() {
|
|
|
3426
3600
|
if (panel.kind === 'features') {
|
|
3427
3601
|
const color = panel.encoding?.color;
|
|
3428
3602
|
const encoding = color
|
|
3429
|
-
? resolveFeatureScale(color.field, color.scale, annotations)
|
|
3603
|
+
? resolveFeatureScale(color.field, color.scale, self.allAnnotations, annotations)
|
|
3430
3604
|
: featureFillEncoding;
|
|
3431
3605
|
const label = panel.encoding?.label;
|
|
3432
3606
|
return {
|
|
@@ -3701,6 +3875,51 @@ function stateModelFactory() {
|
|
|
3701
3875
|
self.scrollX = clamp(-span.startCol * self.colWidth, self.maxScrollX, 0);
|
|
3702
3876
|
});
|
|
3703
3877
|
},
|
|
3878
|
+
/**
|
|
3879
|
+
* #action
|
|
3880
|
+
* zoom and scroll so the selected columns fill the alignment's width,
|
|
3881
|
+
* and, where the selection names its rows, so those rows fill its height
|
|
3882
|
+
*/
|
|
3883
|
+
zoomToSelection() {
|
|
3884
|
+
const { selection, resolvedSelection } = self;
|
|
3885
|
+
if (!selection || !resolvedSelection) {
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
transaction(() => {
|
|
3889
|
+
this.zoomToRegion({ start: selection.start, end: selection.end });
|
|
3890
|
+
if (selection.rows && self.viewInitialized) {
|
|
3891
|
+
const { rowRuns } = resolvedSelection;
|
|
3892
|
+
const first = rowRuns[0][0];
|
|
3893
|
+
const last = rowRuns.at(-1)[1];
|
|
3894
|
+
self.rowHeight = clamp(self.msaAreaHeight / (last - first + 1), minRowHeight, maxCellSize);
|
|
3895
|
+
self.scrollY = clamp(-first * self.rowHeight, self.maxScrollY, 0);
|
|
3896
|
+
}
|
|
3897
|
+
});
|
|
3898
|
+
},
|
|
3899
|
+
/**
|
|
3900
|
+
* #action
|
|
3901
|
+
* select the block between two cells given as visible column and row
|
|
3902
|
+
* indices, the coordinates a drag reports, clamped to the alignment. A
|
|
3903
|
+
* cell with no `row` selects the columns across every row, and so does a
|
|
3904
|
+
* block spanning every row on screen.
|
|
3905
|
+
*/
|
|
3906
|
+
selectBlock(anchor, head) {
|
|
3907
|
+
const { numColumns, numRows, rowNames } = self;
|
|
3908
|
+
if (numColumns === 0 || numRows === 0) {
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
const ordered = (a, b, last) => [clamp(a, 0, last), clamp(b, 0, last)].sort((x, y) => x - y);
|
|
3912
|
+
const [startCol, endCol] = ordered(anchor.col, head.col, numColumns - 1);
|
|
3913
|
+
const [firstRow, lastRow] = anchor.row === undefined || head.row === undefined
|
|
3914
|
+
? [0, numRows - 1]
|
|
3915
|
+
: ordered(anchor.row, head.row, numRows - 1);
|
|
3916
|
+
const everyRow = firstRow === 0 && lastRow === numRows - 1;
|
|
3917
|
+
self.setSelection({
|
|
3918
|
+
start: self.visibleColToGlobalCol(startCol) + 1,
|
|
3919
|
+
end: self.visibleColToGlobalCol(endCol) + 1,
|
|
3920
|
+
...(everyRow ? {} : { rows: rowNames.slice(firstRow, lastRow + 1) }),
|
|
3921
|
+
});
|
|
3922
|
+
},
|
|
3704
3923
|
/**
|
|
3705
3924
|
* #action
|
|
3706
3925
|
*/
|
|
@@ -3931,7 +4150,7 @@ function stateModelFactory() {
|
|
|
3931
4150
|
// xref solution https://github.com/mobxjs/mobx/issues/266#issuecomment-222007278
|
|
3932
4151
|
// xref problem https://github.com/GMOD/react-msaview/issues/75
|
|
3933
4152
|
addDisposer(self, autorun(() => {
|
|
3934
|
-
if (self.
|
|
4153
|
+
if (self.dynamicColorSchemeName) {
|
|
3935
4154
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
3936
4155
|
self.colStats;
|
|
3937
4156
|
}
|
|
@@ -3981,7 +4200,7 @@ function stateModelFactory() {
|
|
|
3981
4200
|
// stripDefault handles per-property defaults; this drops inline documents
|
|
3982
4201
|
// whose filehandle can refetch them
|
|
3983
4202
|
...rest,
|
|
3984
|
-
...smallColumnTracks(columnTracks),
|
|
4203
|
+
...smallColumnTracks(columnTracks, maxInlineSnapshotBytes),
|
|
3985
4204
|
data: {
|
|
3986
4205
|
...(rest.treeFilehandle ? {} : { tree: data.tree }),
|
|
3987
4206
|
...(rest.msaFilehandle ? {} : { msa: data.msa }),
|