react-msaview 8.4.1 → 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/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1892 -7
- package/dist/components/MSAViewer.js +144 -202
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +4 -3
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +3 -2
- 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 +48 -1
- 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/TreeCanvas.js +4 -4
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeOverview.js +10 -5
- package/dist/components/tree/TreeOverview.js.map +1 -1
- package/dist/components/tree/TreePanel.js +6 -2
- package/dist/components/tree/TreePanel.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +2 -2
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +22 -17
- package/dist/components/tree/cladeBrackets.js +40 -9
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +2 -0
- package/dist/components/tree/clickMap.js.map +1 -1
- package/dist/components/tree/ellipsize.d.ts +8 -0
- package/dist/components/tree/ellipsize.js +20 -0
- package/dist/components/tree/ellipsize.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.js +30 -32
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeMouseover.js +1 -1
- package/dist/components/tree/renderTreeMouseover.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/constants.d.ts +6 -3
- package/dist/constants.js +16 -6
- package/dist/constants.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 +10 -8
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +14 -25
- package/dist/model/treeModel.js +16 -30
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +108 -79
- package/dist/model.js +258 -209
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +49 -135
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +48 -48
- package/dist/tipLabels.d.ts +8 -0
- package/dist/tipLabels.js +46 -0
- package/dist/tipLabels.js.map +1 -0
- 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/MSAView.tsx +2 -2
- package/src/components/MSAViewer.tsx +276 -256
- package/src/components/ResizeHandles.tsx +10 -3
- package/src/components/Track.tsx +3 -2
- 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 +67 -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/TreeCanvas.tsx +10 -4
- package/src/components/tree/TreeCanvasBlock.tsx +4 -4
- package/src/components/tree/TreeOverview.tsx +10 -5
- package/src/components/tree/TreePanel.tsx +8 -2
- package/src/components/tree/TreeRuler.tsx +7 -3
- package/src/components/tree/cladeBrackets.ts +59 -19
- package/src/components/tree/clickMap.ts +2 -0
- package/src/components/tree/ellipsize.ts +24 -0
- package/src/components/tree/renderTreeCanvas.ts +37 -36
- package/src/components/tree/renderTreeMouseover.ts +1 -1
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/constants.ts +17 -6
- 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 +10 -8
- package/src/model/treeModel.ts +17 -34
- package/src/model.ts +296 -243
- package/src/renderToSvg.tsx +72 -209
- package/src/tipLabels.ts +51 -0
- 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,18 +12,16 @@ 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
|
-
import { cladeHighlightAlpha, cladeHighlightColor, defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, defaultFeaturePanelWidth, defaultHeight, defaultHideGaps, defaultRowHeight, defaultScrollX, defaultScrollY, defaultScrollZoom, defaultScrollZoomAxis, defaultShowDomainLegend, defaultShowDomains, defaultSubFeatureRows,
|
|
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';
|
|
19
18
|
import { createPaletteMap } from './createPaletteMap.js';
|
|
20
19
|
import { exportFileName } from './exportFileName.js';
|
|
21
20
|
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 {
|
|
26
|
-
import { measureTextCanvas } from './measureTextCanvas.js';
|
|
24
|
+
import { collapse, find, forEachDescendant, hierarchy, layoutTree, leafIndex, leaves, mrca, nodeCoveringRows, sort, sum as hierarchySum, } from './hierarchy.js';
|
|
27
25
|
import { DataModelF } from './model/DataModel.js';
|
|
28
26
|
import { DialogQueueSessionMixin } from './model/DialogQueue.js';
|
|
29
27
|
import { MSAModelF } from './model/msaModel.js';
|
|
@@ -37,6 +35,7 @@ import { resolveScale } from './scales.js';
|
|
|
37
35
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.js';
|
|
38
36
|
import { maxBitsFor } from './sequenceLogo.js';
|
|
39
37
|
import { stripDefault } from './stripDefault.js';
|
|
38
|
+
import { treeTips, widestLabel } from './tipLabels.js';
|
|
40
39
|
import { computeRowInsertions, dropBlanks, len, outlineColor, skipBlanks, withAlpha, } from './util.js';
|
|
41
40
|
import { saveAs } from './vendor/fileSaver.js';
|
|
42
41
|
import { writeNewick } from './writeNewick.js';
|
|
@@ -303,7 +302,6 @@ export const preservedOnReset = new Set([
|
|
|
303
302
|
'drawNodeLabels',
|
|
304
303
|
'showTreeOverview',
|
|
305
304
|
'overviewHeight',
|
|
306
|
-
'autoTreeAreaWidth',
|
|
307
305
|
'turnedOffTracks',
|
|
308
306
|
'trackHeights',
|
|
309
307
|
'hideGaps',
|
|
@@ -323,33 +321,71 @@ const seedingMarks = new Set(['collapse', 'focus', 'rotate']);
|
|
|
323
321
|
// shared empty results, so observers don't see a fresh [] as a change
|
|
324
322
|
const noDomains = [];
|
|
325
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
|
+
}
|
|
326
359
|
/**
|
|
327
360
|
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
328
361
|
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
329
|
-
* leaf count `tips` disagrees with. `
|
|
330
|
-
*
|
|
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.
|
|
331
365
|
*/
|
|
332
|
-
function cladeRows(clade, root, index,
|
|
366
|
+
function cladeRows(clade, root, index, tips) {
|
|
333
367
|
if (clade.range) {
|
|
334
|
-
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);
|
|
335
369
|
if (a === undefined || b === undefined) {
|
|
336
370
|
return undefined;
|
|
337
371
|
}
|
|
338
|
-
const
|
|
339
|
-
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;
|
|
340
376
|
}
|
|
341
377
|
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined;
|
|
342
378
|
if (!node) {
|
|
343
379
|
return undefined;
|
|
344
380
|
}
|
|
345
|
-
const
|
|
346
|
-
if (
|
|
381
|
+
const nodeTips = leaves(node);
|
|
382
|
+
if (nodeTips.length !== clade.tips) {
|
|
347
383
|
return undefined;
|
|
348
384
|
}
|
|
349
385
|
let first = Infinity;
|
|
350
386
|
let last = -Infinity;
|
|
351
|
-
for (const tip of
|
|
352
|
-
const row =
|
|
387
|
+
for (const tip of nodeTips) {
|
|
388
|
+
const row = tips.get(tip.data.name)?.row;
|
|
353
389
|
if (row !== undefined) {
|
|
354
390
|
first = Math.min(first, row);
|
|
355
391
|
last = Math.max(last, row);
|
|
@@ -360,15 +396,17 @@ function cladeRows(clade, root, index, rowNamesSet) {
|
|
|
360
396
|
: undefined;
|
|
361
397
|
}
|
|
362
398
|
/**
|
|
363
|
-
* Every clade that resolves, with its fill color settled, against the
|
|
364
|
-
*
|
|
365
|
-
* rows and the overview against its own, so both take the tree they
|
|
366
|
-
* `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.
|
|
367
404
|
*/
|
|
368
|
-
function resolveClades(clades, root,
|
|
405
|
+
function resolveClades(clades, root, displayed) {
|
|
369
406
|
const index = leafIndex(root);
|
|
407
|
+
const tips = tipRows(displayed);
|
|
370
408
|
return clades.flatMap(clade => {
|
|
371
|
-
const resolved = cladeRows(clade, root, index,
|
|
409
|
+
const resolved = cladeRows(clade, root, index, tips);
|
|
372
410
|
const seeding = seedingMarks.has(clade.mark);
|
|
373
411
|
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
374
412
|
? [
|
|
@@ -471,8 +509,8 @@ function rootTree(tree, treeRoot) {
|
|
|
471
509
|
? generateNodeIds(rerootTree(input, plain.get(target.data)))
|
|
472
510
|
: tree;
|
|
473
511
|
}
|
|
474
|
-
// the inclusive tip indices a node covers, from
|
|
475
|
-
//
|
|
512
|
+
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
513
|
+
// row per tip
|
|
476
514
|
function tipRange(node) {
|
|
477
515
|
return [Math.round(node.xMin - 0.5), Math.round(node.xMax - 0.5)];
|
|
478
516
|
}
|
|
@@ -484,6 +522,15 @@ const featureChannels = new Set([
|
|
|
484
522
|
// seqPos -> column indexes per row, keyed on the parse so they are garbage
|
|
485
523
|
// collected with it. A computed would rebuild every row's index when read
|
|
486
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
|
+
}
|
|
487
534
|
const seqPosIndexCache = new WeakMap();
|
|
488
535
|
// A segment is a 1:1 run. A segment whose sides differ in length is malformed,
|
|
489
536
|
// and the lookups treat it like an uncovered position.
|
|
@@ -769,9 +816,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
769
816
|
/**
|
|
770
817
|
* #property
|
|
771
818
|
* where the view opens, in `highlights` coordinates: `{row, start,
|
|
772
|
-
* end}` zooms onto residues of that row
|
|
773
|
-
*
|
|
774
|
-
* 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.
|
|
775
823
|
*/
|
|
776
824
|
region: types.frozen(),
|
|
777
825
|
/**
|
|
@@ -812,6 +860,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
812
860
|
* of the snapshot so a link opened in the full app shows the toolbar.
|
|
813
861
|
*/
|
|
814
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,
|
|
815
869
|
/**
|
|
816
870
|
* #volatile
|
|
817
871
|
*/
|
|
@@ -1276,17 +1330,15 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1276
1330
|
.views(self => ({
|
|
1277
1331
|
/**
|
|
1278
1332
|
* #getter
|
|
1279
|
-
* 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
|
|
1280
1336
|
*/
|
|
1281
1337
|
get hideGapsEffective() {
|
|
1282
1338
|
return (self.hideGaps &&
|
|
1283
|
-
(self.collapsed.length > 0 ||
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
* #getter
|
|
1287
|
-
*/
|
|
1288
|
-
get realAllowedGappyness() {
|
|
1289
|
-
return this.hideGapsEffective ? self.allowedGappyness : 100;
|
|
1339
|
+
(self.collapsed.length > 0 ||
|
|
1340
|
+
self.showOnly !== undefined ||
|
|
1341
|
+
self.allowedGappyness < 100));
|
|
1290
1342
|
},
|
|
1291
1343
|
/**
|
|
1292
1344
|
* #getter
|
|
@@ -1420,10 +1472,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1420
1472
|
* the row table: extra fields per row, keyed by row name, which the
|
|
1421
1473
|
* `encodings` channels read. It is stored as the JSON string
|
|
1422
1474
|
* `data.treeMetadata`, the name that travels in existing links, so the
|
|
1423
|
-
* inline size limit and `treeMetadataFilehandle` cover it.
|
|
1424
|
-
* reads it on every layout, so a malformed user-supplied file
|
|
1425
|
-
* instead of throwing out of rendering, and every field is a
|
|
1426
|
-
* the time a scale sorts it.
|
|
1475
|
+
* inline size limit and `treeMetadataFilehandle` cover it. The tree area
|
|
1476
|
+
* fit reads it on every layout, so a malformed user-supplied file
|
|
1477
|
+
* returns {} instead of throwing out of rendering, and every field is a
|
|
1478
|
+
* string by the time a scale sorts it.
|
|
1427
1479
|
*/
|
|
1428
1480
|
get rowData() {
|
|
1429
1481
|
const text = self.data.treeMetadata;
|
|
@@ -1535,7 +1587,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1535
1587
|
if (self.clades.length === 0) {
|
|
1536
1588
|
return noClades;
|
|
1537
1589
|
}
|
|
1538
|
-
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), this.
|
|
1590
|
+
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), this.root);
|
|
1539
1591
|
},
|
|
1540
1592
|
/**
|
|
1541
1593
|
* #getter
|
|
@@ -1544,11 +1596,93 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1544
1596
|
* where no clade draws a bar or a label.
|
|
1545
1597
|
*/
|
|
1546
1598
|
get cladeGutterWidth() {
|
|
1547
|
-
return cladeGutterWidth(
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1599
|
+
return cladeGutterWidth(self.clades);
|
|
1600
|
+
},
|
|
1601
|
+
/**
|
|
1602
|
+
* #getter
|
|
1603
|
+
* whether a tree draws beside the labels: false for a file with no
|
|
1604
|
+
* tree, and with drawTree off
|
|
1605
|
+
*/
|
|
1606
|
+
get treeDrawn() {
|
|
1607
|
+
return self.drawTree && !this.noTree;
|
|
1608
|
+
},
|
|
1609
|
+
/**
|
|
1610
|
+
* #getter
|
|
1611
|
+
* the leaf names of the whole tree and its depth in levels. The fit
|
|
1612
|
+
* reads these, so a collapse or a focus leaves the tree area as it is
|
|
1613
|
+
*/
|
|
1614
|
+
get treeTips() {
|
|
1615
|
+
return treeTips(this.tree);
|
|
1616
|
+
},
|
|
1617
|
+
/**
|
|
1618
|
+
* #getter
|
|
1619
|
+
* the widest tip label at labelFitFontSize, measured whether or not a
|
|
1620
|
+
* zoom hides the labels
|
|
1621
|
+
*/
|
|
1622
|
+
get maxTipLabelWidth() {
|
|
1623
|
+
return widestLabel(this.treeTips.names.map(name => this.rowDataOf(name)?.genome || name), labelFitFontSize);
|
|
1624
|
+
},
|
|
1625
|
+
/**
|
|
1626
|
+
* #getter
|
|
1627
|
+
* the px the tip labels take across the tree area: the widest label at
|
|
1628
|
+
* labelFitFontSize and the gaps either side of it, or 0 with drawLabels
|
|
1629
|
+
* off
|
|
1630
|
+
*/
|
|
1631
|
+
get labelColumnWidth() {
|
|
1632
|
+
return self.drawLabels
|
|
1633
|
+
? this.maxTipLabelWidth + (this.treeDrawn ? 10 : 6)
|
|
1634
|
+
: 0;
|
|
1635
|
+
},
|
|
1636
|
+
/**
|
|
1637
|
+
* #getter
|
|
1638
|
+
* the tree area width while treeAreaWidth is undefined: the label column
|
|
1639
|
+
* beside a tree that widens with its depth, up to maxTreeAreaShare of the
|
|
1640
|
+
* view, or the room the row panels leave where there is no alignment.
|
|
1641
|
+
* Where that binds, the tree keeps minTreeWidth and the renderer
|
|
1642
|
+
* ellipsizes the labels
|
|
1643
|
+
*/
|
|
1644
|
+
get fittedTreeAreaWidth() {
|
|
1645
|
+
const chrome = self.marginLeft + this.cladeGutterWidth;
|
|
1646
|
+
const tree = this.treeDrawn
|
|
1647
|
+
? (self.treeWidth ??
|
|
1648
|
+
clamp(this.treeTips.depth * pxPerTreeDepth, minTreeWidth, maxFitTreeWidth))
|
|
1649
|
+
: 0;
|
|
1650
|
+
const fit = chrome + tree + this.labelColumnWidth;
|
|
1651
|
+
const room = self.viewInitialized
|
|
1652
|
+
? this.numColumns > 0
|
|
1653
|
+
? self.width * maxTreeAreaShare
|
|
1654
|
+
: self.width - this.rowPanelsWidth - self.resizeHandleWidth
|
|
1655
|
+
: Infinity;
|
|
1656
|
+
const cap = Math.max(room, chrome + Math.min(tree, minTreeWidth));
|
|
1657
|
+
return Math.ceil(Math.min(fit, cap));
|
|
1658
|
+
},
|
|
1659
|
+
/**
|
|
1660
|
+
* #getter
|
|
1661
|
+
* the width of the area the tree and its labels draw in, px:
|
|
1662
|
+
* treeAreaWidth where set, the fit otherwise
|
|
1663
|
+
*/
|
|
1664
|
+
get effectiveTreeAreaWidth() {
|
|
1665
|
+
return self.treeAreaWidth ?? this.fittedTreeAreaWidth;
|
|
1666
|
+
},
|
|
1667
|
+
/**
|
|
1668
|
+
* #getter
|
|
1669
|
+
* the width of the tree, px: treeWidth where set, otherwise the tree
|
|
1670
|
+
* area less the label column, which a zoom hiding the labels gives to
|
|
1671
|
+
* the tree
|
|
1672
|
+
*/
|
|
1673
|
+
get effectiveTreeWidth() {
|
|
1674
|
+
const labels = this.showTreeText ? this.labelColumnWidth : 0;
|
|
1675
|
+
return (self.treeWidth ??
|
|
1676
|
+
Math.max(minTreeWidth, Math.floor(this.effectiveTreeAreaWidth -
|
|
1677
|
+
self.marginLeft -
|
|
1678
|
+
this.cladeGutterWidth -
|
|
1679
|
+
labels)));
|
|
1680
|
+
},
|
|
1681
|
+
/**
|
|
1682
|
+
* #getter
|
|
1683
|
+
*/
|
|
1684
|
+
get showTreeText() {
|
|
1685
|
+
return self.drawLabels && self.rowHeight >= minLetterRowHeight;
|
|
1552
1686
|
},
|
|
1553
1687
|
/**
|
|
1554
1688
|
* #getter
|
|
@@ -1621,21 +1755,16 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1621
1755
|
if (!self.showTreeOverview) {
|
|
1622
1756
|
return undefined;
|
|
1623
1757
|
}
|
|
1624
|
-
const
|
|
1758
|
+
const layout = layoutTree(buildTreeRoot(this.tree, {
|
|
1625
1759
|
collapsed: self.collapsed,
|
|
1626
1760
|
rotated: self.rotated,
|
|
1627
1761
|
order: self.treeOrder,
|
|
1628
|
-
});
|
|
1629
|
-
const numTips = leaves(root).length;
|
|
1630
|
-
clusterLayout(root, numTips, 1);
|
|
1631
|
-
const rootLen = Math.max(root.data.length || 0, 0);
|
|
1632
|
-
const extent = maxLength(root) - rootLen;
|
|
1633
|
-
setBrLength(root, -rootLen, extent ? 1 / extent : 0);
|
|
1762
|
+
}), { rowHeight: 1, treeWidth: 1 });
|
|
1634
1763
|
return {
|
|
1635
|
-
root,
|
|
1636
|
-
numTips,
|
|
1637
|
-
maxDepthToLeaf:
|
|
1638
|
-
showBranchLen: self.showBranchLen &&
|
|
1764
|
+
root: layout.root,
|
|
1765
|
+
numTips: layout.leaves.length,
|
|
1766
|
+
maxDepthToLeaf: layout.root.depthToLeaf,
|
|
1767
|
+
showBranchLen: self.showBranchLen && layout.rootToTipLength > 0,
|
|
1639
1768
|
};
|
|
1640
1769
|
},
|
|
1641
1770
|
/**
|
|
@@ -1648,8 +1777,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1648
1777
|
if (!layout || self.clades.length === 0) {
|
|
1649
1778
|
return noClades;
|
|
1650
1779
|
}
|
|
1651
|
-
|
|
1652
|
-
return resolveClades(self.clades, layout.root, rowNames);
|
|
1780
|
+
return resolveClades(self.clades, hierarchy(this.tree, d => d.children), layout.root);
|
|
1653
1781
|
},
|
|
1654
1782
|
/**
|
|
1655
1783
|
* #getter
|
|
@@ -1714,7 +1842,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1714
1842
|
*/
|
|
1715
1843
|
get msaAreaWidth() {
|
|
1716
1844
|
return (self.width -
|
|
1717
|
-
|
|
1845
|
+
this.effectiveTreeAreaWidth -
|
|
1718
1846
|
this.rowPanelsWidth -
|
|
1719
1847
|
self.resizeHandleWidth);
|
|
1720
1848
|
},
|
|
@@ -1724,13 +1852,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1724
1852
|
* margin and the bracket gutter
|
|
1725
1853
|
*/
|
|
1726
1854
|
get treeAreaWidthMinusMargin() {
|
|
1727
|
-
return
|
|
1855
|
+
return (this.effectiveTreeAreaWidth - self.marginLeft - this.cladeGutterWidth);
|
|
1728
1856
|
},
|
|
1729
1857
|
/**
|
|
1730
1858
|
* #getter
|
|
1731
1859
|
*/
|
|
1732
1860
|
get blanks() {
|
|
1733
|
-
const { hideGapsEffective,
|
|
1861
|
+
const { hideGapsEffective, allowedGappyness } = self;
|
|
1734
1862
|
if (!hideGapsEffective) {
|
|
1735
1863
|
return [];
|
|
1736
1864
|
}
|
|
@@ -1746,7 +1874,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1746
1874
|
// row that stops early counts as gapped for the remainder
|
|
1747
1875
|
const numCols = this.MSA?.getWidth() ?? 0;
|
|
1748
1876
|
const numRows = strs.length;
|
|
1749
|
-
const threshold = Math.ceil((
|
|
1877
|
+
const threshold = Math.ceil((allowedGappyness / 100) * numRows);
|
|
1750
1878
|
const blankCounts = new Uint32Array(numCols);
|
|
1751
1879
|
for (let j = 0; j < numRows; j++) {
|
|
1752
1880
|
const str = strs[j];
|
|
@@ -1828,11 +1956,19 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1828
1956
|
}
|
|
1829
1957
|
return index;
|
|
1830
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
|
+
},
|
|
1831
1968
|
/**
|
|
1832
1969
|
* #getter
|
|
1833
|
-
* every sequence in the alignment, keyed by
|
|
1834
|
-
*
|
|
1835
|
-
* 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
|
|
1836
1972
|
*/
|
|
1837
1973
|
get rowMap() {
|
|
1838
1974
|
const MSA = this.MSA;
|
|
@@ -1860,7 +1996,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1860
1996
|
* #getter
|
|
1861
1997
|
*/
|
|
1862
1998
|
get fontSize() {
|
|
1863
|
-
return
|
|
1999
|
+
return fontSizeFor(self.rowHeight);
|
|
1864
2000
|
},
|
|
1865
2001
|
/**
|
|
1866
2002
|
* #getter
|
|
@@ -1875,10 +2011,11 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1875
2011
|
*/
|
|
1876
2012
|
get sequenceType() {
|
|
1877
2013
|
const letters = this.colStats.lettersPresent;
|
|
1878
|
-
//
|
|
1879
|
-
//
|
|
1880
|
-
const
|
|
1881
|
-
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]);
|
|
1882
2019
|
const isDna = letters.size > 0 && [...letters].every(l => dna.has(l));
|
|
1883
2020
|
const isRna = letters.size > 0 && [...letters].every(l => rna.has(l));
|
|
1884
2021
|
return isDna ? 'dna' : isRna ? 'rna' : 'amino';
|
|
@@ -1959,17 +2096,18 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1959
2096
|
/**
|
|
1960
2097
|
* #getter
|
|
1961
2098
|
* `root` laid out on new nodes in row and branch-length units, read in
|
|
1962
|
-
* pixels through `rowHeight` and `
|
|
1963
|
-
* node in it as they are, and an observer reading a node's `x`
|
|
1964
|
-
* observes the size it scales by.
|
|
2099
|
+
* pixels through `rowHeight` and `effectiveTreeWidth`. A zoom leaves it
|
|
2100
|
+
* and every node in it as they are, and an observer reading a node's `x`
|
|
2101
|
+
* or `len` observes the size it scales by.
|
|
1965
2102
|
*/
|
|
1966
2103
|
get treeLayout() {
|
|
2104
|
+
const treeWidth = () => this.effectiveTreeWidth;
|
|
1967
2105
|
return layoutTree(this.root, {
|
|
1968
2106
|
get rowHeight() {
|
|
1969
2107
|
return self.rowHeight;
|
|
1970
2108
|
},
|
|
1971
2109
|
get treeWidth() {
|
|
1972
|
-
return
|
|
2110
|
+
return treeWidth();
|
|
1973
2111
|
},
|
|
1974
2112
|
});
|
|
1975
2113
|
},
|
|
@@ -2000,14 +2138,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2000
2138
|
get rootToTipLength() {
|
|
2001
2139
|
return this.treeLayout.rootToTipLength;
|
|
2002
2140
|
},
|
|
2003
|
-
/**
|
|
2004
|
-
* #getter
|
|
2005
|
-
* x-position of the farthest tip in a phylogram, px: treeWidth, or 0 for
|
|
2006
|
-
* a tree with no branch lengths (drawn as a cladogram)
|
|
2007
|
-
*/
|
|
2008
|
-
get maxBranchLength() {
|
|
2009
|
-
return this.rootToTipLength ? self.treeWidth : 0;
|
|
2010
|
-
},
|
|
2011
2141
|
/**
|
|
2012
2142
|
* #getter
|
|
2013
2143
|
* pixels per unit of branch length in the phylogram layout, 0 in
|
|
@@ -2015,7 +2145,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2015
2145
|
*/
|
|
2016
2146
|
get pxPerBranchLength() {
|
|
2017
2147
|
const max = this.rootToTipLength;
|
|
2018
|
-
return this.showBranchLenEffective && max
|
|
2148
|
+
return this.showBranchLenEffective && max
|
|
2149
|
+
? this.effectiveTreeWidth / max
|
|
2150
|
+
: 0;
|
|
2019
2151
|
},
|
|
2020
2152
|
/**
|
|
2021
2153
|
* #getter
|
|
@@ -2056,12 +2188,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2056
2188
|
self.colWidth >= minLetterColWidth &&
|
|
2057
2189
|
self.colWidth > self.rowHeight / 2);
|
|
2058
2190
|
},
|
|
2059
|
-
/**
|
|
2060
|
-
* #getter
|
|
2061
|
-
*/
|
|
2062
|
-
get showTreeText() {
|
|
2063
|
-
return self.drawLabels && self.rowHeight >= minLetterRowHeight;
|
|
2064
|
-
},
|
|
2065
2191
|
}))
|
|
2066
2192
|
.views(self => {
|
|
2067
2193
|
// a spec is a frozen value, so it keys its computed until setColumnTracks
|
|
@@ -2167,7 +2293,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2167
2293
|
data: content?.data,
|
|
2168
2294
|
arcs: content?.arcs,
|
|
2169
2295
|
},
|
|
2170
|
-
ReactComponent: TrackBlocks,
|
|
2171
2296
|
};
|
|
2172
2297
|
});
|
|
2173
2298
|
columnTrackModels.set(track, computedModel);
|
|
@@ -2183,48 +2308,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2183
2308
|
};
|
|
2184
2309
|
})
|
|
2185
2310
|
.views(self => ({
|
|
2186
|
-
/**
|
|
2187
|
-
* #getter
|
|
2188
|
-
*/
|
|
2189
|
-
get labelWidthMap() {
|
|
2190
|
-
const { showTreeText, leaves } = self;
|
|
2191
|
-
// gated on the renderer's label condition, so hidden labels reserve no
|
|
2192
|
-
// gutter. Measured once at a reference size and scaled by
|
|
2193
|
-
// labelWidthScale: re-measuring per vertical-zoom frame cost ~200ms on a
|
|
2194
|
-
// 50k-leaf tree
|
|
2195
|
-
return showTreeText
|
|
2196
|
-
? new Map(leaves.map(node => {
|
|
2197
|
-
const { name } = node.data;
|
|
2198
|
-
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
2199
|
-
// to the row name
|
|
2200
|
-
const displayName = self.rowDataOf(name)?.genome || name;
|
|
2201
|
-
return [
|
|
2202
|
-
name,
|
|
2203
|
-
measureTextCanvas(displayName, labelReferenceFontSize),
|
|
2204
|
-
];
|
|
2205
|
-
}))
|
|
2206
|
-
: new Map();
|
|
2207
|
-
},
|
|
2208
|
-
/**
|
|
2209
|
-
* #getter
|
|
2210
|
-
* factor turning a labelWidthMap entry into its width at the current
|
|
2211
|
-
* font size
|
|
2212
|
-
*/
|
|
2213
|
-
get labelWidthScale() {
|
|
2214
|
-
return self.fontSize / labelReferenceFontSize;
|
|
2215
|
-
},
|
|
2216
|
-
get labelsWidth() {
|
|
2217
|
-
// a loop, not Math.max(...): spreading passes one argument per leaf,
|
|
2218
|
-
// and the ~125k argument limit throws RangeError on the 230k-tip COVID
|
|
2219
|
-
// tree
|
|
2220
|
-
let max = 0;
|
|
2221
|
-
for (const width of this.labelWidthMap.values()) {
|
|
2222
|
-
if (width > max) {
|
|
2223
|
-
max = width;
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
return max * this.labelWidthScale;
|
|
2227
|
-
},
|
|
2228
2311
|
/**
|
|
2229
2312
|
* #getter
|
|
2230
2313
|
*/
|
|
@@ -2267,7 +2350,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2267
2350
|
data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
|
|
2268
2351
|
height: rowHeight,
|
|
2269
2352
|
},
|
|
2270
|
-
ReactComponent: TrackBlocks,
|
|
2271
2353
|
}));
|
|
2272
2354
|
},
|
|
2273
2355
|
/**
|
|
@@ -2289,7 +2371,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2289
2371
|
height: self.trackHeight('arc'),
|
|
2290
2372
|
arcs,
|
|
2291
2373
|
},
|
|
2292
|
-
ReactComponent: TrackBlocks,
|
|
2293
2374
|
},
|
|
2294
2375
|
]
|
|
2295
2376
|
: [];
|
|
@@ -2336,7 +2417,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2336
2417
|
heightKey: resizableKinds.has(model.kind) ? model.kind : undefined,
|
|
2337
2418
|
height: self.trackHeight(model.kind),
|
|
2338
2419
|
},
|
|
2339
|
-
ReactComponent: TrackBlocks,
|
|
2340
2420
|
}));
|
|
2341
2421
|
},
|
|
2342
2422
|
get tracks() {
|
|
@@ -2372,7 +2452,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2372
2452
|
* @returns The character at that position
|
|
2373
2453
|
*/
|
|
2374
2454
|
visibleColToRowLetter(rowName, visibleCol) {
|
|
2375
|
-
return self.
|
|
2455
|
+
return self.rowSequence(rowName)?.[this.visibleColToGlobalCol(visibleCol)];
|
|
2376
2456
|
},
|
|
2377
2457
|
/**
|
|
2378
2458
|
* #method
|
|
@@ -2447,7 +2527,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2447
2527
|
* @returns The global column index in the full MSA, or undefined
|
|
2448
2528
|
*/
|
|
2449
2529
|
seqPosToGlobalCol(rowName, seqPos) {
|
|
2450
|
-
const seq = self.
|
|
2530
|
+
const seq = self.rowSequence(rowName);
|
|
2451
2531
|
if (seq === undefined) {
|
|
2452
2532
|
return undefined;
|
|
2453
2533
|
}
|
|
@@ -2663,7 +2743,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2663
2743
|
* or when the tree area is too narrow for one
|
|
2664
2744
|
*/
|
|
2665
2745
|
get treeScaleBar() {
|
|
2666
|
-
return scaleBarLength(self.pxPerBranchLength, self.
|
|
2746
|
+
return scaleBarLength(self.pxPerBranchLength, self.effectiveTreeAreaWidth - self.marginLeft * 2);
|
|
2667
2747
|
},
|
|
2668
2748
|
/**
|
|
2669
2749
|
* #getter
|
|
@@ -2713,7 +2793,8 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2713
2793
|
return self.allAnnotations.filter(r => !self.turnedOffFeatures.get(r.accession));
|
|
2714
2794
|
},
|
|
2715
2795
|
get annotationsByRow() {
|
|
2716
|
-
|
|
2796
|
+
const { rowNamesSet } = self;
|
|
2797
|
+
return groupBy(this.filteredAnnotations, r => annotationRowName(r.id, rowNamesSet));
|
|
2717
2798
|
},
|
|
2718
2799
|
}))
|
|
2719
2800
|
.views(self => ({
|
|
@@ -2801,7 +2882,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2801
2882
|
const { annotations, rowNamesSet } = self;
|
|
2802
2883
|
return self.dataInitialized &&
|
|
2803
2884
|
annotations.length > 0 &&
|
|
2804
|
-
!annotations.some(a => rowNamesSet.has(a.id))
|
|
2885
|
+
!annotations.some(a => rowNamesSet.has(annotationRowName(a.id, rowNamesSet)))
|
|
2805
2886
|
? [
|
|
2806
2887
|
`0 of ${annotations.length} annotations name a row in this alignment`,
|
|
2807
2888
|
]
|
|
@@ -2926,10 +3007,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2926
3007
|
* #action
|
|
2927
3008
|
* swap in a different tree over the same alignment. Clears `collapsed`,
|
|
2928
3009
|
* `rotated` and `showOnly`, since path-derived node ids (node-0-0-1) from the old
|
|
2929
|
-
* 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
|
|
2930
3013
|
*/
|
|
2931
3014
|
replaceTree(newick) {
|
|
2932
3015
|
transaction(() => {
|
|
3016
|
+
self.setTreeFilehandle(undefined);
|
|
2933
3017
|
self.collapsed.clear();
|
|
2934
3018
|
self.rotated.clear();
|
|
2935
3019
|
self.setShowOnly(undefined);
|
|
@@ -2951,30 +3035,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2951
3035
|
zoomAtCenter(scaleFactor, axis = 'both') {
|
|
2952
3036
|
this.zoomToPos(scaleFactor, self.msaCanvasWidth / 2, self.msaAreaHeight / 2, axis);
|
|
2953
3037
|
},
|
|
2954
|
-
/**
|
|
2955
|
-
* #action
|
|
2956
|
-
*/
|
|
2957
|
-
zoomOutHorizontal() {
|
|
2958
|
-
this.zoomAtCenter(0.75, 'horizontal');
|
|
2959
|
-
},
|
|
2960
|
-
/**
|
|
2961
|
-
* #action
|
|
2962
|
-
*/
|
|
2963
|
-
zoomInHorizontal() {
|
|
2964
|
-
this.zoomAtCenter(1.5, 'horizontal');
|
|
2965
|
-
},
|
|
2966
|
-
/**
|
|
2967
|
-
* #action
|
|
2968
|
-
*/
|
|
2969
|
-
zoomInVertical() {
|
|
2970
|
-
this.zoomAtCenter(1.5, 'vertical');
|
|
2971
|
-
},
|
|
2972
|
-
/**
|
|
2973
|
-
* #action
|
|
2974
|
-
*/
|
|
2975
|
-
zoomOutVertical() {
|
|
2976
|
-
this.zoomAtCenter(0.75, 'vertical');
|
|
2977
|
-
},
|
|
2978
3038
|
/**
|
|
2979
3039
|
* #action
|
|
2980
3040
|
*/
|
|
@@ -3041,15 +3101,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3041
3101
|
setAnnotations(annotations) {
|
|
3042
3102
|
self.annotations = annotations;
|
|
3043
3103
|
},
|
|
3044
|
-
/**
|
|
3045
|
-
* #action
|
|
3046
|
-
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
3047
|
-
* for downstream plugins that hold the EBI wire format; new code should
|
|
3048
|
-
* adapt to Annotation[] and call setAnnotations.
|
|
3049
|
-
*/
|
|
3050
|
-
setDomains(data) {
|
|
3051
|
-
this.setAnnotations(data ? interProScanToAnnotations(data) : []);
|
|
3052
|
-
},
|
|
3053
3104
|
applyGFFText(gffText) {
|
|
3054
3105
|
this.setAnnotations(gffToAnnotations(parseGFF(gffText)));
|
|
3055
3106
|
},
|
|
@@ -3130,7 +3181,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3130
3181
|
column: column + 1,
|
|
3131
3182
|
row,
|
|
3132
3183
|
residue: self.visibleColToSeqPosOneBased(row, visibleCol),
|
|
3133
|
-
letter: self.
|
|
3184
|
+
letter: self.rowSequence(row)?.[column],
|
|
3134
3185
|
};
|
|
3135
3186
|
},
|
|
3136
3187
|
/**
|
|
@@ -3295,7 +3346,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3295
3346
|
: visibleDomainTypes.map(d => ({
|
|
3296
3347
|
id: d.accession,
|
|
3297
3348
|
label: d.name,
|
|
3298
|
-
color: fillPalette[d.accession],
|
|
3349
|
+
color: d.color ?? fillPalette[d.accession],
|
|
3299
3350
|
})), 'domains');
|
|
3300
3351
|
}
|
|
3301
3352
|
for (const { channel, field, legend } of this.resolvedEncodings) {
|
|
@@ -3531,7 +3582,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3531
3582
|
* to `end`, gaps included
|
|
3532
3583
|
*/
|
|
3533
3584
|
get selectionFasta() {
|
|
3534
|
-
const { rowNames
|
|
3585
|
+
const { rowNames } = self;
|
|
3535
3586
|
const resolved = this.resolvedSelection;
|
|
3536
3587
|
const span = this.selectionFileSpan;
|
|
3537
3588
|
if (!resolved || !span) {
|
|
@@ -3540,7 +3591,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3540
3591
|
const rows = resolved.rowRuns
|
|
3541
3592
|
.flatMap(([first, last]) => rowNames.slice(first, last + 1))
|
|
3542
3593
|
.flatMap(name => {
|
|
3543
|
-
const seq =
|
|
3594
|
+
const seq = self.rowSequence(name);
|
|
3544
3595
|
return seq === undefined ? [] : [[name, seq]];
|
|
3545
3596
|
});
|
|
3546
3597
|
return blockFasta(rows, span.start, span.end);
|
|
@@ -3619,7 +3670,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3619
3670
|
this.visibleDomainTypes.map(d => ({
|
|
3620
3671
|
id: d.accession,
|
|
3621
3672
|
label: d.name,
|
|
3622
|
-
color: fillPalette[d.accession],
|
|
3673
|
+
color: d.color ?? fillPalette[d.accession],
|
|
3623
3674
|
})),
|
|
3624
3675
|
};
|
|
3625
3676
|
}
|
|
@@ -3768,6 +3819,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3768
3819
|
setHideHeader(arg) {
|
|
3769
3820
|
self.hideHeader = arg;
|
|
3770
3821
|
},
|
|
3822
|
+
/**
|
|
3823
|
+
* #action
|
|
3824
|
+
*/
|
|
3825
|
+
setImportForm(arg) {
|
|
3826
|
+
self.importForm = arg;
|
|
3827
|
+
},
|
|
3771
3828
|
/**
|
|
3772
3829
|
* #action
|
|
3773
3830
|
* focus the subtree a click `y` pixels down the tree overview lands on.
|
|
@@ -3875,6 +3932,24 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3875
3932
|
self.scrollX = clamp(-span.startCol * self.colWidth, self.maxScrollX, 0);
|
|
3876
3933
|
});
|
|
3877
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
|
+
},
|
|
3878
3953
|
/**
|
|
3879
3954
|
* #action
|
|
3880
3955
|
* zoom and scroll so the selected columns fill the alignment's width,
|
|
@@ -3975,7 +4050,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3975
4050
|
self.viewInitialized &&
|
|
3976
4051
|
self.numColumns > 0 &&
|
|
3977
4052
|
!(self.treeFilehandle && !self.data.tree)) {
|
|
3978
|
-
this.
|
|
4053
|
+
this.revealRegion(region);
|
|
3979
4054
|
self.setRegion(undefined);
|
|
3980
4055
|
}
|
|
3981
4056
|
}));
|
|
@@ -4037,7 +4112,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4037
4112
|
};
|
|
4038
4113
|
try {
|
|
4039
4114
|
setLoading?.(true);
|
|
4040
|
-
|
|
4115
|
+
if (!optional) {
|
|
4116
|
+
self.setError(undefined);
|
|
4117
|
+
}
|
|
4041
4118
|
const text = await fetchTextWithProgress(openLocation(filehandle), status => {
|
|
4042
4119
|
if (isCurrent()) {
|
|
4043
4120
|
self.setStatus(status);
|
|
@@ -4166,34 +4243,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4166
4243
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
4167
4244
|
self.columns;
|
|
4168
4245
|
}));
|
|
4169
|
-
// with autoTreeAreaWidth and no tree drawn, size the tree area to the
|
|
4170
|
-
// labels. Gated on noTree/!drawTree so it does not loop with the
|
|
4171
|
-
// treeWidth autorun below
|
|
4172
|
-
addDisposer(self, autorun(() => {
|
|
4173
|
-
if (self.autoTreeAreaWidth &&
|
|
4174
|
-
(self.noTree || !self.drawTree) &&
|
|
4175
|
-
self.labelsWidth) {
|
|
4176
|
-
self.setTreeAreaWidth(self.labelsWidth + self.marginLeft + 12 + self.cladeGutterWidth);
|
|
4177
|
-
}
|
|
4178
|
-
}));
|
|
4179
|
-
// treeWidth follows the tree area less the labels. A non-default
|
|
4180
|
-
// snapshot treeWidth (jbrowse-plugin-msaview opens at 100 in a 200px
|
|
4181
|
-
// area) is kept until the tree area changes. Not a getter, because
|
|
4182
|
-
// labelsWidth is measured off leaves laid out against treeWidth.
|
|
4183
|
-
let pinnedAreaWidth = self.treeWidth === defaultTreeWidth ? undefined : self.treeAreaWidth;
|
|
4184
|
-
addDisposer(self, autorun(() => {
|
|
4185
|
-
const areaWidth = self.treeAreaWidth;
|
|
4186
|
-
const labelsWidth = self.labelsWidth;
|
|
4187
|
-
if (pinnedAreaWidth === areaWidth) {
|
|
4188
|
-
return;
|
|
4189
|
-
}
|
|
4190
|
-
pinnedAreaWidth = undefined;
|
|
4191
|
-
self.setTreeWidth(Math.max(50, areaWidth -
|
|
4192
|
-
labelsWidth -
|
|
4193
|
-
10 -
|
|
4194
|
-
self.marginLeft -
|
|
4195
|
-
self.cladeGutterWidth));
|
|
4196
|
-
}));
|
|
4197
4246
|
},
|
|
4198
4247
|
}))
|
|
4199
4248
|
.postProcessSnapshot(({ data, columnTracks, ...rest }) => ({
|