react-msaview 9.0.0 → 9.1.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/README.md +19 -10
- package/bundle/index.js +74 -74
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -3
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +0 -1
- package/dist/columnCounts.js +1 -1
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/Loading.js +5 -5
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1891 -4
- package/dist/components/MSAViewer.js +144 -201
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +3 -2
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +2 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.d.ts +11 -0
- package/dist/components/controls.js +48 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/dialogs/AnnotationFileDialog.js +5 -16
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -7
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/HelpDialog.d.ts +6 -0
- package/dist/components/dialogs/HelpDialog.js +30 -0
- package/dist/components/dialogs/HelpDialog.js.map +1 -0
- package/dist/components/header/FileMenu.js +33 -7
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +4 -6
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.d.ts +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -32
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +28 -44
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/settingsMenuItems.d.ts +1 -0
- package/dist/components/header/settingsMenuItems.js +44 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/data/seq2.d.ts +0 -1
- package/dist/components/import/data/seq2.js +0 -17
- package/dist/components/import/data/seq2.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +2 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +2 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
- package/dist/components/msa/drawFeatureSpans.js +99 -56
- package/dist/components/msa/drawFeatureSpans.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +14 -1
- package/dist/components/msa/msaRaster.js +19 -9
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +19 -8
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +2 -2
- package/dist/components/msa/renderHighlights.js +2 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +2 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
- package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -53
- package/dist/components/tracks/drawTracks.js +5 -5
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/CladeLabels.d.ts +1 -1
- package/dist/components/tree/CladeLabels.js +22 -34
- package/dist/components/tree/CladeLabels.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +17 -10
- package/dist/components/tree/cladeBrackets.js +33 -3
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -8
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/element.js +3 -6
- package/dist/element.js.map +1 -1
- package/dist/exportLayout.d.ts +39 -0
- package/dist/exportLayout.js +78 -0
- package/dist/exportLayout.js.map +1 -0
- package/dist/headlessRenderEnv.d.ts +12 -39
- package/dist/headlessRenderEnv.js +18 -12
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/hierarchy.d.ts +5 -8
- package/dist/hierarchy.js +5 -79
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +7 -0
- package/dist/measureTextCanvas.js +8 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +0 -5
- package/dist/model/treeModel.js +0 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +38 -44
- package/dist/model.js +153 -112
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +32 -120
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +12 -20
- package/dist/types.d.ts +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewerSnapshot.d.ts +169 -0
- package/dist/viewerSnapshot.js +100 -0
- package/dist/viewerSnapshot.js.map +1 -0
- package/package.json +3 -3
- package/src/colorSchemes.ts +1 -4
- package/src/columnCounts.ts +1 -1
- package/src/components/Loading.tsx +19 -12
- package/src/components/MSAViewer.tsx +275 -251
- package/src/components/ResizeHandles.tsx +9 -2
- package/src/components/Track.tsx +2 -1
- package/src/components/controls.ts +61 -0
- package/src/components/dialogs/AnnotationFileDialog.tsx +5 -21
- package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
- package/src/components/dialogs/HelpDialog.tsx +79 -0
- package/src/components/header/FileMenu.tsx +33 -7
- package/src/components/header/Header.tsx +4 -6
- package/src/components/header/TreeSettingsMenu.tsx +2 -32
- package/src/components/header/ZoomControls.tsx +30 -66
- package/src/components/header/settingsMenuItems.ts +59 -0
- package/src/components/import/data/seq2.ts +0 -18
- package/src/components/msa/MSACanvas.tsx +2 -1
- package/src/components/msa/MSACanvasBlock.tsx +2 -6
- package/src/components/msa/drawFeatureSpans.ts +160 -79
- package/src/components/msa/msaRaster.ts +27 -9
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +39 -24
- package/src/components/msa/renderHighlights.ts +2 -3
- package/src/components/msa/renderMSAMouseover.ts +5 -1
- package/src/components/renderCtx.ts +0 -1
- package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
- package/src/components/tracks/drawTracks.ts +5 -5
- package/src/components/tree/CladeLabels.tsx +30 -54
- package/src/components/tree/cladeBrackets.ts +50 -3
- package/src/components/tree/renderTreeCanvas.ts +2 -13
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/element.ts +3 -7
- package/src/exportLayout.ts +134 -0
- package/src/headlessRenderEnv.ts +24 -11
- package/src/hierarchy.ts +5 -88
- package/src/index.ts +5 -5
- package/src/measureTextCanvas.ts +8 -6
- package/src/model/treeModel.ts +0 -7
- package/src/model.ts +170 -122
- package/src/renderToSvg.tsx +55 -194
- package/src/types.ts +0 -1
- package/src/version.ts +1 -1
- package/src/viewerSnapshot.ts +147 -0
- package/dist/components/header/ZoomMenu.d.ts +0 -6
- package/dist/components/header/ZoomMenu.js +0 -37
- package/dist/components/header/ZoomMenu.js.map +0 -1
- package/src/components/header/ZoomMenu.tsx +0 -48
package/dist/model.js
CHANGED
|
@@ -3,7 +3,7 @@ import { openLocation } from '@jbrowse/core/util/io';
|
|
|
3
3
|
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
|
-
import { generateNodeIds, gffToAnnotations,
|
|
6
|
+
import { generateNodeIds, gffToAnnotations, parseEmfTree, parseGFF, parseMSA, parseNewick, } from 'msa-parsers';
|
|
7
7
|
import { blockFasta } from './blockFasta.js';
|
|
8
8
|
import { calculateBlocks } from './calculateBlocks.js';
|
|
9
9
|
import { clustalXColumnColors } from './clustalX.js';
|
|
@@ -12,7 +12,6 @@ import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.js';
|
|
|
12
12
|
import { columnStats } from './columnStats.js';
|
|
13
13
|
import { packDomainLanes } from './components/msa/packDomainLanes.js';
|
|
14
14
|
import { visibleColRange } from './components/msa/visibleColRange.js';
|
|
15
|
-
import TrackBlocks from './components/tracks/TrackBlocks.js';
|
|
16
15
|
import { cladeGutterWidth } from './components/tree/cladeBrackets.js';
|
|
17
16
|
import { scaleBarLength } from './components/tree/scaleBar.js';
|
|
18
17
|
import { cladeHighlightAlpha, cladeHighlightColor, defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultFeaturePanelWidth, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows, fontSizeFor, labelFitFontSize, maxCellSize, maxFitTreeWidth, maxTreeAreaShare, maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes, maxNeighborJoiningRows, minColWidth, minLetterColWidth, minLetterRowHeight, minRowHeight, minTreeWidth, pxPerTreeDepth, rowPanelHeaderHeight, rowTintAlpha, scrollZoomAxes, segmentFeatureTypes, segmentShades, treeScaleBarHeight, } from './constants.js';
|
|
@@ -22,7 +21,7 @@ import { featureField, featureName } from './featureFields.js';
|
|
|
22
21
|
import { featuresToAnnotations } from './featuresLayer.js';
|
|
23
22
|
import { fetchTextWithProgress, isAbortError } from './fetchUtils.js';
|
|
24
23
|
import { flatToTree } from './flatToTree.js';
|
|
25
|
-
import {
|
|
24
|
+
import { collapse, find, forEachDescendant, hierarchy, layoutTree, leafIndex, leaves, mrca, nodeCoveringRows, sort, sum as hierarchySum, } from './hierarchy.js';
|
|
26
25
|
import { DataModelF } from './model/DataModel.js';
|
|
27
26
|
import { DialogQueueSessionMixin } from './model/DialogQueue.js';
|
|
28
27
|
import { MSAModelF } from './model/msaModel.js';
|
|
@@ -322,33 +321,71 @@ const seedingMarks = new Set(['collapse', 'focus', 'rotate']);
|
|
|
322
321
|
// shared empty results, so observers don't see a fresh [] as a change
|
|
323
322
|
const noDomains = [];
|
|
324
323
|
const noClades = [];
|
|
324
|
+
/**
|
|
325
|
+
* Each tip of the full tree in the order the tree draws it, with the on-screen
|
|
326
|
+
* row that shows it: its own, or the row of the collapsed node standing for it
|
|
327
|
+
*/
|
|
328
|
+
function tipRows(root) {
|
|
329
|
+
const rows = new Map();
|
|
330
|
+
let row = 0;
|
|
331
|
+
const pushReversed = (stack, nodes) => {
|
|
332
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
333
|
+
stack.push(nodes[i]);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const stack = [root];
|
|
337
|
+
while (stack.length > 0) {
|
|
338
|
+
const node = stack.pop();
|
|
339
|
+
if (node.children?.length) {
|
|
340
|
+
pushReversed(stack, node.children);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
const hidden = [node];
|
|
344
|
+
while (hidden.length > 0) {
|
|
345
|
+
const tip = hidden.pop();
|
|
346
|
+
const kids = tip.children ?? tip._children;
|
|
347
|
+
if (kids?.length) {
|
|
348
|
+
pushReversed(hidden, kids);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
rows.set(tip.data.name, { order: rows.size, row });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
row++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return rows;
|
|
358
|
+
}
|
|
325
359
|
/**
|
|
326
360
|
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
327
361
|
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
328
|
-
* leaf count `tips` disagrees with. `
|
|
329
|
-
*
|
|
362
|
+
* leaf count `tips` disagrees with. A `range` counts its tips in the order
|
|
363
|
+
* `tips` gives them, so a collapsed clade inside it counts every leaf it
|
|
364
|
+
* stands for. `index` is the memoized leaf pass over the tree.
|
|
330
365
|
*/
|
|
331
|
-
function cladeRows(clade, root, index,
|
|
366
|
+
function cladeRows(clade, root, index, tips) {
|
|
332
367
|
if (clade.range) {
|
|
333
|
-
const [a, b] = clade.range.map(name => index.get(name) ?
|
|
368
|
+
const [a, b] = clade.range.map(name => index.get(name) ? tips.get(name) : undefined);
|
|
334
369
|
if (a === undefined || b === undefined) {
|
|
335
370
|
return undefined;
|
|
336
371
|
}
|
|
337
|
-
const
|
|
338
|
-
return
|
|
372
|
+
const [first, last] = a.order <= b.order ? [a, b] : [b, a];
|
|
373
|
+
return last.order - first.order + 1 === clade.tips
|
|
374
|
+
? { rows: [first.row, last.row] }
|
|
375
|
+
: undefined;
|
|
339
376
|
}
|
|
340
377
|
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined;
|
|
341
378
|
if (!node) {
|
|
342
379
|
return undefined;
|
|
343
380
|
}
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
381
|
+
const nodeTips = leaves(node);
|
|
382
|
+
if (nodeTips.length !== clade.tips) {
|
|
346
383
|
return undefined;
|
|
347
384
|
}
|
|
348
385
|
let first = Infinity;
|
|
349
386
|
let last = -Infinity;
|
|
350
|
-
for (const tip of
|
|
351
|
-
const row =
|
|
387
|
+
for (const tip of nodeTips) {
|
|
388
|
+
const row = tips.get(tip.data.name)?.row;
|
|
352
389
|
if (row !== undefined) {
|
|
353
390
|
first = Math.min(first, row);
|
|
354
391
|
last = Math.max(last, row);
|
|
@@ -359,15 +396,17 @@ function cladeRows(clade, root, index, rowNamesSet) {
|
|
|
359
396
|
: undefined;
|
|
360
397
|
}
|
|
361
398
|
/**
|
|
362
|
-
* Every clade that resolves, with its fill color settled, against the
|
|
363
|
-
*
|
|
364
|
-
* rows and the overview against its own, so both take the tree they
|
|
365
|
-
* `range` record names no node, so the seeding marks, which need one,
|
|
399
|
+
* Every clade that resolves, with its fill color settled, against the rows of
|
|
400
|
+
* `displayed`, the tree as drawn. The tree panel resolves against the
|
|
401
|
+
* displayed rows and the overview against its own, so both take the tree they
|
|
402
|
+
* draw. A `range` record names no node, so the seeding marks, which need one,
|
|
403
|
+
* drop it.
|
|
366
404
|
*/
|
|
367
|
-
function resolveClades(clades, root,
|
|
405
|
+
function resolveClades(clades, root, displayed) {
|
|
368
406
|
const index = leafIndex(root);
|
|
407
|
+
const tips = tipRows(displayed);
|
|
369
408
|
return clades.flatMap(clade => {
|
|
370
|
-
const resolved = cladeRows(clade, root, index,
|
|
409
|
+
const resolved = cladeRows(clade, root, index, tips);
|
|
371
410
|
const seeding = seedingMarks.has(clade.mark);
|
|
372
411
|
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
373
412
|
? [
|
|
@@ -470,8 +509,8 @@ function rootTree(tree, treeRoot) {
|
|
|
470
509
|
? generateNodeIds(rerootTree(input, plain.get(target.data)))
|
|
471
510
|
: tree;
|
|
472
511
|
}
|
|
473
|
-
// the inclusive tip indices a node covers, from
|
|
474
|
-
//
|
|
512
|
+
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
513
|
+
// row per tip
|
|
475
514
|
function tipRange(node) {
|
|
476
515
|
return [Math.round(node.xMin - 0.5), Math.round(node.xMax - 0.5)];
|
|
477
516
|
}
|
|
@@ -483,6 +522,15 @@ const featureChannels = new Set([
|
|
|
483
522
|
// seqPos -> column indexes per row, keyed on the parse so they are garbage
|
|
484
523
|
// collected with it. A computed would rebuild every row's index when read
|
|
485
524
|
// outside a reactive context.
|
|
525
|
+
// Newick tools write a colon in a name as an underscore, so a tree's row
|
|
526
|
+
// `chr1_100-200` is the GFF's `chr1:100-200`, as FastaMSA.getRow resolves it
|
|
527
|
+
function annotationRowName(id, rowNames) {
|
|
528
|
+
if (rowNames.has(id)) {
|
|
529
|
+
return id;
|
|
530
|
+
}
|
|
531
|
+
const underscored = id.replaceAll(':', '_');
|
|
532
|
+
return rowNames.has(underscored) ? underscored : id;
|
|
533
|
+
}
|
|
486
534
|
const seqPosIndexCache = new WeakMap();
|
|
487
535
|
// A segment is a 1:1 run. A segment whose sides differ in length is malformed,
|
|
488
536
|
// and the lookups treat it like an uncovered position.
|
|
@@ -768,9 +816,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
768
816
|
/**
|
|
769
817
|
* #property
|
|
770
818
|
* where the view opens, in `highlights` coordinates: `{row, start,
|
|
771
|
-
* end}` zooms onto residues of that row
|
|
772
|
-
*
|
|
773
|
-
* then cleared, so a reloaded
|
|
819
|
+
* end}` zooms onto residues of that row and scrolls the row to the
|
|
820
|
+
* middle, `{start, end}` zooms onto alignment columns. Applied once the
|
|
821
|
+
* alignment and any tree file have loaded, then cleared, so a reloaded
|
|
822
|
+
* session keeps the reader's own scroll.
|
|
774
823
|
*/
|
|
775
824
|
region: types.frozen(),
|
|
776
825
|
/**
|
|
@@ -811,6 +860,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
811
860
|
* of the snapshot so a link opened in the full app shows the toolbar.
|
|
812
861
|
*/
|
|
813
862
|
hideHeader: false,
|
|
863
|
+
/**
|
|
864
|
+
* #volatile
|
|
865
|
+
* shows the import form while the view has no data. MSAViewer turns it
|
|
866
|
+
* off, since its host supplies the data and the form is the demo app's
|
|
867
|
+
*/
|
|
868
|
+
importForm: true,
|
|
814
869
|
/**
|
|
815
870
|
* #volatile
|
|
816
871
|
*/
|
|
@@ -1275,17 +1330,15 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1275
1330
|
.views(self => ({
|
|
1276
1331
|
/**
|
|
1277
1332
|
* #getter
|
|
1278
|
-
* hideGaps takes effect when
|
|
1333
|
+
* hideGaps takes effect when a collapse or a focus hides rows, whose
|
|
1334
|
+
* residues leave columns all gaps among the rest, or when
|
|
1335
|
+
* allowedGappyness is under 100
|
|
1279
1336
|
*/
|
|
1280
1337
|
get hideGapsEffective() {
|
|
1281
1338
|
return (self.hideGaps &&
|
|
1282
|
-
(self.collapsed.length > 0 ||
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
* #getter
|
|
1286
|
-
*/
|
|
1287
|
-
get realAllowedGappyness() {
|
|
1288
|
-
return this.hideGapsEffective ? self.allowedGappyness : 100;
|
|
1339
|
+
(self.collapsed.length > 0 ||
|
|
1340
|
+
self.showOnly !== undefined ||
|
|
1341
|
+
self.allowedGappyness < 100));
|
|
1289
1342
|
},
|
|
1290
1343
|
/**
|
|
1291
1344
|
* #getter
|
|
@@ -1534,7 +1587,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1534
1587
|
if (self.clades.length === 0) {
|
|
1535
1588
|
return noClades;
|
|
1536
1589
|
}
|
|
1537
|
-
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), this.
|
|
1590
|
+
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), this.root);
|
|
1538
1591
|
},
|
|
1539
1592
|
/**
|
|
1540
1593
|
* #getter
|
|
@@ -1702,21 +1755,16 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1702
1755
|
if (!self.showTreeOverview) {
|
|
1703
1756
|
return undefined;
|
|
1704
1757
|
}
|
|
1705
|
-
const
|
|
1758
|
+
const layout = layoutTree(buildTreeRoot(this.tree, {
|
|
1706
1759
|
collapsed: self.collapsed,
|
|
1707
1760
|
rotated: self.rotated,
|
|
1708
1761
|
order: self.treeOrder,
|
|
1709
|
-
});
|
|
1710
|
-
const numTips = leaves(root).length;
|
|
1711
|
-
clusterLayout(root, numTips, 1);
|
|
1712
|
-
const rootLen = Math.max(root.data.length || 0, 0);
|
|
1713
|
-
const extent = maxLength(root) - rootLen;
|
|
1714
|
-
setBrLength(root, -rootLen, extent ? 1 / extent : 0);
|
|
1762
|
+
}), { rowHeight: 1, treeWidth: 1 });
|
|
1715
1763
|
return {
|
|
1716
|
-
root,
|
|
1717
|
-
numTips,
|
|
1718
|
-
maxDepthToLeaf:
|
|
1719
|
-
showBranchLen: self.showBranchLen &&
|
|
1764
|
+
root: layout.root,
|
|
1765
|
+
numTips: layout.leaves.length,
|
|
1766
|
+
maxDepthToLeaf: layout.root.depthToLeaf,
|
|
1767
|
+
showBranchLen: self.showBranchLen && layout.rootToTipLength > 0,
|
|
1720
1768
|
};
|
|
1721
1769
|
},
|
|
1722
1770
|
/**
|
|
@@ -1729,8 +1777,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1729
1777
|
if (!layout || self.clades.length === 0) {
|
|
1730
1778
|
return noClades;
|
|
1731
1779
|
}
|
|
1732
|
-
|
|
1733
|
-
return resolveClades(self.clades, layout.root, rowNames);
|
|
1780
|
+
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), layout.root);
|
|
1734
1781
|
},
|
|
1735
1782
|
/**
|
|
1736
1783
|
* #getter
|
|
@@ -1811,7 +1858,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1811
1858
|
* #getter
|
|
1812
1859
|
*/
|
|
1813
1860
|
get blanks() {
|
|
1814
|
-
const { hideGapsEffective,
|
|
1861
|
+
const { hideGapsEffective, allowedGappyness } = self;
|
|
1815
1862
|
if (!hideGapsEffective) {
|
|
1816
1863
|
return [];
|
|
1817
1864
|
}
|
|
@@ -1827,7 +1874,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1827
1874
|
// row that stops early counts as gapped for the remainder
|
|
1828
1875
|
const numCols = this.MSA?.getWidth() ?? 0;
|
|
1829
1876
|
const numRows = strs.length;
|
|
1830
|
-
const threshold = Math.ceil((
|
|
1877
|
+
const threshold = Math.ceil((allowedGappyness / 100) * numRows);
|
|
1831
1878
|
const blankCounts = new Uint32Array(numCols);
|
|
1832
1879
|
for (let j = 0; j < numRows; j++) {
|
|
1833
1880
|
const str = strs[j];
|
|
@@ -1909,11 +1956,19 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1909
1956
|
}
|
|
1910
1957
|
return index;
|
|
1911
1958
|
},
|
|
1959
|
+
/**
|
|
1960
|
+
* #method
|
|
1961
|
+
* the row's aligned sequence by the name the tree gives it, including
|
|
1962
|
+
* rows a collapsed clade hides. `MSA.getRow` resolves a FASTA name whose
|
|
1963
|
+
* colon a Newick tool wrote as an underscore, which `rowMap` does not
|
|
1964
|
+
*/
|
|
1965
|
+
rowSequence(name) {
|
|
1966
|
+
return this.MSA?.getRow(name) || undefined;
|
|
1967
|
+
},
|
|
1912
1968
|
/**
|
|
1913
1969
|
* #getter
|
|
1914
|
-
* every sequence in the alignment, keyed by
|
|
1915
|
-
*
|
|
1916
|
-
* by row name use this
|
|
1970
|
+
* every sequence in the alignment, keyed by the alignment's own row
|
|
1971
|
+
* names, including rows a collapsed clade hides
|
|
1917
1972
|
*/
|
|
1918
1973
|
get rowMap() {
|
|
1919
1974
|
const MSA = this.MSA;
|
|
@@ -1956,10 +2011,11 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1956
2011
|
*/
|
|
1957
2012
|
get sequenceType() {
|
|
1958
2013
|
const letters = this.colStats.lettersPresent;
|
|
1959
|
-
//
|
|
1960
|
-
//
|
|
1961
|
-
const
|
|
1962
|
-
const
|
|
2014
|
+
// the two-base IUPAC codes keep an ambiguous read nucleotide; the
|
|
2015
|
+
// three-base ones (B, D, H, V) are common amino acids, so they don't
|
|
2016
|
+
const ambiguous = ['N', 'R', 'Y', 'K', 'M', 'S', 'W'];
|
|
2017
|
+
const dna = new Set(['A', 'C', 'G', 'T', ...ambiguous]);
|
|
2018
|
+
const rna = new Set(['A', 'C', 'G', 'U', ...ambiguous]);
|
|
1963
2019
|
const isDna = letters.size > 0 && [...letters].every(l => dna.has(l));
|
|
1964
2020
|
const isRna = letters.size > 0 && [...letters].every(l => rna.has(l));
|
|
1965
2021
|
return isDna ? 'dna' : isRna ? 'rna' : 'amino';
|
|
@@ -2082,14 +2138,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2082
2138
|
get rootToTipLength() {
|
|
2083
2139
|
return this.treeLayout.rootToTipLength;
|
|
2084
2140
|
},
|
|
2085
|
-
/**
|
|
2086
|
-
* #getter
|
|
2087
|
-
* x-position of the farthest tip in a phylogram, px: effectiveTreeWidth,
|
|
2088
|
-
* or 0 for a tree with no branch lengths (drawn as a cladogram)
|
|
2089
|
-
*/
|
|
2090
|
-
get maxBranchLength() {
|
|
2091
|
-
return this.rootToTipLength ? this.effectiveTreeWidth : 0;
|
|
2092
|
-
},
|
|
2093
2141
|
/**
|
|
2094
2142
|
* #getter
|
|
2095
2143
|
* pixels per unit of branch length in the phylogram layout, 0 in
|
|
@@ -2245,7 +2293,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2245
2293
|
data: content?.data,
|
|
2246
2294
|
arcs: content?.arcs,
|
|
2247
2295
|
},
|
|
2248
|
-
ReactComponent: TrackBlocks,
|
|
2249
2296
|
};
|
|
2250
2297
|
});
|
|
2251
2298
|
columnTrackModels.set(track, computedModel);
|
|
@@ -2303,7 +2350,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2303
2350
|
data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
|
|
2304
2351
|
height: rowHeight,
|
|
2305
2352
|
},
|
|
2306
|
-
ReactComponent: TrackBlocks,
|
|
2307
2353
|
}));
|
|
2308
2354
|
},
|
|
2309
2355
|
/**
|
|
@@ -2325,7 +2371,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2325
2371
|
height: self.trackHeight('arc'),
|
|
2326
2372
|
arcs,
|
|
2327
2373
|
},
|
|
2328
|
-
ReactComponent: TrackBlocks,
|
|
2329
2374
|
},
|
|
2330
2375
|
]
|
|
2331
2376
|
: [];
|
|
@@ -2372,7 +2417,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2372
2417
|
heightKey: resizableKinds.has(model.kind) ? model.kind : undefined,
|
|
2373
2418
|
height: self.trackHeight(model.kind),
|
|
2374
2419
|
},
|
|
2375
|
-
ReactComponent: TrackBlocks,
|
|
2376
2420
|
}));
|
|
2377
2421
|
},
|
|
2378
2422
|
get tracks() {
|
|
@@ -2408,7 +2452,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2408
2452
|
* @returns The character at that position
|
|
2409
2453
|
*/
|
|
2410
2454
|
visibleColToRowLetter(rowName, visibleCol) {
|
|
2411
|
-
return self.
|
|
2455
|
+
return self.rowSequence(rowName)?.[this.visibleColToGlobalCol(visibleCol)];
|
|
2412
2456
|
},
|
|
2413
2457
|
/**
|
|
2414
2458
|
* #method
|
|
@@ -2483,7 +2527,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2483
2527
|
* @returns The global column index in the full MSA, or undefined
|
|
2484
2528
|
*/
|
|
2485
2529
|
seqPosToGlobalCol(rowName, seqPos) {
|
|
2486
|
-
const seq = self.
|
|
2530
|
+
const seq = self.rowSequence(rowName);
|
|
2487
2531
|
if (seq === undefined) {
|
|
2488
2532
|
return undefined;
|
|
2489
2533
|
}
|
|
@@ -2749,7 +2793,8 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2749
2793
|
return self.allAnnotations.filter(r => !self.turnedOffFeatures.get(r.accession));
|
|
2750
2794
|
},
|
|
2751
2795
|
get annotationsByRow() {
|
|
2752
|
-
|
|
2796
|
+
const { rowNamesSet } = self;
|
|
2797
|
+
return groupBy(this.filteredAnnotations, r => annotationRowName(r.id, rowNamesSet));
|
|
2753
2798
|
},
|
|
2754
2799
|
}))
|
|
2755
2800
|
.views(self => ({
|
|
@@ -2837,7 +2882,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2837
2882
|
const { annotations, rowNamesSet } = self;
|
|
2838
2883
|
return self.dataInitialized &&
|
|
2839
2884
|
annotations.length > 0 &&
|
|
2840
|
-
!annotations.some(a => rowNamesSet.has(a.id))
|
|
2885
|
+
!annotations.some(a => rowNamesSet.has(annotationRowName(a.id, rowNamesSet)))
|
|
2841
2886
|
? [
|
|
2842
2887
|
`0 of ${annotations.length} annotations name a row in this alignment`,
|
|
2843
2888
|
]
|
|
@@ -2962,10 +3007,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2962
3007
|
* #action
|
|
2963
3008
|
* swap in a different tree over the same alignment. Clears `collapsed`,
|
|
2964
3009
|
* `rotated` and `showOnly`, since path-derived node ids (node-0-0-1) from the old
|
|
2965
|
-
* tree would match unrelated nodes in the new one
|
|
3010
|
+
* tree would match unrelated nodes in the new one, and the tree's
|
|
3011
|
+
* filehandle, which the snapshot would otherwise reopen in place of
|
|
3012
|
+
* the new tree
|
|
2966
3013
|
*/
|
|
2967
3014
|
replaceTree(newick) {
|
|
2968
3015
|
transaction(() => {
|
|
3016
|
+
self.setTreeFilehandle(undefined);
|
|
2969
3017
|
self.collapsed.clear();
|
|
2970
3018
|
self.rotated.clear();
|
|
2971
3019
|
self.setShowOnly(undefined);
|
|
@@ -2987,30 +3035,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2987
3035
|
zoomAtCenter(scaleFactor, axis = 'both') {
|
|
2988
3036
|
this.zoomToPos(scaleFactor, self.msaCanvasWidth / 2, self.msaAreaHeight / 2, axis);
|
|
2989
3037
|
},
|
|
2990
|
-
/**
|
|
2991
|
-
* #action
|
|
2992
|
-
*/
|
|
2993
|
-
zoomOutHorizontal() {
|
|
2994
|
-
this.zoomAtCenter(0.75, 'horizontal');
|
|
2995
|
-
},
|
|
2996
|
-
/**
|
|
2997
|
-
* #action
|
|
2998
|
-
*/
|
|
2999
|
-
zoomInHorizontal() {
|
|
3000
|
-
this.zoomAtCenter(1.5, 'horizontal');
|
|
3001
|
-
},
|
|
3002
|
-
/**
|
|
3003
|
-
* #action
|
|
3004
|
-
*/
|
|
3005
|
-
zoomInVertical() {
|
|
3006
|
-
this.zoomAtCenter(1.5, 'vertical');
|
|
3007
|
-
},
|
|
3008
|
-
/**
|
|
3009
|
-
* #action
|
|
3010
|
-
*/
|
|
3011
|
-
zoomOutVertical() {
|
|
3012
|
-
this.zoomAtCenter(0.75, 'vertical');
|
|
3013
|
-
},
|
|
3014
3038
|
/**
|
|
3015
3039
|
* #action
|
|
3016
3040
|
*/
|
|
@@ -3077,15 +3101,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3077
3101
|
setAnnotations(annotations) {
|
|
3078
3102
|
self.annotations = annotations;
|
|
3079
3103
|
},
|
|
3080
|
-
/**
|
|
3081
|
-
* #action
|
|
3082
|
-
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
3083
|
-
* for downstream plugins that hold the EBI wire format; new code should
|
|
3084
|
-
* adapt to Annotation[] and call setAnnotations.
|
|
3085
|
-
*/
|
|
3086
|
-
setDomains(data) {
|
|
3087
|
-
this.setAnnotations(data ? interProScanToAnnotations(data) : []);
|
|
3088
|
-
},
|
|
3089
3104
|
applyGFFText(gffText) {
|
|
3090
3105
|
this.setAnnotations(gffToAnnotations(parseGFF(gffText)));
|
|
3091
3106
|
},
|
|
@@ -3166,7 +3181,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3166
3181
|
column: column + 1,
|
|
3167
3182
|
row,
|
|
3168
3183
|
residue: self.visibleColToSeqPosOneBased(row, visibleCol),
|
|
3169
|
-
letter: self.
|
|
3184
|
+
letter: self.rowSequence(row)?.[column],
|
|
3170
3185
|
};
|
|
3171
3186
|
},
|
|
3172
3187
|
/**
|
|
@@ -3331,7 +3346,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3331
3346
|
: visibleDomainTypes.map(d => ({
|
|
3332
3347
|
id: d.accession,
|
|
3333
3348
|
label: d.name,
|
|
3334
|
-
color: fillPalette[d.accession],
|
|
3349
|
+
color: d.color ?? fillPalette[d.accession],
|
|
3335
3350
|
})), 'domains');
|
|
3336
3351
|
}
|
|
3337
3352
|
for (const { channel, field, legend } of this.resolvedEncodings) {
|
|
@@ -3567,7 +3582,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3567
3582
|
* to `end`, gaps included
|
|
3568
3583
|
*/
|
|
3569
3584
|
get selectionFasta() {
|
|
3570
|
-
const { rowNames
|
|
3585
|
+
const { rowNames } = self;
|
|
3571
3586
|
const resolved = this.resolvedSelection;
|
|
3572
3587
|
const span = this.selectionFileSpan;
|
|
3573
3588
|
if (!resolved || !span) {
|
|
@@ -3576,7 +3591,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3576
3591
|
const rows = resolved.rowRuns
|
|
3577
3592
|
.flatMap(([first, last]) => rowNames.slice(first, last + 1))
|
|
3578
3593
|
.flatMap(name => {
|
|
3579
|
-
const seq =
|
|
3594
|
+
const seq = self.rowSequence(name);
|
|
3580
3595
|
return seq === undefined ? [] : [[name, seq]];
|
|
3581
3596
|
});
|
|
3582
3597
|
return blockFasta(rows, span.start, span.end);
|
|
@@ -3655,7 +3670,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3655
3670
|
this.visibleDomainTypes.map(d => ({
|
|
3656
3671
|
id: d.accession,
|
|
3657
3672
|
label: d.name,
|
|
3658
|
-
color: fillPalette[d.accession],
|
|
3673
|
+
color: d.color ?? fillPalette[d.accession],
|
|
3659
3674
|
})),
|
|
3660
3675
|
};
|
|
3661
3676
|
}
|
|
@@ -3804,6 +3819,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3804
3819
|
setHideHeader(arg) {
|
|
3805
3820
|
self.hideHeader = arg;
|
|
3806
3821
|
},
|
|
3822
|
+
/**
|
|
3823
|
+
* #action
|
|
3824
|
+
*/
|
|
3825
|
+
setImportForm(arg) {
|
|
3826
|
+
self.importForm = arg;
|
|
3827
|
+
},
|
|
3807
3828
|
/**
|
|
3808
3829
|
* #action
|
|
3809
3830
|
* focus the subtree a click `y` pixels down the tree overview lands on.
|
|
@@ -3911,6 +3932,24 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3911
3932
|
self.scrollX = clamp(-span.startCol * self.colWidth, self.maxScrollX, 0);
|
|
3912
3933
|
});
|
|
3913
3934
|
},
|
|
3935
|
+
/**
|
|
3936
|
+
* #action
|
|
3937
|
+
* zoomToRegion, and for a region naming a row, also scroll that row to
|
|
3938
|
+
* the middle of the alignment, as Go to does. What `region` opens on; a
|
|
3939
|
+
* caller following a range as it moves wants zoomToRegion alone, which
|
|
3940
|
+
* leaves the reader's vertical scroll where they put it.
|
|
3941
|
+
*/
|
|
3942
|
+
revealRegion(region) {
|
|
3943
|
+
transaction(() => {
|
|
3944
|
+
this.zoomToRegion(region);
|
|
3945
|
+
const row = region.row === undefined
|
|
3946
|
+
? undefined
|
|
3947
|
+
: self.rowNamesSet.get(region.row);
|
|
3948
|
+
if (row !== undefined && self.viewInitialized) {
|
|
3949
|
+
self.setScrollY(-(row + 0.5) * self.rowHeight + self.msaAreaHeight / 2);
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3952
|
+
},
|
|
3914
3953
|
/**
|
|
3915
3954
|
* #action
|
|
3916
3955
|
* zoom and scroll so the selected columns fill the alignment's width,
|
|
@@ -4011,7 +4050,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4011
4050
|
self.viewInitialized &&
|
|
4012
4051
|
self.numColumns > 0 &&
|
|
4013
4052
|
!(self.treeFilehandle && !self.data.tree)) {
|
|
4014
|
-
this.
|
|
4053
|
+
this.revealRegion(region);
|
|
4015
4054
|
self.setRegion(undefined);
|
|
4016
4055
|
}
|
|
4017
4056
|
}));
|
|
@@ -4073,7 +4112,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4073
4112
|
};
|
|
4074
4113
|
try {
|
|
4075
4114
|
setLoading?.(true);
|
|
4076
|
-
|
|
4115
|
+
if (!optional) {
|
|
4116
|
+
self.setError(undefined);
|
|
4117
|
+
}
|
|
4077
4118
|
const text = await fetchTextWithProgress(openLocation(filehandle), status => {
|
|
4078
4119
|
if (isCurrent()) {
|
|
4079
4120
|
self.setStatus(status);
|