react-msaview 1.2.10 → 1.3.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/colorSchemes.d.ts +16 -0
- package/bundle/colorSchemes.js +455 -0
- package/bundle/components/AboutDlg.d.ts +5 -0
- package/bundle/components/AboutDlg.js +47 -0
- package/bundle/components/AddTrackDlg.d.ts +8 -0
- package/bundle/components/AddTrackDlg.js +26 -0
- package/bundle/components/AnnotationDlg.d.ts +11 -0
- package/bundle/components/AnnotationDlg.js +77 -0
- package/bundle/components/BoxTrack.d.ts +7 -0
- package/bundle/components/BoxTrack.js +143 -0
- package/bundle/components/DetailsDlg.d.ts +8 -0
- package/bundle/components/DetailsDlg.js +12 -0
- package/bundle/components/Header.d.ts +6 -0
- package/bundle/components/Header.js +63 -0
- package/bundle/components/ImportForm.d.ts +6 -0
- package/bundle/components/ImportForm.js +89 -0
- package/bundle/components/MSACanvas.d.ts +6 -0
- package/bundle/components/MSACanvas.js +210 -0
- package/bundle/components/MSAView.d.ts +6 -0
- package/bundle/components/MSAView.js +88 -0
- package/bundle/components/MoreInfoDlg.d.ts +6 -0
- package/bundle/components/MoreInfoDlg.js +11 -0
- package/bundle/components/ResizeHandles.d.ts +8 -0
- package/bundle/components/ResizeHandles.js +110 -0
- package/bundle/components/Rubberband.d.ts +7 -0
- package/bundle/components/Rubberband.js +196 -0
- package/bundle/components/Ruler.d.ts +20 -0
- package/bundle/components/Ruler.js +121 -0
- package/bundle/components/SettingsDlg.d.ts +8 -0
- package/bundle/components/SettingsDlg.js +40 -0
- package/bundle/components/TextTrack.d.ts +7 -0
- package/bundle/components/TextTrack.js +72 -0
- package/bundle/components/Track.d.ts +11 -0
- package/bundle/components/Track.js +81 -0
- package/bundle/components/TrackInfoDlg.d.ts +6 -0
- package/bundle/components/TrackInfoDlg.js +33 -0
- package/bundle/components/TracklistDlg.d.ts +8 -0
- package/bundle/components/TracklistDlg.js +18 -0
- package/bundle/components/TreeCanvas.d.ts +6 -0
- package/bundle/components/TreeCanvas.js +431 -0
- package/bundle/components/TreeRuler.d.ts +6 -0
- package/bundle/components/TreeRuler.js +8 -0
- package/bundle/components/data/seq2.d.ts +3 -0
- package/bundle/components/data/seq2.js +3 -0
- package/bundle/index.d.ts +4 -0
- package/bundle/index.js +97100 -0
- package/bundle/layout.d.ts +23 -0
- package/bundle/layout.js +53 -0
- package/bundle/model.d.ts +364 -0
- package/bundle/model.js +894 -0
- package/bundle/parseNewick.d.ts +64 -0
- package/bundle/parseNewick.js +94 -0
- package/bundle/parsers/ClustalMSA.d.ts +39 -0
- package/bundle/parsers/ClustalMSA.js +77 -0
- package/bundle/parsers/FastaMSA.d.ts +26 -0
- package/bundle/parsers/FastaMSA.js +78 -0
- package/bundle/parsers/StockholmMSA.d.ts +75 -0
- package/bundle/parsers/StockholmMSA.js +142 -0
- package/bundle/util.d.ts +17 -0
- package/bundle/util.js +33 -0
- package/dist/colorSchemes.js +1 -1
- package/dist/components/AboutDlg.js +1 -1
- package/dist/components/AddTrackDlg.js +1 -1
- package/dist/components/AnnotationDlg.js +1 -1
- package/dist/components/BoxTrack.js +4 -4
- package/dist/components/Header.js +1 -1
- package/dist/components/ImportForm.js +10 -2
- package/dist/components/MSACanvas.js +2 -2
- package/dist/components/Rubberband.js +1 -1
- package/dist/components/Ruler.js +2 -2
- package/dist/components/SettingsDlg.js +3 -3
- package/dist/components/TextTrack.js +1 -1
- package/dist/components/TreeCanvas.js +2 -1
- package/dist/components/package.json +13 -6
- package/dist/model.d.ts +68 -4
- package/dist/model.js +8 -6
- package/dist/parsers/StockholmMSA.js +1 -1
- package/dist/util.js +2 -2
- package/package.json +13 -6
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Dialog, DialogTitle, DialogContent } from '@material-ui/core';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
import { Attributes } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail';
|
|
5
|
+
export default observer(function (_a) {
|
|
6
|
+
var info = _a.info, onClose = _a.onClose;
|
|
7
|
+
return (React.createElement(Dialog, { onClose: function () { return onClose(); }, open: true },
|
|
8
|
+
React.createElement(DialogTitle, null, "Metadata"),
|
|
9
|
+
React.createElement(DialogContent, null,
|
|
10
|
+
React.createElement(Attributes, { attributes: info }))));
|
|
11
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { MsaViewModel } from '../model';
|
|
3
|
+
export declare const VerticalResizeHandle: ({ model }: {
|
|
4
|
+
model: MsaViewModel;
|
|
5
|
+
}) => JSX.Element;
|
|
6
|
+
export declare const HorizontalResizeHandle: ({ model }: {
|
|
7
|
+
model: MsaViewModel;
|
|
8
|
+
}) => JSX.Element;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
export var VerticalResizeHandle = observer(function (_a) {
|
|
4
|
+
var model = _a.model;
|
|
5
|
+
var resizeHandleWidth = model.resizeHandleWidth;
|
|
6
|
+
var _b = useState(false), mouseDragging = _b[0], setMouseDragging = _b[1];
|
|
7
|
+
var scheduled = useRef(false);
|
|
8
|
+
var prevX = useRef(0);
|
|
9
|
+
useEffect(function () {
|
|
10
|
+
function globalMouseMove(event) {
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
var currX = event.clientX;
|
|
13
|
+
if (prevX.current === 0) {
|
|
14
|
+
prevX.current = event.clientX;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
var distance_1 = currX - prevX.current;
|
|
18
|
+
if (distance_1) {
|
|
19
|
+
// use rAF to make it so multiple event handlers aren't fired per-frame
|
|
20
|
+
// see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
|
|
21
|
+
if (!scheduled.current) {
|
|
22
|
+
scheduled.current = true;
|
|
23
|
+
window.requestAnimationFrame(function () {
|
|
24
|
+
model.setTreeAreaWidth(model.treeAreaWidth + distance_1);
|
|
25
|
+
scheduled.current = false;
|
|
26
|
+
prevX.current = event.clientX;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function globalMouseUp() {
|
|
33
|
+
prevX.current = 0;
|
|
34
|
+
if (mouseDragging) {
|
|
35
|
+
setMouseDragging(false);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (mouseDragging) {
|
|
39
|
+
document.addEventListener('mousemove', globalMouseMove, true);
|
|
40
|
+
document.addEventListener('mouseup', globalMouseUp, true);
|
|
41
|
+
return function () {
|
|
42
|
+
document.removeEventListener('mousemove', globalMouseMove, true);
|
|
43
|
+
document.removeEventListener('mouseup', globalMouseUp, true);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return function () { };
|
|
47
|
+
}, [mouseDragging, model]);
|
|
48
|
+
return (React.createElement("div", null,
|
|
49
|
+
React.createElement("div", { onMouseDown: function () { return setMouseDragging(true); }, style: {
|
|
50
|
+
cursor: 'ew-resize',
|
|
51
|
+
height: '100%',
|
|
52
|
+
width: resizeHandleWidth,
|
|
53
|
+
background: "rgba(200,200,200)",
|
|
54
|
+
position: 'relative',
|
|
55
|
+
} })));
|
|
56
|
+
});
|
|
57
|
+
export var HorizontalResizeHandle = observer(function (_a) {
|
|
58
|
+
var model = _a.model;
|
|
59
|
+
var resizeHandleWidth = model.resizeHandleWidth;
|
|
60
|
+
var _b = useState(false), mouseDragging = _b[0], setMouseDragging = _b[1];
|
|
61
|
+
var scheduled = useRef(false);
|
|
62
|
+
var prevY = useRef(0);
|
|
63
|
+
useEffect(function () {
|
|
64
|
+
function globalMouseMove(event) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
var currY = event.clientY;
|
|
67
|
+
if (prevY.current === 0) {
|
|
68
|
+
prevY.current = event.clientY;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
var distance_2 = currY - prevY.current;
|
|
72
|
+
if (distance_2) {
|
|
73
|
+
// use rAF to make it so multiple event handlers aren't fired per-frame
|
|
74
|
+
// see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
|
|
75
|
+
if (!scheduled.current) {
|
|
76
|
+
scheduled.current = true;
|
|
77
|
+
window.requestAnimationFrame(function () {
|
|
78
|
+
model.setHeight(model.height + distance_2);
|
|
79
|
+
scheduled.current = false;
|
|
80
|
+
prevY.current = event.clientY;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function globalMouseUp() {
|
|
87
|
+
prevY.current = 0;
|
|
88
|
+
if (mouseDragging) {
|
|
89
|
+
setMouseDragging(false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (mouseDragging) {
|
|
93
|
+
document.addEventListener('mousemove', globalMouseMove, true);
|
|
94
|
+
document.addEventListener('mouseup', globalMouseUp, true);
|
|
95
|
+
return function () {
|
|
96
|
+
document.removeEventListener('mousemove', globalMouseMove, true);
|
|
97
|
+
document.removeEventListener('mouseup', globalMouseUp, true);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return function () { };
|
|
101
|
+
}, [mouseDragging, model]);
|
|
102
|
+
return (React.createElement("div", null,
|
|
103
|
+
React.createElement("div", { onMouseDown: function () { return setMouseDragging(true); }, style: {
|
|
104
|
+
cursor: 'ns-resize',
|
|
105
|
+
width: '100%',
|
|
106
|
+
height: resizeHandleWidth,
|
|
107
|
+
background: "rgba(200,200,200)",
|
|
108
|
+
position: 'relative',
|
|
109
|
+
} })));
|
|
110
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
// material ui
|
|
4
|
+
import { Popover, Tooltip, Typography, makeStyles, alpha, } from '@material-ui/core';
|
|
5
|
+
import AssignmentIcon from '@material-ui/icons/Assignment';
|
|
6
|
+
// import { stringify } from '@jbrowse/core/util'
|
|
7
|
+
import { Menu } from '@jbrowse/core/ui';
|
|
8
|
+
var useStyles = makeStyles(function (theme) {
|
|
9
|
+
var background = theme.palette.tertiary
|
|
10
|
+
? alpha(theme.palette.tertiary.main, 0.7)
|
|
11
|
+
: alpha(theme.palette.primary.main, 0.7);
|
|
12
|
+
return {
|
|
13
|
+
rubberband: {
|
|
14
|
+
height: '100%',
|
|
15
|
+
background: background,
|
|
16
|
+
position: 'absolute',
|
|
17
|
+
zIndex: 10,
|
|
18
|
+
textAlign: 'center',
|
|
19
|
+
overflow: 'hidden',
|
|
20
|
+
},
|
|
21
|
+
rubberbandControl: {
|
|
22
|
+
cursor: 'crosshair',
|
|
23
|
+
width: '100%',
|
|
24
|
+
minHeight: 8,
|
|
25
|
+
},
|
|
26
|
+
rubberbandText: {
|
|
27
|
+
color: theme.palette.tertiary
|
|
28
|
+
? theme.palette.tertiary.contrastText
|
|
29
|
+
: theme.palette.primary.contrastText,
|
|
30
|
+
},
|
|
31
|
+
popover: {
|
|
32
|
+
mouseEvents: 'none',
|
|
33
|
+
cursor: 'crosshair',
|
|
34
|
+
},
|
|
35
|
+
paper: {
|
|
36
|
+
paddingLeft: theme.spacing(1),
|
|
37
|
+
paddingRight: theme.spacing(1),
|
|
38
|
+
},
|
|
39
|
+
guide: {
|
|
40
|
+
pointerEvents: 'none',
|
|
41
|
+
height: '100%',
|
|
42
|
+
width: 1,
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
zIndex: 10,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
var VerticalGuide = observer(function (_a) {
|
|
49
|
+
var model = _a.model, coordX = _a.coordX;
|
|
50
|
+
var treeAreaWidth = model.treeAreaWidth;
|
|
51
|
+
var classes = useStyles();
|
|
52
|
+
return (React.createElement(React.Fragment, null,
|
|
53
|
+
React.createElement(Tooltip, { open: true, placement: "top", title: "" + (model.pxToBp(coordX) + 1), arrow: true },
|
|
54
|
+
React.createElement("div", { style: {
|
|
55
|
+
left: coordX + treeAreaWidth,
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
height: 1,
|
|
58
|
+
} })),
|
|
59
|
+
React.createElement("div", { className: classes.guide, style: {
|
|
60
|
+
left: coordX + treeAreaWidth,
|
|
61
|
+
background: 'red',
|
|
62
|
+
} })));
|
|
63
|
+
});
|
|
64
|
+
function Rubberband(_a) {
|
|
65
|
+
var model = _a.model, _b = _a.ControlComponent, ControlComponent = _b === void 0 ? React.createElement("div", null) : _b;
|
|
66
|
+
var treeAreaWidth = model.treeAreaWidth;
|
|
67
|
+
var _c = useState(), startX = _c[0], setStartX = _c[1];
|
|
68
|
+
var _d = useState(), currentX = _d[0], setCurrentX = _d[1];
|
|
69
|
+
// clientX and clientY used for anchorPosition for menu
|
|
70
|
+
// offsetX used for calculations about width of selection
|
|
71
|
+
var _e = useState(), anchorPosition = _e[0], setAnchorPosition = _e[1];
|
|
72
|
+
var _f = useState(), guideX = _f[0], setGuideX = _f[1];
|
|
73
|
+
var controlsRef = useRef(null);
|
|
74
|
+
var rubberbandRef = useRef(null);
|
|
75
|
+
var classes = useStyles();
|
|
76
|
+
var mouseDragging = startX !== undefined && anchorPosition === undefined;
|
|
77
|
+
useEffect(function () {
|
|
78
|
+
function globalMouseMove(event) {
|
|
79
|
+
if (controlsRef.current && mouseDragging) {
|
|
80
|
+
var relativeX = event.clientX - controlsRef.current.getBoundingClientRect().left;
|
|
81
|
+
setCurrentX(relativeX);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function globalMouseUp(event) {
|
|
85
|
+
if (startX !== undefined && controlsRef.current) {
|
|
86
|
+
var clientX = event.clientX, clientY = event.clientY;
|
|
87
|
+
var ref = controlsRef.current;
|
|
88
|
+
var offsetX = clientX - ref.getBoundingClientRect().left;
|
|
89
|
+
// as stated above, store both clientX/Y and offsetX for different
|
|
90
|
+
// purposes
|
|
91
|
+
setAnchorPosition({
|
|
92
|
+
offsetX: offsetX,
|
|
93
|
+
clientX: clientX,
|
|
94
|
+
clientY: clientY,
|
|
95
|
+
});
|
|
96
|
+
setGuideX(undefined);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (mouseDragging) {
|
|
100
|
+
window.addEventListener('mousemove', globalMouseMove);
|
|
101
|
+
window.addEventListener('mouseup', globalMouseUp);
|
|
102
|
+
return function () {
|
|
103
|
+
window.removeEventListener('mousemove', globalMouseMove);
|
|
104
|
+
window.removeEventListener('mouseup', globalMouseUp);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return function () { };
|
|
108
|
+
}, [startX, mouseDragging, anchorPosition]);
|
|
109
|
+
useEffect(function () {
|
|
110
|
+
if (!mouseDragging &&
|
|
111
|
+
currentX !== undefined &&
|
|
112
|
+
startX !== undefined &&
|
|
113
|
+
Math.abs(currentX - startX) <= 3) {
|
|
114
|
+
handleClose();
|
|
115
|
+
}
|
|
116
|
+
}, [mouseDragging, currentX, startX, model.colWidth]);
|
|
117
|
+
function mouseDown(event) {
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
event.stopPropagation();
|
|
120
|
+
var relativeX = event.clientX -
|
|
121
|
+
event.target.getBoundingClientRect().left;
|
|
122
|
+
setStartX(relativeX);
|
|
123
|
+
setCurrentX(relativeX);
|
|
124
|
+
}
|
|
125
|
+
function mouseMove(event) {
|
|
126
|
+
var target = event.target;
|
|
127
|
+
setGuideX(event.clientX - target.getBoundingClientRect().left);
|
|
128
|
+
}
|
|
129
|
+
function mouseOut() {
|
|
130
|
+
setGuideX(undefined);
|
|
131
|
+
model.clearAnnotPos();
|
|
132
|
+
}
|
|
133
|
+
function handleClose() {
|
|
134
|
+
setAnchorPosition(undefined);
|
|
135
|
+
setStartX(undefined);
|
|
136
|
+
setCurrentX(undefined);
|
|
137
|
+
}
|
|
138
|
+
//eslint-disable-next-line @typescript-eslint/ban-types
|
|
139
|
+
function handleMenuItemClick(_, callback) {
|
|
140
|
+
callback();
|
|
141
|
+
handleClose();
|
|
142
|
+
}
|
|
143
|
+
if (startX === undefined) {
|
|
144
|
+
return (React.createElement(React.Fragment, null,
|
|
145
|
+
guideX !== undefined ? (React.createElement(VerticalGuide, { model: model, coordX: guideX })) : null,
|
|
146
|
+
React.createElement("div", { "data-testid": "rubberband_controls", className: classes.rubberbandControl, role: "presentation", ref: controlsRef, onMouseDown: mouseDown, onMouseOut: mouseOut, onMouseMove: mouseMove }, ControlComponent)));
|
|
147
|
+
}
|
|
148
|
+
var right = anchorPosition ? anchorPosition.offsetX : currentX || 0;
|
|
149
|
+
var left = right < startX ? right : startX;
|
|
150
|
+
var width = Math.abs(right - startX);
|
|
151
|
+
var leftBpOffset = model.pxToBp(left);
|
|
152
|
+
var rightBpOffset = model.pxToBp(left + width);
|
|
153
|
+
var numOfBpSelected = Math.ceil(width / model.colWidth);
|
|
154
|
+
var menuItems = [
|
|
155
|
+
{
|
|
156
|
+
label: 'Create annotation',
|
|
157
|
+
icon: AssignmentIcon,
|
|
158
|
+
onClick: function () {
|
|
159
|
+
model.setOffsets(leftBpOffset, rightBpOffset);
|
|
160
|
+
handleClose();
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
return (React.createElement(React.Fragment, null,
|
|
165
|
+
rubberbandRef.current ? (React.createElement(React.Fragment, null,
|
|
166
|
+
React.createElement(Popover, { className: classes.popover, classes: {
|
|
167
|
+
paper: classes.paper,
|
|
168
|
+
}, open: true, anchorEl: rubberbandRef.current, anchorOrigin: {
|
|
169
|
+
vertical: 'top',
|
|
170
|
+
horizontal: 'left',
|
|
171
|
+
}, transformOrigin: {
|
|
172
|
+
vertical: 'bottom',
|
|
173
|
+
horizontal: 'right',
|
|
174
|
+
}, keepMounted: true, disableRestoreFocus: true },
|
|
175
|
+
React.createElement(Typography, null, leftBpOffset + 1)),
|
|
176
|
+
React.createElement(Popover, { className: classes.popover, classes: {
|
|
177
|
+
paper: classes.paper,
|
|
178
|
+
}, open: true, anchorEl: rubberbandRef.current, anchorOrigin: {
|
|
179
|
+
vertical: 'top',
|
|
180
|
+
horizontal: 'right',
|
|
181
|
+
}, transformOrigin: {
|
|
182
|
+
vertical: 'bottom',
|
|
183
|
+
horizontal: 'left',
|
|
184
|
+
}, keepMounted: true, disableRestoreFocus: true },
|
|
185
|
+
React.createElement(Typography, null, rightBpOffset + 1)))) : null,
|
|
186
|
+
React.createElement("div", { ref: rubberbandRef, className: classes.rubberband, style: { left: left + treeAreaWidth, width: width } },
|
|
187
|
+
React.createElement(Typography, { variant: "h6", className: classes.rubberbandText },
|
|
188
|
+
numOfBpSelected.toLocaleString('en-US'),
|
|
189
|
+
" bp")),
|
|
190
|
+
React.createElement("div", { "data-testid": "rubberband_controls", className: classes.rubberbandControl, role: "presentation", ref: controlsRef, onMouseDown: mouseDown, onMouseOut: mouseOut, onMouseMove: mouseMove }, ControlComponent),
|
|
191
|
+
anchorPosition ? (React.createElement(Menu, { anchorReference: "anchorPosition", anchorPosition: {
|
|
192
|
+
left: anchorPosition.clientX,
|
|
193
|
+
top: anchorPosition.clientY,
|
|
194
|
+
}, onMenuItemClick: handleMenuItemClick, open: Boolean(anchorPosition), onClose: handleClose, menuItems: menuItems })) : null));
|
|
195
|
+
}
|
|
196
|
+
export default observer(Rubberband);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { MsaViewModel } from '../model';
|
|
3
|
+
/**
|
|
4
|
+
* Given a scale ( bp/px ) and minimum distances (px) between major and minor
|
|
5
|
+
* gridlines, return an object like `{ majorPitch: bp, minorPitch: bp }` giving
|
|
6
|
+
* the gridline pitches to use.
|
|
7
|
+
*/
|
|
8
|
+
export declare function chooseGridPitch(scale: number, minMajorPitchPx: number, minMinorPitchPx: number): {
|
|
9
|
+
majorPitch: number;
|
|
10
|
+
minorPitch: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function makeTicks(start: number, end: number, bpPerPx: number, emitMajor?: boolean, emitMinor?: boolean): {
|
|
13
|
+
type: string;
|
|
14
|
+
base: number;
|
|
15
|
+
index: number;
|
|
16
|
+
}[];
|
|
17
|
+
declare const Ruler: ({ model }: {
|
|
18
|
+
model: MsaViewModel;
|
|
19
|
+
}) => JSX.Element | null;
|
|
20
|
+
export default Ruler;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { makeStyles } from '@material-ui/core';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
/**
|
|
5
|
+
* Given a scale ( bp/px ) and minimum distances (px) between major and minor
|
|
6
|
+
* gridlines, return an object like `{ majorPitch: bp, minorPitch: bp }` giving
|
|
7
|
+
* the gridline pitches to use.
|
|
8
|
+
*/
|
|
9
|
+
export function chooseGridPitch(scale, minMajorPitchPx, minMinorPitchPx) {
|
|
10
|
+
scale = Math.abs(scale);
|
|
11
|
+
var minMajorPitchBp = minMajorPitchPx * scale;
|
|
12
|
+
var majorMagnitude = parseInt(Number(minMajorPitchBp).toExponential().split(/e/i)[1], 10);
|
|
13
|
+
var majorPitch = Math.pow(10, majorMagnitude);
|
|
14
|
+
while (majorPitch < minMajorPitchBp) {
|
|
15
|
+
majorPitch *= 2;
|
|
16
|
+
if (majorPitch >= minMajorPitchBp) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
majorPitch *= 2.5;
|
|
20
|
+
}
|
|
21
|
+
majorPitch = Math.max(majorPitch, 5);
|
|
22
|
+
var majorPitchPx = majorPitch / scale;
|
|
23
|
+
var minorPitch = 0;
|
|
24
|
+
if (!(majorPitch % 10) && majorPitchPx / 10 >= minMinorPitchPx) {
|
|
25
|
+
minorPitch = majorPitch / 10;
|
|
26
|
+
}
|
|
27
|
+
else if (!(majorPitch % 5) && majorPitchPx / 5 >= minMinorPitchPx) {
|
|
28
|
+
minorPitch = majorPitch / 5;
|
|
29
|
+
}
|
|
30
|
+
else if (!(majorPitch % 2) && majorPitchPx / 2 >= minMinorPitchPx) {
|
|
31
|
+
minorPitch = majorPitch / 2;
|
|
32
|
+
}
|
|
33
|
+
return { majorPitch: majorPitch, minorPitch: minorPitch };
|
|
34
|
+
}
|
|
35
|
+
export function makeTicks(start, end, bpPerPx, emitMajor, emitMinor) {
|
|
36
|
+
var _a;
|
|
37
|
+
if (emitMajor === void 0) { emitMajor = true; }
|
|
38
|
+
if (emitMinor === void 0) { emitMinor = true; }
|
|
39
|
+
var gridPitch = chooseGridPitch(bpPerPx, 60, 15);
|
|
40
|
+
var minBase = start;
|
|
41
|
+
var maxBase = end;
|
|
42
|
+
if (minBase === null || maxBase === null) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
if (bpPerPx < 0) {
|
|
46
|
+
_a = [maxBase, minBase], minBase = _a[0], maxBase = _a[1];
|
|
47
|
+
}
|
|
48
|
+
// add 20px additional on the right and left to allow us to draw the ends of
|
|
49
|
+
// labels that lie a little outside our region
|
|
50
|
+
minBase -= Math.abs(20 * bpPerPx);
|
|
51
|
+
maxBase += Math.abs(20 * bpPerPx) + 1;
|
|
52
|
+
var iterPitch = gridPitch.minorPitch || gridPitch.majorPitch;
|
|
53
|
+
var index = 0;
|
|
54
|
+
var ticks = [];
|
|
55
|
+
for (var base = Math.ceil(minBase / iterPitch) * iterPitch; base < maxBase; base += iterPitch) {
|
|
56
|
+
if (emitMinor && base % (gridPitch.majorPitch * 2)) {
|
|
57
|
+
ticks.push({ type: 'minor', base: base - 1, index: index });
|
|
58
|
+
index += 1;
|
|
59
|
+
}
|
|
60
|
+
else if (emitMajor && !(base % (gridPitch.majorPitch * 2))) {
|
|
61
|
+
ticks.push({ type: 'major', base: base - 1, index: index });
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return ticks;
|
|
66
|
+
}
|
|
67
|
+
function mathPower(num) {
|
|
68
|
+
if (num < 999) {
|
|
69
|
+
return String(num);
|
|
70
|
+
}
|
|
71
|
+
return mathPower(~~(num / 1000)) + "," + ("00" + ~~(num % 1000)).substr(-3, 3);
|
|
72
|
+
}
|
|
73
|
+
var useStyles = makeStyles(function ( /* theme */) { return ({
|
|
74
|
+
majorTickLabel: {
|
|
75
|
+
fontSize: '11px',
|
|
76
|
+
},
|
|
77
|
+
majorTick: {
|
|
78
|
+
stroke: '#555',
|
|
79
|
+
},
|
|
80
|
+
minorTick: {
|
|
81
|
+
stroke: '#999',
|
|
82
|
+
},
|
|
83
|
+
}); });
|
|
84
|
+
function RulerBlock(_a) {
|
|
85
|
+
var start = _a.start, end = _a.end, bpPerPx = _a.bpPerPx, reversed = _a.reversed, major = _a.major, minor = _a.minor;
|
|
86
|
+
var classes = useStyles();
|
|
87
|
+
var ticks = makeTicks(start, end, bpPerPx, major, minor);
|
|
88
|
+
return (React.createElement(React.Fragment, null,
|
|
89
|
+
ticks.map(function (tick) {
|
|
90
|
+
var x = (reversed ? end - tick.base : tick.base - start) / bpPerPx;
|
|
91
|
+
return (React.createElement("line", { key: tick.base, x1: x, x2: x, y1: 11, y2: tick.type === 'major' ? 11 + 6 : 11 + 4, strokeWidth: 1, stroke: tick.type === 'major' ? '#555' : '#999', className: tick.type === 'major' ? classes.majorTick : classes.minorTick, "data-bp": tick.base }));
|
|
92
|
+
}),
|
|
93
|
+
ticks
|
|
94
|
+
.filter(function (tick) { return tick.type === 'major'; })
|
|
95
|
+
.map(function (tick) {
|
|
96
|
+
var x = (reversed ? end - tick.base : tick.base - start) / bpPerPx;
|
|
97
|
+
return (React.createElement("text", { x: x, y: 10, key: "label-" + tick.base, textAnchor: "middle", style: { fontSize: '11px' }, className: classes.majorTickLabel }, mathPower(tick.base + 1)));
|
|
98
|
+
})));
|
|
99
|
+
}
|
|
100
|
+
var Ruler = observer(function (_a) {
|
|
101
|
+
var model = _a.model;
|
|
102
|
+
var MSA = model.MSA, colWidth = model.colWidth, msaAreaWidth = model.msaAreaWidth, resizeHandleWidth = model.resizeHandleWidth, scrollX = model.scrollX, blocksX = model.blocksX, blockSize = model.blockSize;
|
|
103
|
+
var ref = useRef(null);
|
|
104
|
+
var offsetX = blocksX[0];
|
|
105
|
+
return !MSA ? null : (React.createElement("div", { ref: ref, style: {
|
|
106
|
+
position: 'relative',
|
|
107
|
+
width: msaAreaWidth,
|
|
108
|
+
cursor: 'crosshair',
|
|
109
|
+
overflow: 'hidden',
|
|
110
|
+
height: 20,
|
|
111
|
+
background: '#ccc',
|
|
112
|
+
} },
|
|
113
|
+
React.createElement("svg", { style: {
|
|
114
|
+
width: blocksX.length * blockSize,
|
|
115
|
+
position: 'absolute',
|
|
116
|
+
left: scrollX + offsetX + resizeHandleWidth,
|
|
117
|
+
pointerEvents: 'none',
|
|
118
|
+
} },
|
|
119
|
+
React.createElement(RulerBlock, { key: offsetX, start: offsetX / colWidth, end: offsetX / colWidth + (blockSize * blocksX.length) / colWidth, bpPerPx: 1 / colWidth }))));
|
|
120
|
+
});
|
|
121
|
+
export default Ruler;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
import { Button, Checkbox, Dialog, DialogActions, DialogTitle, DialogContent, FormControlLabel, MenuItem, TextField, } from '@material-ui/core';
|
|
4
|
+
import colorSchemes from '../colorSchemes';
|
|
5
|
+
export default observer(function (_a) {
|
|
6
|
+
var model = _a.model, onClose = _a.onClose, open = _a.open;
|
|
7
|
+
var rowHeightInit = model.rowHeight, colWidthInit = model.colWidth, treeWidthInit = model.treeWidth, colorSchemeName = model.colorSchemeName, noTree = model.noTree;
|
|
8
|
+
var _b = useState("" + rowHeightInit), rowHeight = _b[0], setRowHeight = _b[1];
|
|
9
|
+
var _c = useState("" + colWidthInit), colWidth = _c[0], setColWidth = _c[1];
|
|
10
|
+
var _d = useState("" + treeWidthInit), treeWidth = _d[0], setTreeWidth = _d[1];
|
|
11
|
+
function error(n) {
|
|
12
|
+
return Number.isNaN(+n) || +n < 0;
|
|
13
|
+
}
|
|
14
|
+
var rowHeightError = error(rowHeight);
|
|
15
|
+
var colWidthError = error(colWidth);
|
|
16
|
+
var treeWidthError = error(treeWidth);
|
|
17
|
+
return (React.createElement(Dialog, { onClose: function () { return onClose(); }, open: open },
|
|
18
|
+
React.createElement(DialogTitle, null, "Settings"),
|
|
19
|
+
React.createElement(DialogContent, null,
|
|
20
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: model.showBranchLen, onChange: function () { return model.toggleBranchLen(); } }), label: "Show branch length" }),
|
|
21
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: model.bgColor, onChange: function () { return model.toggleBgColor(); } }), label: "Color background" }),
|
|
22
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: model.drawNodeBubbles, onChange: function () { return model.toggleNodeBubbles(); } }), label: "Draw node bubbles" }),
|
|
23
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: model.drawTree, onChange: function () { return model.toggleDrawTree(); } }), label: "Draw tree (if available)" }),
|
|
24
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: model.labelsAlignRight, onChange: function () { return model.toggleLabelsAlignRight(); } }), label: "Labels align right (note: labels may draw over tree, but can adjust tree width or tree area width in UI)" }),
|
|
25
|
+
React.createElement(TextField, { label: "Row height (px)", value: rowHeight, error: rowHeightError, onChange: function (event) { return setRowHeight(event.target.value); } }),
|
|
26
|
+
React.createElement(TextField, { label: "Column width (px)", value: colWidth, error: colWidthError, onChange: function (event) { return setColWidth(event.target.value); } }),
|
|
27
|
+
React.createElement("br", null),
|
|
28
|
+
!noTree ? (React.createElement(TextField, { label: "Tree width (px)", value: treeWidth, error: treeWidthError, onChange: function (event) { return setTreeWidth(event.target.value); } })) : null,
|
|
29
|
+
React.createElement("br", null),
|
|
30
|
+
React.createElement(TextField, { select: true, label: "Color scheme", value: colorSchemeName, onChange: function (event) { return model.setColorSchemeName(event.target.value); } }, Object.keys(colorSchemes).map(function (option) { return (React.createElement(MenuItem, { key: option, value: option }, option)); })),
|
|
31
|
+
React.createElement(DialogActions, null,
|
|
32
|
+
React.createElement(Button, { disabled: rowHeightError || colWidthError || treeWidthError, onClick: function () {
|
|
33
|
+
model.setRowHeight(+rowHeight);
|
|
34
|
+
model.setColWidth(+colWidth);
|
|
35
|
+
if (!noTree) {
|
|
36
|
+
model.setTreeWidth(+treeWidth);
|
|
37
|
+
}
|
|
38
|
+
onClose();
|
|
39
|
+
}, variant: "contained", color: "primary" }, "Submit")))));
|
|
40
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useRef, useMemo, useEffect } from 'react';
|
|
2
|
+
import { useTheme } from '@material-ui/core';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
import { colorContrast } from '../util';
|
|
5
|
+
var AnnotationBlock = observer(function (_a) {
|
|
6
|
+
var track = _a.track, model = _a.model, offsetX = _a.offsetX;
|
|
7
|
+
var blockSize = model.blockSize, scrollX = model.scrollX, bgColor = model.bgColor, modelColorScheme = model.colorScheme, colWidth = model.colWidth, rowHeight = model.rowHeight, highResScaleFactor = model.highResScaleFactor;
|
|
8
|
+
var _b = track.model, customColorScheme = _b.customColorScheme, data = _b.data;
|
|
9
|
+
var colorScheme = customColorScheme || modelColorScheme;
|
|
10
|
+
var theme = useTheme();
|
|
11
|
+
var ref = useRef(null);
|
|
12
|
+
var contrastScheme = useMemo(function () { return colorContrast(colorScheme, theme); }, [colorScheme, theme]);
|
|
13
|
+
useEffect(function () {
|
|
14
|
+
if (!ref.current) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
var ctx = ref.current.getContext('2d');
|
|
18
|
+
if (!ctx) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// this logic is very similar to MSACanvas
|
|
22
|
+
ctx.resetTransform();
|
|
23
|
+
ctx.scale(highResScaleFactor, highResScaleFactor);
|
|
24
|
+
ctx.clearRect(0, 0, blockSize, rowHeight);
|
|
25
|
+
ctx.translate(-offsetX, 0);
|
|
26
|
+
ctx.textAlign = 'center';
|
|
27
|
+
ctx.font = ctx.font.replace(/\d+px/, Math.max(8, rowHeight - 8) + "px");
|
|
28
|
+
var xStart = Math.max(0, Math.floor(offsetX / colWidth));
|
|
29
|
+
var xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth));
|
|
30
|
+
var str = data === null || data === void 0 ? void 0 : data.slice(xStart, xEnd);
|
|
31
|
+
for (var i = 0; str && i < str.length; i++) {
|
|
32
|
+
var letter = str[i];
|
|
33
|
+
var color = colorScheme[letter.toUpperCase()];
|
|
34
|
+
if (bgColor) {
|
|
35
|
+
var x = i * colWidth + offsetX - (offsetX % colWidth);
|
|
36
|
+
ctx.fillStyle = color || 'white';
|
|
37
|
+
ctx.fillRect(x, 0, colWidth, rowHeight);
|
|
38
|
+
if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
|
|
39
|
+
ctx.fillStyle = contrastScheme[letter.toUpperCase()] || 'black';
|
|
40
|
+
ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1); //+1 to avoid cutoff at height:10
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, [
|
|
45
|
+
bgColor,
|
|
46
|
+
blockSize,
|
|
47
|
+
colWidth,
|
|
48
|
+
rowHeight,
|
|
49
|
+
offsetX,
|
|
50
|
+
contrastScheme,
|
|
51
|
+
colorScheme,
|
|
52
|
+
highResScaleFactor,
|
|
53
|
+
data,
|
|
54
|
+
]);
|
|
55
|
+
return (React.createElement("canvas", { ref: ref, height: rowHeight * highResScaleFactor, width: blockSize * highResScaleFactor, style: {
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
left: scrollX + offsetX,
|
|
58
|
+
width: blockSize,
|
|
59
|
+
height: rowHeight,
|
|
60
|
+
} }));
|
|
61
|
+
});
|
|
62
|
+
var AnnotationTrack = observer(function (_a) {
|
|
63
|
+
var track = _a.track, model = _a.model;
|
|
64
|
+
var blocksX = model.blocksX, msaAreaWidth = model.msaAreaWidth, rowHeight = model.rowHeight;
|
|
65
|
+
return (React.createElement("div", { style: {
|
|
66
|
+
position: 'relative',
|
|
67
|
+
height: rowHeight,
|
|
68
|
+
width: msaAreaWidth,
|
|
69
|
+
overflow: 'hidden',
|
|
70
|
+
} }, blocksX.map(function (bx) { return (React.createElement(AnnotationBlock, { key: bx, track: track, model: model, offsetX: bx })); })));
|
|
71
|
+
});
|
|
72
|
+
export default AnnotationTrack;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { MsaViewModel } from '../model';
|
|
3
|
+
export declare const TrackLabel: ({ model, track }: {
|
|
4
|
+
model: MsaViewModel;
|
|
5
|
+
track: any;
|
|
6
|
+
}) => JSX.Element;
|
|
7
|
+
declare const Track: ({ model, track }: {
|
|
8
|
+
model: MsaViewModel;
|
|
9
|
+
track: any;
|
|
10
|
+
}) => JSX.Element;
|
|
11
|
+
export default Track;
|