react-msaview 5.6.1 → 5.6.2
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 +95 -95
- package/bundle/index.js.map +4 -4
- package/dist/clustalX.d.ts +4 -3
- package/dist/clustalX.js +25 -4
- package/dist/clustalX.js.map +1 -1
- package/dist/columnCounts.d.ts +37 -0
- package/dist/columnCounts.js +148 -0
- package/dist/columnCounts.js.map +1 -0
- package/dist/components/MSAView.js +1 -8
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +6 -0
- package/dist/components/getVisibleLeaves.js.map +1 -0
- package/dist/components/msa/MSACanvasBlock.js +17 -54
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +21 -19
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +97 -96
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +9 -32
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +23 -0
- package/dist/components/msa/useMsaBlockMouse.js +45 -0
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +14 -19
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +40 -111
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +2 -0
- package/dist/components/tree/renderTreeCanvas.js +15 -3
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/useTreeHover.d.ts +24 -0
- package/dist/components/tree/useTreeHover.js +44 -0
- package/dist/components/tree/useTreeHover.js.map +1 -0
- package/dist/hierarchy.js +15 -9
- package/dist/hierarchy.js.map +1 -1
- package/dist/model/msaModel.js +4 -3
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.js +9 -8
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +52 -10
- package/dist/model.js +208 -93
- package/dist/model.js.map +1 -1
- package/dist/propertyConservation.d.ts +2 -1
- package/dist/propertyConservation.js +36 -25
- package/dist/propertyConservation.js.map +1 -1
- package/dist/seqPosToGlobalCol.d.ts +7 -0
- package/dist/seqPosToGlobalCol.js +17 -0
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/stripDefault.d.ts +21 -0
- package/dist/stripDefault.js +26 -0
- package/dist/stripDefault.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/clustalX.test.ts +9 -11
- package/src/clustalX.ts +27 -28
- package/src/columnCounts.test.ts +58 -0
- package/src/columnCounts.ts +170 -0
- package/src/components/MSAView.tsx +1 -9
- package/src/components/{msa/getVisibleLeaves.ts → getVisibleLeaves.ts} +1 -1
- package/src/components/msa/MSACanvasBlock.tsx +42 -110
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +25 -18
- package/src/components/msa/renderMSABlock.ts +113 -125
- package/src/components/msa/renderMSAMouseover.ts +11 -34
- package/src/components/msa/useMsaBlockMouse.ts +81 -0
- package/src/components/tree/TreeCanvas.tsx +16 -23
- package/src/components/tree/TreeCanvasBlock.tsx +62 -143
- package/src/components/tree/renderTreeCanvas.test.ts +100 -190
- package/src/components/tree/renderTreeCanvas.ts +14 -3
- package/src/components/tree/useTreeHover.ts +69 -0
- package/src/domainBands.test.ts +98 -0
- package/src/hierarchy.ts +15 -9
- package/src/model/msaModel.ts +4 -6
- package/src/model/treeModel.ts +9 -14
- package/src/model.ts +241 -112
- package/src/propertyConservation.test.ts +2 -13
- package/src/propertyConservation.ts +40 -31
- package/src/rowSpace.test.ts +46 -0
- package/src/seqPosToGlobalCol.ts +18 -0
- package/src/stripDefault.test.ts +48 -0
- package/src/stripDefault.ts +35 -0
- package/src/types.ts +23 -0
- package/src/version.ts +1 -1
- package/dist/components/msa/getVisibleLeaves.d.ts +0 -6
- package/dist/components/msa/getVisibleLeaves.js.map +0 -1
- package/dist/useDevicePixelRatio.d.ts +0 -1
- package/dist/useDevicePixelRatio.js +0 -27
- package/dist/useDevicePixelRatio.js.map +0 -1
- package/src/useDevicePixelRatio.ts +0 -29
- /package/dist/components/{msa/getVisibleLeaves.js → getVisibleLeaves.js} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useRef, useState } from 'react'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
|
|
4
|
+
import { ClickMapIndex } from './clickMap.ts'
|
|
5
|
+
|
|
6
|
+
import type { ClickEntry } from './clickMap.ts'
|
|
7
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
8
|
+
|
|
9
|
+
export interface TreeHoverTarget extends ClickEntry {
|
|
10
|
+
clientX: number
|
|
11
|
+
clientY: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Owns hit-testing for one tree block: the spatial index the render pass fills
|
|
16
|
+
* in, the entry currently under the pointer, and the model hover state that the
|
|
17
|
+
* MSA panel mirrors. The index is a render artifact rather than display state, so
|
|
18
|
+
* it lives in a ref; everything the component draws comes back as plain state.
|
|
19
|
+
*/
|
|
20
|
+
export function useTreeHover({
|
|
21
|
+
model,
|
|
22
|
+
offsetY,
|
|
23
|
+
}: {
|
|
24
|
+
model: MsaViewModel
|
|
25
|
+
offsetY: number
|
|
26
|
+
}) {
|
|
27
|
+
const clickMapRef = useRef<ClickMapIndex>(null)
|
|
28
|
+
clickMapRef.current ??= new ClickMapIndex()
|
|
29
|
+
const clickMap = clickMapRef.current
|
|
30
|
+
const [hovered, setHovered] = useState<TreeHoverTarget>()
|
|
31
|
+
|
|
32
|
+
// leaf labels win over the branch/bubble targets they overlap, so a click on a
|
|
33
|
+
// name opens the node menu rather than the branch menu
|
|
34
|
+
function hitTest(event: React.MouseEvent) {
|
|
35
|
+
const x = event.nativeEvent.offsetX
|
|
36
|
+
const y = event.nativeEvent.offsetY + offsetY
|
|
37
|
+
const entries = clickMap.search({
|
|
38
|
+
minX: x,
|
|
39
|
+
maxX: x + 1,
|
|
40
|
+
minY: y,
|
|
41
|
+
maxY: y + 1,
|
|
42
|
+
})
|
|
43
|
+
return entries.find(entry => !entry.branch) ?? entries[0]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function onMouseMove(event: React.MouseEvent) {
|
|
47
|
+
const entry = hitTest(event)
|
|
48
|
+
setHovered(
|
|
49
|
+
entry
|
|
50
|
+
? { ...entry, clientX: event.clientX, clientY: event.clientY }
|
|
51
|
+
: undefined,
|
|
52
|
+
)
|
|
53
|
+
// hovering an internal node highlights every tip below it; hovering a leaf
|
|
54
|
+
// label additionally drives the single-row highlight
|
|
55
|
+
model.setHoveredTreeNode(entry?.id)
|
|
56
|
+
model.setMousePos(
|
|
57
|
+
undefined,
|
|
58
|
+
entry && !entry.branch ? model.rowNamesSet.get(entry.name) : undefined,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function onMouseLeave() {
|
|
63
|
+
setHovered(undefined)
|
|
64
|
+
model.setHoveredTreeNode(undefined)
|
|
65
|
+
model.setMousePos(undefined, undefined)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { clickMap, hovered, hitTest, onMouseMove, onMouseLeave }
|
|
69
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
// The domain overlay resolves each annotation to the visible column span it is
|
|
3
|
+
// drawn across once, on the model, instead of converting sequence positions
|
|
4
|
+
// inside every canvas block. These tests pin that span (including across hidden
|
|
5
|
+
// gappy columns) and the hit-test built on top of it.
|
|
6
|
+
import { expect, test } from 'vitest'
|
|
7
|
+
|
|
8
|
+
import MSAModelF from './model.ts'
|
|
9
|
+
|
|
10
|
+
// row `b` has a 4-residue insertion relative to `a`, which becomes a run of
|
|
11
|
+
// mostly-gap columns that gap hiding can remove
|
|
12
|
+
const msa = `>a
|
|
13
|
+
MKLV----WYAC
|
|
14
|
+
>b
|
|
15
|
+
MKLVQQQQWYAC`
|
|
16
|
+
|
|
17
|
+
const gff = `##gff-version 3
|
|
18
|
+
a Pfam protein_match 1 4 . . . Name=PF00001;signature_desc=First
|
|
19
|
+
a Pfam protein_match 5 8 . . . Name=PF00002;signature_desc=Second`
|
|
20
|
+
|
|
21
|
+
function makeModel() {
|
|
22
|
+
const model = MSAModelF().create({
|
|
23
|
+
id: 'domain-bands-test',
|
|
24
|
+
type: 'MsaView',
|
|
25
|
+
msaFormat: 'fasta',
|
|
26
|
+
height: 400,
|
|
27
|
+
data: { msa },
|
|
28
|
+
})
|
|
29
|
+
model.setWidth(800)
|
|
30
|
+
model.applyGFFText(gff)
|
|
31
|
+
return model
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('bands span the columns their residues occupy', () => {
|
|
35
|
+
const model = makeModel()
|
|
36
|
+
const bands = model.domainBands.get('a')!
|
|
37
|
+
expect(
|
|
38
|
+
bands.map(b => [b.annotation.accession, b.startCol, b.endCol]),
|
|
39
|
+
).toEqual([
|
|
40
|
+
['PF00001', 0, 4],
|
|
41
|
+
['PF00002', 8, 12],
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('bands follow the columns when gappy columns are hidden', () => {
|
|
46
|
+
const model = makeModel()
|
|
47
|
+
model.setHideGaps(true)
|
|
48
|
+
model.setAllowedGappyness(50)
|
|
49
|
+
// the 4 insertion columns are gaps in half the rows, so they are hidden and
|
|
50
|
+
// everything to their right shifts left by 4
|
|
51
|
+
expect(model.blanks).toEqual([4, 5, 6, 7])
|
|
52
|
+
const bands = model.domainBands.get('a')!
|
|
53
|
+
expect(bands.map(b => [b.startCol, b.endCol])).toEqual([
|
|
54
|
+
[0, 4],
|
|
55
|
+
[4, 8],
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('mouseOverDomains hit-tests against the drawn span', () => {
|
|
60
|
+
const model = makeModel()
|
|
61
|
+
const rowIndex = model.rowNames.indexOf('a')
|
|
62
|
+
|
|
63
|
+
model.setMousePos(0, rowIndex)
|
|
64
|
+
expect(model.mouseOverDomains.map(d => d.accession)).toEqual(['PF00001'])
|
|
65
|
+
|
|
66
|
+
// the columns between the two domains are the insertion gap: no domain there
|
|
67
|
+
model.setMousePos(5, rowIndex)
|
|
68
|
+
expect(model.mouseOverDomains).toEqual([])
|
|
69
|
+
|
|
70
|
+
model.setMousePos(8, rowIndex)
|
|
71
|
+
expect(model.mouseOverDomains.map(d => d.accession)).toEqual(['PF00002'])
|
|
72
|
+
|
|
73
|
+
// end column is exclusive
|
|
74
|
+
model.setMousePos(12, rowIndex)
|
|
75
|
+
expect(model.mouseOverDomains).toEqual([])
|
|
76
|
+
|
|
77
|
+
// the other row has no annotations of its own
|
|
78
|
+
model.setMousePos(0, model.rowNames.indexOf('b'))
|
|
79
|
+
expect(model.mouseOverDomains).toEqual([])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('filtering a domain off removes its band', () => {
|
|
83
|
+
const model = makeModel()
|
|
84
|
+
model.setFilter('PF00001', false)
|
|
85
|
+
expect(model.domainBands.get('a')!.map(b => b.annotation.accession)).toEqual([
|
|
86
|
+
'PF00002',
|
|
87
|
+
])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('highlighted columns collapse into contiguous runs', () => {
|
|
91
|
+
const model = makeModel()
|
|
92
|
+
model.setHighlightedColumns([5, 1, 2, 3, 9])
|
|
93
|
+
expect(model.highlightedColumnRuns).toEqual([
|
|
94
|
+
{ start: 1, end: 3 },
|
|
95
|
+
{ start: 5, end: 5 },
|
|
96
|
+
{ start: 9, end: 9 },
|
|
97
|
+
])
|
|
98
|
+
})
|
package/src/hierarchy.ts
CHANGED
|
@@ -288,17 +288,23 @@ export function collapsedSubtreeLengthExtent<T extends { length?: number }>(
|
|
|
288
288
|
// x. Memoizes onto node.depthToLeaf since the layout walks the tree repeatedly.
|
|
289
289
|
// See https://github.com/emmanuelparadis/ape/blob/master/R/plot.phylo.R
|
|
290
290
|
export function calcDepthToLeaf<T>(node: HierarchyNode<T>): number {
|
|
291
|
-
|
|
292
|
-
for
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
291
|
+
// the memo check has to guard the traversal, not just the arithmetic: the tree
|
|
292
|
+
// renderer asks for the depth of every node on every pass, and re-walking each
|
|
293
|
+
// subtree makes that quadratic (a 5000-tip caterpillar tree spent >1s per draw
|
|
294
|
+
// here). Once a node is memoized so are all of its descendants.
|
|
295
|
+
if (node.depthToLeaf === undefined) {
|
|
296
|
+
const nodes = descendants(node)
|
|
297
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
298
|
+
const n = nodes[i]!
|
|
299
|
+
if (n.depthToLeaf === undefined) {
|
|
300
|
+
let maxDepth = 0
|
|
301
|
+
if (n.children) {
|
|
302
|
+
for (const child of n.children) {
|
|
303
|
+
maxDepth = Math.max(maxDepth, 1 + child.depthToLeaf!)
|
|
304
|
+
}
|
|
299
305
|
}
|
|
306
|
+
n.depthToLeaf = maxDepth
|
|
300
307
|
}
|
|
301
|
-
n.depthToLeaf = maxDepth
|
|
302
308
|
}
|
|
303
309
|
}
|
|
304
310
|
return node.depthToLeaf!
|
package/src/model/msaModel.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
defaultColorSchemeName,
|
|
6
6
|
defaultShowColumnStats,
|
|
7
7
|
} from '../constants.ts'
|
|
8
|
+
import { stripDefault } from '../stripDefault.ts'
|
|
8
9
|
|
|
9
10
|
import type { MSAFormat } from 'msa-parsers'
|
|
10
11
|
|
|
@@ -20,23 +21,20 @@ export function MSAModelF() {
|
|
|
20
21
|
* #property
|
|
21
22
|
* draw MSA tiles with a background color
|
|
22
23
|
*/
|
|
23
|
-
bgColor:
|
|
24
|
+
bgColor: stripDefault(types.boolean, defaultBgColor),
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* #property
|
|
27
28
|
* default color scheme name
|
|
28
29
|
*/
|
|
29
|
-
colorSchemeName:
|
|
30
|
+
colorSchemeName: stripDefault(types.string, defaultColorSchemeName),
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* #property
|
|
33
34
|
* show a per-column statistics tooltip (consensus, conservation, gaps,
|
|
34
35
|
* residue distribution) while hovering the alignment
|
|
35
36
|
*/
|
|
36
|
-
showColumnStats: types.
|
|
37
|
-
types.boolean,
|
|
38
|
-
defaultShowColumnStats,
|
|
39
|
-
),
|
|
37
|
+
showColumnStats: stripDefault(types.boolean, defaultShowColumnStats),
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
40
|
* #property
|
package/src/model/treeModel.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
defaultTreeAreaWidth,
|
|
10
10
|
defaultTreeWidth,
|
|
11
11
|
} from '../constants.ts'
|
|
12
|
+
import { stripDefault } from '../stripDefault.ts'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* #stateModel Tree
|
|
@@ -19,28 +20,25 @@ export function TreeModelF() {
|
|
|
19
20
|
/**
|
|
20
21
|
* #property
|
|
21
22
|
*/
|
|
22
|
-
drawLabels:
|
|
23
|
+
drawLabels: stripDefault(types.boolean, defaultDrawLabels),
|
|
23
24
|
/**
|
|
24
25
|
* #property
|
|
25
26
|
* right-align the labels
|
|
26
27
|
*/
|
|
27
|
-
labelsAlignRight: types.
|
|
28
|
-
types.boolean,
|
|
29
|
-
defaultLabelsAlignRight,
|
|
30
|
-
),
|
|
28
|
+
labelsAlignRight: stripDefault(types.boolean, defaultLabelsAlignRight),
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
31
|
* #property
|
|
34
32
|
* width of the area the tree is drawn in, px
|
|
35
33
|
*/
|
|
36
|
-
treeAreaWidth:
|
|
34
|
+
treeAreaWidth: stripDefault(types.number, defaultTreeAreaWidth),
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
37
|
* #property
|
|
40
38
|
* width of the tree within the treeArea, px. automatically synced to
|
|
41
39
|
* fit within treeAreaWidth
|
|
42
40
|
*/
|
|
43
|
-
treeWidth:
|
|
41
|
+
treeWidth: stripDefault(types.number, defaultTreeWidth),
|
|
44
42
|
|
|
45
43
|
/**
|
|
46
44
|
* #property
|
|
@@ -48,22 +46,19 @@ export function TreeModelF() {
|
|
|
48
46
|
* lengths. if false, the layout is a "cladogram" that does not take into
|
|
49
47
|
* account evolutionary distances
|
|
50
48
|
*/
|
|
51
|
-
showBranchLen:
|
|
49
|
+
showBranchLen: stripDefault(types.boolean, defaultShowBranchLen),
|
|
52
50
|
|
|
53
51
|
/**
|
|
54
52
|
* #property
|
|
55
53
|
* draw tree, boolean
|
|
56
54
|
*/
|
|
57
|
-
drawTree:
|
|
55
|
+
drawTree: stripDefault(types.boolean, defaultDrawTree),
|
|
58
56
|
|
|
59
57
|
/**
|
|
60
58
|
* #property
|
|
61
59
|
* draw clickable node bubbles on the tree
|
|
62
60
|
*/
|
|
63
|
-
drawNodeBubbles: types.
|
|
64
|
-
types.boolean,
|
|
65
|
-
defaultDrawNodeBubbles,
|
|
66
|
-
),
|
|
61
|
+
drawNodeBubbles: stripDefault(types.boolean, defaultDrawNodeBubbles),
|
|
67
62
|
|
|
68
63
|
/**
|
|
69
64
|
* #property
|
|
@@ -71,7 +66,7 @@ export function TreeModelF() {
|
|
|
71
66
|
* instead of using a fixed width. useful when there is no tree, so the
|
|
72
67
|
* label gutter isn't padded out to the default 400px
|
|
73
68
|
*/
|
|
74
|
-
autoTreeAreaWidth:
|
|
69
|
+
autoTreeAreaWidth: stripDefault(types.boolean, false),
|
|
75
70
|
})
|
|
76
71
|
.actions(self => ({
|
|
77
72
|
/**
|