react-msaview 5.10.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.js +99 -99
- package/bundle/index.js.map +4 -4
- package/dist/clustalX.js +22 -22
- package/dist/clustalX.js.map +1 -1
- package/dist/columnCounts.d.ts +7 -0
- package/dist/columnCounts.js +18 -0
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +9 -0
- package/dist/components/CopyButton.js +23 -0
- package/dist/components/CopyButton.js.map +1 -0
- package/dist/components/DragHandle.d.ts +25 -0
- package/dist/components/DragHandle.js +49 -0
- package/dist/components/DragHandle.js.map +1 -0
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +4 -2
- package/dist/components/ResizeHandles.js +35 -54
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -10
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +11 -25
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +3 -3
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +52 -129
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
- package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
- package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
- package/dist/components/dialogs/TrackInfoDialog.js +8 -14
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/FileMenu.js +3 -3
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +1 -1
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
- package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
- package/dist/components/header/getAnnotationsMenu.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +15 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +66 -57
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +40 -23
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.d.ts +2 -1
- package/dist/components/minimap/MinimapSVG.js +3 -4
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +10 -1
- package/dist/components/minimap/minimapLayout.js +15 -10
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
- package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
- package/dist/components/msa/AnnotationLegend.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +9 -3
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +12 -3
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +1 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/MSAPanel.js +2 -2
- package/dist/components/msa/MSAPanel.js.map +1 -1
- package/dist/components/msa/domainBandCursor.d.ts +17 -0
- package/dist/components/msa/domainBandCursor.js +39 -0
- package/dist/components/msa/domainBandCursor.js.map +1 -0
- package/dist/components/msa/msaRaster.d.ts +63 -0
- package/dist/components/msa/msaRaster.js +252 -0
- package/dist/components/msa/msaRaster.js.map +1 -0
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +27 -41
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/tileColor.d.ts +6 -0
- package/dist/components/msa/tileColor.js +35 -0
- package/dist/components/msa/tileColor.js.map +1 -0
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.d.ts +14 -0
- package/dist/components/tracks/TrackBlocks.js +65 -0
- package/dist/components/tracks/TrackBlocks.js.map +1 -0
- package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
- package/dist/components/tracks/drawTracks.js +193 -0
- package/dist/components/tracks/drawTracks.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -2
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +7 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +5 -2
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +5 -2
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -1
- package/dist/ggplotPalettes.d.ts +0 -1
- package/dist/ggplotPalettes.js +0 -3
- package/dist/ggplotPalettes.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -6
- package/dist/hierarchy.js +4 -6
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/launchInterProScan.d.ts +0 -23
- package/dist/launchInterProScan.js +2 -5
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/measureTextCanvas.js +21 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/DataModel.d.ts +1 -4
- package/dist/model/DataModel.js +8 -10
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +86 -107
- package/dist/model.js +238 -162
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +34 -18
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderTestEnv.d.ts +43 -0
- package/dist/renderTestEnv.js +88 -0
- package/dist/renderTestEnv.js.map +1 -0
- package/dist/renderToSvg.js +3 -3
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +13 -17
- package/dist/rowCoordinateCalculations.js +45 -39
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/sequenceLogo.d.ts +28 -0
- package/dist/sequenceLogo.js +47 -0
- package/dist/sequenceLogo.js.map +1 -0
- package/dist/types.d.ts +6 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/clustalX.ts +22 -22
- package/src/colSpace.test.ts +27 -0
- package/src/columnCounts.test.ts +15 -0
- package/src/columnCounts.ts +19 -0
- package/src/components/CopyButton.tsx +44 -0
- package/src/components/DragHandle.tsx +81 -0
- package/src/components/Loading.tsx +10 -1
- package/src/components/MSAView.tsx +2 -3
- package/src/components/ResizeHandles.tsx +70 -87
- package/src/components/SequenceTextArea.tsx +3 -16
- package/src/components/VerticalScrollbar.tsx +20 -33
- package/src/components/dialogs/FeatureDialog.tsx +3 -4
- package/src/components/dialogs/InterProScanDialog.tsx +54 -131
- package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
- package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
- package/src/components/header/FileMenu.tsx +3 -3
- package/src/components/header/GappynessSlider.tsx +4 -1
- package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
- package/src/components/header/settingsMenuItems.ts +18 -0
- package/src/components/import/ImportForm.tsx +1 -1
- package/src/components/import/ImportFormExamples.tsx +82 -133
- package/src/components/minimap/Minimap.tsx +67 -32
- package/src/components/minimap/MinimapSVG.tsx +32 -31
- package/src/components/minimap/minimapLayout.test.ts +60 -0
- package/src/components/minimap/minimapLayout.ts +15 -10
- package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
- package/src/components/msa/MSACanvas.tsx +24 -11
- package/src/components/msa/MSACanvasBlock.tsx +13 -3
- package/src/components/msa/MSAMouseoverCanvas.tsx +1 -3
- package/src/components/msa/MSAPanel.tsx +2 -2
- package/src/components/msa/domainBandCursor.ts +41 -0
- package/src/components/msa/msaRaster.test.ts +145 -0
- package/src/components/msa/msaRaster.ts +350 -0
- package/src/components/msa/renderMSABlock.ts +34 -43
- package/src/components/msa/renderMSAMouseover.ts +1 -3
- package/src/components/msa/tileColor.ts +37 -0
- package/src/components/renderCtx.ts +2 -0
- package/src/components/tracks/TrackBlocks.tsx +111 -0
- package/src/components/tracks/drawTracks.ts +323 -0
- package/src/components/tree/TreeCanvas.tsx +16 -4
- package/src/components/tree/TreeCanvasBlock.tsx +18 -11
- package/src/components/tree/TreeRuler.tsx +7 -2
- package/src/components/tree/renderTreeCanvas.test.ts +15 -7
- package/src/components/tree/renderTreeCanvas.ts +5 -1
- package/src/constants.ts +10 -0
- package/src/domainBands.test.ts +24 -0
- package/src/domainLetterColor.test.ts +80 -0
- package/src/ggplotPalettes.ts +0 -4
- package/src/hierarchy.test.ts +7 -7
- package/src/hierarchy.ts +6 -9
- package/src/impgRender.test.tsx +2 -57
- package/src/index.ts +4 -1
- package/src/largeTree.test.ts +39 -4
- package/src/launchInterProScan.ts +3 -30
- package/src/loadErrorDisplay.test.tsx +102 -0
- package/src/measureTextCanvas.ts +22 -6
- package/src/model/DataModel.ts +11 -11
- package/src/model.ts +262 -172
- package/src/neighborJoining.ts +36 -18
- package/src/renderDomainsSvg.test.tsx +2 -57
- package/src/renderMinimapSvg.test.tsx +55 -0
- package/src/renderSequenceLogoSvg.test.tsx +176 -0
- package/src/renderTestEnv.ts +96 -0
- package/src/renderToSvg.tsx +3 -3
- package/src/rowCoordinateCalculations.ts +48 -44
- package/src/sequenceLogo.test.ts +88 -0
- package/src/sequenceLogo.ts +63 -0
- package/src/types.ts +17 -16
- package/src/version.ts +1 -1
- package/src/viewChrome.test.tsx +134 -0
- package/dist/components/ConservationTrack.d.ts +0 -8
- package/dist/components/ConservationTrack.js +0 -49
- package/dist/components/ConservationTrack.js.map +0 -1
- package/dist/components/TextTrack.d.ts +0 -8
- package/dist/components/TextTrack.js +0 -58
- package/dist/components/TextTrack.js.map +0 -1
- package/dist/components/dialogs/DomainDialog.d.ts +0 -6
- package/dist/components/dialogs/DomainDialog.js +0 -19
- package/dist/components/dialogs/DomainDialog.js.map +0 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
- package/dist/components/header/SettingsMenu.d.ts +0 -6
- package/dist/components/header/SettingsMenu.js +0 -40
- package/dist/components/header/SettingsMenu.js.map +0 -1
- package/dist/components/header/getDomainsMenu.js.map +0 -1
- package/dist/components/msa/DomainLegend.js.map +0 -1
- package/dist/components/tracks/renderTracksSvg.js +0 -126
- package/dist/components/tracks/renderTracksSvg.js.map +0 -1
- package/src/components/ConservationTrack.tsx +0 -99
- package/src/components/TextTrack.tsx +0 -103
- package/src/components/dialogs/DomainDialog.tsx +0 -45
- package/src/components/header/SettingsMenu.tsx +0 -57
- package/src/components/tracks/renderTracksSvg.ts +0 -247
|
@@ -16,6 +16,25 @@ import { isBlank } from './util.js';
|
|
|
16
16
|
* This counts only non-gap characters ('-' and '.' are gaps).
|
|
17
17
|
* Each row can have different seqPos values for the same globalCol due to gaps.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* Index of the first element in `[0, len)` for which `pred` holds, over a range
|
|
21
|
+
* where `pred` is false up to some point and true from there on. Both
|
|
22
|
+
* conversions below are that shape, since `blanks` is sorted ascending.
|
|
23
|
+
*/
|
|
24
|
+
function partitionPoint(len, pred) {
|
|
25
|
+
let left = 0;
|
|
26
|
+
let right = len;
|
|
27
|
+
while (left < right) {
|
|
28
|
+
const mid = (left + right) >>> 1;
|
|
29
|
+
if (pred(mid)) {
|
|
30
|
+
right = mid;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
left = mid + 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return left;
|
|
37
|
+
}
|
|
19
38
|
/**
|
|
20
39
|
* Convert a visible column index to a global column index.
|
|
21
40
|
* This is used when translating mouse/screen coordinates to MSA coordinates.
|
|
@@ -25,26 +44,10 @@ import { isBlank } from './util.js';
|
|
|
25
44
|
* @returns The corresponding global column index in the full MSA
|
|
26
45
|
*/
|
|
27
46
|
export function visibleColToGlobalCol(blanks, visibleCol) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Skip any leading blank columns (blanks at the very beginning)
|
|
33
|
-
while (blankArrayIndex < blanksLen && blanks[blankArrayIndex] === globalCol) {
|
|
34
|
-
blankArrayIndex++;
|
|
35
|
-
globalCol++;
|
|
36
|
-
}
|
|
37
|
-
while (currentVisibleCol < visibleCol) {
|
|
38
|
-
currentVisibleCol++;
|
|
39
|
-
globalCol++;
|
|
40
|
-
// Skip any blank columns after incrementing
|
|
41
|
-
while (blankArrayIndex < blanksLen &&
|
|
42
|
-
blanks[blankArrayIndex] === globalCol) {
|
|
43
|
-
blankArrayIndex++;
|
|
44
|
-
globalCol++;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return globalCol;
|
|
47
|
+
// `blanks[i] - i` counts the visible columns before the i-th blank, so the
|
|
48
|
+
// first blank with more than visibleCol of them in front of it is the first
|
|
49
|
+
// one sitting past this column. Its index is how many blanks shift it right.
|
|
50
|
+
return (visibleCol + partitionPoint(blanks.length, i => blanks[i] - i > visibleCol));
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
53
|
* Convert a global column index to a visible column index.
|
|
@@ -55,25 +58,28 @@ export function visibleColToGlobalCol(blanks, visibleCol) {
|
|
|
55
58
|
* @returns The visible column index, or undefined if the column is hidden
|
|
56
59
|
*/
|
|
57
60
|
export function globalColToVisibleCol(blanks, globalCol) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
61
|
+
const blanksBefore = countBlanksBefore(blanks, globalCol);
|
|
62
|
+
return blanks[blanksBefore] === globalCol
|
|
63
|
+
? undefined // Column is hidden
|
|
64
|
+
: globalCol - blanksBefore;
|
|
65
|
+
}
|
|
66
|
+
function countBlanksBefore(blanks, globalCol) {
|
|
67
|
+
return partitionPoint(blanks.length, i => blanks[i] >= globalCol);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How many visible columns come before a global column. For a visible column
|
|
71
|
+
* that is its own visible index; for a hidden one it is the index of the next
|
|
72
|
+
* visible column, i.e. where the hidden column collapses to.
|
|
73
|
+
*
|
|
74
|
+
* That total answer is what spans want: resolving both ends of a span through
|
|
75
|
+
* globalColToVisibleCol instead loses the whole span whenever either end lands
|
|
76
|
+
* in a hidden column.
|
|
77
|
+
*
|
|
78
|
+
* @param blanks - Sorted array of global column indices that are hidden
|
|
79
|
+
* @param globalCol - The global column index in the full MSA
|
|
80
|
+
*/
|
|
81
|
+
export function visibleColsBefore(blanks, globalCol) {
|
|
82
|
+
return globalCol - countBlanksBefore(blanks, globalCol);
|
|
77
83
|
}
|
|
78
84
|
/**
|
|
79
85
|
* Convert a visible column to a row-specific sequence position.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rowCoordinateCalculations.js","sourceRoot":"","sources":["../src/rowCoordinateCalculations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;;;;;;;;;;;;;;GAgBG;AAEH
|
|
1
|
+
{"version":3,"file":"rowCoordinateCalculations.js","sourceRoot":"","sources":["../src/rowCoordinateCalculations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ColumnCounts } from './columnCounts.ts';
|
|
2
|
+
export interface LogoLetter {
|
|
3
|
+
letter: string;
|
|
4
|
+
/** height of this letter's slice of the stack, in bits */
|
|
5
|
+
bits: number;
|
|
6
|
+
}
|
|
7
|
+
/** the y-axis ceiling of a logo: the information content of a fully conserved
|
|
8
|
+
* column, log2 of the alphabet size */
|
|
9
|
+
export declare function maxBitsFor(sequenceType: 'dna' | 'rna' | 'amino'): number;
|
|
10
|
+
/**
|
|
11
|
+
* One column of a sequence logo, tallest letter last so a caller can draw the
|
|
12
|
+
* stack bottom-up in array order.
|
|
13
|
+
*
|
|
14
|
+
* Heights follow Schneider & Stephens: the column's information content is
|
|
15
|
+
* `log2(alphabet) - H` bits, and each residue takes the share of that its own
|
|
16
|
+
* frequency earns. Frequencies come from the non-gap residues only, so a column
|
|
17
|
+
* of two residues and eight gaps is read as a two-way choice rather than a
|
|
18
|
+
* ten-way one -- otherwise every gappy column would look uninformative for the
|
|
19
|
+
* wrong reason.
|
|
20
|
+
*
|
|
21
|
+
* The whole stack is then scaled by the non-gap fraction, which plain WebLogo
|
|
22
|
+
* does not do. Here it earns its place: the track sits directly under an
|
|
23
|
+
* alignment where the gaps are visible, and a column that is 90% gap drawing a
|
|
24
|
+
* full-height stack off its one remaining pair of residues reads as a conserved
|
|
25
|
+
* site. Scaling makes stack height mean "how much this column tells you about
|
|
26
|
+
* the family", matching what the conservation track above it already plots.
|
|
27
|
+
*/
|
|
28
|
+
export declare function columnLogoStack(colStats: ColumnCounts, col: number, maxBits: number): LogoLetter[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { letterOfResidueSlot } from './columnCounts.js';
|
|
2
|
+
/** the y-axis ceiling of a logo: the information content of a fully conserved
|
|
3
|
+
* column, log2 of the alphabet size */
|
|
4
|
+
export function maxBitsFor(sequenceType) {
|
|
5
|
+
return Math.log2(sequenceType === 'amino' ? 20 : 4);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One column of a sequence logo, tallest letter last so a caller can draw the
|
|
9
|
+
* stack bottom-up in array order.
|
|
10
|
+
*
|
|
11
|
+
* Heights follow Schneider & Stephens: the column's information content is
|
|
12
|
+
* `log2(alphabet) - H` bits, and each residue takes the share of that its own
|
|
13
|
+
* frequency earns. Frequencies come from the non-gap residues only, so a column
|
|
14
|
+
* of two residues and eight gaps is read as a two-way choice rather than a
|
|
15
|
+
* ten-way one -- otherwise every gappy column would look uninformative for the
|
|
16
|
+
* wrong reason.
|
|
17
|
+
*
|
|
18
|
+
* The whole stack is then scaled by the non-gap fraction, which plain WebLogo
|
|
19
|
+
* does not do. Here it earns its place: the track sits directly under an
|
|
20
|
+
* alignment where the gaps are visible, and a column that is 90% gap drawing a
|
|
21
|
+
* full-height stack off its one remaining pair of residues reads as a conserved
|
|
22
|
+
* site. Scaling makes stack height mean "how much this column tells you about
|
|
23
|
+
* the family", matching what the conservation track above it already plots.
|
|
24
|
+
*/
|
|
25
|
+
export function columnLogoStack(colStats, col, maxBits) {
|
|
26
|
+
const total = colStats.total(col);
|
|
27
|
+
const nonGapTotal = total - colStats.gapCount(col);
|
|
28
|
+
if (nonGapTotal === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const informationContent = Math.max(0, maxBits - colStats.entropy(col)) * (nonGapTotal / total);
|
|
32
|
+
if (informationContent === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const stack = [];
|
|
36
|
+
colStats.forEachResidue(col, (slot, count) => {
|
|
37
|
+
stack.push({
|
|
38
|
+
letter: letterOfResidueSlot(slot),
|
|
39
|
+
bits: (count / nonGapTotal) * informationContent,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// ascending, so drawing in order stacks the tallest letter on top. Ties break
|
|
43
|
+
// on letter to keep the stack order stable between redraws.
|
|
44
|
+
stack.sort((a, b) => a.bits - b.bits || a.letter.localeCompare(b.letter));
|
|
45
|
+
return stack;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=sequenceLogo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequenceLogo.js","sourceRoot":"","sources":["../src/sequenceLogo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAUvD;uCACuC;AACvC,MAAM,UAAU,UAAU,CAAC,YAAqC;IAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACrD,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAsB,EACtB,GAAW,EACX,OAAe;IAEf,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACjC,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,kBAAkB,GACtB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,CAAA;IACtE,IAAI,kBAAkB,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,KAAK,GAAiB,EAAE,CAAA;IAC9B,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC3C,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,mBAAmB,CAAC,IAAI,CAAC;YACjC,IAAI,EAAE,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,kBAAkB;SACjD,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,8EAA8E;IAC9E,4DAA4D;IAC5D,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { Annotation } from 'msa-parsers';
|
|
1
2
|
export interface Accession {
|
|
2
3
|
accession: string;
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
}
|
|
7
|
+
export type TrackKind = 'text' | 'bar' | 'logo';
|
|
6
8
|
export interface BasicTrackModel {
|
|
7
9
|
id: string;
|
|
8
10
|
name: string;
|
|
9
11
|
associatedRowName?: string;
|
|
10
12
|
height: number;
|
|
13
|
+
kind: TrackKind;
|
|
11
14
|
}
|
|
12
15
|
export interface TextTrackModel extends BasicTrackModel {
|
|
13
16
|
customColorScheme?: Record<string, string>;
|
|
@@ -20,18 +23,10 @@ export interface BasicTrack {
|
|
|
20
23
|
ReactComponent: React.FC<any>;
|
|
21
24
|
model: TextTrackModel & BarTrackModel;
|
|
22
25
|
}
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
name: string;
|
|
26
|
-
accession: string;
|
|
27
|
-
description: string;
|
|
28
|
-
featureType?: string;
|
|
29
|
-
start: number;
|
|
30
|
-
end: number;
|
|
31
|
-
strand?: number;
|
|
32
|
-
}
|
|
26
|
+
export type { Annotation };
|
|
27
|
+
export type TidyDomainAnnotation = Annotation;
|
|
33
28
|
export interface DomainBand {
|
|
34
|
-
annotation:
|
|
29
|
+
annotation: Annotation;
|
|
35
30
|
startCol: number;
|
|
36
31
|
endCol: number;
|
|
37
32
|
stackIndex: number;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "6.1.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '6.1.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.1.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"@gmod/newick": "^1.0.0",
|
|
44
44
|
"colord": "^2.9.3",
|
|
45
45
|
"flatbush": "^4.6.2",
|
|
46
|
-
"@jbrowse/svgcanvas": "
|
|
47
|
-
"msa-parsers": "
|
|
46
|
+
"@jbrowse/svgcanvas": "6.1.0",
|
|
47
|
+
"msa-parsers": "6.1.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"clean": "rimraf dist bundle",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"build:esm": "tsc --project tsconfig.build.json",
|
|
54
54
|
"build:umd": "node esbuild.mjs",
|
|
55
55
|
"build": "pnpm clean && pnpm build:esm && pnpm build:umd",
|
|
56
|
-
"statedocs": "
|
|
57
|
-
"poststatedocs": "
|
|
56
|
+
"statedocs": "node --experimental-strip-types docgen/generateStateModelDocs.ts",
|
|
57
|
+
"poststatedocs": "oxfmt apidocs",
|
|
58
58
|
"test": "vitest"
|
|
59
59
|
},
|
|
60
60
|
"main": "dist/index.js"
|
package/src/clustalX.ts
CHANGED
|
@@ -20,34 +20,34 @@ const CYAN = 'rgb(26, 179, 179)'
|
|
|
20
20
|
*/
|
|
21
21
|
export function clustalXColumnColors(counts: ColumnCounts, col: number) {
|
|
22
22
|
const total = counts.total(col)
|
|
23
|
-
const
|
|
24
|
-
const W =
|
|
25
|
-
const L =
|
|
26
|
-
const V =
|
|
27
|
-
const I =
|
|
28
|
-
const M =
|
|
29
|
-
const A =
|
|
30
|
-
const F =
|
|
31
|
-
const C =
|
|
32
|
-
const H =
|
|
33
|
-
const P =
|
|
34
|
-
const R =
|
|
35
|
-
const K =
|
|
36
|
-
const Q =
|
|
37
|
-
const E =
|
|
38
|
-
const D =
|
|
39
|
-
const T =
|
|
40
|
-
const S =
|
|
41
|
-
const G =
|
|
42
|
-
const Y =
|
|
43
|
-
const N =
|
|
23
|
+
const count = (letter: string) => counts.count(col, letter)
|
|
24
|
+
const W = count('W')
|
|
25
|
+
const L = count('L')
|
|
26
|
+
const V = count('V')
|
|
27
|
+
const I = count('I')
|
|
28
|
+
const M = count('M')
|
|
29
|
+
const A = count('A')
|
|
30
|
+
const F = count('F')
|
|
31
|
+
const C = count('C')
|
|
32
|
+
const H = count('H')
|
|
33
|
+
const P = count('P')
|
|
34
|
+
const R = count('R')
|
|
35
|
+
const K = count('K')
|
|
36
|
+
const Q = count('Q')
|
|
37
|
+
const E = count('E')
|
|
38
|
+
const D = count('D')
|
|
39
|
+
const T = count('T')
|
|
40
|
+
const S = count('S')
|
|
41
|
+
const G = count('G')
|
|
42
|
+
const Y = count('Y')
|
|
43
|
+
const N = count('N')
|
|
44
44
|
|
|
45
45
|
const hydrophobic = W + L + V + I + M + A + F + C + H + P + Y
|
|
46
46
|
const KR = K + R
|
|
47
47
|
const QE = Q + E
|
|
48
48
|
const ED = E + D
|
|
49
49
|
const TS = T + S
|
|
50
|
-
const p = (
|
|
50
|
+
const p = (subtotal: number) => subtotal / total
|
|
51
51
|
|
|
52
52
|
const colors: Record<string, string> = {}
|
|
53
53
|
|
package/src/colSpace.test.ts
CHANGED
|
@@ -38,3 +38,30 @@ test('the last column agrees with the column statistics', () => {
|
|
|
38
38
|
expect(model.mouseOverColumnStats?.col).toBe(9)
|
|
39
39
|
expect(model.visibleColToRowLetter('b', 9)).toBe('C')
|
|
40
40
|
})
|
|
41
|
+
|
|
42
|
+
// enough rows to force the vertical scrollbar, which eats into the width the
|
|
43
|
+
// alignment canvas actually gets
|
|
44
|
+
function makeTallModel() {
|
|
45
|
+
const names = Array.from({ length: 60 }, (_, i) => `seq${i}`)
|
|
46
|
+
const model = MSAModelF().create({
|
|
47
|
+
id: 'col-fit-test',
|
|
48
|
+
type: 'MsaView',
|
|
49
|
+
msaFormat: 'fasta',
|
|
50
|
+
height: 300,
|
|
51
|
+
data: { msa: names.map(n => `>${n}\n${'ACGT'.repeat(50)}`).join('\n') },
|
|
52
|
+
})
|
|
53
|
+
model.setWidth(800)
|
|
54
|
+
return model
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('fit horizontally leaves every column on screen', () => {
|
|
58
|
+
const model = makeTallModel()
|
|
59
|
+
expect(model.showVerticalScrollbar).toBe(true)
|
|
60
|
+
|
|
61
|
+
model.fitHorizontally()
|
|
62
|
+
// the canvas is the msa area minus the scrollbar; fitting to the msa area
|
|
63
|
+
// alone pushed the last columns past the right edge, with no minimap to
|
|
64
|
+
// show that anything was cut off
|
|
65
|
+
expect(model.totalWidth).toBeLessThanOrEqual(model.msaCanvasWidth)
|
|
66
|
+
expect(model.showHorizontalScrollbar).toBe(false)
|
|
67
|
+
})
|
package/src/columnCounts.test.ts
CHANGED
|
@@ -47,6 +47,21 @@ describe('columnCounts', () => {
|
|
|
47
47
|
expect(counts.total(3)).toBe(1)
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
test('entropy is taken over the non-gap residues only', () => {
|
|
51
|
+
const counts = columnCountsFromColumns(['AAAA', 'AACC', 'ACGT', 'AC--'])
|
|
52
|
+
// fully conserved: no choice left to make
|
|
53
|
+
expect(counts.entropy(0)).toBeCloseTo(0, 10)
|
|
54
|
+
// even two-way and four-way splits
|
|
55
|
+
expect(counts.entropy(1)).toBeCloseTo(1, 10)
|
|
56
|
+
expect(counts.entropy(2)).toBeCloseTo(2, 10)
|
|
57
|
+
// the two gaps do not turn a 1-bit choice into a 2-bit one
|
|
58
|
+
expect(counts.entropy(3)).toBeCloseTo(1, 10)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('an all-gap column has no entropy rather than NaN', () => {
|
|
62
|
+
expect(columnCountsFromColumns(['----']).entropy(0)).toBe(0)
|
|
63
|
+
})
|
|
64
|
+
|
|
50
65
|
test('residue slots round-trip to their letters', () => {
|
|
51
66
|
const counts = columnCountsFromColumns(['WY'])
|
|
52
67
|
const slots: number[] = []
|
package/src/columnCounts.ts
CHANGED
|
@@ -109,6 +109,25 @@ export class ColumnCounts {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Shannon entropy of a column in bits, taken over its non-gap residues only:
|
|
114
|
+
* a column of two residues and eight gaps is a two-way choice, not a ten-way
|
|
115
|
+
* one, and gap occupancy is accounted for separately by every caller. 0 for
|
|
116
|
+
* an all-gap column.
|
|
117
|
+
*/
|
|
118
|
+
entropy(col: number) {
|
|
119
|
+
const nonGapTotal = this.total(col) - this.gapCount(col)
|
|
120
|
+
if (nonGapTotal === 0) {
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
123
|
+
let entropy = 0
|
|
124
|
+
this.forEachResidue(col, (_slot, count) => {
|
|
125
|
+
const freq = count / nonGapTotal
|
|
126
|
+
entropy -= freq * Math.log2(freq)
|
|
127
|
+
})
|
|
128
|
+
return entropy
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
/** non-gap [letter, count] pairs for one column, for display */
|
|
113
132
|
residueEntries(col: number) {
|
|
114
133
|
const entries: [string, number][] = []
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
import copy from '../vendor/copyToClipboard.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Copy-to-clipboard button that confirms on the label for a moment. The timer is
|
|
9
|
+
* cleared on unmount, since these live in dialogs the copy itself often closes.
|
|
10
|
+
*/
|
|
11
|
+
export default function CopyButton({
|
|
12
|
+
text,
|
|
13
|
+
label = 'Copy to clipboard',
|
|
14
|
+
}: {
|
|
15
|
+
text: string
|
|
16
|
+
label?: string
|
|
17
|
+
}) {
|
|
18
|
+
const [copied, setCopied] = useState(false)
|
|
19
|
+
const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
20
|
+
|
|
21
|
+
useEffect(
|
|
22
|
+
() => () => {
|
|
23
|
+
clearTimeout(timer.current)
|
|
24
|
+
},
|
|
25
|
+
[],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Button
|
|
30
|
+
variant="contained"
|
|
31
|
+
color="primary"
|
|
32
|
+
onClick={() => {
|
|
33
|
+
copy(text)
|
|
34
|
+
setCopied(true)
|
|
35
|
+
clearTimeout(timer.current)
|
|
36
|
+
timer.current = setTimeout(() => {
|
|
37
|
+
setCopied(false)
|
|
38
|
+
}, 500)
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : label}
|
|
42
|
+
</Button>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useDragScroll } from '../useDragScroll.ts'
|
|
4
|
+
|
|
5
|
+
export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
|
|
6
|
+
|
|
7
|
+
// The scroll thumbs (minimap, vertical scrollbar) and the resize dividers are
|
|
8
|
+
// one widget -- press, drag along an axis, report the pixel delta -- differing
|
|
9
|
+
// only in the affordance they present, so the variant carries the whole
|
|
10
|
+
// difference rather than a second copy of the drag wiring.
|
|
11
|
+
const variants: Record<
|
|
12
|
+
'thumb' | 'resizer',
|
|
13
|
+
{
|
|
14
|
+
fill: string
|
|
15
|
+
fillHovered: string
|
|
16
|
+
cursor: (axis: 'x' | 'y') => string
|
|
17
|
+
position: React.CSSProperties['position']
|
|
18
|
+
zIndex?: number
|
|
19
|
+
}
|
|
20
|
+
> = {
|
|
21
|
+
thumb: {
|
|
22
|
+
fill: scrollbarThumbFill,
|
|
23
|
+
fillHovered: 'rgba(66,119,127,0.6)',
|
|
24
|
+
cursor: () => 'pointer',
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
zIndex: 100,
|
|
27
|
+
},
|
|
28
|
+
resizer: {
|
|
29
|
+
fill: 'rgba(200,200,200)',
|
|
30
|
+
fillHovered: 'rgba(150,150,150)',
|
|
31
|
+
cursor: axis => (axis === 'x' ? 'ew-resize' : 'ns-resize'),
|
|
32
|
+
position: 'relative',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A handle dragged along a single axis. `getStart` is read at mousedown and
|
|
38
|
+
* handed back to `onDrag` alongside the pixel delta since, so a caller only
|
|
39
|
+
* supplies the dimension it drives and where the handle sits.
|
|
40
|
+
*
|
|
41
|
+
* Memoize `onDrag` (useCallback) so the document listeners are not re-attached
|
|
42
|
+
* on every render.
|
|
43
|
+
*/
|
|
44
|
+
export default function DragHandle({
|
|
45
|
+
axis,
|
|
46
|
+
variant,
|
|
47
|
+
getStart,
|
|
48
|
+
onDrag,
|
|
49
|
+
style,
|
|
50
|
+
}: {
|
|
51
|
+
axis: 'x' | 'y'
|
|
52
|
+
variant: keyof typeof variants
|
|
53
|
+
getStart: () => number
|
|
54
|
+
onDrag: (delta: number, startValue: number) => void
|
|
55
|
+
style: React.CSSProperties
|
|
56
|
+
}) {
|
|
57
|
+
const [hovered, setHovered] = useState(false)
|
|
58
|
+
const { startDrag } = useDragScroll(axis, onDrag)
|
|
59
|
+
const { fill, fillHovered, cursor, position, zIndex } = variants[variant]
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
onMouseEnter={() => {
|
|
64
|
+
setHovered(true)
|
|
65
|
+
}}
|
|
66
|
+
onMouseLeave={() => {
|
|
67
|
+
setHovered(false)
|
|
68
|
+
}}
|
|
69
|
+
onMouseDown={event => {
|
|
70
|
+
startDrag(event, getStart())
|
|
71
|
+
}}
|
|
72
|
+
style={{
|
|
73
|
+
position,
|
|
74
|
+
zIndex,
|
|
75
|
+
cursor: cursor(axis),
|
|
76
|
+
background: hovered ? fillHovered : fill,
|
|
77
|
+
...style,
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -83,7 +83,8 @@ const Reset = observer(function ({
|
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
86
|
-
const { isLoading, dataInitialized, msaFilehandle, treeFilehandle } =
|
|
86
|
+
const { isLoading, dataInitialized, error, msaFilehandle, treeFilehandle } =
|
|
87
|
+
model
|
|
87
88
|
const hasPendingFilehandle = !!(msaFilehandle || treeFilehandle)
|
|
88
89
|
|
|
89
90
|
return (
|
|
@@ -97,6 +98,14 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
97
98
|
) : (
|
|
98
99
|
<MSAView model={model} />
|
|
99
100
|
)
|
|
101
|
+
) : error ? (
|
|
102
|
+
// Ahead of the spinner, because a failed load KEEPS its filehandle --
|
|
103
|
+
// only an abort clears it. So hasPendingFilehandle stays true, this
|
|
104
|
+
// fell to the spinner below, and the ImportForm that renders
|
|
105
|
+
// `model.error` was never reached: the reader got "Downloading file"
|
|
106
|
+
// and a Cancel button for as long as they were willing to watch it.
|
|
107
|
+
// Found on AlphaFold's files/msa/ prefix answering 403 to every key.
|
|
108
|
+
<Reset model={model} error={error} />
|
|
100
109
|
) : hasPendingFilehandle || isLoading ? (
|
|
101
110
|
<LoadingSpinner model={model} />
|
|
102
111
|
) : (
|
|
@@ -40,8 +40,7 @@ const TrackColumnIndicator = observer(function ({
|
|
|
40
40
|
treeAreaWidth,
|
|
41
41
|
resizeHandleWidth,
|
|
42
42
|
totalTrackAreaHeight,
|
|
43
|
-
|
|
44
|
-
verticalScrollbarWidth,
|
|
43
|
+
msaCanvasWidth,
|
|
45
44
|
} = model
|
|
46
45
|
|
|
47
46
|
// hovered column, then the pinned one, matching the alignment's own overlay
|
|
@@ -56,7 +55,7 @@ const TrackColumnIndicator = observer(function ({
|
|
|
56
55
|
position: 'absolute',
|
|
57
56
|
left: treeAreaWidth + resizeHandleWidth,
|
|
58
57
|
top: 0,
|
|
59
|
-
width:
|
|
58
|
+
width: msaCanvasWidth,
|
|
60
59
|
height: totalTrackAreaHeight,
|
|
61
60
|
overflow: 'hidden',
|
|
62
61
|
pointerEvents: 'none',
|