react-msaview 9.1.0 → 9.1.1
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 +111 -115
- package/bundle/index.js.map +4 -4
- package/dist/blosum62.d.ts +3 -0
- package/dist/blosum62.js +43 -0
- package/dist/blosum62.js.map +1 -0
- package/dist/colorSchemes.js +1 -0
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/Loading.d.ts +3 -2
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.d.ts +3 -2
- package/dist/components/MSAView.js +7 -4
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +62 -12
- package/dist/components/MetadataTable.d.ts +4 -1
- package/dist/components/MetadataTable.js +55 -16
- package/dist/components/MetadataTable.js.map +1 -1
- package/dist/components/Track.js +6 -0
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.js +2 -1
- package/dist/components/controls.js.map +1 -1
- package/dist/components/dialogs/AnnotationFileDialog.js +7 -2
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +2 -2
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/{header/MSASettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
- package/dist/components/dialogs/GapThresholdDialog.js +15 -0
- package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
- package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
- package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
- package/dist/components/dialogs/GappynessSlider.js.map +1 -0
- package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
- package/dist/components/dialogs/GoToBox.js +48 -0
- package/dist/components/dialogs/GoToBox.js.map +1 -0
- package/dist/components/dialogs/GoToDialog.d.ts +6 -0
- package/dist/components/dialogs/GoToDialog.js +10 -0
- package/dist/components/dialogs/GoToDialog.js.map +1 -0
- package/dist/components/dialogs/MetadataDialog.js +11 -3
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/dialogs/goTo.d.ts +5 -0
- package/dist/components/{header → dialogs}/goTo.js +1 -1
- package/dist/components/dialogs/goTo.js.map +1 -0
- package/dist/components/dialogs/metadataLabels.d.ts +4 -0
- package/dist/components/dialogs/metadataLabels.js +52 -0
- package/dist/components/dialogs/metadataLabels.js.map +1 -0
- package/dist/components/dialogs/metadataRecord.d.ts +1 -0
- package/dist/components/dialogs/metadataRecord.js +38 -0
- package/dist/components/dialogs/metadataRecord.js.map +1 -0
- package/dist/components/header/Header.d.ts +6 -2
- package/dist/components/header/Header.js +30 -32
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{TreeSettingsMenu.d.ts → HiddenColumnsChip.d.ts} +2 -2
- package/dist/components/header/HiddenColumnsChip.js +15 -0
- package/dist/components/header/HiddenColumnsChip.js.map +1 -0
- package/dist/components/header/MenuBar.d.ts +6 -0
- package/dist/components/header/MenuBar.js +33 -0
- package/dist/components/header/MenuBar.js.map +1 -0
- package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
- package/dist/components/header/ScrollZoomCheckbox.js +19 -0
- package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
- package/dist/components/header/ZoomControls.js +10 -30
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
- package/dist/components/header/colorSchemeMenuItems.js +106 -0
- package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
- package/dist/components/header/getAnnotationsMenu.js +48 -54
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/header/menuBarItems.d.ts +11 -0
- package/dist/components/header/menuBarItems.js +277 -0
- package/dist/components/header/menuBarItems.js.map +1 -0
- package/dist/components/import/ImportForm.js +12 -6
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/minimap/Minimap.js +7 -3
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +2 -1
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +12 -0
- package/dist/components/minimap/minimapLayout.js +20 -0
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +26 -9
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +15 -11
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/WheelZoomHint.d.ts +7 -0
- package/dist/components/msa/WheelZoomHint.js +38 -0
- package/dist/components/msa/WheelZoomHint.js.map +1 -0
- package/dist/components/msa/msaKeys.js +3 -0
- package/dist/components/msa/msaKeys.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +4 -5
- package/dist/components/msa/msaRaster.js +5 -5
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +22 -2
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +0 -3
- package/dist/components/msa/renderHighlights.js +3 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +6 -1
- package/dist/components/msa/renderMSABlock.js +44 -17
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.js +7 -0
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +2 -2
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/settings/MenuItemsForm.d.ts +10 -0
- package/dist/components/settings/MenuItemsForm.js +80 -0
- package/dist/components/settings/MenuItemsForm.js.map +1 -0
- package/dist/components/settings/SettingsPanel.d.ts +12 -0
- package/dist/components/settings/SettingsPanel.js +92 -0
- package/dist/components/settings/SettingsPanel.js.map +1 -0
- package/dist/components/{header/FileMenu.d.ts → settings/TrackList.d.ts} +2 -2
- package/dist/components/settings/TrackList.js +55 -0
- package/dist/components/settings/TrackList.js.map +1 -0
- package/dist/components/tracks/TrackBlocks.js +2 -2
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +2 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeMenu.js +4 -6
- package/dist/components/tree/TreeMenu.js.map +1 -1
- package/dist/components/tree/TreeOverview.js +2 -2
- package/dist/components/tree/TreeOverview.js.map +1 -1
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +7 -1
- package/dist/constants.js.map +1 -1
- package/dist/darkReader.d.ts +7 -0
- package/dist/darkReader.js +42 -0
- package/dist/darkReader.js.map +1 -0
- package/dist/element.js +22 -47
- package/dist/element.js.map +1 -1
- package/dist/featurePanels.d.ts +37 -0
- package/dist/featurePanels.js +187 -0
- package/dist/featurePanels.js.map +1 -0
- package/dist/filehandleLoader.d.ts +41 -0
- package/dist/filehandleLoader.js +99 -0
- package/dist/filehandleLoader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/treeModel.d.ts +9 -0
- package/dist/model/treeModel.js +12 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +142 -32
- package/dist/model.js +268 -569
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +1 -40
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToSvg.js +1 -13
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +1 -0
- package/dist/rerootTree.js +7 -6
- package/dist/rerootTree.js.map +1 -1
- package/dist/schemeResidueColor.d.ts +12 -0
- package/dist/schemeResidueColor.js +33 -0
- package/dist/schemeResidueColor.js.map +1 -0
- package/dist/similarityColors.d.ts +11 -0
- package/dist/similarityColors.js +90 -0
- package/dist/similarityColors.js.map +1 -0
- package/dist/svgTestUtil.d.ts +62 -12
- package/dist/treeDisplay.d.ts +37 -0
- package/dist/treeDisplay.js +209 -0
- package/dist/treeDisplay.js.map +1 -0
- package/dist/types.d.ts +6 -2
- package/dist/useWheelScroll.d.ts +3 -1
- package/dist/useWheelScroll.js +36 -17
- package/dist/useWheelScroll.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wheelZoom.d.ts +8 -3
- package/dist/wheelZoom.js +44 -32
- package/dist/wheelZoom.js.map +1 -1
- package/dist/zoomSnapshot.d.ts +29 -0
- package/dist/zoomSnapshot.js +47 -0
- package/dist/zoomSnapshot.js.map +1 -0
- package/package.json +11 -11
- package/src/blosum62.ts +45 -0
- package/src/colorSchemes.ts +1 -0
- package/src/components/Loading.tsx +6 -2
- package/src/components/MSAView.tsx +22 -5
- package/src/components/MetadataTable.tsx +83 -26
- package/src/components/Track.tsx +13 -0
- package/src/components/controls.ts +2 -1
- package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
- package/src/components/dialogs/FeatureDialog.tsx +2 -5
- package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
- package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
- package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
- package/src/components/dialogs/GoToDialog.tsx +30 -0
- package/src/components/dialogs/MetadataDialog.tsx +32 -4
- package/src/components/{header → dialogs}/goTo.ts +1 -1
- package/src/components/dialogs/metadataLabels.ts +54 -0
- package/src/components/dialogs/metadataRecord.tsx +61 -0
- package/src/components/header/Header.tsx +75 -42
- package/src/components/header/HiddenColumnsChip.tsx +39 -0
- package/src/components/header/MenuBar.tsx +64 -0
- package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
- package/src/components/header/ZoomControls.tsx +20 -35
- package/src/components/header/colorSchemeMenuItems.ts +121 -0
- package/src/components/header/getAnnotationsMenu.ts +52 -56
- package/src/components/header/menuBarItems.ts +357 -0
- package/src/components/import/ImportForm.tsx +19 -7
- package/src/components/minimap/Minimap.tsx +11 -3
- package/src/components/minimap/MinimapSVG.tsx +17 -1
- package/src/components/minimap/minimapLayout.ts +27 -0
- package/src/components/msa/MSACanvas.tsx +22 -3
- package/src/components/msa/MSACanvasBlock.tsx +18 -11
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
- package/src/components/msa/WheelZoomHint.tsx +56 -0
- package/src/components/msa/msaKeys.ts +3 -0
- package/src/components/msa/msaRaster.ts +6 -8
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
- package/src/components/msa/renderHighlights.ts +3 -3
- package/src/components/msa/renderMSABlock.ts +51 -15
- package/src/components/msa/tileColor.ts +7 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
- package/src/components/settings/MenuItemsForm.tsx +225 -0
- package/src/components/settings/SettingsPanel.tsx +156 -0
- package/src/components/settings/TrackList.tsx +119 -0
- package/src/components/tracks/TrackBlocks.tsx +2 -2
- package/src/components/tree/TreeCanvasBlock.tsx +2 -2
- package/src/components/tree/TreeMenu.tsx +4 -6
- package/src/components/tree/TreeOverview.tsx +2 -2
- package/src/constants.ts +9 -1
- package/src/darkReader.ts +57 -0
- package/src/element.ts +34 -57
- package/src/featurePanels.ts +269 -0
- package/src/filehandleLoader.ts +136 -0
- package/src/index.ts +4 -0
- package/src/model/treeModel.ts +13 -0
- package/src/model.ts +345 -748
- package/src/neighborJoining.ts +1 -42
- package/src/renderToSvg.tsx +1 -13
- package/src/rerootTree.ts +7 -6
- package/src/schemeResidueColor.ts +37 -0
- package/src/similarityColors.ts +112 -0
- package/src/treeDisplay.ts +278 -0
- package/src/types.ts +6 -3
- package/src/useWheelScroll.ts +43 -22
- package/src/util.ts +3 -0
- package/src/version.ts +1 -1
- package/src/wheelZoom.ts +55 -33
- package/src/zoomSnapshot.ts +80 -0
- package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
- package/dist/components/header/ColorSchemeMenu.js +0 -37
- package/dist/components/header/ColorSchemeMenu.js.map +0 -1
- package/dist/components/header/FileMenu.js +0 -92
- package/dist/components/header/FileMenu.js.map +0 -1
- package/dist/components/header/GappynessSlider.js.map +0 -1
- package/dist/components/header/GoToBox.js +0 -31
- package/dist/components/header/GoToBox.js.map +0 -1
- package/dist/components/header/MSASettingsMenu.js +0 -11
- package/dist/components/header/MSASettingsMenu.js.map +0 -1
- package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
- package/dist/components/header/ScrollZoomIcons.js +0 -35
- package/dist/components/header/ScrollZoomIcons.js.map +0 -1
- package/dist/components/header/TreeSettingsMenu.js +0 -11
- package/dist/components/header/TreeSettingsMenu.js.map +0 -1
- package/dist/components/header/goTo.d.ts +0 -20
- package/dist/components/header/goTo.js.map +0 -1
- package/dist/components/header/settingsMenuItems.d.ts +0 -5
- package/dist/components/header/settingsMenuItems.js +0 -171
- package/dist/components/header/settingsMenuItems.js.map +0 -1
- package/src/components/header/ColorSchemeMenu.tsx +0 -56
- package/src/components/header/FileMenu.tsx +0 -107
- package/src/components/header/MSASettingsMenu.tsx +0 -26
- package/src/components/header/ScrollZoomIcons.tsx +0 -52
- package/src/components/header/TreeSettingsMenu.tsx +0 -30
- package/src/components/header/settingsMenuItems.ts +0 -216
package/dist/model.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { clamp, groupBy, notEmpty, sum } from '@jbrowse/core/util';
|
|
2
|
-
import { openLocation } from '@jbrowse/core/util/io';
|
|
3
2
|
import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst';
|
|
4
|
-
import { addDisposer, applySnapshot, cast, getSnapshot,
|
|
3
|
+
import { addDisposer, applySnapshot, cast, getSnapshot, types, } from '@jbrowse/mobx-state-tree';
|
|
5
4
|
import { autorun, compareStructural, computed, reaction, transaction, } from 'mobx';
|
|
6
|
-
import { generateNodeIds, gffToAnnotations,
|
|
5
|
+
import { generateNodeIds, gffToAnnotations, parseGFF, parseMSA, } from 'msa-parsers';
|
|
7
6
|
import { blockFasta } from './blockFasta.js';
|
|
8
7
|
import { calculateBlocks } from './calculateBlocks.js';
|
|
9
8
|
import { clustalXColumnColors } from './clustalX.js';
|
|
@@ -14,38 +13,34 @@ import { packDomainLanes } from './components/msa/packDomainLanes.js';
|
|
|
14
13
|
import { visibleColRange } from './components/msa/visibleColRange.js';
|
|
15
14
|
import { cladeGutterWidth } from './components/tree/cladeBrackets.js';
|
|
16
15
|
import { scaleBarLength } from './components/tree/scaleBar.js';
|
|
17
|
-
import {
|
|
16
|
+
import { defaultAllowedGappyness, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawMsaLetters, 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, settingsPanelWidth, treeScaleBarHeight, } from './constants.js';
|
|
18
17
|
import { createPaletteMap } from './createPaletteMap.js';
|
|
19
18
|
import { exportFileName } from './exportFileName.js';
|
|
20
|
-
import {
|
|
19
|
+
import { featureName } from './featureFields.js';
|
|
20
|
+
import { colorByRow, featureColorMap, featureLabelMap, featurePanelSpans, resolveFeatureScale, rowPanelWidth, stringFields, } from './featurePanels.js';
|
|
21
21
|
import { featuresToAnnotations } from './featuresLayer.js';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { collapse, find, forEachDescendant, hierarchy, layoutTree, leafIndex, leaves, mrca, nodeCoveringRows, sort, sum as hierarchySum, } from './hierarchy.js';
|
|
22
|
+
import { installFilehandleLoader } from './filehandleLoader.js';
|
|
23
|
+
import { find, hierarchy, layoutTree, leaves, nodeCoveringRows, } from './hierarchy.js';
|
|
25
24
|
import { DataModelF } from './model/DataModel.js';
|
|
26
25
|
import { DialogQueueSessionMixin } from './model/DialogQueue.js';
|
|
27
26
|
import { MSAModelF } from './model/msaModel.js';
|
|
28
27
|
import { TreeModelF } from './model/treeModel.js';
|
|
29
28
|
import { calculateNeighborJoiningTree } from './neighborJoining.js';
|
|
30
|
-
import { parseAsn1 } from './parseAsn1.js';
|
|
31
29
|
import { calculatePropertyConservation } from './propertyConservation.js';
|
|
32
|
-
import {
|
|
30
|
+
import { preorder } from './rerootTree.js';
|
|
33
31
|
import { globalColToVisibleCol, seqPosOfGlobalCol, visibleColToGlobalCol, visibleColsBefore, } from './rowCoordinateCalculations.js';
|
|
34
32
|
import { resolveScale } from './scales.js';
|
|
35
33
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.js';
|
|
36
34
|
import { maxBitsFor } from './sequenceLogo.js';
|
|
35
|
+
import { blosum62ColumnColors, identityColumnColors, } from './similarityColors.js';
|
|
37
36
|
import { stripDefault } from './stripDefault.js';
|
|
38
37
|
import { treeTips, widestLabel } from './tipLabels.js';
|
|
39
|
-
import {
|
|
38
|
+
import { buildTreeRoot, outgroupNode, parseTreeText, resolveClades, rootTree, tipRange, } from './treeDisplay.js';
|
|
39
|
+
import { computeRowInsertions, dropBlanks, len, readComputed, skipBlanks, withAlpha, } from './util.js';
|
|
40
40
|
import { saveAs } from './vendor/fileSaver.js';
|
|
41
41
|
import { writeNewick } from './writeNewick.js';
|
|
42
42
|
import { parseWuss } from './wuss.js';
|
|
43
|
-
|
|
44
|
-
if (text.startsWith('BioTreeContainer')) {
|
|
45
|
-
return flatToTree(parseAsn1(text));
|
|
46
|
-
}
|
|
47
|
-
return parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text);
|
|
48
|
-
}
|
|
43
|
+
import { takeZoomSnapshot, zoomSnapshotCovers } from './zoomSnapshot.js';
|
|
49
44
|
// The height each kind draws at before the user drags a divider. A text track
|
|
50
45
|
// is absent: it is one alignment row tall and follows rowHeight, so the zoom
|
|
51
46
|
// controls already size it.
|
|
@@ -69,186 +64,6 @@ const ownHeightKey = (id) => `own:${id}`;
|
|
|
69
64
|
// them
|
|
70
65
|
const HELIX_ARC = '#4e79a7';
|
|
71
66
|
const PSEUDOKNOT_ARC = '#e15759';
|
|
72
|
-
// the fill of a span under a scale that gives its value no color, such as a
|
|
73
|
-
// gene a `map` over gene names leaves out
|
|
74
|
-
const UNSCALED_FEATURE = '#d9d9d9';
|
|
75
|
-
// the fraction of a span a following one may cover before it takes a lane of
|
|
76
|
-
// its own in a `features` panel
|
|
77
|
-
const laneOverlap = 0.1;
|
|
78
|
-
// a row panel's own width, or a default: the row height, which makes a strip
|
|
79
|
-
// cell square, and a gene neighborhood's width for a features panel. The row
|
|
80
|
-
// height is read only when used, so a panel with a width of its own does not
|
|
81
|
-
// recompute on a vertical zoom
|
|
82
|
-
function rowPanelWidth(panel, view) {
|
|
83
|
-
return (panel.width ??
|
|
84
|
-
(panel.kind === 'features' ? defaultFeaturePanelWidth : view.rowHeight));
|
|
85
|
-
}
|
|
86
|
-
// one row of the row table as the channels read it. A number or a boolean
|
|
87
|
-
// reads as its string, the way a GFF value does, and null or an object drops
|
|
88
|
-
function stringFields(row) {
|
|
89
|
-
if (typeof row !== 'object' || row === null) {
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
const fields = {};
|
|
93
|
-
for (const [field, value] of Object.entries(row)) {
|
|
94
|
-
if (typeof value === 'string') {
|
|
95
|
-
fields[field] = value;
|
|
96
|
-
}
|
|
97
|
-
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
98
|
-
fields[field] = String(value);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return fields;
|
|
102
|
-
}
|
|
103
|
-
// the color each row takes from a field of the row table under a scale
|
|
104
|
-
function colorByRow(rowData, field, colorOf) {
|
|
105
|
-
const colors = new Map();
|
|
106
|
-
for (const [name, row] of Object.entries(rowData)) {
|
|
107
|
-
const value = row?.[field];
|
|
108
|
-
const color = value === undefined ? undefined : colorOf(value);
|
|
109
|
-
if (color) {
|
|
110
|
-
colors.set(name, color);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return colors;
|
|
114
|
-
}
|
|
115
|
-
// the fill and outline of every feature in a list: its own GFF `color=` first,
|
|
116
|
-
// then the scale an encoding resolves over one of its fields, then the
|
|
117
|
-
// accession palette
|
|
118
|
-
function featureColorMap(annotations, encoding, fillPalette) {
|
|
119
|
-
const strokes = new Map();
|
|
120
|
-
const strokeOf = (fill) => {
|
|
121
|
-
const hit = strokes.get(fill);
|
|
122
|
-
if (hit !== undefined) {
|
|
123
|
-
return hit;
|
|
124
|
-
}
|
|
125
|
-
const stroke = outlineColor(fill);
|
|
126
|
-
strokes.set(fill, stroke);
|
|
127
|
-
return stroke;
|
|
128
|
-
};
|
|
129
|
-
return new Map(annotations.map(annotation => {
|
|
130
|
-
const value = encoding
|
|
131
|
-
? featureField(annotation, encoding.field)
|
|
132
|
-
: undefined;
|
|
133
|
-
const fill = annotation.color ??
|
|
134
|
-
(value === undefined ? undefined : encoding.colorOf(value)) ??
|
|
135
|
-
(encoding ? UNSCALED_FEATURE : fillPalette[annotation.accession]);
|
|
136
|
-
return [annotation, { fill, stroke: strokeOf(fill) }];
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
// the text a label channel draws inside each span, for the features carrying
|
|
140
|
-
// the field it names
|
|
141
|
-
function featureLabelMap(annotations, field) {
|
|
142
|
-
const labels = new Map();
|
|
143
|
-
for (const annotation of annotations) {
|
|
144
|
-
const value = featureField(annotation, field);
|
|
145
|
-
if (value !== undefined) {
|
|
146
|
-
labels.set(annotation, value);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return labels;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Lanes for `position: "strandpile"`, gggenomes' `position_strandpile`: the
|
|
153
|
-
* forward features stack above the line and the reverse below it, each strand
|
|
154
|
-
* packing on its own. The deepest row on each side sets the grid every row lays
|
|
155
|
-
* out on, so the line between the strands sits at one height down the panel and
|
|
156
|
-
* a reader can scan it. A feature with no strand piles with the forward ones.
|
|
157
|
-
*/
|
|
158
|
-
function strandpileLanes(rows) {
|
|
159
|
-
const piled = rows.map(([name, bands]) => {
|
|
160
|
-
const reverse = packDomainLanes(bands.filter(b => b.annotation.strand === -1));
|
|
161
|
-
const forward = packDomainLanes(bands.filter(b => b.annotation.strand !== -1));
|
|
162
|
-
return { name, forward, reverse };
|
|
163
|
-
});
|
|
164
|
-
const up = Math.max(0, ...piled.map(p => p.forward[0]?.laneCount ?? 0));
|
|
165
|
-
const down = Math.max(0, ...piled.map(p => p.reverse[0]?.laneCount ?? 0));
|
|
166
|
-
const laneCount = up + down;
|
|
167
|
-
return new Map(piled.map(({ name, forward, reverse }) => [
|
|
168
|
-
name,
|
|
169
|
-
[
|
|
170
|
-
// forward level 0 is the lane just above the line, stacking upward
|
|
171
|
-
...forward.map(b => ({ ...b, lane: up - 1 - b.lane, laneCount })),
|
|
172
|
-
...reverse.map(b => ({ ...b, lane: up + b.lane, laneCount })),
|
|
173
|
-
],
|
|
174
|
-
]));
|
|
175
|
-
}
|
|
176
|
-
/** one row's spans laid out by the panel's `position` */
|
|
177
|
-
function panelLanes(panel, rows) {
|
|
178
|
-
return panel.position === 'strandpile'
|
|
179
|
-
? strandpileLanes(rows)
|
|
180
|
-
: new Map(rows.map(([name, bands]) => [name, packDomainLanes(bands)]));
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* The spans a `features` panel draws, keyed by row name and measured in the
|
|
184
|
-
* panel's own pixels. `column` takes the bands the overlay draws, at the
|
|
185
|
-
* alignment's column width. `position` packs each row's features in its own
|
|
186
|
-
* residue positions, shifted by an `align` transform, and maps the extent they
|
|
187
|
-
* cover across every row onto the panel width, so a row with no alignment
|
|
188
|
-
* still has an x.
|
|
189
|
-
*/
|
|
190
|
-
function featurePanelSpans({ panel, width, colWidth, domainBands, annotationsByRow, shifts, }) {
|
|
191
|
-
if (panel.x === 'column') {
|
|
192
|
-
return panelLanes(panel, [...domainBands].map(([name, bands]) => [
|
|
193
|
-
name,
|
|
194
|
-
bands.map(band => ({
|
|
195
|
-
...band,
|
|
196
|
-
xStart: band.startCol * colWidth,
|
|
197
|
-
xEnd: band.endCol * colWidth,
|
|
198
|
-
})),
|
|
199
|
-
]));
|
|
200
|
-
}
|
|
201
|
-
const shifted = Object.entries(annotationsByRow).map(([name, annotations]) => {
|
|
202
|
-
const shift = shifts?.get(name) ?? 0;
|
|
203
|
-
return [
|
|
204
|
-
name,
|
|
205
|
-
annotations.map(annotation => ({
|
|
206
|
-
annotation,
|
|
207
|
-
start: annotation.start - 1 + shift,
|
|
208
|
-
end: annotation.end + shift,
|
|
209
|
-
})),
|
|
210
|
-
];
|
|
211
|
-
});
|
|
212
|
-
let min = Infinity;
|
|
213
|
-
let max = -Infinity;
|
|
214
|
-
for (const [, features] of shifted) {
|
|
215
|
-
for (const { start, end } of features) {
|
|
216
|
-
min = Math.min(min, start);
|
|
217
|
-
max = Math.max(max, end);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// a gene arrow ends where its feature does, so the extent maps onto the whole
|
|
221
|
-
// panel less the pixel the rightmost stroke needs
|
|
222
|
-
const drawable = Math.max(1, width - 1);
|
|
223
|
-
const scale = max > min ? drawable / (max - min) : 0;
|
|
224
|
-
// a lane opens where a span covers more than a tenth of the one before it,
|
|
225
|
-
// which keeps the genes of an operon on one lane: adjacent genes commonly
|
|
226
|
-
// share a few bases, and a stop codon overlapping the next start reads as a
|
|
227
|
-
// second lane over the whole row
|
|
228
|
-
return panelLanes(panel, shifted.map(([name, features]) => [
|
|
229
|
-
name,
|
|
230
|
-
features.map(({ annotation, start, end }) => ({
|
|
231
|
-
annotation,
|
|
232
|
-
xStart: (start - min) * scale,
|
|
233
|
-
xEnd: (end - min) * scale,
|
|
234
|
-
startCol: start,
|
|
235
|
-
endCol: end - (end - start) * laneOverlap,
|
|
236
|
-
})),
|
|
237
|
-
]));
|
|
238
|
-
}
|
|
239
|
-
// a scale over the values a field takes across every feature loaded, so hiding
|
|
240
|
-
// a type in the filter dialog leaves the rest in their colors, and a legend of
|
|
241
|
-
// the values the features drawn carry
|
|
242
|
-
function resolveFeatureScale(field, scale, annotations, drawn) {
|
|
243
|
-
const valuesOf = (list) => list.map(a => featureField(a, field)).filter(notEmpty);
|
|
244
|
-
const resolved = resolveScale(scale, valuesOf(annotations));
|
|
245
|
-
const shown = new Set(valuesOf(drawn));
|
|
246
|
-
return {
|
|
247
|
-
field,
|
|
248
|
-
...resolved,
|
|
249
|
-
legend: resolved.legend.filter(entry => shown.has(entry.id)),
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
67
|
const columnTrackBytes = new WeakMap();
|
|
253
68
|
function columnTrackSize(track) {
|
|
254
69
|
let bytes = columnTrackBytes.get(track);
|
|
@@ -301,6 +116,7 @@ export const preservedOnReset = new Set([
|
|
|
301
116
|
'drawNodeBubbles',
|
|
302
117
|
'drawNodeLabels',
|
|
303
118
|
'showTreeOverview',
|
|
119
|
+
'showTreeScaleBar',
|
|
304
120
|
'overviewHeight',
|
|
305
121
|
'turnedOffTracks',
|
|
306
122
|
'trackHeights',
|
|
@@ -308,212 +124,17 @@ export const preservedOnReset = new Set([
|
|
|
308
124
|
'allowedGappyness',
|
|
309
125
|
'subFeatureRows',
|
|
310
126
|
'showDomainLegend',
|
|
127
|
+
'domainFill',
|
|
128
|
+
'embeddedMode',
|
|
311
129
|
]);
|
|
312
130
|
// `turnedOffTracks` holds only the user's explicit choices, so a hidden-by-default
|
|
313
|
-
// track writes nothing into the snapshot
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
function trackIsOff(turnedOffTracks, id, defaultOff) {
|
|
317
|
-
return turnedOffTracks.get(id) ?? (defaultOff || defaultOffTracks.has(id));
|
|
131
|
+
// track writes nothing into the snapshot
|
|
132
|
+
function trackIsOff(turnedOffTracks, id) {
|
|
133
|
+
return turnedOffTracks.get(id) ?? defaultOffTracks.has(id);
|
|
318
134
|
}
|
|
319
|
-
// the marks that set tree state from a node rather than drawing
|
|
320
|
-
const seedingMarks = new Set(['collapse', 'focus', 'rotate']);
|
|
321
135
|
// shared empty results, so observers don't see a fresh [] as a change
|
|
322
136
|
const noDomains = [];
|
|
323
137
|
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
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
361
|
-
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
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.
|
|
365
|
-
*/
|
|
366
|
-
function cladeRows(clade, root, index, tips) {
|
|
367
|
-
if (clade.range) {
|
|
368
|
-
const [a, b] = clade.range.map(name => index.get(name) ? tips.get(name) : undefined);
|
|
369
|
-
if (a === undefined || b === undefined) {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
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;
|
|
376
|
-
}
|
|
377
|
-
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined;
|
|
378
|
-
if (!node) {
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|
|
381
|
-
const nodeTips = leaves(node);
|
|
382
|
-
if (nodeTips.length !== clade.tips) {
|
|
383
|
-
return undefined;
|
|
384
|
-
}
|
|
385
|
-
let first = Infinity;
|
|
386
|
-
let last = -Infinity;
|
|
387
|
-
for (const tip of nodeTips) {
|
|
388
|
-
const row = tips.get(tip.data.name)?.row;
|
|
389
|
-
if (row !== undefined) {
|
|
390
|
-
first = Math.min(first, row);
|
|
391
|
-
last = Math.max(last, row);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return first <= last
|
|
395
|
-
? { rows: [first, last], nodeId: node.data.id }
|
|
396
|
-
: undefined;
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
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.
|
|
404
|
-
*/
|
|
405
|
-
function resolveClades(clades, root, displayed) {
|
|
406
|
-
const index = leafIndex(root);
|
|
407
|
-
const tips = tipRows(displayed);
|
|
408
|
-
return clades.flatMap(clade => {
|
|
409
|
-
const resolved = cladeRows(clade, root, index, tips);
|
|
410
|
-
const seeding = seedingMarks.has(clade.mark);
|
|
411
|
-
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
412
|
-
? [
|
|
413
|
-
{
|
|
414
|
-
...resolved,
|
|
415
|
-
mark: clade.mark,
|
|
416
|
-
color: withAlpha(clade.color ?? cladeHighlightColor, cladeHighlightAlpha),
|
|
417
|
-
markColor: clade.color,
|
|
418
|
-
label: clade.label,
|
|
419
|
-
},
|
|
420
|
-
]
|
|
421
|
-
: [];
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
const childOrders = {
|
|
425
|
-
input: undefined,
|
|
426
|
-
branchLength: (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1),
|
|
427
|
-
ladderize: (a, b) => a.value - b.value,
|
|
428
|
-
ladderizeReverse: (a, b) => b.value - a.value,
|
|
429
|
-
};
|
|
430
|
-
/**
|
|
431
|
-
* The tree with the display transforms applied: leaf counts summed, children
|
|
432
|
-
* put in `order` and each `rotated` node's reversed, `showOnly` taken as the
|
|
433
|
-
* new root, and each `collapsed` clade folded. The overview builds the same
|
|
434
|
-
* tree without the focus, so it shows the focused subtree inside the whole.
|
|
435
|
-
*/
|
|
436
|
-
function buildTreeRoot(tree, { collapsed, rotated, order, showOnly, }) {
|
|
437
|
-
let hier = hierarchy(tree, d => d.children);
|
|
438
|
-
hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1));
|
|
439
|
-
const compare = childOrders[order];
|
|
440
|
-
if (compare) {
|
|
441
|
-
sort(hier, compare);
|
|
442
|
-
}
|
|
443
|
-
if (rotated.length > 0) {
|
|
444
|
-
const flip = new Set(rotated);
|
|
445
|
-
forEachDescendant(hier, n => {
|
|
446
|
-
if (flip.has(n.data.id)) {
|
|
447
|
-
n.children?.reverse();
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
if (showOnly) {
|
|
452
|
-
const res = find(hier, n => n.data.id === showOnly);
|
|
453
|
-
if (res) {
|
|
454
|
-
hier = res;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
for (const collapsedId of collapsed) {
|
|
458
|
-
const node = find(hier, n => n.data.id === collapsedId);
|
|
459
|
-
if (node) {
|
|
460
|
-
if (node.children) {
|
|
461
|
-
collapse(node);
|
|
462
|
-
}
|
|
463
|
-
else if (node.parent?.children) {
|
|
464
|
-
node.parent.children = node.parent.children.filter(c => c.data.id !== collapsedId);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return hier;
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* The node whose branch an outgroup roots the tree on: the outgroup's most
|
|
472
|
-
* recent common ancestor, or the rest of the tips' where that ancestor is the
|
|
473
|
-
* root, since the outgroup then straddles the root and the branch above the
|
|
474
|
-
* other side is the same branch of the unrooted tree.
|
|
475
|
-
*/
|
|
476
|
-
function outgroupNode(root, outgroup) {
|
|
477
|
-
const index = leafIndex(root);
|
|
478
|
-
const node = mrca(root, outgroup, index);
|
|
479
|
-
if (node !== root) {
|
|
480
|
-
return node;
|
|
481
|
-
}
|
|
482
|
-
const out = new Set(outgroup);
|
|
483
|
-
const rest = leaves(root)
|
|
484
|
-
.map(n => n.data.name)
|
|
485
|
-
.filter(name => !out.has(name));
|
|
486
|
-
const other = mrca(root, rest, index);
|
|
487
|
-
return other === root ? undefined : other;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* `tree` rerooted as `treeRoot` asks, with fresh path ids. The ids of the tree
|
|
491
|
-
* as written become names on unnamed nodes, and a rerooting would carry them
|
|
492
|
-
* along as labels, so they come off first.
|
|
493
|
-
*/
|
|
494
|
-
function rootTree(tree, treeRoot) {
|
|
495
|
-
const plain = new Map();
|
|
496
|
-
for (const node of preorder(tree).reverse()) {
|
|
497
|
-
plain.set(node, {
|
|
498
|
-
name: node.name === node.id ? undefined : node.name,
|
|
499
|
-
length: node.length,
|
|
500
|
-
children: node.children.map(c => plain.get(c)),
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
const input = plain.get(tree);
|
|
504
|
-
if (treeRoot === 'midpoint') {
|
|
505
|
-
return generateNodeIds(midpointRoot(input));
|
|
506
|
-
}
|
|
507
|
-
const target = outgroupNode(hierarchy(tree, d => d.children), treeRoot.outgroup);
|
|
508
|
-
return target
|
|
509
|
-
? generateNodeIds(rerootTree(input, plain.get(target.data)))
|
|
510
|
-
: tree;
|
|
511
|
-
}
|
|
512
|
-
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
513
|
-
// row per tip
|
|
514
|
-
function tipRange(node) {
|
|
515
|
-
return [Math.round(node.xMin - 0.5), Math.round(node.xMax - 0.5)];
|
|
516
|
-
}
|
|
517
138
|
// the channels reading the feature table; every other channel reads rowData
|
|
518
139
|
const featureChannels = new Set([
|
|
519
140
|
'featureFill',
|
|
@@ -538,6 +159,22 @@ function sameLength(segment) {
|
|
|
538
159
|
return (segment.rowEnd - segment.rowStart ===
|
|
539
160
|
segment.structEnd - segment.structStart);
|
|
540
161
|
}
|
|
162
|
+
// A zoom that passes the default cell size stops on it, and one that starts on
|
|
163
|
+
// it holds there until the wheel has gathered `zoomDetent` worth of scale, so
|
|
164
|
+
// a zoom in or out finds its way back to the default
|
|
165
|
+
const zoomDetent = Math.log(1.3);
|
|
166
|
+
function detentZoom(value, scale, home, held, min, canHold) {
|
|
167
|
+
if (value === home && canHold) {
|
|
168
|
+
const total = held + Math.log(scale);
|
|
169
|
+
return Math.abs(total) < zoomDetent
|
|
170
|
+
? { value, held: total }
|
|
171
|
+
: { value: clamp(home * Math.exp(total), min, maxCellSize), held: 0 };
|
|
172
|
+
}
|
|
173
|
+
const next = clamp(value * scale, min, maxCellSize);
|
|
174
|
+
return (value - home) * (next - home) < 0
|
|
175
|
+
? { value: home, held: 0 }
|
|
176
|
+
: { value: next, held: 0 };
|
|
177
|
+
}
|
|
541
178
|
// fit() divides the viewport by the row or column count and multiplies back, so
|
|
542
179
|
// an exact fit can land a fraction of a pixel over. A plain `>` would then show
|
|
543
180
|
// a scrollbar that shrinks the viewport and leaves a gap.
|
|
@@ -569,19 +206,6 @@ function sharedValue(values) {
|
|
|
569
206
|
? first
|
|
570
207
|
: undefined;
|
|
571
208
|
}
|
|
572
|
-
// preorder, so reversing the list puts every node after its descendants
|
|
573
|
-
function preorder(tree) {
|
|
574
|
-
const order = [];
|
|
575
|
-
const stack = [tree];
|
|
576
|
-
while (stack.length > 0) {
|
|
577
|
-
const node = stack.pop();
|
|
578
|
-
order.push(node);
|
|
579
|
-
for (const child of node.children) {
|
|
580
|
-
stack.push(child);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
return order;
|
|
584
|
-
}
|
|
585
209
|
/**
|
|
586
210
|
* #stateModel MsaView
|
|
587
211
|
*
|
|
@@ -619,6 +243,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
619
243
|
* #property
|
|
620
244
|
*/
|
|
621
245
|
showDomains: stripDefault(types.boolean, defaultShowDomains),
|
|
246
|
+
/**
|
|
247
|
+
* #property
|
|
248
|
+
* whether shown domains fill the cells they cover. Off, the residue
|
|
249
|
+
* scheme keeps the cells and each domain draws as a bar under its
|
|
250
|
+
* row. See `cellFill`.
|
|
251
|
+
*/
|
|
252
|
+
domainFill: stripDefault(types.boolean, true),
|
|
622
253
|
/**
|
|
623
254
|
* #property
|
|
624
255
|
* whether the domain legend is expanded. The legend floats over the
|
|
@@ -647,6 +278,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
647
278
|
* #property
|
|
648
279
|
*/
|
|
649
280
|
drawMsaLetters: stripDefault(types.boolean, defaultDrawMsaLetters),
|
|
281
|
+
/**
|
|
282
|
+
* #property
|
|
283
|
+
* draws the toolbar's menus and Settings as icons without text, for a
|
|
284
|
+
* host with little room, such as a JBrowse view
|
|
285
|
+
*/
|
|
286
|
+
embeddedMode: stripDefault(types.boolean, false),
|
|
650
287
|
/**
|
|
651
288
|
* #property
|
|
652
289
|
* zoom in/out on plain mouse-wheel without holding ctrl
|
|
@@ -771,10 +408,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
771
408
|
data: types.optional(DataModelF(maxInlineSnapshotBytes), {}),
|
|
772
409
|
/**
|
|
773
410
|
* #property
|
|
774
|
-
* the user's
|
|
775
|
-
* accession with the value meaning "off", like
|
|
776
|
-
* untouched accession is absent and
|
|
777
|
-
* only with the user's filters
|
|
411
|
+
* the user's departures from each annotation accession's default,
|
|
412
|
+
* keyed by accession with the value meaning "off", like
|
|
413
|
+
* `turnedOffTracks`. An untouched accession is absent and takes its
|
|
414
|
+
* default, so the shared URL grows only with the user's filters
|
|
778
415
|
*/
|
|
779
416
|
turnedOffFeatures: stripDefault(types.map(types.boolean), {}),
|
|
780
417
|
/**
|
|
@@ -854,6 +491,18 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
854
491
|
* #volatile
|
|
855
492
|
*/
|
|
856
493
|
headerHeight: 0,
|
|
494
|
+
/**
|
|
495
|
+
* #volatile
|
|
496
|
+
* whether the settings panel docks beside the alignment, taking
|
|
497
|
+
* `settingsPanelWidth` out of the width the view lays out in
|
|
498
|
+
*/
|
|
499
|
+
settingsOpen: false,
|
|
500
|
+
/**
|
|
501
|
+
* #volatile
|
|
502
|
+
* the log scale a wheel zoom has gathered against the detent at the
|
|
503
|
+
* default column width and row height, per axis
|
|
504
|
+
*/
|
|
505
|
+
zoomHeld: { col: 0, row: 0 },
|
|
857
506
|
/**
|
|
858
507
|
* #volatile
|
|
859
508
|
* leaves the toolbar out, for a host drawing its own controls. Kept out
|
|
@@ -875,6 +524,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
875
524
|
* canvas scale factor, from the device pixel ratio
|
|
876
525
|
*/
|
|
877
526
|
highResScaleFactor: typeof window === 'undefined' ? 1 : window.devicePixelRatio,
|
|
527
|
+
/**
|
|
528
|
+
* #volatile
|
|
529
|
+
* the cell size and block grid the alignment last drew at, held from a
|
|
530
|
+
* wheel-zoom frame until the gesture settles. MSACanvas stretches those
|
|
531
|
+
* blocks to the current cell size with one CSS transform
|
|
532
|
+
*/
|
|
533
|
+
zoomSnapshot: undefined,
|
|
878
534
|
/**
|
|
879
535
|
* #volatile
|
|
880
536
|
*/
|
|
@@ -1019,6 +675,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1019
675
|
setWidth(arg) {
|
|
1020
676
|
self.volatileWidth = arg;
|
|
1021
677
|
},
|
|
678
|
+
/**
|
|
679
|
+
* #action
|
|
680
|
+
*/
|
|
681
|
+
setSettingsOpen(arg) {
|
|
682
|
+
self.settingsOpen = arg;
|
|
683
|
+
},
|
|
1022
684
|
/**
|
|
1023
685
|
* #action
|
|
1024
686
|
* update the canvas scale factor when the device pixel ratio changes
|
|
@@ -1156,6 +818,23 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1156
818
|
setShowDomains(arg) {
|
|
1157
819
|
self.showDomains = arg;
|
|
1158
820
|
},
|
|
821
|
+
/**
|
|
822
|
+
* #action
|
|
823
|
+
*/
|
|
824
|
+
setDomainFill(arg) {
|
|
825
|
+
self.domainFill = arg;
|
|
826
|
+
},
|
|
827
|
+
/**
|
|
828
|
+
* #action
|
|
829
|
+
* hand the cell fill to the residue scheme, the domains, or neither,
|
|
830
|
+
* which colors the letters instead
|
|
831
|
+
*/
|
|
832
|
+
setCellFill(mode) {
|
|
833
|
+
self.setBgColor(mode !== 'none');
|
|
834
|
+
if (mode !== 'none') {
|
|
835
|
+
self.domainFill = mode === 'domain';
|
|
836
|
+
}
|
|
837
|
+
},
|
|
1159
838
|
/**
|
|
1160
839
|
* #action
|
|
1161
840
|
* expand or collapse the domain legend that floats over the alignment
|
|
@@ -1183,6 +862,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1183
862
|
*/
|
|
1184
863
|
setRowHeight(n) {
|
|
1185
864
|
self.rowHeight = n;
|
|
865
|
+
self.zoomHeld = { ...self.zoomHeld, row: 0 };
|
|
1186
866
|
},
|
|
1187
867
|
/**
|
|
1188
868
|
* #action
|
|
@@ -1190,6 +870,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1190
870
|
*/
|
|
1191
871
|
setColWidth(n) {
|
|
1192
872
|
self.colWidth = n;
|
|
873
|
+
self.zoomHeld = { ...self.zoomHeld, col: 0 };
|
|
1193
874
|
},
|
|
1194
875
|
/**
|
|
1195
876
|
* #action
|
|
@@ -1340,22 +1021,6 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1340
1021
|
self.showOnly !== undefined ||
|
|
1341
1022
|
self.allowedGappyness < 100));
|
|
1342
1023
|
},
|
|
1343
|
-
/**
|
|
1344
|
-
* #getter
|
|
1345
|
-
* the annotations the overlay, the legend and the encodings draw: the
|
|
1346
|
-
* GFF's, then the `features` layer's
|
|
1347
|
-
*/
|
|
1348
|
-
get allAnnotations() {
|
|
1349
|
-
return self.features.length > 0
|
|
1350
|
-
? [...self.annotations, ...featuresToAnnotations(self.features)]
|
|
1351
|
-
: self.annotations;
|
|
1352
|
-
},
|
|
1353
|
-
/**
|
|
1354
|
-
* #getter
|
|
1355
|
-
*/
|
|
1356
|
-
get actuallyShowDomains() {
|
|
1357
|
-
return self.showDomains && this.allAnnotations.length > 0;
|
|
1358
|
-
},
|
|
1359
1024
|
/**
|
|
1360
1025
|
* #getter
|
|
1361
1026
|
* whether the host restores the loaded documents outside the snapshot.
|
|
@@ -1380,7 +1045,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1380
1045
|
if (self.volatileWidth === undefined) {
|
|
1381
1046
|
throw new Error('not initialized');
|
|
1382
1047
|
}
|
|
1383
|
-
return self.volatileWidth;
|
|
1048
|
+
return self.volatileWidth - (self.settingsOpen ? settingsPanelWidth : 0);
|
|
1384
1049
|
},
|
|
1385
1050
|
}))
|
|
1386
1051
|
.views(self => ({
|
|
@@ -1427,7 +1092,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1427
1092
|
return !!this.tree.noTree;
|
|
1428
1093
|
},
|
|
1429
1094
|
get noDomains() {
|
|
1430
|
-
return
|
|
1095
|
+
return this.allAnnotations.length === 0;
|
|
1431
1096
|
},
|
|
1432
1097
|
/**
|
|
1433
1098
|
* #getter
|
|
@@ -1528,6 +1193,27 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
1528
1193
|
? parseMSA(text, self.currentAlignment, self.msaFormat)
|
|
1529
1194
|
: null;
|
|
1530
1195
|
},
|
|
1196
|
+
/**
|
|
1197
|
+
* #getter
|
|
1198
|
+
* the annotations the overlay, the legend and the encodings draw: the
|
|
1199
|
+
* GFF's, the `features` layer's, then the file's own per-row lines
|
|
1200
|
+
*/
|
|
1201
|
+
get allAnnotations() {
|
|
1202
|
+
const inline = this.MSA?.annotations ?? [];
|
|
1203
|
+
return self.features.length > 0 || inline.length > 0
|
|
1204
|
+
? [
|
|
1205
|
+
...self.annotations,
|
|
1206
|
+
...featuresToAnnotations(self.features),
|
|
1207
|
+
...inline,
|
|
1208
|
+
]
|
|
1209
|
+
: self.annotations;
|
|
1210
|
+
},
|
|
1211
|
+
/**
|
|
1212
|
+
* #getter
|
|
1213
|
+
*/
|
|
1214
|
+
get actuallyShowDomains() {
|
|
1215
|
+
return self.showDomains && this.allAnnotations.length > 0;
|
|
1216
|
+
},
|
|
1531
1217
|
/**
|
|
1532
1218
|
* #getter
|
|
1533
1219
|
*/
|
|
@@ -2056,6 +1742,18 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2056
1742
|
const { colStats } = this;
|
|
2057
1743
|
return Array.from({ length: colStats.numColumns }, (_, col) => clustalXColumnColors(colStats, col));
|
|
2058
1744
|
},
|
|
1745
|
+
/**
|
|
1746
|
+
* #getter
|
|
1747
|
+
* Pre-computed similarity shades per column, for blosum62_dynamic.
|
|
1748
|
+
* Returns a map of letter -> color for each column. Amino acids shade by
|
|
1749
|
+
* BLOSUM62 similarity to the rest of the column, and an alignment of
|
|
1750
|
+
* nucleotides by identity, since the matrix names amino acids.
|
|
1751
|
+
*/
|
|
1752
|
+
get colSimilarity() {
|
|
1753
|
+
const { colStats, sequenceType } = this;
|
|
1754
|
+
const columnColors = sequenceType === 'amino' ? blosum62ColumnColors : identityColumnColors;
|
|
1755
|
+
return Array.from({ length: colStats.numColumns }, (_, col) => columnColors(colStats, col));
|
|
1756
|
+
},
|
|
2059
1757
|
/**
|
|
2060
1758
|
* #getter
|
|
2061
1759
|
* Conservation score per column using Shannon entropy (biojs-msa style).
|
|
@@ -2433,7 +2131,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2433
2131
|
* #getter
|
|
2434
2132
|
*/
|
|
2435
2133
|
get turnedOnTracks() {
|
|
2436
|
-
return this.tracks.filter(f => !trackIsOff(self.turnedOffTracks, f.model.id
|
|
2134
|
+
return this.tracks.filter(f => !trackIsOff(self.turnedOffTracks, f.model.id));
|
|
2437
2135
|
},
|
|
2438
2136
|
/**
|
|
2439
2137
|
* #getter
|
|
@@ -2722,7 +2420,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2722
2420
|
if (mapping.structure.id !== structureId) {
|
|
2723
2421
|
continue;
|
|
2724
2422
|
}
|
|
2725
|
-
if (asymId !== undefined &&
|
|
2423
|
+
if (asymId !== undefined &&
|
|
2424
|
+
mapping.structure.asymId !== undefined &&
|
|
2425
|
+
mapping.structure.asymId !== asymId) {
|
|
2726
2426
|
continue;
|
|
2727
2427
|
}
|
|
2728
2428
|
const segment = mapping.segments.find(seg => position >= seg.structStart && position <= seg.structEnd);
|
|
@@ -2739,10 +2439,14 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2739
2439
|
.views(self => ({
|
|
2740
2440
|
/**
|
|
2741
2441
|
* #getter
|
|
2742
|
-
* the branch-length scale bar over the tree, undefined
|
|
2743
|
-
* or when the tree area is
|
|
2442
|
+
* the branch-length scale bar over the tree, undefined while
|
|
2443
|
+
* `showTreeScaleBar` is off, in cladogram mode, or when the tree area is
|
|
2444
|
+
* too narrow for one
|
|
2744
2445
|
*/
|
|
2745
2446
|
get treeScaleBar() {
|
|
2447
|
+
if (!self.showTreeScaleBar) {
|
|
2448
|
+
return undefined;
|
|
2449
|
+
}
|
|
2746
2450
|
return scaleBarLength(self.pxPerBranchLength, self.effectiveTreeAreaWidth - self.marginLeft * 2);
|
|
2747
2451
|
},
|
|
2748
2452
|
/**
|
|
@@ -2789,8 +2493,38 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2789
2493
|
}
|
|
2790
2494
|
return types;
|
|
2791
2495
|
},
|
|
2496
|
+
/**
|
|
2497
|
+
* accessions hidden until the user turns them on, such as a Stockholm
|
|
2498
|
+
* file's #=GR lines
|
|
2499
|
+
*/
|
|
2500
|
+
get defaultOffAccessions() {
|
|
2501
|
+
return new Set(self.allAnnotations.filter(a => a.defaultOff).map(a => a.accession));
|
|
2502
|
+
},
|
|
2503
|
+
/**
|
|
2504
|
+
* #method
|
|
2505
|
+
*/
|
|
2506
|
+
featureOff(accession) {
|
|
2507
|
+
return (self.turnedOffFeatures.get(accession) ??
|
|
2508
|
+
this.defaultOffAccessions.has(accession));
|
|
2509
|
+
},
|
|
2792
2510
|
get filteredAnnotations() {
|
|
2793
|
-
return self.allAnnotations.filter(r => !
|
|
2511
|
+
return self.allAnnotations.filter(r => !this.featureOff(r.accession));
|
|
2512
|
+
},
|
|
2513
|
+
/**
|
|
2514
|
+
* #getter
|
|
2515
|
+
* what colors the alignment's cells: the residue scheme, the domains
|
|
2516
|
+
* covering them, or nothing, which leaves the scheme to the letters.
|
|
2517
|
+
* Domains own the fill only while some are drawn, so a file whose
|
|
2518
|
+
* annotations all start hidden keeps its residue colors
|
|
2519
|
+
*/
|
|
2520
|
+
get cellFill() {
|
|
2521
|
+
return !self.bgColor
|
|
2522
|
+
? 'none'
|
|
2523
|
+
: self.actuallyShowDomains &&
|
|
2524
|
+
self.domainFill &&
|
|
2525
|
+
this.filteredAnnotations.length > 0
|
|
2526
|
+
? 'domain'
|
|
2527
|
+
: 'residue';
|
|
2794
2528
|
},
|
|
2795
2529
|
get annotationsByRow() {
|
|
2796
2530
|
const { rowNamesSet } = self;
|
|
@@ -2866,6 +2600,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2866
2600
|
get blocks2d() {
|
|
2867
2601
|
return self.blocksY.flatMap(by => self.blocksX.map(bx => [bx, by]));
|
|
2868
2602
|
},
|
|
2603
|
+
/**
|
|
2604
|
+
* #getter
|
|
2605
|
+
*/
|
|
2606
|
+
get wheelZooming() {
|
|
2607
|
+
return self.zoomSnapshot !== undefined;
|
|
2608
|
+
},
|
|
2869
2609
|
/**
|
|
2870
2610
|
* #getter
|
|
2871
2611
|
*/
|
|
@@ -3062,11 +2802,20 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3062
2802
|
transaction(() => {
|
|
3063
2803
|
const colInView = (-self.scrollX + offsetX) / self.colWidth;
|
|
3064
2804
|
const rowInView = (-self.scrollY + offsetY) / self.rowHeight;
|
|
2805
|
+
// zooming both axes, an axis holds at the default only while the
|
|
2806
|
+
// other sits there too, or one would hold while the other grows
|
|
2807
|
+
const canHold = axis !== 'both' ||
|
|
2808
|
+
(self.colWidth === defaultColWidth &&
|
|
2809
|
+
self.rowHeight === defaultRowHeight);
|
|
3065
2810
|
if (axis !== 'vertical') {
|
|
3066
|
-
|
|
2811
|
+
const { value, held } = detentZoom(self.colWidth, scaleFactor, defaultColWidth, self.zoomHeld.col, minColWidth, canHold);
|
|
2812
|
+
self.colWidth = value;
|
|
2813
|
+
self.zoomHeld = { ...self.zoomHeld, col: held };
|
|
3067
2814
|
}
|
|
3068
2815
|
if (axis !== 'horizontal') {
|
|
3069
|
-
|
|
2816
|
+
const { value, held } = detentZoom(self.rowHeight, scaleFactor, defaultRowHeight, self.zoomHeld.row, minRowHeight, canHold);
|
|
2817
|
+
self.rowHeight = value;
|
|
2818
|
+
self.zoomHeld = { ...self.zoomHeld, row: held };
|
|
3070
2819
|
}
|
|
3071
2820
|
self.scrollX = clamp(offsetX - colInView * self.colWidth, self.maxScrollX, 0);
|
|
3072
2821
|
const anchoredScrollY = offsetY - rowInView * self.rowHeight;
|
|
@@ -3077,6 +2826,25 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3077
2826
|
self.scrollY = clamp(anchoredScrollY * (1 - topBias), self.maxScrollY, 0);
|
|
3078
2827
|
});
|
|
3079
2828
|
},
|
|
2829
|
+
/**
|
|
2830
|
+
* #action
|
|
2831
|
+
* zoomToPos for one wheel or pinch frame. The alignment keeps the blocks
|
|
2832
|
+
* it drew at the start of the gesture, scaled, and redraws them only once
|
|
2833
|
+
* they stop covering the viewport; endWheelZoom redraws them sharp
|
|
2834
|
+
*/
|
|
2835
|
+
wheelZoomToPos(scaleFactor, offsetX, offsetY) {
|
|
2836
|
+
self.zoomSnapshot ??= takeZoomSnapshot(self, false);
|
|
2837
|
+
this.zoomToPos(scaleFactor, offsetX, offsetY, self.wheelZoomAxis);
|
|
2838
|
+
if (!zoomSnapshotCovers(self, self.zoomSnapshot)) {
|
|
2839
|
+
self.zoomSnapshot = takeZoomSnapshot(self, true);
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
/**
|
|
2843
|
+
* #action
|
|
2844
|
+
*/
|
|
2845
|
+
endWheelZoom() {
|
|
2846
|
+
self.zoomSnapshot = undefined;
|
|
2847
|
+
},
|
|
3080
2848
|
/**
|
|
3081
2849
|
* #action
|
|
3082
2850
|
*/
|
|
@@ -3155,8 +2923,23 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3155
2923
|
* #action
|
|
3156
2924
|
*/
|
|
3157
2925
|
toggleTrack(id) {
|
|
3158
|
-
|
|
3159
|
-
|
|
2926
|
+
self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id));
|
|
2927
|
+
},
|
|
2928
|
+
/**
|
|
2929
|
+
* #action
|
|
2930
|
+
* show or hide several tracks at once, as the track selector's Show all
|
|
2931
|
+
* and Hide all do. A choice matching the track's default is dropped, so
|
|
2932
|
+
* it stays out of the shared URL
|
|
2933
|
+
*/
|
|
2934
|
+
setTracksShown(ids, shown) {
|
|
2935
|
+
for (const id of ids) {
|
|
2936
|
+
if (defaultOffTracks.has(id) === !shown) {
|
|
2937
|
+
self.turnedOffTracks.delete(id);
|
|
2938
|
+
}
|
|
2939
|
+
else {
|
|
2940
|
+
self.turnedOffTracks.set(id, !shown);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
3160
2943
|
},
|
|
3161
2944
|
/**
|
|
3162
2945
|
* #action
|
|
@@ -3302,7 +3085,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3302
3085
|
*/
|
|
3303
3086
|
get visibleDomainTypes() {
|
|
3304
3087
|
return this.categoricalDomainTypes
|
|
3305
|
-
.filter(d => !self.
|
|
3088
|
+
.filter(d => !self.featureOff(d.accession))
|
|
3306
3089
|
.toSorted((a, b) => a.start - b.start);
|
|
3307
3090
|
},
|
|
3308
3091
|
/**
|
|
@@ -3362,15 +3145,15 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3362
3145
|
/**
|
|
3363
3146
|
* #getter
|
|
3364
3147
|
* whether the overlay marks each domain with a bar under its row instead
|
|
3365
|
-
* of filling the row behind the letters
|
|
3366
|
-
*
|
|
3367
|
-
*
|
|
3148
|
+
* of filling the row behind the letters: whenever the domains do not own
|
|
3149
|
+
* the cell fill (`cellFill`), a filled box would paint over the residue
|
|
3150
|
+
* colors the user picked. Sub-row layout already stacks
|
|
3368
3151
|
* the boxes clear of the letters, and with the letters too small to draw
|
|
3369
3152
|
* the filled box is the only thing left to read.
|
|
3370
3153
|
*/
|
|
3371
3154
|
get domainUnderline() {
|
|
3372
3155
|
return (self.actuallyShowDomains &&
|
|
3373
|
-
|
|
3156
|
+
self.cellFill !== 'domain' &&
|
|
3374
3157
|
!self.subFeatureRows &&
|
|
3375
3158
|
self.showMsaLetters);
|
|
3376
3159
|
},
|
|
@@ -3819,6 +3602,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3819
3602
|
setHideHeader(arg) {
|
|
3820
3603
|
self.hideHeader = arg;
|
|
3821
3604
|
},
|
|
3605
|
+
/**
|
|
3606
|
+
* #action
|
|
3607
|
+
*/
|
|
3608
|
+
setEmbeddedMode(arg) {
|
|
3609
|
+
self.embeddedMode = arg;
|
|
3610
|
+
},
|
|
3822
3611
|
/**
|
|
3823
3612
|
* #action
|
|
3824
3613
|
*/
|
|
@@ -3883,15 +3672,16 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3883
3672
|
},
|
|
3884
3673
|
/**
|
|
3885
3674
|
* #action
|
|
3886
|
-
* show or hide an annotation type. Only
|
|
3887
|
-
* `turnedOffFeatures`
|
|
3675
|
+
* show or hide an annotation type. Only a choice that differs from the
|
|
3676
|
+
* type's default is recorded; see `turnedOffFeatures`
|
|
3888
3677
|
*/
|
|
3889
3678
|
setFilter(accession, shown) {
|
|
3890
|
-
|
|
3891
|
-
|
|
3679
|
+
// only a departure from the accession's default is recorded
|
|
3680
|
+
if (shown === self.defaultOffAccessions.has(accession)) {
|
|
3681
|
+
self.turnedOffFeatures.set(accession, !shown);
|
|
3892
3682
|
}
|
|
3893
3683
|
else {
|
|
3894
|
-
self.turnedOffFeatures.
|
|
3684
|
+
self.turnedOffFeatures.delete(accession);
|
|
3895
3685
|
}
|
|
3896
3686
|
},
|
|
3897
3687
|
/**
|
|
@@ -3899,20 +3689,23 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3899
3689
|
*/
|
|
3900
3690
|
fit() {
|
|
3901
3691
|
// two passes: each direction's viewport depends on whether the other
|
|
3902
|
-
// overflows (the minimap takes height, the vertical scrollbar width)
|
|
3692
|
+
// overflows (the minimap takes height, the vertical scrollbar width).
|
|
3693
|
+
// Fit shrinks an alignment into the window but never grows one past
|
|
3694
|
+
// the default cell size, which would stretch a small alignment's rows
|
|
3695
|
+
// and columns out of proportion
|
|
3903
3696
|
transaction(() => {
|
|
3904
3697
|
for (let pass = 0; pass < 2; pass++) {
|
|
3905
|
-
this.fitHorizontally();
|
|
3906
|
-
this.fitVertically();
|
|
3698
|
+
this.fitHorizontally(defaultColWidth);
|
|
3699
|
+
this.fitVertically(defaultRowHeight);
|
|
3907
3700
|
}
|
|
3908
3701
|
});
|
|
3909
3702
|
},
|
|
3910
3703
|
/**
|
|
3911
3704
|
* #action
|
|
3912
3705
|
*/
|
|
3913
|
-
fitVertically() {
|
|
3706
|
+
fitVertically(maxRowHeight = maxCellSize) {
|
|
3914
3707
|
if (self.numRows > 0) {
|
|
3915
|
-
self.
|
|
3708
|
+
self.setRowHeight(clamp(self.msaAreaHeight / self.numRows, minRowHeight, maxRowHeight));
|
|
3916
3709
|
}
|
|
3917
3710
|
self.scrollY = 0;
|
|
3918
3711
|
},
|
|
@@ -3928,7 +3721,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3928
3721
|
return;
|
|
3929
3722
|
}
|
|
3930
3723
|
transaction(() => {
|
|
3931
|
-
self.
|
|
3724
|
+
self.setColWidth(clamp(self.msaCanvasWidth / (span.endCol - span.startCol + 1), minColWidth, maxCellSize));
|
|
3932
3725
|
self.scrollX = clamp(-span.startCol * self.colWidth, self.maxScrollX, 0);
|
|
3933
3726
|
});
|
|
3934
3727
|
},
|
|
@@ -3966,7 +3759,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3966
3759
|
const { rowRuns } = resolvedSelection;
|
|
3967
3760
|
const first = rowRuns[0][0];
|
|
3968
3761
|
const last = rowRuns.at(-1)[1];
|
|
3969
|
-
self.
|
|
3762
|
+
self.setRowHeight(clamp(self.msaAreaHeight / (last - first + 1), minRowHeight, maxCellSize));
|
|
3970
3763
|
self.scrollY = clamp(-first * self.rowHeight, self.maxScrollY, 0);
|
|
3971
3764
|
}
|
|
3972
3765
|
});
|
|
@@ -3998,10 +3791,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3998
3791
|
/**
|
|
3999
3792
|
* #action
|
|
4000
3793
|
*/
|
|
4001
|
-
fitHorizontally() {
|
|
3794
|
+
fitHorizontally(maxColWidth = maxCellSize) {
|
|
4002
3795
|
if (self.numColumns > 0) {
|
|
4003
3796
|
// msaCanvasWidth excludes the vertical scrollbar's 20px
|
|
4004
|
-
self.
|
|
3797
|
+
self.setColWidth(clamp(self.msaCanvasWidth / self.numColumns, minColWidth, maxColWidth));
|
|
4005
3798
|
}
|
|
4006
3799
|
self.scrollX = 0;
|
|
4007
3800
|
},
|
|
@@ -4009,17 +3802,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4009
3802
|
if (self.highlightColumns?.length) {
|
|
4010
3803
|
self.setHighlightedColumns(self.highlightColumns);
|
|
4011
3804
|
}
|
|
4012
|
-
|
|
4013
|
-
//
|
|
4014
|
-
// `
|
|
4015
|
-
//
|
|
4016
|
-
//
|
|
4017
|
-
//
|
|
4018
|
-
|
|
4019
|
-
// `dataInitialized` is true once the MSA alone has loaded, when the
|
|
4020
|
-
// tree is still the flat stub, so a tree filehandle holds the seeding
|
|
4021
|
-
// until its text lands.
|
|
4022
|
-
addDisposer(self, reaction(() => self.dataInitialized && !(self.treeFilehandle && !self.data.tree)
|
|
3805
|
+
const awaitingTree = () => !!self.treeFilehandle && !self.data.tree;
|
|
3806
|
+
// `collapse`, `focus` and `rotate` marks seed `collapsed`, `showOnly`
|
|
3807
|
+
// and `rotated`. Seeding runs once per data load, `clades` change and
|
|
3808
|
+
// reroot, so expanding a seeded clade sticks until one of those. A tree
|
|
3809
|
+
// filehandle holds it back, since `dataInitialized` is true while the
|
|
3810
|
+
// tree is still the flat stub.
|
|
3811
|
+
addDisposer(self, reaction(() => self.dataInitialized && !awaitingTree()
|
|
4023
3812
|
? { clades: getSnapshot(self.clades), treeRoot: self.treeRoot }
|
|
4024
3813
|
: undefined, seed => {
|
|
4025
3814
|
if (!seed?.clades.length) {
|
|
@@ -4049,7 +3838,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4049
3838
|
if (region &&
|
|
4050
3839
|
self.viewInitialized &&
|
|
4051
3840
|
self.numColumns > 0 &&
|
|
4052
|
-
!(
|
|
3841
|
+
!awaitingTree()) {
|
|
4053
3842
|
this.revealRegion(region);
|
|
4054
3843
|
self.setRegion(undefined);
|
|
4055
3844
|
}
|
|
@@ -4071,89 +3860,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4071
3860
|
mql.removeEventListener('change', onChange);
|
|
4072
3861
|
});
|
|
4073
3862
|
}
|
|
4074
|
-
|
|
4075
|
-
* Fetch a filehandle whenever it changes, and hand the text to
|
|
4076
|
-
* `onLoad`.
|
|
4077
|
-
*
|
|
4078
|
-
* A generation guard keeps a slower superseded request from overwriting
|
|
4079
|
-
* the newer one's data, status or loading flag. A superseded or cleared
|
|
4080
|
-
* request is aborted and clears its status line.
|
|
4081
|
-
*
|
|
4082
|
-
* `clearFilehandle` runs after a local file loads, since a blob has no
|
|
4083
|
-
* URL to refetch, and when the user cancels, which returns the view to
|
|
4084
|
-
* the import form.
|
|
4085
|
-
*
|
|
4086
|
-
* `what` names the layer in a failure message. An `optional` layer
|
|
4087
|
-
* (annotations, row metadata) that fails adds a warning instead of an
|
|
4088
|
-
* error.
|
|
4089
|
-
*/
|
|
4090
|
-
const loadOnFilehandleChange = ({ what, getFilehandle, onLoad, setLoading, clearFilehandle, optional = false, }) => {
|
|
4091
|
-
let generation = 0;
|
|
4092
|
-
let cancelInFlight;
|
|
4093
|
-
addDisposer(self, () => {
|
|
4094
|
-
cancelInFlight?.();
|
|
4095
|
-
});
|
|
4096
|
-
addDisposer(self, autorun(async () => {
|
|
4097
|
-
const filehandle = getFilehandle();
|
|
4098
|
-
const current = ++generation;
|
|
4099
|
-
cancelInFlight?.();
|
|
4100
|
-
cancelInFlight = undefined;
|
|
4101
|
-
if (!filehandle) {
|
|
4102
|
-
setLoading?.(false);
|
|
4103
|
-
return;
|
|
4104
|
-
}
|
|
4105
|
-
const isCurrent = () => current === generation && isAlive(self);
|
|
4106
|
-
const controller = new AbortController();
|
|
4107
|
-
cancelInFlight = () => {
|
|
4108
|
-
controller.abort();
|
|
4109
|
-
if (isAlive(self)) {
|
|
4110
|
-
self.setStatus(undefined);
|
|
4111
|
-
}
|
|
4112
|
-
};
|
|
4113
|
-
try {
|
|
4114
|
-
setLoading?.(true);
|
|
4115
|
-
if (!optional) {
|
|
4116
|
-
self.setError(undefined);
|
|
4117
|
-
}
|
|
4118
|
-
const text = await fetchTextWithProgress(openLocation(filehandle), status => {
|
|
4119
|
-
if (isCurrent()) {
|
|
4120
|
-
self.setStatus(status);
|
|
4121
|
-
}
|
|
4122
|
-
}, { controller });
|
|
4123
|
-
if (isCurrent()) {
|
|
4124
|
-
transaction(() => {
|
|
4125
|
-
onLoad(text);
|
|
4126
|
-
if (filehandle.locationType === 'BlobLocation') {
|
|
4127
|
-
clearFilehandle?.();
|
|
4128
|
-
}
|
|
4129
|
-
});
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
catch (e) {
|
|
4133
|
-
if (isCurrent()) {
|
|
4134
|
-
if (isAbortError(e)) {
|
|
4135
|
-
clearFilehandle?.();
|
|
4136
|
-
}
|
|
4137
|
-
else {
|
|
4138
|
-
console.error(e);
|
|
4139
|
-
if (optional) {
|
|
4140
|
-
self.addWarning(`The ${what} did not load: ${e}`);
|
|
4141
|
-
}
|
|
4142
|
-
else {
|
|
4143
|
-
self.setError(e);
|
|
4144
|
-
}
|
|
4145
|
-
}
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
finally {
|
|
4149
|
-
if (isCurrent()) {
|
|
4150
|
-
cancelInFlight = undefined;
|
|
4151
|
-
setLoading?.(false);
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4154
|
-
}));
|
|
4155
|
-
};
|
|
4156
|
-
loadOnFilehandleChange({
|
|
3863
|
+
installFilehandleLoader(self, {
|
|
4157
3864
|
what: 'tree',
|
|
4158
3865
|
getFilehandle: () => self.treeFilehandle,
|
|
4159
3866
|
onLoad: text => {
|
|
@@ -4167,7 +3874,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4167
3874
|
},
|
|
4168
3875
|
});
|
|
4169
3876
|
// treeMetadata has no import-form step, so no loading flag or cancel
|
|
4170
|
-
|
|
3877
|
+
installFilehandleLoader(self, {
|
|
4171
3878
|
what: 'row metadata',
|
|
4172
3879
|
optional: true,
|
|
4173
3880
|
getFilehandle: () => self.treeMetadataFilehandle,
|
|
@@ -4196,7 +3903,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4196
3903
|
}
|
|
4197
3904
|
}));
|
|
4198
3905
|
// gffFilehandle loads into data.gff, which the autorun above parses
|
|
4199
|
-
|
|
3906
|
+
installFilehandleLoader(self, {
|
|
4200
3907
|
what: 'annotations',
|
|
4201
3908
|
optional: true,
|
|
4202
3909
|
getFilehandle: () => self.gffFilehandle,
|
|
@@ -4207,7 +3914,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4207
3914
|
self.setGFFFilehandle(undefined);
|
|
4208
3915
|
},
|
|
4209
3916
|
});
|
|
4210
|
-
|
|
3917
|
+
installFilehandleLoader(self, {
|
|
4211
3918
|
what: 'alignment',
|
|
4212
3919
|
getFilehandle: () => self.msaFilehandle,
|
|
4213
3920
|
onLoad: text => {
|
|
@@ -4228,20 +3935,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4228
3935
|
// xref problem https://github.com/GMOD/react-msaview/issues/75
|
|
4229
3936
|
addDisposer(self, autorun(() => {
|
|
4230
3937
|
if (self.dynamicColorSchemeName) {
|
|
4231
|
-
|
|
4232
|
-
self.colStats;
|
|
3938
|
+
readComputed(self.colStats);
|
|
4233
3939
|
}
|
|
4234
3940
|
if (self.showColumnStats) {
|
|
4235
|
-
|
|
4236
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
4237
|
-
self.colStats;
|
|
4238
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
4239
|
-
self.conservation;
|
|
4240
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
4241
|
-
self.propertyConservation;
|
|
3941
|
+
readComputed(self.colStats, self.conservation, self.propertyConservation);
|
|
4242
3942
|
}
|
|
4243
|
-
|
|
4244
|
-
self.columns;
|
|
3943
|
+
readComputed(self.columns);
|
|
4245
3944
|
}));
|
|
4246
3945
|
},
|
|
4247
3946
|
}))
|