react-msaview 9.1.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.js +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 +64 -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 +58 -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/tracks/TrackTooltipContent.js +2 -1
- package/dist/components/tracks/TrackTooltipContent.js.map +1 -1
- package/dist/components/tracks/drawTracks.js +13 -6
- package/dist/components/tracks/drawTracks.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 +148 -32
- package/dist/model.js +291 -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 +64 -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 +7 -3
- 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 +124 -0
- package/src/components/tracks/TrackBlocks.tsx +2 -2
- package/src/components/tracks/TrackTooltipContent.tsx +2 -1
- package/src/components/tracks/drawTracks.ts +15 -7
- 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 +370 -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 +7 -4
- 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).
|
|
@@ -2419,6 +2117,28 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2419
2117
|
},
|
|
2420
2118
|
}));
|
|
2421
2119
|
},
|
|
2120
|
+
/**
|
|
2121
|
+
* #getter
|
|
2122
|
+
* the reference row (`relativeTo`) repeated above the alignment, so it
|
|
2123
|
+
* stays in view while the rows compared against it scroll
|
|
2124
|
+
*/
|
|
2125
|
+
get referenceTrackModels() {
|
|
2126
|
+
const { relativeTo } = self;
|
|
2127
|
+
const data = relativeTo === undefined ? undefined : self.columns.get(relativeTo);
|
|
2128
|
+
return relativeTo === undefined || data === undefined
|
|
2129
|
+
? []
|
|
2130
|
+
: [
|
|
2131
|
+
{
|
|
2132
|
+
model: {
|
|
2133
|
+
id: 'reference-row',
|
|
2134
|
+
name: relativeTo,
|
|
2135
|
+
kind: 'reference',
|
|
2136
|
+
data,
|
|
2137
|
+
height: self.rowHeight,
|
|
2138
|
+
},
|
|
2139
|
+
},
|
|
2140
|
+
];
|
|
2141
|
+
},
|
|
2422
2142
|
get tracks() {
|
|
2423
2143
|
return [
|
|
2424
2144
|
...this.adapterTrackModels,
|
|
@@ -2427,13 +2147,14 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2427
2147
|
// every computed track reads the alignment's columns, and a tree, a
|
|
2428
2148
|
// GFF and a features panel draw a figure with none
|
|
2429
2149
|
...(self.numColumns > 0 ? this.computedTrackModels : []),
|
|
2150
|
+
...this.referenceTrackModels,
|
|
2430
2151
|
];
|
|
2431
2152
|
},
|
|
2432
2153
|
/**
|
|
2433
2154
|
* #getter
|
|
2434
2155
|
*/
|
|
2435
2156
|
get turnedOnTracks() {
|
|
2436
|
-
return this.tracks.filter(f => !trackIsOff(self.turnedOffTracks, f.model.id
|
|
2157
|
+
return this.tracks.filter(f => !trackIsOff(self.turnedOffTracks, f.model.id));
|
|
2437
2158
|
},
|
|
2438
2159
|
/**
|
|
2439
2160
|
* #getter
|
|
@@ -2722,7 +2443,9 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2722
2443
|
if (mapping.structure.id !== structureId) {
|
|
2723
2444
|
continue;
|
|
2724
2445
|
}
|
|
2725
|
-
if (asymId !== undefined &&
|
|
2446
|
+
if (asymId !== undefined &&
|
|
2447
|
+
mapping.structure.asymId !== undefined &&
|
|
2448
|
+
mapping.structure.asymId !== asymId) {
|
|
2726
2449
|
continue;
|
|
2727
2450
|
}
|
|
2728
2451
|
const segment = mapping.segments.find(seg => position >= seg.structStart && position <= seg.structEnd);
|
|
@@ -2739,10 +2462,14 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2739
2462
|
.views(self => ({
|
|
2740
2463
|
/**
|
|
2741
2464
|
* #getter
|
|
2742
|
-
* the branch-length scale bar over the tree, undefined
|
|
2743
|
-
* or when the tree area is
|
|
2465
|
+
* the branch-length scale bar over the tree, undefined while
|
|
2466
|
+
* `showTreeScaleBar` is off, in cladogram mode, or when the tree area is
|
|
2467
|
+
* too narrow for one
|
|
2744
2468
|
*/
|
|
2745
2469
|
get treeScaleBar() {
|
|
2470
|
+
if (!self.showTreeScaleBar) {
|
|
2471
|
+
return undefined;
|
|
2472
|
+
}
|
|
2746
2473
|
return scaleBarLength(self.pxPerBranchLength, self.effectiveTreeAreaWidth - self.marginLeft * 2);
|
|
2747
2474
|
},
|
|
2748
2475
|
/**
|
|
@@ -2789,8 +2516,38 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2789
2516
|
}
|
|
2790
2517
|
return types;
|
|
2791
2518
|
},
|
|
2519
|
+
/**
|
|
2520
|
+
* accessions hidden until the user turns them on, such as a Stockholm
|
|
2521
|
+
* file's #=GR lines
|
|
2522
|
+
*/
|
|
2523
|
+
get defaultOffAccessions() {
|
|
2524
|
+
return new Set(self.allAnnotations.filter(a => a.defaultOff).map(a => a.accession));
|
|
2525
|
+
},
|
|
2526
|
+
/**
|
|
2527
|
+
* #method
|
|
2528
|
+
*/
|
|
2529
|
+
featureOff(accession) {
|
|
2530
|
+
return (self.turnedOffFeatures.get(accession) ??
|
|
2531
|
+
this.defaultOffAccessions.has(accession));
|
|
2532
|
+
},
|
|
2792
2533
|
get filteredAnnotations() {
|
|
2793
|
-
return self.allAnnotations.filter(r => !
|
|
2534
|
+
return self.allAnnotations.filter(r => !this.featureOff(r.accession));
|
|
2535
|
+
},
|
|
2536
|
+
/**
|
|
2537
|
+
* #getter
|
|
2538
|
+
* what colors the alignment's cells: the residue scheme, the domains
|
|
2539
|
+
* covering them, or nothing, which leaves the scheme to the letters.
|
|
2540
|
+
* Domains own the fill only while some are drawn, so a file whose
|
|
2541
|
+
* annotations all start hidden keeps its residue colors
|
|
2542
|
+
*/
|
|
2543
|
+
get cellFill() {
|
|
2544
|
+
return !self.bgColor
|
|
2545
|
+
? 'none'
|
|
2546
|
+
: self.actuallyShowDomains &&
|
|
2547
|
+
self.domainFill &&
|
|
2548
|
+
this.filteredAnnotations.length > 0
|
|
2549
|
+
? 'domain'
|
|
2550
|
+
: 'residue';
|
|
2794
2551
|
},
|
|
2795
2552
|
get annotationsByRow() {
|
|
2796
2553
|
const { rowNamesSet } = self;
|
|
@@ -2866,6 +2623,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
2866
2623
|
get blocks2d() {
|
|
2867
2624
|
return self.blocksY.flatMap(by => self.blocksX.map(bx => [bx, by]));
|
|
2868
2625
|
},
|
|
2626
|
+
/**
|
|
2627
|
+
* #getter
|
|
2628
|
+
*/
|
|
2629
|
+
get wheelZooming() {
|
|
2630
|
+
return self.zoomSnapshot !== undefined;
|
|
2631
|
+
},
|
|
2869
2632
|
/**
|
|
2870
2633
|
* #getter
|
|
2871
2634
|
*/
|
|
@@ -3062,11 +2825,20 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3062
2825
|
transaction(() => {
|
|
3063
2826
|
const colInView = (-self.scrollX + offsetX) / self.colWidth;
|
|
3064
2827
|
const rowInView = (-self.scrollY + offsetY) / self.rowHeight;
|
|
2828
|
+
// zooming both axes, an axis holds at the default only while the
|
|
2829
|
+
// other sits there too, or one would hold while the other grows
|
|
2830
|
+
const canHold = axis !== 'both' ||
|
|
2831
|
+
(self.colWidth === defaultColWidth &&
|
|
2832
|
+
self.rowHeight === defaultRowHeight);
|
|
3065
2833
|
if (axis !== 'vertical') {
|
|
3066
|
-
|
|
2834
|
+
const { value, held } = detentZoom(self.colWidth, scaleFactor, defaultColWidth, self.zoomHeld.col, minColWidth, canHold);
|
|
2835
|
+
self.colWidth = value;
|
|
2836
|
+
self.zoomHeld = { ...self.zoomHeld, col: held };
|
|
3067
2837
|
}
|
|
3068
2838
|
if (axis !== 'horizontal') {
|
|
3069
|
-
|
|
2839
|
+
const { value, held } = detentZoom(self.rowHeight, scaleFactor, defaultRowHeight, self.zoomHeld.row, minRowHeight, canHold);
|
|
2840
|
+
self.rowHeight = value;
|
|
2841
|
+
self.zoomHeld = { ...self.zoomHeld, row: held };
|
|
3070
2842
|
}
|
|
3071
2843
|
self.scrollX = clamp(offsetX - colInView * self.colWidth, self.maxScrollX, 0);
|
|
3072
2844
|
const anchoredScrollY = offsetY - rowInView * self.rowHeight;
|
|
@@ -3077,6 +2849,25 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3077
2849
|
self.scrollY = clamp(anchoredScrollY * (1 - topBias), self.maxScrollY, 0);
|
|
3078
2850
|
});
|
|
3079
2851
|
},
|
|
2852
|
+
/**
|
|
2853
|
+
* #action
|
|
2854
|
+
* zoomToPos for one wheel or pinch frame. The alignment keeps the blocks
|
|
2855
|
+
* it drew at the start of the gesture, scaled, and redraws them only once
|
|
2856
|
+
* they stop covering the viewport; endWheelZoom redraws them sharp
|
|
2857
|
+
*/
|
|
2858
|
+
wheelZoomToPos(scaleFactor, offsetX, offsetY) {
|
|
2859
|
+
self.zoomSnapshot ??= takeZoomSnapshot(self, false);
|
|
2860
|
+
this.zoomToPos(scaleFactor, offsetX, offsetY, self.wheelZoomAxis);
|
|
2861
|
+
if (!zoomSnapshotCovers(self, self.zoomSnapshot)) {
|
|
2862
|
+
self.zoomSnapshot = takeZoomSnapshot(self, true);
|
|
2863
|
+
}
|
|
2864
|
+
},
|
|
2865
|
+
/**
|
|
2866
|
+
* #action
|
|
2867
|
+
*/
|
|
2868
|
+
endWheelZoom() {
|
|
2869
|
+
self.zoomSnapshot = undefined;
|
|
2870
|
+
},
|
|
3080
2871
|
/**
|
|
3081
2872
|
* #action
|
|
3082
2873
|
*/
|
|
@@ -3155,8 +2946,23 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3155
2946
|
* #action
|
|
3156
2947
|
*/
|
|
3157
2948
|
toggleTrack(id) {
|
|
3158
|
-
|
|
3159
|
-
|
|
2949
|
+
self.turnedOffTracks.set(id, !trackIsOff(self.turnedOffTracks, id));
|
|
2950
|
+
},
|
|
2951
|
+
/**
|
|
2952
|
+
* #action
|
|
2953
|
+
* show or hide several tracks at once, as the track selector's Show all
|
|
2954
|
+
* and Hide all do. A choice matching the track's default is dropped, so
|
|
2955
|
+
* it stays out of the shared URL
|
|
2956
|
+
*/
|
|
2957
|
+
setTracksShown(ids, shown) {
|
|
2958
|
+
for (const id of ids) {
|
|
2959
|
+
if (defaultOffTracks.has(id) === !shown) {
|
|
2960
|
+
self.turnedOffTracks.delete(id);
|
|
2961
|
+
}
|
|
2962
|
+
else {
|
|
2963
|
+
self.turnedOffTracks.set(id, !shown);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
3160
2966
|
},
|
|
3161
2967
|
/**
|
|
3162
2968
|
* #action
|
|
@@ -3302,7 +3108,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3302
3108
|
*/
|
|
3303
3109
|
get visibleDomainTypes() {
|
|
3304
3110
|
return this.categoricalDomainTypes
|
|
3305
|
-
.filter(d => !self.
|
|
3111
|
+
.filter(d => !self.featureOff(d.accession))
|
|
3306
3112
|
.toSorted((a, b) => a.start - b.start);
|
|
3307
3113
|
},
|
|
3308
3114
|
/**
|
|
@@ -3362,15 +3168,15 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3362
3168
|
/**
|
|
3363
3169
|
* #getter
|
|
3364
3170
|
* whether the overlay marks each domain with a bar under its row instead
|
|
3365
|
-
* of filling the row behind the letters
|
|
3366
|
-
*
|
|
3367
|
-
*
|
|
3171
|
+
* of filling the row behind the letters: whenever the domains do not own
|
|
3172
|
+
* the cell fill (`cellFill`), a filled box would paint over the residue
|
|
3173
|
+
* colors the user picked. Sub-row layout already stacks
|
|
3368
3174
|
* the boxes clear of the letters, and with the letters too small to draw
|
|
3369
3175
|
* the filled box is the only thing left to read.
|
|
3370
3176
|
*/
|
|
3371
3177
|
get domainUnderline() {
|
|
3372
3178
|
return (self.actuallyShowDomains &&
|
|
3373
|
-
|
|
3179
|
+
self.cellFill !== 'domain' &&
|
|
3374
3180
|
!self.subFeatureRows &&
|
|
3375
3181
|
self.showMsaLetters);
|
|
3376
3182
|
},
|
|
@@ -3819,6 +3625,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3819
3625
|
setHideHeader(arg) {
|
|
3820
3626
|
self.hideHeader = arg;
|
|
3821
3627
|
},
|
|
3628
|
+
/**
|
|
3629
|
+
* #action
|
|
3630
|
+
*/
|
|
3631
|
+
setEmbeddedMode(arg) {
|
|
3632
|
+
self.embeddedMode = arg;
|
|
3633
|
+
},
|
|
3822
3634
|
/**
|
|
3823
3635
|
* #action
|
|
3824
3636
|
*/
|
|
@@ -3883,15 +3695,16 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3883
3695
|
},
|
|
3884
3696
|
/**
|
|
3885
3697
|
* #action
|
|
3886
|
-
* show or hide an annotation type. Only
|
|
3887
|
-
* `turnedOffFeatures`
|
|
3698
|
+
* show or hide an annotation type. Only a choice that differs from the
|
|
3699
|
+
* type's default is recorded; see `turnedOffFeatures`
|
|
3888
3700
|
*/
|
|
3889
3701
|
setFilter(accession, shown) {
|
|
3890
|
-
|
|
3891
|
-
|
|
3702
|
+
// only a departure from the accession's default is recorded
|
|
3703
|
+
if (shown === self.defaultOffAccessions.has(accession)) {
|
|
3704
|
+
self.turnedOffFeatures.set(accession, !shown);
|
|
3892
3705
|
}
|
|
3893
3706
|
else {
|
|
3894
|
-
self.turnedOffFeatures.
|
|
3707
|
+
self.turnedOffFeatures.delete(accession);
|
|
3895
3708
|
}
|
|
3896
3709
|
},
|
|
3897
3710
|
/**
|
|
@@ -3899,20 +3712,23 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3899
3712
|
*/
|
|
3900
3713
|
fit() {
|
|
3901
3714
|
// two passes: each direction's viewport depends on whether the other
|
|
3902
|
-
// overflows (the minimap takes height, the vertical scrollbar width)
|
|
3715
|
+
// overflows (the minimap takes height, the vertical scrollbar width).
|
|
3716
|
+
// Fit shrinks an alignment into the window but never grows one past
|
|
3717
|
+
// the default cell size, which would stretch a small alignment's rows
|
|
3718
|
+
// and columns out of proportion
|
|
3903
3719
|
transaction(() => {
|
|
3904
3720
|
for (let pass = 0; pass < 2; pass++) {
|
|
3905
|
-
this.fitHorizontally();
|
|
3906
|
-
this.fitVertically();
|
|
3721
|
+
this.fitHorizontally(defaultColWidth);
|
|
3722
|
+
this.fitVertically(defaultRowHeight);
|
|
3907
3723
|
}
|
|
3908
3724
|
});
|
|
3909
3725
|
},
|
|
3910
3726
|
/**
|
|
3911
3727
|
* #action
|
|
3912
3728
|
*/
|
|
3913
|
-
fitVertically() {
|
|
3729
|
+
fitVertically(maxRowHeight = maxCellSize) {
|
|
3914
3730
|
if (self.numRows > 0) {
|
|
3915
|
-
self.
|
|
3731
|
+
self.setRowHeight(clamp(self.msaAreaHeight / self.numRows, minRowHeight, maxRowHeight));
|
|
3916
3732
|
}
|
|
3917
3733
|
self.scrollY = 0;
|
|
3918
3734
|
},
|
|
@@ -3928,7 +3744,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3928
3744
|
return;
|
|
3929
3745
|
}
|
|
3930
3746
|
transaction(() => {
|
|
3931
|
-
self.
|
|
3747
|
+
self.setColWidth(clamp(self.msaCanvasWidth / (span.endCol - span.startCol + 1), minColWidth, maxCellSize));
|
|
3932
3748
|
self.scrollX = clamp(-span.startCol * self.colWidth, self.maxScrollX, 0);
|
|
3933
3749
|
});
|
|
3934
3750
|
},
|
|
@@ -3966,7 +3782,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3966
3782
|
const { rowRuns } = resolvedSelection;
|
|
3967
3783
|
const first = rowRuns[0][0];
|
|
3968
3784
|
const last = rowRuns.at(-1)[1];
|
|
3969
|
-
self.
|
|
3785
|
+
self.setRowHeight(clamp(self.msaAreaHeight / (last - first + 1), minRowHeight, maxCellSize));
|
|
3970
3786
|
self.scrollY = clamp(-first * self.rowHeight, self.maxScrollY, 0);
|
|
3971
3787
|
}
|
|
3972
3788
|
});
|
|
@@ -3998,10 +3814,10 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
3998
3814
|
/**
|
|
3999
3815
|
* #action
|
|
4000
3816
|
*/
|
|
4001
|
-
fitHorizontally() {
|
|
3817
|
+
fitHorizontally(maxColWidth = maxCellSize) {
|
|
4002
3818
|
if (self.numColumns > 0) {
|
|
4003
3819
|
// msaCanvasWidth excludes the vertical scrollbar's 20px
|
|
4004
|
-
self.
|
|
3820
|
+
self.setColWidth(clamp(self.msaCanvasWidth / self.numColumns, minColWidth, maxColWidth));
|
|
4005
3821
|
}
|
|
4006
3822
|
self.scrollX = 0;
|
|
4007
3823
|
},
|
|
@@ -4009,17 +3825,13 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4009
3825
|
if (self.highlightColumns?.length) {
|
|
4010
3826
|
self.setHighlightedColumns(self.highlightColumns);
|
|
4011
3827
|
}
|
|
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)
|
|
3828
|
+
const awaitingTree = () => !!self.treeFilehandle && !self.data.tree;
|
|
3829
|
+
// `collapse`, `focus` and `rotate` marks seed `collapsed`, `showOnly`
|
|
3830
|
+
// and `rotated`. Seeding runs once per data load, `clades` change and
|
|
3831
|
+
// reroot, so expanding a seeded clade sticks until one of those. A tree
|
|
3832
|
+
// filehandle holds it back, since `dataInitialized` is true while the
|
|
3833
|
+
// tree is still the flat stub.
|
|
3834
|
+
addDisposer(self, reaction(() => self.dataInitialized && !awaitingTree()
|
|
4023
3835
|
? { clades: getSnapshot(self.clades), treeRoot: self.treeRoot }
|
|
4024
3836
|
: undefined, seed => {
|
|
4025
3837
|
if (!seed?.clades.length) {
|
|
@@ -4049,7 +3861,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4049
3861
|
if (region &&
|
|
4050
3862
|
self.viewInitialized &&
|
|
4051
3863
|
self.numColumns > 0 &&
|
|
4052
|
-
!(
|
|
3864
|
+
!awaitingTree()) {
|
|
4053
3865
|
this.revealRegion(region);
|
|
4054
3866
|
self.setRegion(undefined);
|
|
4055
3867
|
}
|
|
@@ -4071,89 +3883,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4071
3883
|
mql.removeEventListener('change', onChange);
|
|
4072
3884
|
});
|
|
4073
3885
|
}
|
|
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({
|
|
3886
|
+
installFilehandleLoader(self, {
|
|
4157
3887
|
what: 'tree',
|
|
4158
3888
|
getFilehandle: () => self.treeFilehandle,
|
|
4159
3889
|
onLoad: text => {
|
|
@@ -4167,7 +3897,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4167
3897
|
},
|
|
4168
3898
|
});
|
|
4169
3899
|
// treeMetadata has no import-form step, so no loading flag or cancel
|
|
4170
|
-
|
|
3900
|
+
installFilehandleLoader(self, {
|
|
4171
3901
|
what: 'row metadata',
|
|
4172
3902
|
optional: true,
|
|
4173
3903
|
getFilehandle: () => self.treeMetadataFilehandle,
|
|
@@ -4196,7 +3926,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4196
3926
|
}
|
|
4197
3927
|
}));
|
|
4198
3928
|
// gffFilehandle loads into data.gff, which the autorun above parses
|
|
4199
|
-
|
|
3929
|
+
installFilehandleLoader(self, {
|
|
4200
3930
|
what: 'annotations',
|
|
4201
3931
|
optional: true,
|
|
4202
3932
|
getFilehandle: () => self.gffFilehandle,
|
|
@@ -4207,7 +3937,7 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4207
3937
|
self.setGFFFilehandle(undefined);
|
|
4208
3938
|
},
|
|
4209
3939
|
});
|
|
4210
|
-
|
|
3940
|
+
installFilehandleLoader(self, {
|
|
4211
3941
|
what: 'alignment',
|
|
4212
3942
|
getFilehandle: () => self.msaFilehandle,
|
|
4213
3943
|
onLoad: text => {
|
|
@@ -4228,20 +3958,12 @@ function stateModelFactory({ maxInlineSnapshotBytes = defaultMaxInlineSnapshotBy
|
|
|
4228
3958
|
// xref problem https://github.com/GMOD/react-msaview/issues/75
|
|
4229
3959
|
addDisposer(self, autorun(() => {
|
|
4230
3960
|
if (self.dynamicColorSchemeName) {
|
|
4231
|
-
|
|
4232
|
-
self.colStats;
|
|
3961
|
+
readComputed(self.colStats);
|
|
4233
3962
|
}
|
|
4234
3963
|
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;
|
|
3964
|
+
readComputed(self.colStats, self.conservation, self.propertyConservation);
|
|
4242
3965
|
}
|
|
4243
|
-
|
|
4244
|
-
self.columns;
|
|
3966
|
+
readComputed(self.columns);
|
|
4245
3967
|
}));
|
|
4246
3968
|
},
|
|
4247
3969
|
}))
|