@teselagen/ove 0.8.3 → 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 +4183 -2813
- package/index.es.js +2135 -765
- package/index.umd.js +3714 -2344
- package/ove.css +92 -6
- 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 +7 -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 +15 -5
- package/src/withEditorInteractions/index.js +3 -1
- package/utils/editorUtils.d.ts +2 -1
- package/utils/getAlignedAminoAcidSequenceProps.d.ts +49 -0
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
.alignmentViewTrackContainer {
|
|
12
12
|
position: relative;
|
|
13
13
|
}
|
|
14
|
+
.alignmentViewTrackContainer.isTrackSelected {
|
|
15
|
+
background-color: #c0e2f5;
|
|
16
|
+
}
|
|
17
|
+
.bp3-dark .alignmentViewTrackContainer.isTrackSelected {
|
|
18
|
+
background-color: #2277a9;
|
|
19
|
+
}
|
|
14
20
|
.alignmentViewTrackContainer,
|
|
15
21
|
.alignmentViewTrackContainer .veVectorInteractionWrapper,
|
|
16
22
|
.alignmentViewTrackContainer .veVectorInteractionWrapper > div {
|
|
@@ -91,6 +97,11 @@
|
|
|
91
97
|
.minimapLane.lane-hovered .miniBluePath {
|
|
92
98
|
fill: rgb(169, 169, 245) !important;
|
|
93
99
|
}
|
|
100
|
+
.minimapLane.isTrackSelected .miniBluePath {
|
|
101
|
+
fill: rgb(137, 168, 255) !important;
|
|
102
|
+
stroke: rgb(0, 0, 0);
|
|
103
|
+
stroke-width: 0.5px;
|
|
104
|
+
}
|
|
94
105
|
|
|
95
106
|
.veAlignmentName {
|
|
96
107
|
padding-top: 1px;
|
|
@@ -105,3 +116,19 @@
|
|
|
105
116
|
text-overflow: ellipsis;
|
|
106
117
|
white-space: nowrap;
|
|
107
118
|
}
|
|
119
|
+
|
|
120
|
+
.veLabileSites {
|
|
121
|
+
position: absolute;
|
|
122
|
+
top: 0;
|
|
123
|
+
height: 100%;
|
|
124
|
+
z-index: 10;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.veAlignmentViewLabileSiteLine {
|
|
128
|
+
position: absolute;
|
|
129
|
+
height: 100%;
|
|
130
|
+
width: 4px;
|
|
131
|
+
background: #1585c5;
|
|
132
|
+
opacity: 0.5;
|
|
133
|
+
top: 0;
|
|
134
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SVG BarPlot component
|
|
3
|
+
* @param {Object} props
|
|
4
|
+
* @param {number[]} props.data - Array of numbers to plot
|
|
5
|
+
* @param {number} [props.width=300]
|
|
6
|
+
* @param {number} [props.height=150]
|
|
7
|
+
* @param {string[]} [props.barColors]
|
|
8
|
+
*/
|
|
9
|
+
export function BarPlot({
|
|
10
|
+
data,
|
|
11
|
+
width = 300,
|
|
12
|
+
height = 30,
|
|
13
|
+
barColors,
|
|
14
|
+
className
|
|
15
|
+
}) {
|
|
16
|
+
if (!data || data.length === 0) return null;
|
|
17
|
+
const maxVal = Math.max(...data);
|
|
18
|
+
const barWidth = width / data.length;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<svg width={width} height={height} className={className}>
|
|
22
|
+
{data.map((val, i) => {
|
|
23
|
+
const barHeight = (val / maxVal) * (height - 2);
|
|
24
|
+
return (
|
|
25
|
+
<rect
|
|
26
|
+
data-tip={`${val?.toFixed(1)}%`}
|
|
27
|
+
key={i}
|
|
28
|
+
x={i * barWidth + 1}
|
|
29
|
+
y={height - barHeight}
|
|
30
|
+
width={barWidth - 2}
|
|
31
|
+
height={barHeight}
|
|
32
|
+
fill={barColors ? barColors[i % barColors.length] : "#3498db"}
|
|
33
|
+
rx={2}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
{/* 50% horizontal line */}
|
|
38
|
+
<line
|
|
39
|
+
x1={1}
|
|
40
|
+
y1={height - 0.5 * (height - 2)}
|
|
41
|
+
x2={width}
|
|
42
|
+
y2={height - 0.5 * (height - 2)}
|
|
43
|
+
stroke="white"
|
|
44
|
+
strokeDasharray="4,2"
|
|
45
|
+
strokeWidth={1}
|
|
46
|
+
/>
|
|
47
|
+
<line
|
|
48
|
+
x1={0}
|
|
49
|
+
y1={height}
|
|
50
|
+
x2={width}
|
|
51
|
+
y2={height}
|
|
52
|
+
stroke="#333"
|
|
53
|
+
strokeWidth={1}
|
|
54
|
+
/>
|
|
55
|
+
</svg>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AminoAcidCirclePlot({ data, width, className }) {
|
|
60
|
+
// width: total SVG width to fit all circles
|
|
61
|
+
const n = data.length;
|
|
62
|
+
if (n === 0) return null;
|
|
63
|
+
|
|
64
|
+
// Calculate spacing and radius so circles fit the width
|
|
65
|
+
const padding = 5; // space at each end
|
|
66
|
+
const availableWidth = width - 3 * padding;
|
|
67
|
+
const spacing = n > 1 ? availableWidth / (n - 1) : 0;
|
|
68
|
+
// Make radius as large as possible without overlap
|
|
69
|
+
const maxRadius = spacing / 2 - 2 > 0 ? spacing / 2 - 2 : 8;
|
|
70
|
+
const radius = Math.max(8, Math.min(maxRadius, 20));
|
|
71
|
+
const svgHeight = radius * 2 + 10;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<svg width={width} height={svgHeight} className={className}>
|
|
75
|
+
{/* Circles */}
|
|
76
|
+
{data.map((d, idx) => (
|
|
77
|
+
<g key={idx}>
|
|
78
|
+
<circle
|
|
79
|
+
data-tip={d.group}
|
|
80
|
+
cx={idx * spacing + radius}
|
|
81
|
+
cy={radius + 2}
|
|
82
|
+
r={radius}
|
|
83
|
+
fill={d.color}
|
|
84
|
+
stroke="#333"
|
|
85
|
+
strokeWidth={1}
|
|
86
|
+
/>
|
|
87
|
+
</g>
|
|
88
|
+
))}
|
|
89
|
+
</svg>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Stacked SVG BarPlot component for multiple properties per bar
|
|
95
|
+
* @param {Object} props
|
|
96
|
+
* @param {number[][]} props.data - Array of arrays, each inner array is the values for that position
|
|
97
|
+
* @param {number} [props.width=300]
|
|
98
|
+
* @param {number} [props.height=30]
|
|
99
|
+
* @param {string[]} [props.barColors]
|
|
100
|
+
*/
|
|
101
|
+
export function StackedBarPlot({ data, width = 300, height = 30, barColors }) {
|
|
102
|
+
if (!data || data.length === 0) return null;
|
|
103
|
+
|
|
104
|
+
const _data = data.map(vals => Object.values(vals));
|
|
105
|
+
const legends = data.map(vals => Object.keys(vals));
|
|
106
|
+
|
|
107
|
+
// Find the max sum for stacking
|
|
108
|
+
const maxVal = Math.max(
|
|
109
|
+
..._data.map(vals => vals.reduce((a, b) => a + b, 0))
|
|
110
|
+
);
|
|
111
|
+
const barWidth = width / _data.length;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<svg width={width} height={height}>
|
|
115
|
+
{_data.map((vals, i) => {
|
|
116
|
+
let yOffset = height;
|
|
117
|
+
return vals.map((val, j) => {
|
|
118
|
+
const barHeight = (val / maxVal) * (height - 2);
|
|
119
|
+
yOffset -= barHeight;
|
|
120
|
+
return (
|
|
121
|
+
<rect
|
|
122
|
+
data-tip={legends[i][j]}
|
|
123
|
+
key={j}
|
|
124
|
+
x={i * barWidth + 1}
|
|
125
|
+
y={yOffset}
|
|
126
|
+
width={barWidth - 2}
|
|
127
|
+
height={barHeight}
|
|
128
|
+
fill={barColors ? barColors[j % barColors.length] : colorMap[j]}
|
|
129
|
+
rx={2}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
})}
|
|
134
|
+
<line
|
|
135
|
+
x1={0}
|
|
136
|
+
y1={height}
|
|
137
|
+
x2={width}
|
|
138
|
+
y2={height}
|
|
139
|
+
stroke="#333"
|
|
140
|
+
strokeWidth={1}
|
|
141
|
+
/>
|
|
142
|
+
</svg>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const colorMap = [
|
|
147
|
+
"#1f77b4", // blue
|
|
148
|
+
"#ff7f0e", // orange
|
|
149
|
+
"#2ca02c", // green
|
|
150
|
+
"#d62728", // red
|
|
151
|
+
"#9467bd", // purple
|
|
152
|
+
"#8c564b", // brown
|
|
153
|
+
"#e377c2", // pink
|
|
154
|
+
"#7f7f7f", // gray
|
|
155
|
+
"#bcbd22" // olive
|
|
156
|
+
];
|
|
@@ -15,7 +15,8 @@ function Caret({
|
|
|
15
15
|
innerRadius,
|
|
16
16
|
outerRadius,
|
|
17
17
|
isProtein,
|
|
18
|
-
selectionMessage
|
|
18
|
+
selectionMessage,
|
|
19
|
+
showAminoAcidUnitAsCodon
|
|
19
20
|
}) {
|
|
20
21
|
const { startAngle, endAngle } = getRangeAngles(
|
|
21
22
|
{ start: caretPosition, end: caretPosition },
|
|
@@ -37,7 +38,12 @@ function Caret({
|
|
|
37
38
|
>
|
|
38
39
|
<title>
|
|
39
40
|
{selectionMessage ||
|
|
40
|
-
getSelectionMessage({
|
|
41
|
+
getSelectionMessage({
|
|
42
|
+
caretPosition,
|
|
43
|
+
isProtein,
|
|
44
|
+
sequenceLength,
|
|
45
|
+
showAminoAcidUnitAsCodon
|
|
46
|
+
})}
|
|
41
47
|
</title>
|
|
42
48
|
<line
|
|
43
49
|
strokeWidth="1.5px"
|
|
@@ -21,7 +21,8 @@ function SelectionLayer({
|
|
|
21
21
|
onRightClicked,
|
|
22
22
|
onClick,
|
|
23
23
|
index,
|
|
24
|
-
isProtein
|
|
24
|
+
isProtein,
|
|
25
|
+
showAminoAcidUnitAsCodon
|
|
25
26
|
}) {
|
|
26
27
|
const {
|
|
27
28
|
color,
|
|
@@ -47,7 +48,8 @@ function SelectionLayer({
|
|
|
47
48
|
const selectionMessage = getSelectionMessage({
|
|
48
49
|
sequenceLength,
|
|
49
50
|
selectionLayer,
|
|
50
|
-
isProtein
|
|
51
|
+
isProtein,
|
|
52
|
+
showAminoAcidUnitAsCodon
|
|
51
53
|
});
|
|
52
54
|
// let section2 = sector({
|
|
53
55
|
// center: [0, 0], //the center is always 0,0 for our annotations :) we rotate later!
|
|
@@ -168,6 +168,7 @@ export function CircularView(props) {
|
|
|
168
168
|
readOnly,
|
|
169
169
|
hideName = false,
|
|
170
170
|
editorName,
|
|
171
|
+
showAminoAcidUnitAsCodon,
|
|
171
172
|
smartCircViewLabelRender,
|
|
172
173
|
showCicularViewInternalLabels,
|
|
173
174
|
withRotateCircularView: _withRotateCircularView,
|
|
@@ -600,6 +601,7 @@ export function CircularView(props) {
|
|
|
600
601
|
key={"veCircularViewSelectionLayer" + index}
|
|
601
602
|
{...{
|
|
602
603
|
index,
|
|
604
|
+
showAminoAcidUnitAsCodon,
|
|
603
605
|
isDraggable: true,
|
|
604
606
|
isProtein,
|
|
605
607
|
selectionLayer,
|
|
@@ -637,6 +639,7 @@ export function CircularView(props) {
|
|
|
637
639
|
key="veCircularViewCaret"
|
|
638
640
|
{...{
|
|
639
641
|
caretPosition,
|
|
642
|
+
showAminoAcidUnitAsCodon,
|
|
640
643
|
sequenceLength,
|
|
641
644
|
isProtein,
|
|
642
645
|
innerRadius,
|
|
@@ -651,8 +654,9 @@ export function CircularView(props) {
|
|
|
651
654
|
if (radius < 150) radius = 150;
|
|
652
655
|
const widthToUse = Math.max(Number(width) || 300);
|
|
653
656
|
const heightToUse = Math.max(Number(height) || 300);
|
|
657
|
+
const proteinUnits = showAminoAcidUnitAsCodon ? "codons" : "AAs";
|
|
654
658
|
const bpTitle = isProtein
|
|
655
|
-
? `${Math.floor(sequenceLength / 3)}
|
|
659
|
+
? `${Math.floor(sequenceLength / 3)} ${proteinUnits}`
|
|
656
660
|
: `${sequenceLength} bps`;
|
|
657
661
|
const nameEl = (
|
|
658
662
|
<div
|
package/src/Editor/darkmode.css
CHANGED
|
@@ -65,6 +65,13 @@
|
|
|
65
65
|
fill: #f5f8fa !important;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
.bp3-dark .rowViewTextContainer *::selection {
|
|
69
|
+
fill: #f5f8fa !important;
|
|
70
|
+
}
|
|
71
|
+
.bp3-dark .veRowViewAxis *::selection {
|
|
72
|
+
fill: #f5f8fa !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
/* .bp3-dark .veRowViewAxis text {
|
|
69
76
|
stroke: #f5f8fa !important;
|
|
70
77
|
} */
|
package/src/Editor/index.js
CHANGED
|
@@ -361,6 +361,7 @@ export class Editor extends React.Component {
|
|
|
361
361
|
hoveredId,
|
|
362
362
|
isFullscreen,
|
|
363
363
|
maxInsertSize,
|
|
364
|
+
showAminoAcidUnitAsCodon,
|
|
364
365
|
maxAnnotationsToDisplay,
|
|
365
366
|
minHeight = 400,
|
|
366
367
|
onlyShowLabelsThatDoNotFit = true,
|
|
@@ -594,6 +595,7 @@ export class Editor extends React.Component {
|
|
|
594
595
|
{...panelPropsToSpread}
|
|
595
596
|
editorName={editorName}
|
|
596
597
|
maxInsertSize={maxInsertSize}
|
|
598
|
+
showAminoAcidUnitAsCodon={showAminoAcidUnitAsCodon}
|
|
597
599
|
isProtein={sequenceData.isProtein}
|
|
598
600
|
onlyShowLabelsThatDoNotFit={onlyShowLabelsThatDoNotFit}
|
|
599
601
|
tabHeight={tabHeight}
|
|
@@ -961,6 +963,7 @@ export class Editor extends React.Component {
|
|
|
961
963
|
)
|
|
962
964
|
}
|
|
963
965
|
editorName={editorName}
|
|
966
|
+
showAminoAcidUnitAsCodon={showAminoAcidUnitAsCodon}
|
|
964
967
|
{...StatusBarProps}
|
|
965
968
|
/>
|
|
966
969
|
)}
|
package/src/FindBar/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
export function SequenceName({
|
|
3
|
+
export function SequenceName({
|
|
4
|
+
sequenceName,
|
|
5
|
+
sequenceLength,
|
|
6
|
+
isProtein,
|
|
7
|
+
showAminoAcidUnitAsCodon
|
|
8
|
+
}) {
|
|
9
|
+
const proteinUnits = showAminoAcidUnitAsCodon ? "codons" : "AAs";
|
|
4
10
|
return (
|
|
5
11
|
<div key="sequenceNameText" className="sequenceNameText">
|
|
6
12
|
<span>{sequenceName} </span>
|
|
7
13
|
<br />
|
|
8
14
|
<span>
|
|
9
15
|
{isProtein
|
|
10
|
-
? `${Math.floor(sequenceLength / 3)}
|
|
16
|
+
? `${Math.floor(sequenceLength / 3)} ${proteinUnits}`
|
|
11
17
|
: `${sequenceLength} bps`}
|
|
12
18
|
</span>
|
|
13
19
|
</div>
|
package/src/LinearView/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
editorDragStopped
|
|
29
29
|
} from "../withEditorInteractions/clickAndDragUtils";
|
|
30
30
|
import { store } from "@risingstack/react-easy-state";
|
|
31
|
+
import { BarPlot, AminoAcidCirclePlot } from "../BarPlot";
|
|
31
32
|
|
|
32
33
|
const defaultMarginWidth = 10;
|
|
33
34
|
|
|
@@ -173,6 +174,7 @@ class _LinearView extends React.Component {
|
|
|
173
174
|
annotationVisibilityOverrides,
|
|
174
175
|
isProtein,
|
|
175
176
|
noWarnings,
|
|
177
|
+
showAminoAcidUnitAsCodon,
|
|
176
178
|
...rest
|
|
177
179
|
} = this.props;
|
|
178
180
|
|
|
@@ -291,6 +293,7 @@ class _LinearView extends React.Component {
|
|
|
291
293
|
<SequenceName
|
|
292
294
|
{...{
|
|
293
295
|
isProtein,
|
|
296
|
+
showAminoAcidUnitAsCodon,
|
|
294
297
|
sequenceName,
|
|
295
298
|
sequenceLength: sequenceData.sequence
|
|
296
299
|
? sequenceData.sequence.length
|
|
@@ -302,6 +305,24 @@ class _LinearView extends React.Component {
|
|
|
302
305
|
<VeTopRightContainer>{this.paredDownMessages}</VeTopRightContainer>
|
|
303
306
|
)}
|
|
304
307
|
<PinchHelperToUse {...(linearZoomEnabled && pinchHandler)}>
|
|
308
|
+
{sequenceData.isProtein && sequenceData.name === "Consensus" && (
|
|
309
|
+
<>
|
|
310
|
+
{annotationVisibilityOverrides.conservation && (
|
|
311
|
+
<BarPlot
|
|
312
|
+
className="ve-linear-view-conservation-plot"
|
|
313
|
+
data={sequenceData?.aminoAcidProperties?.frequencies}
|
|
314
|
+
width={width}
|
|
315
|
+
/>
|
|
316
|
+
)}
|
|
317
|
+
{annotationVisibilityOverrides.properties && (
|
|
318
|
+
<AminoAcidCirclePlot
|
|
319
|
+
className="ve-linear-view-property-analysis-plot"
|
|
320
|
+
data={sequenceData?.aminoAcidProperties?.propertyAnalysis}
|
|
321
|
+
width={width}
|
|
322
|
+
/>
|
|
323
|
+
)}
|
|
324
|
+
</>
|
|
325
|
+
)}
|
|
305
326
|
<RowItem
|
|
306
327
|
{...{
|
|
307
328
|
...rest,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates detailed amino acid frequency, including counts and percentages for
|
|
3
|
+
* all 20 standard amino acids.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} sequence The protein sequence.
|
|
6
|
+
* @returns {{
|
|
7
|
+
* totalCount: number,
|
|
8
|
+
* frequencies: Object<string, {count: number, percentage: number}>,
|
|
9
|
+
* nonStandard: Object<string, number>
|
|
10
|
+
* }} A comprehensive analysis object.
|
|
11
|
+
*/
|
|
12
|
+
export function calculateAminoAcidFrequency(sequence, isProtein) {
|
|
13
|
+
// 1. Validate input
|
|
14
|
+
if (!sequence || typeof sequence !== "string") {
|
|
15
|
+
console.warn("Invalid or empty amino acid sequence provided.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const standards = isProtein
|
|
19
|
+
? "ACDEFGHIKLMNPQRSTVWY-".split("")
|
|
20
|
+
: "ATCG-".split("");
|
|
21
|
+
const frequencies = {};
|
|
22
|
+
standards.forEach(a => {
|
|
23
|
+
frequencies[a] = { count: 0, percentage: 0 };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const nonStandard = {}; // For gaps '-', 'X', 'B', 'Z', etc.
|
|
27
|
+
let totalCount = 0;
|
|
28
|
+
|
|
29
|
+
// 2. Iterate and count
|
|
30
|
+
for (const char of sequence.toUpperCase()) {
|
|
31
|
+
if (frequencies[char]) {
|
|
32
|
+
frequencies[char].count++;
|
|
33
|
+
totalCount++;
|
|
34
|
+
} else {
|
|
35
|
+
// It's a non-standard character (like a gap or 'X')
|
|
36
|
+
nonStandard[char] = (nonStandard[char] || 0) + 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Calculate percentages
|
|
41
|
+
if (totalCount > 0) {
|
|
42
|
+
for (const a of standards) {
|
|
43
|
+
frequencies[a].percentage = (frequencies[a].count / totalCount) * 100;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
totalStandards: totalCount, // Total count of elements
|
|
49
|
+
totalLength: sequence.length, // Total length including non-standard
|
|
50
|
+
frequencies,
|
|
51
|
+
nonStandard
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const aminoAcidShortNames = {
|
|
56
|
+
A: "Ala",
|
|
57
|
+
C: "Cys",
|
|
58
|
+
D: "Asp",
|
|
59
|
+
E: "Glu",
|
|
60
|
+
F: "Phe",
|
|
61
|
+
G: "Gly",
|
|
62
|
+
H: "His",
|
|
63
|
+
I: "Ile",
|
|
64
|
+
K: "Lys",
|
|
65
|
+
L: "Leu",
|
|
66
|
+
M: "Met",
|
|
67
|
+
N: "Asn",
|
|
68
|
+
P: "Pro",
|
|
69
|
+
Q: "Gln",
|
|
70
|
+
R: "Arg",
|
|
71
|
+
S: "Ser",
|
|
72
|
+
T: "Thr",
|
|
73
|
+
V: "Val",
|
|
74
|
+
W: "Trp",
|
|
75
|
+
Y: "Tyr",
|
|
76
|
+
"-": "Gaps"
|
|
77
|
+
};
|