@teselagen/ove 0.8.2 → 0.8.4
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/AlignmentView/LabileSitesLayer.d.ts +13 -0
- package/AlignmentView/PairwiseAlignmentView.d.ts +1 -9
- package/BarPlot/index.d.ts +33 -0
- package/LinearView/SequenceName.d.ts +2 -1
- package/PropertySidePanel/calculateAminoAcidFrequency.d.ts +46 -0
- package/PropertySidePanel/index.d.ts +6 -0
- package/RowItem/Caret/index.d.ts +2 -1
- package/StatusBar/index.d.ts +2 -1
- package/aaprops.svg +2287 -0
- package/constants/dnaToColor.d.ts +122 -4
- package/index.cjs.js +4214 -7859
- package/index.es.js +2166 -5811
- package/index.umd.js +3745 -7390
- package/ove.css +100 -19
- package/package.json +2 -2
- package/src/AlignmentView/AlignmentVisibilityTool.js +141 -37
- package/src/AlignmentView/LabileSitesLayer.js +33 -0
- package/src/AlignmentView/Minimap.js +5 -3
- package/src/AlignmentView/PairwiseAlignmentView.js +55 -61
- package/src/AlignmentView/index.js +476 -257
- package/src/AlignmentView/style.css +27 -0
- package/src/BarPlot/index.js +156 -0
- package/src/CircularView/Caret.js +8 -2
- package/src/CircularView/SelectionLayer.js +4 -2
- package/src/CircularView/index.js +5 -1
- package/src/Editor/darkmode.css +10 -0
- package/src/Editor/index.js +3 -0
- package/src/Editor/userDefinedHandlersAndOpts.js +2 -1
- package/src/FindBar/index.js +2 -3
- package/src/LinearView/SequenceName.js +8 -2
- package/src/LinearView/index.js +21 -0
- package/src/PropertySidePanel/calculateAminoAcidFrequency.js +77 -0
- package/src/PropertySidePanel/index.js +236 -0
- package/src/PropertySidePanel/style.css +39 -0
- package/src/RowItem/Caret/index.js +8 -2
- package/src/RowItem/Labels.js +1 -1
- package/src/RowItem/SelectionLayer/index.js +5 -1
- package/src/RowItem/Sequence.js +99 -5
- package/src/RowItem/Translations/Translation.js +3 -2
- package/src/RowItem/Translations/index.js +2 -0
- package/src/RowItem/index.js +74 -8
- package/src/RowItem/style.css +3 -4
- package/src/StatusBar/index.js +11 -4
- package/src/constants/dnaToColor.js +151 -0
- package/src/helperComponents/PinchHelper/PinchHelper.js +5 -1
- package/src/helperComponents/SelectDialog.js +5 -2
- package/src/style.css +2 -2
- package/src/utils/editorUtils.js +5 -3
- package/src/utils/getAlignedAminoAcidSequenceProps.js +379 -0
- package/src/withEditorInteractions/createSequenceInputPopup.js +19 -5
- package/src/withEditorInteractions/index.js +9 -3
- package/utils/editorUtils.d.ts +2 -1
- package/utils/getAlignedAminoAcidSequenceProps.d.ts +49 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import "./style.css";
|
|
3
|
+
import {
|
|
4
|
+
calculateAminoAcidFrequency,
|
|
5
|
+
aminoAcidShortNames
|
|
6
|
+
} from "./calculateAminoAcidFrequency";
|
|
7
|
+
import { Button } from "@blueprintjs/core";
|
|
8
|
+
|
|
9
|
+
export default ({ properties, setProperties, style }) => {
|
|
10
|
+
const sidebarRef = React.useRef(null);
|
|
11
|
+
const [mismatchesCount, setMismatchesCount] = React.useState(0);
|
|
12
|
+
const [mismatchesInRange, setMismatchesInRange] = React.useState(0);
|
|
13
|
+
|
|
14
|
+
const { track, isOpen, selection, isPairwise } = properties;
|
|
15
|
+
|
|
16
|
+
const getSequenceInRegion = useCallback(() => {
|
|
17
|
+
const seq = track?.alignmentData?.sequence ?? "";
|
|
18
|
+
if (!selection || selection.start === -1 || selection.end === -1) {
|
|
19
|
+
return seq;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const start = Math.max(0, selection.start);
|
|
23
|
+
const end = Math.min(seq.length - 1, selection.end);
|
|
24
|
+
if (start > end) return "";
|
|
25
|
+
return seq.slice(start, end + 1);
|
|
26
|
+
}, [track, selection]);
|
|
27
|
+
|
|
28
|
+
const mismatchKey = isPairwise ? "additionalSelectionLayers" : "mismatches";
|
|
29
|
+
|
|
30
|
+
const trackMismatches = useMemo(() => {
|
|
31
|
+
const tr = track?.[mismatchKey];
|
|
32
|
+
if (!Array.isArray(tr)) return [];
|
|
33
|
+
return isPairwise ? tr.filter(m => m?.color === "red") : tr;
|
|
34
|
+
}, [track, mismatchKey, isPairwise]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!isOpen || sidebarRef.current === null || !track) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
sidebarRef.current.focus();
|
|
42
|
+
let mismatchCount = 0;
|
|
43
|
+
|
|
44
|
+
trackMismatches?.forEach(tm => {
|
|
45
|
+
if (tm === null || tm.start === null || tm.end === null) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const overlapStart = tm.start;
|
|
50
|
+
const overlapEnd = tm.end;
|
|
51
|
+
if (overlapEnd >= overlapStart) {
|
|
52
|
+
mismatchCount += overlapEnd - overlapStart + 1;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
setMismatchesCount(mismatchCount);
|
|
57
|
+
setMismatchesInRange(mismatchCount);
|
|
58
|
+
|
|
59
|
+
if (selection && selection.start > -1 && selection.end > -1) {
|
|
60
|
+
let count = 0;
|
|
61
|
+
|
|
62
|
+
trackMismatches?.forEach(tm => {
|
|
63
|
+
if (tm === null || tm.start === null || tm.end === null) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const overlapStart = Math.max(tm.start, selection.start);
|
|
68
|
+
const overlapEnd = Math.min(tm.end, selection.end);
|
|
69
|
+
if (overlapEnd >= overlapStart) {
|
|
70
|
+
count += overlapEnd - overlapStart + 1;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
setMismatchesInRange(count);
|
|
74
|
+
}
|
|
75
|
+
}, [isOpen, track, selection, trackMismatches]);
|
|
76
|
+
|
|
77
|
+
const aminoFreq = useMemo(() => {
|
|
78
|
+
const seq = getSequenceInRegion();
|
|
79
|
+
return calculateAminoAcidFrequency(
|
|
80
|
+
seq,
|
|
81
|
+
track?.sequenceData?.isProtein ?? false
|
|
82
|
+
);
|
|
83
|
+
}, [getSequenceInRegion, track]);
|
|
84
|
+
|
|
85
|
+
if (!isOpen) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
let trackInner;
|
|
89
|
+
|
|
90
|
+
if (track) {
|
|
91
|
+
const {
|
|
92
|
+
name,
|
|
93
|
+
isProtein,
|
|
94
|
+
proteinSize,
|
|
95
|
+
size,
|
|
96
|
+
molecularWeight,
|
|
97
|
+
isoPoint,
|
|
98
|
+
extinctionCoefficient
|
|
99
|
+
} = track.sequenceData;
|
|
100
|
+
|
|
101
|
+
const frequencyEntries = Object.entries(aminoFreq.frequencies);
|
|
102
|
+
trackInner = (
|
|
103
|
+
<>
|
|
104
|
+
<div
|
|
105
|
+
style={{
|
|
106
|
+
display: "flex",
|
|
107
|
+
padding: 4,
|
|
108
|
+
paddingTop: 11,
|
|
109
|
+
paddingBottom: 11,
|
|
110
|
+
width: "100%"
|
|
111
|
+
}}
|
|
112
|
+
></div>
|
|
113
|
+
<h5>Track Properties</h5>
|
|
114
|
+
|
|
115
|
+
<div className="bp3-tab-panel">
|
|
116
|
+
<RowItem item={name} title="Name" />
|
|
117
|
+
<RowItem item={isProtein ? proteinSize : size} title="Length" />
|
|
118
|
+
<RowItem
|
|
119
|
+
item={molecularWeight?.toFixed(2)}
|
|
120
|
+
title="Molecular Weight"
|
|
121
|
+
units={isProtein ? "Da" : "g/mol"}
|
|
122
|
+
/>
|
|
123
|
+
{name !== "Consensus" && isProtein && (
|
|
124
|
+
<>
|
|
125
|
+
<RowItem item={isoPoint} title="Isoelectric Point" />
|
|
126
|
+
<RowItem
|
|
127
|
+
item={extinctionCoefficient}
|
|
128
|
+
title="Extinction Coefficient"
|
|
129
|
+
/>
|
|
130
|
+
</>
|
|
131
|
+
)}
|
|
132
|
+
<RowItem
|
|
133
|
+
item={`${mismatchesInRange}/${mismatchesCount}`}
|
|
134
|
+
title="Mismatches"
|
|
135
|
+
/>
|
|
136
|
+
<RowItem
|
|
137
|
+
item={
|
|
138
|
+
selection && selection.start > -1 ? (
|
|
139
|
+
<span>
|
|
140
|
+
{selection.start + 1} - {selection.end + 1}
|
|
141
|
+
</span>
|
|
142
|
+
) : (
|
|
143
|
+
<span>1 - {isProtein ? proteinSize : size}</span>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
title="Region"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
<h5>{isProtein ? "Amino Acid" : "Base Pair"} Frequencies</h5>
|
|
150
|
+
<div className="sidebar-table">
|
|
151
|
+
<div className="sidebar-row">
|
|
152
|
+
<div className="sidebar-cell">Amino Acid</div>
|
|
153
|
+
<div className="sidebar-cell">Count</div>
|
|
154
|
+
<div className="sidebar-cell">Percentage</div>
|
|
155
|
+
</div>
|
|
156
|
+
{frequencyEntries.map(([aa, data], idx) => {
|
|
157
|
+
return (
|
|
158
|
+
<div className={`sidebar-row property-amino-acid-${idx}`}>
|
|
159
|
+
<div className="sidebar-cell">
|
|
160
|
+
{aa} {isProtein ? `(${aminoAcidShortNames[aa]})` : ""}
|
|
161
|
+
</div>
|
|
162
|
+
<div className="sidebar-cell">{data.count}</div>
|
|
163
|
+
<div className="sidebar-cell">
|
|
164
|
+
{data.percentage.toFixed(1)}%
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</div>
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
trackInner = (
|
|
174
|
+
<div
|
|
175
|
+
style={{
|
|
176
|
+
marginTop: 20,
|
|
177
|
+
fontStyle: "italic",
|
|
178
|
+
fontSize: 16
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
Click on a track to view its properties
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div
|
|
188
|
+
ref={sidebarRef}
|
|
189
|
+
style={{
|
|
190
|
+
width: isOpen ? 350 : 0,
|
|
191
|
+
minWidth: isOpen ? 350 : 0,
|
|
192
|
+
maxWidth: isOpen ? 350 : 0,
|
|
193
|
+
paddingLeft: 20,
|
|
194
|
+
paddingRight: 20,
|
|
195
|
+
...style
|
|
196
|
+
}}
|
|
197
|
+
className="ove-sidebar-container"
|
|
198
|
+
tabIndex={0}
|
|
199
|
+
onKeyDown={e => {
|
|
200
|
+
if (e.key === "Escape") {
|
|
201
|
+
setProperties({ isOpen: false });
|
|
202
|
+
}
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<Button
|
|
206
|
+
style={{
|
|
207
|
+
position: "absolute",
|
|
208
|
+
top: 5,
|
|
209
|
+
right: 10,
|
|
210
|
+
zIndex: 1,
|
|
211
|
+
cursor: "pointer"
|
|
212
|
+
}}
|
|
213
|
+
minimal
|
|
214
|
+
intent="primary"
|
|
215
|
+
data-tip="Hide Track Properties"
|
|
216
|
+
icon="cross"
|
|
217
|
+
onClick={() => setProperties({ isOpen: false })}
|
|
218
|
+
></Button>
|
|
219
|
+
{trackInner}
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
function RowItem({ item, title, units }) {
|
|
225
|
+
if (!item) return;
|
|
226
|
+
|
|
227
|
+
const propertyClass = title.split(" ").join("-").toLowerCase();
|
|
228
|
+
return (
|
|
229
|
+
<div className={`ve-flex-row property-${propertyClass}`}>
|
|
230
|
+
<div className="ve-column-left">{title}</div>
|
|
231
|
+
<div className="ve-column-right">
|
|
232
|
+
{item} {units ?? ""}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.ove-sidebar-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
box-shadow: 0 0px 1px var(--reversed);
|
|
5
|
+
overflow-y: scroll;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.sidebar-table {
|
|
9
|
+
display: grid;
|
|
10
|
+
/* Define 3 columns with equal width */
|
|
11
|
+
grid-template-columns: repeat(3, 1fr);
|
|
12
|
+
gap: 1px; /* Optional: adds a small gap between cells */
|
|
13
|
+
border: 1px solid #f1f1f1;
|
|
14
|
+
margin: 10px;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.sidebar-row {
|
|
19
|
+
display: contents; /* Makes children participate in the grid layout of the parent */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sidebar-cell {
|
|
23
|
+
padding: 5px;
|
|
24
|
+
border: 1px solid #f1f1f1;
|
|
25
|
+
text-align: center;
|
|
26
|
+
/* Flexbox can be used within cells for content alignment if needed */
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h5 {
|
|
33
|
+
margin: 0;
|
|
34
|
+
font-size: 15px;
|
|
35
|
+
font-weight: bold;
|
|
36
|
+
text-align: center;
|
|
37
|
+
padding: 5px 0;
|
|
38
|
+
border-bottom: 1px solid #f1f1f1;
|
|
39
|
+
}
|
|
@@ -16,7 +16,8 @@ function Caret({
|
|
|
16
16
|
onRightClick,
|
|
17
17
|
style,
|
|
18
18
|
selectionMessage,
|
|
19
|
-
className = ""
|
|
19
|
+
className = "",
|
|
20
|
+
showAminoAcidUnitAsCodon
|
|
20
21
|
}) {
|
|
21
22
|
if (
|
|
22
23
|
(row.start <= caretPosition && row.end + 1 >= caretPosition) ||
|
|
@@ -35,7 +36,12 @@ function Caret({
|
|
|
35
36
|
}
|
|
36
37
|
title={
|
|
37
38
|
selectionMessage ||
|
|
38
|
-
getSelectionMessage({
|
|
39
|
+
getSelectionMessage({
|
|
40
|
+
caretPosition,
|
|
41
|
+
isProtein,
|
|
42
|
+
sequenceLength,
|
|
43
|
+
showAminoAcidUnitAsCodon
|
|
44
|
+
})
|
|
39
45
|
}
|
|
40
46
|
className={classnames(
|
|
41
47
|
{
|
package/src/RowItem/Labels.js
CHANGED
|
@@ -99,7 +99,7 @@ function Labels(props) {
|
|
|
99
99
|
annotation = annotationRange;
|
|
100
100
|
}
|
|
101
101
|
const annotationLength =
|
|
102
|
-
|
|
102
|
+
getTextLengthWithCollapseSpace(
|
|
103
103
|
annotation.name ||
|
|
104
104
|
(annotation.restrictionEnzyme && annotation.restrictionEnzyme.name) ||
|
|
105
105
|
""
|
|
@@ -21,6 +21,7 @@ function SelectionLayer(props) {
|
|
|
21
21
|
regions,
|
|
22
22
|
leftMargin = 0,
|
|
23
23
|
isProtein,
|
|
24
|
+
showAminoAcidUnitAsCodon,
|
|
24
25
|
getGaps,
|
|
25
26
|
hideTitle: topLevelHideTitle,
|
|
26
27
|
customTitle: topLevelCustomTitle,
|
|
@@ -62,7 +63,8 @@ function SelectionLayer(props) {
|
|
|
62
63
|
selectionLayer,
|
|
63
64
|
customTitle: customTitle || topLevelCustomTitle,
|
|
64
65
|
sequenceLength,
|
|
65
|
-
isProtein
|
|
66
|
+
isProtein,
|
|
67
|
+
showAminoAcidUnitAsCodon
|
|
66
68
|
});
|
|
67
69
|
const onSelectionContextMenu = function (event) {
|
|
68
70
|
selectionLayerRightClicked &&
|
|
@@ -107,6 +109,7 @@ function SelectionLayer(props) {
|
|
|
107
109
|
key={key + "caret1"}
|
|
108
110
|
{...{
|
|
109
111
|
isProtein,
|
|
112
|
+
showAminoAcidUnitAsCodon,
|
|
110
113
|
leftMargin,
|
|
111
114
|
onClick: _onClick || preventDefaultStopPropagation,
|
|
112
115
|
onRightClick: onSelectionContextMenu,
|
|
@@ -130,6 +133,7 @@ function SelectionLayer(props) {
|
|
|
130
133
|
key={key + "caret2"}
|
|
131
134
|
{...{
|
|
132
135
|
isProtein,
|
|
136
|
+
showAminoAcidUnitAsCodon,
|
|
133
137
|
leftMargin,
|
|
134
138
|
onClick: _onClick || preventDefaultStopPropagation,
|
|
135
139
|
onRightClick: onSelectionContextMenu,
|
package/src/RowItem/Sequence.js
CHANGED
|
@@ -3,7 +3,27 @@ import { times, map } from "lodash-es";
|
|
|
3
3
|
import { view } from "@risingstack/react-easy-state";
|
|
4
4
|
import { getVisibleStartEnd } from "../utils/getVisibleStartEnd";
|
|
5
5
|
import { fudge2, realCharWidth } from "./constants";
|
|
6
|
-
import dnaToColor, {
|
|
6
|
+
import dnaToColor, {
|
|
7
|
+
getDnaColor,
|
|
8
|
+
getSerineThreonineColor,
|
|
9
|
+
getNegativeColor,
|
|
10
|
+
getPositiveColor,
|
|
11
|
+
getChargedColor,
|
|
12
|
+
getHydrophobicity,
|
|
13
|
+
getPolarColor,
|
|
14
|
+
getAliphaticColor,
|
|
15
|
+
getAromaticColor,
|
|
16
|
+
getColorScheme,
|
|
17
|
+
serineThreonineToColor,
|
|
18
|
+
hydrophobicityColor,
|
|
19
|
+
polarColors,
|
|
20
|
+
negativeColors,
|
|
21
|
+
positiveColors,
|
|
22
|
+
chargedColors,
|
|
23
|
+
aliphaticColors,
|
|
24
|
+
aromaticColors,
|
|
25
|
+
colorScheme
|
|
26
|
+
} from "../constants/dnaToColor";
|
|
7
27
|
import { hoveredAnnEasyStore } from "../helperComponents/withHover";
|
|
8
28
|
import { getOverlapsOfPotentiallyCircularRanges } from "@teselagen/range-utils";
|
|
9
29
|
import { partOverhangs } from "./partOverhangs";
|
|
@@ -13,6 +33,28 @@ import { isSafari } from "@teselagen/ui";
|
|
|
13
33
|
const getChunk = (sequence, chunkSize, chunkNumber) =>
|
|
14
34
|
sequence.slice(chunkSize * chunkNumber, chunkSize * (chunkNumber + 1));
|
|
15
35
|
|
|
36
|
+
function renderColoredLayer(props, fudge, width, toColor) {
|
|
37
|
+
return (
|
|
38
|
+
<svg
|
|
39
|
+
style={{
|
|
40
|
+
left: props.startOffset * props.charWidth,
|
|
41
|
+
height: props.height,
|
|
42
|
+
width,
|
|
43
|
+
position: "absolute"
|
|
44
|
+
}}
|
|
45
|
+
className="rowViewTextContainer"
|
|
46
|
+
height={Math.max(0, Number(props.height))}
|
|
47
|
+
>
|
|
48
|
+
<ColoredSequence
|
|
49
|
+
{...props}
|
|
50
|
+
fudge={fudge}
|
|
51
|
+
totalWidth={width}
|
|
52
|
+
toColor={toColor}
|
|
53
|
+
/>
|
|
54
|
+
</svg>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
16
58
|
class Sequence extends React.Component {
|
|
17
59
|
render() {
|
|
18
60
|
const {
|
|
@@ -29,6 +71,15 @@ class Sequence extends React.Component {
|
|
|
29
71
|
chunkSize = 100,
|
|
30
72
|
scrollData,
|
|
31
73
|
showDnaColors,
|
|
74
|
+
showSerineThreonine,
|
|
75
|
+
showHydrophobicity,
|
|
76
|
+
showPolar,
|
|
77
|
+
showNegative,
|
|
78
|
+
showPositive,
|
|
79
|
+
showCharged,
|
|
80
|
+
showAliphatic,
|
|
81
|
+
showAromatic,
|
|
82
|
+
showColorScheme,
|
|
32
83
|
fivePrimeThreePrimeHints,
|
|
33
84
|
alignmentData,
|
|
34
85
|
sequenceLength,
|
|
@@ -79,6 +130,18 @@ class Sequence extends React.Component {
|
|
|
79
130
|
}
|
|
80
131
|
});
|
|
81
132
|
|
|
133
|
+
const colorLayers = [
|
|
134
|
+
{ show: showSerineThreonine, toColor: "serineThreonine" },
|
|
135
|
+
{ show: showHydrophobicity, toColor: "hydrophobicity" },
|
|
136
|
+
{ show: showPolar, toColor: "polar" },
|
|
137
|
+
{ show: showNegative, toColor: "negative" },
|
|
138
|
+
{ show: showPositive, toColor: "positive" },
|
|
139
|
+
{ show: showCharged, toColor: "charged" },
|
|
140
|
+
{ show: showAliphatic, toColor: "aliphatic" },
|
|
141
|
+
{ show: showAromatic, toColor: "aromatic" },
|
|
142
|
+
{ show: showColorScheme, toColor: "colorScheme" }
|
|
143
|
+
];
|
|
144
|
+
|
|
82
145
|
const style = {
|
|
83
146
|
position: "relative",
|
|
84
147
|
height,
|
|
@@ -173,6 +236,10 @@ class Sequence extends React.Component {
|
|
|
173
236
|
5'
|
|
174
237
|
</div>
|
|
175
238
|
)}
|
|
239
|
+
{colorLayers.map(
|
|
240
|
+
({ show, toColor }) =>
|
|
241
|
+
show && renderColoredLayer(this.props, fudge, width, toColor)
|
|
242
|
+
)}
|
|
176
243
|
{!hideBps && (
|
|
177
244
|
<svg
|
|
178
245
|
style={{
|
|
@@ -189,7 +256,8 @@ class Sequence extends React.Component {
|
|
|
189
256
|
{...{
|
|
190
257
|
...this.props,
|
|
191
258
|
fudge,
|
|
192
|
-
totalWidth: width
|
|
259
|
+
totalWidth: width,
|
|
260
|
+
toColor: "dnaColor"
|
|
193
261
|
}}
|
|
194
262
|
/>
|
|
195
263
|
)}
|
|
@@ -226,20 +294,46 @@ class ColoredSequence extends React.Component {
|
|
|
226
294
|
alignmentData,
|
|
227
295
|
getGaps,
|
|
228
296
|
fudge,
|
|
229
|
-
totalWidth
|
|
297
|
+
totalWidth,
|
|
298
|
+
toColor
|
|
230
299
|
} = this.props;
|
|
231
300
|
if (alignmentData) {
|
|
232
301
|
sequence = sequence.replace(/^-+/g, "").replace(/-+$/g, "");
|
|
233
302
|
}
|
|
303
|
+
const colorMap = {
|
|
304
|
+
dnaColor: dnaToColor,
|
|
305
|
+
serineThreonine: serineThreonineToColor,
|
|
306
|
+
hydrophobicity: hydrophobicityColor,
|
|
307
|
+
polar: polarColors,
|
|
308
|
+
negative: negativeColors,
|
|
309
|
+
positive: positiveColors,
|
|
310
|
+
charged: chargedColors,
|
|
311
|
+
aliphatic: aliphaticColors,
|
|
312
|
+
aromatic: aromaticColors,
|
|
313
|
+
colorScheme: colorScheme
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const colorMethods = {
|
|
317
|
+
dnaColor: getDnaColor,
|
|
318
|
+
serineThreonine: getSerineThreonineColor,
|
|
319
|
+
hydrophobicity: getHydrophobicity,
|
|
320
|
+
polar: getPolarColor,
|
|
321
|
+
negative: getNegativeColor,
|
|
322
|
+
positive: getPositiveColor,
|
|
323
|
+
charged: getChargedColor,
|
|
324
|
+
aliphatic: getAliphaticColor,
|
|
325
|
+
aromatic: getAromaticColor,
|
|
326
|
+
colorScheme: getColorScheme
|
|
327
|
+
};
|
|
234
328
|
//we use big paths instead of many individual rects to improve performance
|
|
235
|
-
const colorPaths = Object.values(
|
|
329
|
+
const colorPaths = Object.values(colorMap[toColor]).reduce((acc, color) => {
|
|
236
330
|
acc[color] = "";
|
|
237
331
|
return acc;
|
|
238
332
|
}, {});
|
|
239
333
|
const gapsBefore = getGaps ? getGaps({ start: 0, end: 0 }).gapsBefore : 0;
|
|
240
334
|
sequence.split("").forEach((char, i) => {
|
|
241
335
|
const width = Number(charWidth);
|
|
242
|
-
const color =
|
|
336
|
+
const color = colorMethods[toColor](char, isReverse);
|
|
243
337
|
const x = (i + gapsBefore) * charWidth;
|
|
244
338
|
const y = 0;
|
|
245
339
|
colorPaths[color] =
|
|
@@ -26,6 +26,7 @@ class Translation extends React.Component {
|
|
|
26
26
|
annotationRange,
|
|
27
27
|
height,
|
|
28
28
|
showAminoAcidNumbers,
|
|
29
|
+
showAminoAcidUnitAsCodon,
|
|
29
30
|
charWidth,
|
|
30
31
|
aminoAcidNumbersHeight,
|
|
31
32
|
onClick,
|
|
@@ -164,10 +165,10 @@ class Translation extends React.Component {
|
|
|
164
165
|
aminoAcidSliver.aminoAcidIndex + 1
|
|
165
166
|
} -- Hydrophobicity: ${aminoAcid.hydrophobicity} -- Mass: ${
|
|
166
167
|
aminoAcid.mass
|
|
167
|
-
}\n
|
|
168
|
+
}\n
|
|
168
169
|
Part of ${annotation.translationType} Translation from BPs ${
|
|
169
170
|
annotation.start + 1
|
|
170
|
-
} to ${annotation.end + 1} (${aminoAcids.length / 3} AAs)`}
|
|
171
|
+
} to ${annotation.end + 1} (${aminoAcids.length / 3} ${showAminoAcidUnitAsCodon ? "codons" : "AAs"})`}
|
|
171
172
|
showAminoAcidNumbers={showAminoAcidNumbers}
|
|
172
173
|
aminoAcidIndex={aminoAcidSliver.aminoAcidIndex}
|
|
173
174
|
onDoubleClick={function (event) {
|
|
@@ -7,6 +7,7 @@ function getExtraInnerCompProps(
|
|
|
7
7
|
annotationRange,
|
|
8
8
|
{
|
|
9
9
|
showAminoAcidNumbers,
|
|
10
|
+
showAminoAcidUnitAsCodon,
|
|
10
11
|
getGaps,
|
|
11
12
|
isProtein,
|
|
12
13
|
colorType,
|
|
@@ -20,6 +21,7 @@ function getExtraInnerCompProps(
|
|
|
20
21
|
|
|
21
22
|
return {
|
|
22
23
|
showAminoAcidNumbers,
|
|
24
|
+
showAminoAcidUnitAsCodon,
|
|
23
25
|
getGaps,
|
|
24
26
|
height: anotationHeightNoSpace,
|
|
25
27
|
aminoAcidNumbersHeight,
|