react-msaview 6.0.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.js +98 -98
- package/bundle/index.js.map +4 -4
- package/dist/columnCounts.d.ts +7 -0
- package/dist/columnCounts.js +18 -0
- package/dist/columnCounts.js.map +1 -1
- 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/ResizeHandles.d.ts +4 -2
- package/dist/components/ResizeHandles.js +29 -32
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +2 -2
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +52 -129
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +16 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/minimap/Minimap.js +32 -4
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +8 -2
- 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/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 +13 -21
- package/dist/components/msa/renderMSABlock.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/renderTreeCanvas.js +27 -21
- 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/model/DataModel.d.ts +1 -4
- package/dist/model/DataModel.js +8 -10
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +1 -1
- package/dist/model/treeModel.js +1 -1
- package/dist/model.d.ts +36 -7
- package/dist/model.js +113 -58
- 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 +1 -1
- package/dist/renderToSvg.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 +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/columnCounts.test.ts +15 -0
- package/src/columnCounts.ts +19 -0
- package/src/components/DragHandle.tsx +81 -0
- package/src/components/ResizeHandles.tsx +46 -54
- package/src/components/VerticalScrollbar.tsx +3 -2
- package/src/components/dialogs/InterProScanDialog.tsx +54 -131
- package/src/components/header/settingsMenuItems.ts +19 -1
- package/src/components/minimap/Minimap.tsx +47 -4
- package/src/components/msa/MSACanvas.tsx +30 -10
- package/src/components/msa/MSACanvasBlock.tsx +13 -3
- package/src/components/msa/msaRaster.test.ts +145 -0
- package/src/components/msa/msaRaster.ts +350 -0
- package/src/components/msa/renderMSABlock.ts +15 -20
- 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/renderTreeCanvas.test.ts +55 -0
- package/src/components/tree/renderTreeCanvas.ts +28 -19
- package/src/constants.ts +10 -0
- package/src/impgRender.test.tsx +2 -57
- package/src/largeTree.test.ts +16 -2
- package/src/model/DataModel.ts +11 -11
- package/src/model/treeModel.ts +1 -1
- package/src/model.ts +128 -60
- package/src/neighborJoining.ts +36 -18
- package/src/renderDomainsSvg.test.tsx +2 -57
- package/src/renderMinimapSvg.test.tsx +2 -56
- package/src/renderSequenceLogoSvg.test.tsx +176 -0
- package/src/renderTestEnv.ts +96 -0
- package/src/renderToSvg.tsx +1 -1
- package/src/sequenceLogo.test.ts +88 -0
- package/src/sequenceLogo.ts +63 -0
- package/src/types.ts +7 -1
- package/src/version.ts +1 -1
- 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/ScrollbarThumb.d.ts +0 -14
- package/dist/components/ScrollbarThumb.js +0 -28
- package/dist/components/ScrollbarThumb.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/tracks/renderTracksSvg.js +0 -126
- package/dist/components/tracks/renderTracksSvg.js.map +0 -1
- package/src/components/ConservationTrack.tsx +0 -99
- package/src/components/ScrollbarThumb.tsx +0 -48
- package/src/components/TextTrack.tsx +0 -103
- package/src/components/tracks/renderTracksSvg.ts +0 -247
|
@@ -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
|
@@ -4,11 +4,13 @@ export interface Accession {
|
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
6
6
|
}
|
|
7
|
+
export type TrackKind = 'text' | 'bar' | 'logo';
|
|
7
8
|
export interface BasicTrackModel {
|
|
8
9
|
id: string;
|
|
9
10
|
name: string;
|
|
10
11
|
associatedRowName?: string;
|
|
11
12
|
height: number;
|
|
13
|
+
kind: TrackKind;
|
|
12
14
|
}
|
|
13
15
|
export interface TextTrackModel extends BasicTrackModel {
|
|
14
16
|
customColorScheme?: Record<string, string>;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "6.
|
|
1
|
+
export declare const version = "6.1.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '6.
|
|
1
|
+
export const version = '6.1.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.1.1",
|
|
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": "6.
|
|
47
|
-
"msa-parsers": "6.
|
|
46
|
+
"@jbrowse/svgcanvas": "6.1.1",
|
|
47
|
+
"msa-parsers": "6.1.1"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"clean": "rimraf dist bundle",
|
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,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
|
+
}
|
|
@@ -1,53 +1,11 @@
|
|
|
1
|
-
import React, { useCallback
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import DragHandle from './DragHandle.tsx'
|
|
6
6
|
|
|
7
7
|
import type { MsaViewModel } from '../model.ts'
|
|
8
|
-
|
|
9
|
-
const handleBg = 'rgba(200,200,200)'
|
|
10
|
-
const handleBgHover = 'rgba(150,150,150)'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A draggable divider. `getStart` is read at mousedown and handed back to
|
|
14
|
-
* `onDrag` alongside the pixel delta since, so each handle below only has to say
|
|
15
|
-
* which model dimension it resizes and where it sits.
|
|
16
|
-
*/
|
|
17
|
-
function ResizeHandle({
|
|
18
|
-
axis,
|
|
19
|
-
getStart,
|
|
20
|
-
onDrag,
|
|
21
|
-
style,
|
|
22
|
-
}: {
|
|
23
|
-
axis: 'x' | 'y'
|
|
24
|
-
getStart: () => number
|
|
25
|
-
onDrag: (delta: number, startValue: number) => void
|
|
26
|
-
style: React.CSSProperties
|
|
27
|
-
}) {
|
|
28
|
-
const [hovered, setHovered] = useState(false)
|
|
29
|
-
const { startDrag } = useDragScroll(axis, onDrag)
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
onMouseDown={event => {
|
|
34
|
-
startDrag(event, getStart())
|
|
35
|
-
}}
|
|
36
|
-
onMouseEnter={() => {
|
|
37
|
-
setHovered(true)
|
|
38
|
-
}}
|
|
39
|
-
onMouseLeave={() => {
|
|
40
|
-
setHovered(false)
|
|
41
|
-
}}
|
|
42
|
-
style={{
|
|
43
|
-
position: 'relative',
|
|
44
|
-
cursor: axis === 'x' ? 'ew-resize' : 'ns-resize',
|
|
45
|
-
background: hovered ? handleBgHover : handleBg,
|
|
46
|
-
...style,
|
|
47
|
-
}}
|
|
48
|
-
/>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
8
|
+
import type { TrackKind } from '../types.ts'
|
|
51
9
|
|
|
52
10
|
export const VerticalResizeHandle = observer(function ({
|
|
53
11
|
model,
|
|
@@ -61,8 +19,9 @@ export const VerticalResizeHandle = observer(function ({
|
|
|
61
19
|
[model],
|
|
62
20
|
)
|
|
63
21
|
return (
|
|
64
|
-
<
|
|
22
|
+
<DragHandle
|
|
65
23
|
axis="x"
|
|
24
|
+
variant="resizer"
|
|
66
25
|
getStart={() => model.treeAreaWidth}
|
|
67
26
|
onDrag={onDrag}
|
|
68
27
|
style={{ width: model.resizeHandleWidth }}
|
|
@@ -82,8 +41,9 @@ export const HorizontalResizeHandle = observer(function ({
|
|
|
82
41
|
[model],
|
|
83
42
|
)
|
|
84
43
|
return (
|
|
85
|
-
<
|
|
44
|
+
<DragHandle
|
|
86
45
|
axis="y"
|
|
46
|
+
variant="resizer"
|
|
87
47
|
getStart={() => model.height}
|
|
88
48
|
onDrag={onDrag}
|
|
89
49
|
style={{ width: '100%', height: model.resizeHandleWidth }}
|
|
@@ -91,21 +51,53 @@ export const HorizontalResizeHandle = observer(function ({
|
|
|
91
51
|
)
|
|
92
52
|
})
|
|
93
53
|
|
|
94
|
-
|
|
54
|
+
// The height behind each resizable track kind. Both bar tracks read one height
|
|
55
|
+
// so conservation and property conservation stay directly comparable; the logo
|
|
56
|
+
// keeps its own, being taller by default. Text tracks are absent: they are one
|
|
57
|
+
// alignment row tall and follow rowHeight, so the zoom controls already size
|
|
58
|
+
// them.
|
|
59
|
+
const trackHeights: Partial<
|
|
60
|
+
Record<
|
|
61
|
+
TrackKind,
|
|
62
|
+
{
|
|
63
|
+
get: (model: MsaViewModel) => number
|
|
64
|
+
set: (model: MsaViewModel, height: number) => void
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
> = {
|
|
68
|
+
bar: {
|
|
69
|
+
get: model => model.conservationTrackHeight,
|
|
70
|
+
set: (model, height) => {
|
|
71
|
+
model.setConservationTrackHeight(height)
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
logo: {
|
|
75
|
+
get: model => model.sequenceLogoTrackHeight,
|
|
76
|
+
set: (model, height) => {
|
|
77
|
+
model.setSequenceLogoTrackHeight(height)
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const TrackResizeHandle = observer(function ({
|
|
95
83
|
model,
|
|
84
|
+
kind,
|
|
96
85
|
}: {
|
|
97
86
|
model: MsaViewModel
|
|
87
|
+
kind: TrackKind
|
|
98
88
|
}) {
|
|
89
|
+
const height = trackHeights[kind]
|
|
99
90
|
const onDrag = useCallback(
|
|
100
91
|
(delta: number, startHeight: number) => {
|
|
101
|
-
model
|
|
92
|
+
height?.set(model, Math.max(10, startHeight + delta))
|
|
102
93
|
},
|
|
103
|
-
[model],
|
|
94
|
+
[model, height],
|
|
104
95
|
)
|
|
105
|
-
return (
|
|
106
|
-
<
|
|
96
|
+
return height ? (
|
|
97
|
+
<DragHandle
|
|
107
98
|
axis="y"
|
|
108
|
-
|
|
99
|
+
variant="resizer"
|
|
100
|
+
getStart={() => height.get(model)}
|
|
109
101
|
onDrag={onDrag}
|
|
110
102
|
style={{
|
|
111
103
|
position: 'absolute',
|
|
@@ -116,5 +108,5 @@ export const ConservationTrackResizeHandle = observer(function ({
|
|
|
116
108
|
zIndex: 1,
|
|
117
109
|
}}
|
|
118
110
|
/>
|
|
119
|
-
)
|
|
111
|
+
) : null
|
|
120
112
|
})
|
|
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import DragHandle from './DragHandle.tsx'
|
|
6
6
|
|
|
7
7
|
import type { MsaViewModel } from '../model.ts'
|
|
8
8
|
|
|
@@ -32,8 +32,9 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
32
32
|
boxSizing: 'border-box',
|
|
33
33
|
}}
|
|
34
34
|
>
|
|
35
|
-
<
|
|
35
|
+
<DragHandle
|
|
36
36
|
axis="y"
|
|
37
|
+
variant="thumb"
|
|
37
38
|
getStart={() => model.scrollY}
|
|
38
39
|
onDrag={onDrag}
|
|
39
40
|
style={{
|
|
@@ -10,6 +10,57 @@ import { launchInterProScan } from '../../launchInterProScan.ts'
|
|
|
10
10
|
|
|
11
11
|
import type { MsaViewModel } from '../../model.ts'
|
|
12
12
|
|
|
13
|
+
const FAMILIES = 'Families, domains, sites & repeats'
|
|
14
|
+
const FEATURES = 'Other sequence features'
|
|
15
|
+
const STRUCTURAL = 'Structural domains'
|
|
16
|
+
const OTHER = 'Other category'
|
|
17
|
+
|
|
18
|
+
// The analyses the EBI iprscan5 endpoint accepts, all on by default. Static, so
|
|
19
|
+
// it lives out here rather than being rebuilt on every render of the dialog.
|
|
20
|
+
const interProScanPrograms = [
|
|
21
|
+
{
|
|
22
|
+
name: 'NCBIfam',
|
|
23
|
+
category: FAMILIES,
|
|
24
|
+
description: 'NCBI RefSeq FAMs including TIGRFAMs',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'SFLD',
|
|
28
|
+
category: FAMILIES,
|
|
29
|
+
description: 'Structure function linkage database',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Phobius',
|
|
33
|
+
category: FEATURES,
|
|
34
|
+
description:
|
|
35
|
+
'A combined transmembrane topology and signal peptide predictor',
|
|
36
|
+
},
|
|
37
|
+
{ name: 'SignalP', category: FEATURES },
|
|
38
|
+
{ name: 'SignalP_EUK', category: OTHER },
|
|
39
|
+
{ name: 'SignalP_GRAM_POSITIVE', category: OTHER },
|
|
40
|
+
{ name: 'SignalP_GRAM_NEGATIVE', category: OTHER },
|
|
41
|
+
{ name: 'SuperFamily', category: STRUCTURAL },
|
|
42
|
+
{ name: 'Panther', category: FAMILIES },
|
|
43
|
+
{ name: 'Gene3d', category: STRUCTURAL },
|
|
44
|
+
{ name: 'HAMAP', category: FAMILIES },
|
|
45
|
+
{ name: 'PrositeProfiles', category: FAMILIES },
|
|
46
|
+
{ name: 'PrositePatterns', category: FAMILIES },
|
|
47
|
+
{ name: 'Coils', category: FEATURES },
|
|
48
|
+
{ name: 'SMART', category: FAMILIES },
|
|
49
|
+
{
|
|
50
|
+
name: 'CDD',
|
|
51
|
+
category: FAMILIES,
|
|
52
|
+
description: 'Conserved Domains Database',
|
|
53
|
+
},
|
|
54
|
+
{ name: 'PRINTS', category: FAMILIES },
|
|
55
|
+
{ name: 'PfamA', category: FAMILIES },
|
|
56
|
+
{ name: 'MobiDBLite', category: FEATURES },
|
|
57
|
+
{ name: 'PIRSF', category: OTHER },
|
|
58
|
+
{ name: 'TMHMM', category: FEATURES },
|
|
59
|
+
{ name: 'AntiFam', category: OTHER },
|
|
60
|
+
{ name: 'FunFam', category: OTHER },
|
|
61
|
+
{ name: 'PIRSR', category: FAMILIES },
|
|
62
|
+
].map(program => ({ ...program, checked: true }))
|
|
63
|
+
|
|
13
64
|
const InterProScanDialog = observer(function ({
|
|
14
65
|
handleClose,
|
|
15
66
|
model,
|
|
@@ -17,134 +68,7 @@ const InterProScanDialog = observer(function ({
|
|
|
17
68
|
handleClose: () => void
|
|
18
69
|
model: MsaViewModel
|
|
19
70
|
}) {
|
|
20
|
-
const [vals, setVals] = useState(
|
|
21
|
-
{
|
|
22
|
-
name: 'NCBIfam',
|
|
23
|
-
description: 'NCBI RefSeq FAMs including TIGRFAMs',
|
|
24
|
-
category: 'Families, domains, sites & repeats',
|
|
25
|
-
checked: true,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: 'SFLD',
|
|
29
|
-
description: 'Structure function linkage database',
|
|
30
|
-
category: 'Families, domains, sites & repeats',
|
|
31
|
-
checked: true,
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: 'Phobius',
|
|
35
|
-
checked: true,
|
|
36
|
-
description:
|
|
37
|
-
'A combined transmembrane topology and signal peptide predictor',
|
|
38
|
-
category: 'Other sequence features',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'SignalP',
|
|
42
|
-
checked: true,
|
|
43
|
-
category: 'Other sequence features',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'SignalP_EUK',
|
|
47
|
-
category: 'Other category',
|
|
48
|
-
checked: true,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'SignalP_GRAM_POSITIVE',
|
|
52
|
-
category: 'Other category',
|
|
53
|
-
checked: true,
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'SignalP_GRAM_NEGATIVE',
|
|
57
|
-
checked: true,
|
|
58
|
-
category: 'Other category',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'SuperFamily',
|
|
62
|
-
category: 'Structural domains',
|
|
63
|
-
checked: true,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'Panther',
|
|
67
|
-
category: 'Families, domains, sites & repeats',
|
|
68
|
-
checked: true,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'Gene3d',
|
|
72
|
-
category: 'Structural domains',
|
|
73
|
-
checked: true,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: 'HAMAP',
|
|
77
|
-
category: 'Families, domains, sites & repeats',
|
|
78
|
-
checked: true,
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: 'PrositeProfiles',
|
|
82
|
-
category: 'Families, domains, sites & repeats',
|
|
83
|
-
checked: true,
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'PrositePatterns',
|
|
87
|
-
category: 'Families, domains, sites & repeats',
|
|
88
|
-
checked: true,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: 'Coils',
|
|
92
|
-
category: 'Other sequence features',
|
|
93
|
-
checked: true,
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: 'SMART',
|
|
97
|
-
category: 'Families, domains, sites & repeats',
|
|
98
|
-
checked: true,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'CDD',
|
|
102
|
-
description: 'Conserved Domains Database',
|
|
103
|
-
category: 'Families, domains, sites & repeats',
|
|
104
|
-
checked: true,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: 'PRINTS',
|
|
108
|
-
category: 'Families, domains, sites & repeats',
|
|
109
|
-
checked: true,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: 'PfamA',
|
|
113
|
-
category: 'Families, domains, sites & repeats',
|
|
114
|
-
checked: true,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: 'MobiDBLite',
|
|
118
|
-
checked: true,
|
|
119
|
-
category: 'Other sequence features',
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: 'PIRSF',
|
|
123
|
-
checked: true,
|
|
124
|
-
category: 'Other category',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'TMHMM',
|
|
128
|
-
checked: true,
|
|
129
|
-
category: 'Other sequence features',
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
name: 'AntiFam',
|
|
134
|
-
checked: true,
|
|
135
|
-
category: 'Other category',
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: 'FunFam',
|
|
139
|
-
checked: true,
|
|
140
|
-
category: 'Other category',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'PIRSR',
|
|
144
|
-
checked: true,
|
|
145
|
-
category: 'Families, domains, sites & repeats',
|
|
146
|
-
},
|
|
147
|
-
])
|
|
71
|
+
const [vals, setVals] = useState(interProScanPrograms)
|
|
148
72
|
|
|
149
73
|
const programs = vals.filter(e => e.checked).map(e => e.name)
|
|
150
74
|
const [show, setShow] = useState(false)
|
|
@@ -197,12 +121,11 @@ const InterProScanDialog = observer(function ({
|
|
|
197
121
|
<tbody>
|
|
198
122
|
{vals
|
|
199
123
|
.toSorted((a, b) => a.category.localeCompare(b.category))
|
|
200
|
-
.map(({ name, checked, category }) => (
|
|
124
|
+
.map(({ name, checked, category, description }) => (
|
|
201
125
|
<tr key={name}>
|
|
202
126
|
<td>
|
|
203
127
|
<input
|
|
204
128
|
type="checkbox"
|
|
205
|
-
key={name}
|
|
206
129
|
checked={checked}
|
|
207
130
|
onChange={() => {
|
|
208
131
|
setVals(
|
|
@@ -215,7 +138,7 @@ const InterProScanDialog = observer(function ({
|
|
|
215
138
|
}}
|
|
216
139
|
/>
|
|
217
140
|
</td>
|
|
218
|
-
<td>{name}</td>
|
|
141
|
+
<td title={description}>{name}</td>
|
|
219
142
|
<td>{category}</td>
|
|
220
143
|
</tr>
|
|
221
144
|
))}
|
|
@@ -12,9 +12,27 @@ function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// Every track the alignment can show, with its current state. Without this the
|
|
16
|
+
// "Close" item on a track's own dropdown is a one-way door -- there is nowhere
|
|
17
|
+
// else in the UI to turn a track back on.
|
|
18
|
+
function tracksSubMenu(model: MsaViewModel): MenuItem[] {
|
|
19
|
+
const shown = new Set(model.turnedOnTracks.map(t => t.model.id))
|
|
20
|
+
return model.tracks.map(({ model: { id, name } }) =>
|
|
21
|
+
toggle(name, shown.has(id), () => {
|
|
22
|
+
model.toggleTrack(id)
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
16
28
|
const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
|
|
17
29
|
return [
|
|
30
|
+
{
|
|
31
|
+
label: 'Tracks',
|
|
32
|
+
type: 'subMenu' as const,
|
|
33
|
+
subMenu: tracksSubMenu(model),
|
|
34
|
+
},
|
|
35
|
+
{ type: 'divider' as const },
|
|
18
36
|
toggle('Show column statistics on hover', showColumnStats, arg => {
|
|
19
37
|
model.setShowColumnStats(arg)
|
|
20
38
|
}),
|
|
@@ -47,7 +65,7 @@ export function treeSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
|
47
65
|
toggle('Show tree', drawTree, arg => {
|
|
48
66
|
model.setDrawTree(arg)
|
|
49
67
|
}),
|
|
50
|
-
toggle('Draw
|
|
68
|
+
toggle('Draw bubbles on tree branches', drawNodeBubbles, arg => {
|
|
51
69
|
model.setDrawNodeBubbles(arg)
|
|
52
70
|
}),
|
|
53
71
|
toggle('Tree labels align right', labelsAlignRight, arg => {
|