@datagrok/sequence-translator 1.0.13 → 1.0.14
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/dist/package-test.js +70496 -1334
- package/dist/package.js +69777 -4379
- package/package.json +5 -3
- package/scripts/build-monomer-lib.py +140 -0
- package/setup-unlink-clean.cmd +14 -0
- package/setup.cmd +14 -11
- package/setup.sh +37 -0
- package/src/autostart/constants.ts +12 -0
- package/src/autostart/registration.ts +18 -4
- package/src/axolabs/constants.ts +10 -10
- package/src/axolabs/define-pattern.ts +13 -12
- package/src/axolabs/draw-svg.ts +140 -201
- package/src/axolabs/helpers.ts +94 -0
- package/src/main/main-view.ts +40 -20
- package/src/package.ts +20 -2
- package/src/structures-works/const.ts +18 -0
- package/src/structures-works/converters.ts +3 -3
- package/src/structures-works/from-monomers.ts +187 -31
- package/src/structures-works/map.ts +6 -7
- package/src/structures-works/mol-transformations.ts +119 -567
- package/src/structures-works/save-sense-antisense.ts +6 -3
- package/src/structures-works/sequence-codes-tools.ts +8 -10
- package/{test-SequenceTranslator-62cc009524f3-4a9916b0.html → test-SequenceTranslator-e8c06047b7e7-eb4db608.html} +6 -6
package/src/axolabs/draw-svg.ts
CHANGED
|
@@ -1,253 +1,192 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
import {NUCLEOTIDES} from '../structures-works/map';
|
|
2
|
+
import {isOverhang, svg, textWidth, countOverhangsOnTheRightEdge, baseColor, textInsideCircle,
|
|
3
|
+
fontColorVisibleOnBackground, isOneDigitNumber} from './helpers';
|
|
4
|
+
|
|
5
|
+
const BASE_RADIUS = 15;
|
|
6
|
+
const BASE_DIAMETER = 2 * BASE_RADIUS;
|
|
7
|
+
const shiftToAlignTwoDigitNumberNearCircle = -10;
|
|
8
|
+
const shiftToAlignOneDigitNumberNearCircle = -5;
|
|
9
|
+
const LEGEND_RADIUS = 6;
|
|
10
|
+
const PS_LINKAGE_RADIUS = 5;
|
|
11
|
+
const BASE_FONT_SIZE = 17;
|
|
12
|
+
const LEGEND_FONT_SIZE = 14;
|
|
13
|
+
const PS_LINKAGE_COLOR = 'red';
|
|
14
|
+
const FONT_COLOR = 'var(--grey-6)';
|
|
15
|
+
const TITLE_FONT_COLOR = 'black';
|
|
16
|
+
const MODIFICATIONS_COLOR = 'red';
|
|
17
|
+
const SS_LEFT_TEXT = 'SS: 5\'';
|
|
18
|
+
const AS_LEFT_TEXT = 'AS: 3\'';
|
|
19
|
+
const SS_RIGHT_TEXT = '3\'';
|
|
20
|
+
const AS_RIGHT_TEXT = '5\'';
|
|
21
|
+
|
|
22
|
+
const WIDTH_OF_LEFT_TEXT = Math.max(
|
|
23
|
+
textWidth(SS_LEFT_TEXT, BASE_FONT_SIZE),
|
|
24
|
+
textWidth(AS_LEFT_TEXT, BASE_FONT_SIZE),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const WIDTH_OF_RIGHT_TEXT = Math.max(
|
|
28
|
+
textWidth(SS_RIGHT_TEXT, BASE_FONT_SIZE),
|
|
29
|
+
textWidth(AS_RIGHT_TEXT, BASE_FONT_SIZE),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const X = {
|
|
33
|
+
TITLE: BASE_RADIUS, // Math.round(width / 4),
|
|
34
|
+
LEFT_TEXTS: 0,
|
|
35
|
+
};
|
|
36
|
+
const X_OF_LEFT_MODIFICATIONS = X.LEFT_TEXTS + WIDTH_OF_LEFT_TEXT - 5;
|
|
37
|
+
|
|
38
|
+
const Y = {
|
|
39
|
+
TITLE: BASE_RADIUS,
|
|
40
|
+
SS_INDICES: 2 * BASE_RADIUS,
|
|
41
|
+
SS_CIRCLES: 3.5 * BASE_RADIUS,
|
|
42
|
+
SS_TEXTS: 4 * BASE_RADIUS,
|
|
43
|
+
AS_CIRCLES: 6.5 * BASE_RADIUS,
|
|
44
|
+
AS_TEXTS: 7 * BASE_RADIUS,
|
|
45
|
+
AS_INDICES: 8.5 * BASE_RADIUS,
|
|
46
|
+
comment: (asExists: boolean) => (asExists) ? 11 * BASE_RADIUS : 8.5 * BASE_RADIUS,
|
|
47
|
+
circlesInLegends: (asExists: boolean) => (asExists) ? 9.5 * BASE_RADIUS : 6 * BASE_RADIUS,
|
|
48
|
+
textLegend: (asExists: boolean) => (asExists) ? 10 * BASE_RADIUS - 3 : Y.AS_CIRCLES - 3,
|
|
49
|
+
svgHeight: (asExists: boolean) => (asExists) ? 11 * BASE_RADIUS : 9 * BASE_RADIUS,
|
|
50
|
+
};
|
|
50
51
|
|
|
51
52
|
export function drawAxolabsPattern(
|
|
52
53
|
patternName: string, asExists: boolean, ssBases: string[],
|
|
53
54
|
asBases: string[], ssPtoStatuses: boolean[], asPtoStatuses: boolean[],
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
enumerateModifications: string[]
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
ss3Modification: string, ss5Modification: string,
|
|
56
|
+
as3Modification: string, as5Modification: string, comment: string,
|
|
57
|
+
enumerateModifications: string[],
|
|
58
|
+
): Element {
|
|
59
|
+
function equidistantXForLegend(index: number): number {
|
|
60
|
+
return Math.round((index + startFrom) * width / (uniqueBases.length + startFrom) + LEGEND_RADIUS);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
function
|
|
63
|
+
function xOfBaseCircles(index: number, rightOverhangs: number): number {
|
|
62
64
|
return widthOfRightModification +
|
|
63
|
-
(resultingNumberOfNucleotidesInStrands - index + rightOverhangs + 1) *
|
|
65
|
+
(resultingNumberOfNucleotidesInStrands - index + rightOverhangs + 1) * BASE_DIAMETER;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
function
|
|
67
|
-
return (nucleotideIndex
|
|
68
|
-
|
|
68
|
+
function shiftToAlignNumberNearCircle(bases: string[], generalIndex: number, nucleotideIndex: number): number {
|
|
69
|
+
return (isOneDigitNumber(nucleotideIndex) || NUCLEOTIDES.includes(bases[generalIndex])) ?
|
|
70
|
+
shiftToAlignOneDigitNumberNearCircle : shiftToAlignTwoDigitNumberNearCircle;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
const svg = {
|
|
72
|
-
xmlns: 'http://www.w3.org/2000/svg',
|
|
73
|
-
render: function(width: number, height: number) {
|
|
74
|
-
const e = document.createElementNS(this.xmlns, 'svg');
|
|
75
|
-
e.setAttribute('id', 'mySvg');
|
|
76
|
-
e.setAttribute('width', String(width));
|
|
77
|
-
e.setAttribute('height', String(height));
|
|
78
|
-
return e;
|
|
79
|
-
},
|
|
80
|
-
circle: function(x: number, y: number, radius: number, color: string) {
|
|
81
|
-
const e = document.createElementNS(this.xmlns, 'circle');
|
|
82
|
-
e.setAttribute('cx', String(x));
|
|
83
|
-
e.setAttribute('cy', String(y));
|
|
84
|
-
e.setAttribute('r', String(radius));
|
|
85
|
-
e.setAttribute('fill', color);
|
|
86
|
-
return e;
|
|
87
|
-
},
|
|
88
|
-
text: function(text: string, x: number, y: number, fontSize: number, color: string) {
|
|
89
|
-
const e = document.createElementNS(this.xmlns, 'text');
|
|
90
|
-
e.setAttribute('x', String(x));
|
|
91
|
-
e.setAttribute('y', String(y));
|
|
92
|
-
e.setAttribute('font-size', String(fontSize));
|
|
93
|
-
e.setAttribute('font-weight', 'normal');
|
|
94
|
-
e.setAttribute('font-family', 'Arial');
|
|
95
|
-
e.setAttribute('fill', color);
|
|
96
|
-
e.innerHTML = text;
|
|
97
|
-
return e;
|
|
98
|
-
},
|
|
99
|
-
star: function(x: number, y: number, fill: string) {
|
|
100
|
-
const e = document.createElementNS(this.xmlns, 'polygon');
|
|
101
|
-
e.setAttribute('points', getPointsToDrawStar(x, y));
|
|
102
|
-
e.setAttribute('fill', fill);
|
|
103
|
-
return e;
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
|
|
107
73
|
ssBases = ssBases.reverse();
|
|
108
74
|
ssPtoStatuses = ssPtoStatuses.reverse();
|
|
109
75
|
|
|
110
|
-
const baseRadius = 15;
|
|
111
|
-
const shiftToAlignTwoDigitNumberInsideCircle = -10;
|
|
112
|
-
const shiftToAlignOneDigitNumberInsideCircle = -5;
|
|
113
|
-
const legendRadius = 6;
|
|
114
|
-
const psLinkageRadius = 5;
|
|
115
|
-
const baseFontSize = 17;
|
|
116
|
-
const legendFontSize = 14;
|
|
117
|
-
const psLinkageColor = 'red';
|
|
118
|
-
const fontColor = 'var(--grey-6)';
|
|
119
|
-
const titleFontColor = 'black';
|
|
120
|
-
const modificationsColor = 'red';
|
|
121
|
-
const ssLeftText = 'SS: 5\'';
|
|
122
|
-
const asLeftText = 'AS: 3\'';
|
|
123
|
-
const ssRightText = '3\'';
|
|
124
|
-
const asRightText = '5\'';
|
|
125
|
-
|
|
126
76
|
const ssRightOverhangs = countOverhangsOnTheRightEdge(ssBases);
|
|
127
77
|
const asRightOverhangs = countOverhangsOnTheRightEdge(asBases);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
78
|
+
|
|
79
|
+
const resultingNumberOfNucleotidesInStrands = Math.max(
|
|
80
|
+
ssBases.length - ssRightOverhangs,
|
|
81
|
+
asBases.length - asRightOverhangs,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const widthOfRightOverhangs = Math.max(ssRightOverhangs, asRightOverhangs);
|
|
85
|
+
const widthOfBases = BASE_DIAMETER * (resultingNumberOfNucleotidesInStrands + widthOfRightOverhangs);
|
|
86
|
+
|
|
87
|
+
const widthOfLeftModification = Math.max(
|
|
88
|
+
textWidth(ss3Modification, BASE_FONT_SIZE),
|
|
89
|
+
textWidth(as5Modification, BASE_FONT_SIZE),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const widthOfRightModification = Math.max(
|
|
93
|
+
textWidth(ss5Modification, BASE_FONT_SIZE),
|
|
94
|
+
textWidth(as3Modification, BASE_FONT_SIZE),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const uniqueBases = asExists ?
|
|
98
|
+
[...new Set(ssBases.concat(asBases))] :
|
|
99
|
+
[...new Set(ssBases)];
|
|
100
|
+
|
|
101
|
+
const isPtoExist = asExists ?
|
|
102
|
+
ssPtoStatuses.concat(asPtoStatuses).includes(true) :
|
|
103
|
+
ssPtoStatuses.includes(true);
|
|
104
|
+
|
|
148
105
|
const startFrom = isPtoExist ? 1 : 0;
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const xOfRightTexts =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const yOfSsTexts = 4 * baseRadius;
|
|
160
|
-
const yOfAsTexts = 7 * baseRadius;
|
|
161
|
-
const yOfComment = asExists ? 11 * baseRadius : 8.5 * baseRadius;
|
|
162
|
-
const yOfSsCircles = 3.5 * baseRadius;
|
|
163
|
-
const yOfAsCircles = 6.5 * baseRadius;
|
|
164
|
-
const yOfCirclesInLegends = asExists ? 9.5 * baseRadius : 6 * baseRadius;
|
|
165
|
-
const yOfTextLegend = asExists ? 10 * baseRadius - 3 : yOfAsCircles - 3;
|
|
166
|
-
|
|
167
|
-
const image = svg.render(width, height);
|
|
106
|
+
|
|
107
|
+
const xOfSsRightModifications = ssRightOverhangs * BASE_DIAMETER + xOfBaseCircles(-0.5, 0);
|
|
108
|
+
const xOfAsRightModifications = asRightOverhangs * BASE_DIAMETER + xOfBaseCircles(-0.5, 0);
|
|
109
|
+
|
|
110
|
+
const xOfRightTexts = Math.max(xOfSsRightModifications, xOfAsRightModifications) + widthOfLeftModification +
|
|
111
|
+
BASE_DIAMETER * widthOfRightOverhangs;
|
|
112
|
+
|
|
113
|
+
const width = WIDTH_OF_LEFT_TEXT + widthOfLeftModification + widthOfBases + widthOfRightModification +
|
|
114
|
+
WIDTH_OF_RIGHT_TEXT + BASE_DIAMETER;
|
|
115
|
+
const image = svg.render(width, Y.svgHeight(asExists));
|
|
168
116
|
|
|
169
117
|
image.append(
|
|
170
|
-
svg.text(
|
|
171
|
-
asExists ? svg.text(
|
|
172
|
-
svg.text(
|
|
173
|
-
asExists ? svg.text(
|
|
174
|
-
svg.text(
|
|
175
|
-
asExists ? svg.text(
|
|
176
|
-
svg.text(
|
|
177
|
-
asExists ? svg.text(
|
|
178
|
-
svg.text(comment,
|
|
179
|
-
isPtoExist ? svg.star(
|
|
180
|
-
isPtoExist ? svg.text('ps linkage', 2 *
|
|
118
|
+
svg.text(SS_LEFT_TEXT, X.LEFT_TEXTS, Y.SS_TEXTS, BASE_FONT_SIZE, FONT_COLOR),
|
|
119
|
+
asExists ? svg.text(AS_LEFT_TEXT, X.LEFT_TEXTS, Y.AS_TEXTS, BASE_FONT_SIZE, FONT_COLOR) : '',
|
|
120
|
+
svg.text(SS_RIGHT_TEXT, xOfRightTexts, Y.SS_TEXTS, BASE_FONT_SIZE, FONT_COLOR),
|
|
121
|
+
asExists ? svg.text(AS_RIGHT_TEXT, xOfRightTexts, Y.AS_TEXTS, BASE_FONT_SIZE, FONT_COLOR) : '',
|
|
122
|
+
svg.text(ss5Modification, X_OF_LEFT_MODIFICATIONS, Y.SS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR),
|
|
123
|
+
asExists ? svg.text(as3Modification, X_OF_LEFT_MODIFICATIONS, Y.AS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR) : '',
|
|
124
|
+
svg.text(ss3Modification, xOfSsRightModifications, Y.SS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR),
|
|
125
|
+
asExists ? svg.text(as5Modification, xOfAsRightModifications, Y.AS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR) : '',
|
|
126
|
+
svg.text(comment, X.LEFT_TEXTS, Y.comment(asExists), LEGEND_FONT_SIZE, FONT_COLOR),
|
|
127
|
+
isPtoExist ? svg.star(BASE_RADIUS, Y.circlesInLegends(asExists), PS_LINKAGE_COLOR) : '',
|
|
128
|
+
isPtoExist ? svg.text('ps linkage', 2 * BASE_RADIUS - 8, Y.textLegend(asExists), LEGEND_FONT_SIZE, FONT_COLOR) : '',
|
|
181
129
|
);
|
|
182
130
|
|
|
183
|
-
|
|
184
|
-
for (let i = 0; i < ssBases.length; i++) {
|
|
185
|
-
if (ssBases[i].slice(-3) != '(o)')
|
|
186
|
-
numberOfSsNucleotides++;
|
|
187
|
-
}
|
|
131
|
+
const numberOfSsNucleotides = ssBases.filter((value) => !isOverhang(value)).length;
|
|
188
132
|
let nucleotideCounter = numberOfSsNucleotides;
|
|
189
133
|
for (let i = ssBases.length - 1; i > -1; i--) {
|
|
190
|
-
const xOfNumbers =
|
|
191
|
-
|
|
192
|
-
if (ssBases[i]
|
|
134
|
+
const xOfNumbers = xOfBaseCircles(i, ssRightOverhangs) +
|
|
135
|
+
shiftToAlignNumberNearCircle(ssBases, ssBases.length - i, numberOfSsNucleotides - nucleotideCounter);
|
|
136
|
+
if (!isOverhang(ssBases[i]))
|
|
193
137
|
nucleotideCounter--;
|
|
194
|
-
const n = (ssBases[i]
|
|
138
|
+
const n = (!isOverhang(ssBases[i]) && enumerateModifications.includes(ssBases[i])) ?
|
|
195
139
|
String(numberOfSsNucleotides - nucleotideCounter) : '';
|
|
196
140
|
image.append(
|
|
197
|
-
svg.text(n, xOfNumbers,
|
|
198
|
-
svg.circle(
|
|
199
|
-
svg.text(
|
|
200
|
-
|
|
141
|
+
svg.text(n, xOfNumbers, Y.SS_INDICES, LEGEND_FONT_SIZE, FONT_COLOR),
|
|
142
|
+
svg.circle(xOfBaseCircles(i, ssRightOverhangs), Y.SS_CIRCLES, BASE_RADIUS, baseColor(ssBases[i])),
|
|
143
|
+
svg.text(textInsideCircle(ssBases, i), xOfNumbers, Y.SS_TEXTS, BASE_FONT_SIZE,
|
|
144
|
+
fontColorVisibleOnBackground(ssBases[i])),
|
|
201
145
|
ssPtoStatuses[i] ?
|
|
202
|
-
svg.star(
|
|
146
|
+
svg.star(xOfBaseCircles(i, ssRightOverhangs) + BASE_RADIUS, Y.SS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) :
|
|
203
147
|
'',
|
|
204
148
|
);
|
|
205
149
|
}
|
|
206
150
|
image.append(
|
|
207
151
|
ssPtoStatuses[ssBases.length] ?
|
|
208
|
-
svg.star(
|
|
209
|
-
|
|
152
|
+
svg.star(xOfBaseCircles(ssBases.length, ssRightOverhangs) +
|
|
153
|
+
BASE_RADIUS, Y.SS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) : '',
|
|
210
154
|
);
|
|
211
155
|
|
|
212
|
-
|
|
213
|
-
for (let i = 0; i < asBases.length; i++) {
|
|
214
|
-
if (asBases[i].slice(-3) != '(o)')
|
|
215
|
-
numberOfAsNucleotides++;
|
|
216
|
-
}
|
|
156
|
+
const numberOfAsNucleotides = asBases.filter((value) => !isOverhang(value)).length;
|
|
217
157
|
if (asExists) {
|
|
218
158
|
let nucleotideCounter = numberOfAsNucleotides;
|
|
219
159
|
for (let i = asBases.length - 1; i > -1; i--) {
|
|
220
|
-
if (asBases[i]
|
|
160
|
+
if (!isOverhang(asBases[i]))
|
|
221
161
|
nucleotideCounter--;
|
|
222
|
-
const xOfNumbers =
|
|
223
|
-
|
|
224
|
-
const n = (asBases[i]
|
|
162
|
+
const xOfNumbers = xOfBaseCircles(i, asRightOverhangs) +
|
|
163
|
+
shiftToAlignNumberNearCircle(asBases, i, nucleotideCounter + 1);
|
|
164
|
+
const n = (!isOverhang(asBases[i]) && enumerateModifications.includes(asBases[i])) ?
|
|
225
165
|
String(nucleotideCounter + 1) : '';
|
|
226
166
|
image.append(
|
|
227
|
-
svg.text(n, xOfNumbers,
|
|
228
|
-
svg.circle(
|
|
229
|
-
svg.text(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
asPtoStatuses[i] ? svg.star(
|
|
233
|
-
|
|
167
|
+
svg.text(n, xOfNumbers, Y.AS_INDICES, LEGEND_FONT_SIZE, FONT_COLOR),
|
|
168
|
+
svg.circle(xOfBaseCircles(i, asRightOverhangs), Y.AS_CIRCLES, BASE_RADIUS, baseColor(asBases[i])),
|
|
169
|
+
svg.text(textInsideCircle(asBases, i),
|
|
170
|
+
xOfBaseCircles(i, asRightOverhangs) + shiftToAlignNumberNearCircle(asBases, i, nucleotideCounter + 1),
|
|
171
|
+
Y.AS_TEXTS, BASE_FONT_SIZE, fontColorVisibleOnBackground(asBases[i])),
|
|
172
|
+
asPtoStatuses[i] ? svg.star(xOfBaseCircles(i, asRightOverhangs) +
|
|
173
|
+
BASE_RADIUS, Y.AS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) : '',
|
|
234
174
|
);
|
|
235
175
|
}
|
|
236
176
|
image.append(
|
|
237
177
|
asPtoStatuses[asBases.length] ?
|
|
238
|
-
svg.star(
|
|
239
|
-
|
|
178
|
+
svg.star(xOfBaseCircles(asBases.length, asRightOverhangs) + BASE_RADIUS, Y.AS_TEXTS + PS_LINKAGE_RADIUS,
|
|
179
|
+
PS_LINKAGE_COLOR) : '',
|
|
240
180
|
);
|
|
241
181
|
}
|
|
242
182
|
|
|
243
|
-
const title = patternName
|
|
244
|
-
|
|
245
|
-
image.append(svg.text(title, xOfTitle, yOfTitle, baseFontSize, titleFontColor));
|
|
183
|
+
const title = `${patternName} for ${numberOfSsNucleotides}${(asExists ? `/${numberOfAsNucleotides}` : '')}mer`;
|
|
184
|
+
image.append(svg.text(title, X.TITLE, Y.TITLE, BASE_FONT_SIZE, TITLE_FONT_COLOR));
|
|
246
185
|
for (let i = 0; i < uniqueBases.length; i++) {
|
|
247
186
|
image.append(
|
|
248
|
-
svg.circle(
|
|
249
|
-
svg.text(uniqueBases[i],
|
|
250
|
-
|
|
187
|
+
svg.circle(equidistantXForLegend(i), Y.circlesInLegends(asExists), LEGEND_RADIUS, baseColor(uniqueBases[i])),
|
|
188
|
+
svg.text(uniqueBases[i], equidistantXForLegend(i) + LEGEND_RADIUS + 4, Y.textLegend(asExists), LEGEND_FONT_SIZE,
|
|
189
|
+
FONT_COLOR),
|
|
251
190
|
);
|
|
252
191
|
}
|
|
253
192
|
return image;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {AXOLABS_MAP} from './constants';
|
|
2
|
+
import {NUCLEOTIDES} from '../structures-works/map';
|
|
3
|
+
|
|
4
|
+
export function isOverhang(modification: string): boolean {
|
|
5
|
+
return modification.slice(-3) == '(o)';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isOneDigitNumber(n: number): boolean {
|
|
9
|
+
return n < 10;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// https://uxdesign.cc/star-rating-make-svg-great-again-d4ce4731347e
|
|
13
|
+
export function getPointsToDrawStar(centerX: number, centerY: number): string {
|
|
14
|
+
const innerCirclePoints = 5; // a 5 point star
|
|
15
|
+
const innerRadius = 15 / innerCirclePoints;
|
|
16
|
+
const innerOuterRadiusRatio = 2; // outter circle is x2 the inner
|
|
17
|
+
const outerRadius = innerRadius * innerOuterRadiusRatio;
|
|
18
|
+
const angle = Math.PI / innerCirclePoints;
|
|
19
|
+
const angleOffsetToCenterStar = 60;
|
|
20
|
+
const totalNumberOfPoints = innerCirclePoints * 2; // 10 in a 5-points star
|
|
21
|
+
|
|
22
|
+
let points = '';
|
|
23
|
+
for (let i = 0; i < totalNumberOfPoints; i++) {
|
|
24
|
+
const r = (i % 2 == 0) ? outerRadius : innerRadius;
|
|
25
|
+
const currentX = centerX + Math.cos(i * angle + angleOffsetToCenterStar) * r;
|
|
26
|
+
const currentY = centerY + Math.sin(i * angle + angleOffsetToCenterStar) * r;
|
|
27
|
+
points += `${currentX},${currentY} `;
|
|
28
|
+
}
|
|
29
|
+
return points;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function countOverhangsOnTheRightEdge(modifications: string[]): number {
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < modifications.length && isOverhang(modifications[i]))
|
|
35
|
+
i++;
|
|
36
|
+
return (i == modifications.length - 1) ? 0 : i;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function textWidth(text: string, font: number): number {
|
|
40
|
+
const context = document.createElement('canvas').getContext('2d');
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
context.font = String(font);
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
return 2 * context.measureText(text).width;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function textInsideCircle(bases: string[], index: number): string {
|
|
48
|
+
return (isOverhang(bases[index]) || !NUCLEOTIDES.includes(bases[index])) ? '' : bases[index];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function fontColorVisibleOnBackground(base: string): string {
|
|
52
|
+
const rgbIntList = AXOLABS_MAP[base].color.match(/\d+/g)!.map((e) => Number(e));
|
|
53
|
+
return (rgbIntList[0] * 0.299 + rgbIntList[1] * 0.587 + rgbIntList[2] * 0.114) > 186 ? '#33333' : '#ffffff';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function baseColor(base: string): string {
|
|
57
|
+
return AXOLABS_MAP[base].color;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const svg = {
|
|
61
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
62
|
+
render: function(width: number, height: number): Element {
|
|
63
|
+
const e = document.createElementNS(this.xmlns, 'svg');
|
|
64
|
+
e.setAttribute('id', 'mySvg');
|
|
65
|
+
e.setAttribute('width', String(width));
|
|
66
|
+
e.setAttribute('height', String(height));
|
|
67
|
+
return e;
|
|
68
|
+
},
|
|
69
|
+
circle: function(x: number, y: number, radius: number, color: string): Element {
|
|
70
|
+
const e = document.createElementNS(this.xmlns, 'circle');
|
|
71
|
+
e.setAttribute('cx', String(x));
|
|
72
|
+
e.setAttribute('cy', String(y));
|
|
73
|
+
e.setAttribute('r', String(radius));
|
|
74
|
+
e.setAttribute('fill', color);
|
|
75
|
+
return e;
|
|
76
|
+
},
|
|
77
|
+
text: function(text: string, x: number, y: number, fontSize: number, color: string): Element {
|
|
78
|
+
const e = document.createElementNS(this.xmlns, 'text');
|
|
79
|
+
e.setAttribute('x', String(x));
|
|
80
|
+
e.setAttribute('y', String(y));
|
|
81
|
+
e.setAttribute('font-size', String(fontSize));
|
|
82
|
+
e.setAttribute('font-weight', 'normal');
|
|
83
|
+
e.setAttribute('font-family', 'Arial');
|
|
84
|
+
e.setAttribute('fill', color);
|
|
85
|
+
e.innerHTML = text;
|
|
86
|
+
return e;
|
|
87
|
+
},
|
|
88
|
+
star: function(x: number, y: number, fill: string): Element {
|
|
89
|
+
const e = document.createElementNS(this.xmlns, 'polygon');
|
|
90
|
+
e.setAttribute('points', getPointsToDrawStar(x, y));
|
|
91
|
+
e.setAttribute('fill', fill);
|
|
92
|
+
return e;
|
|
93
|
+
},
|
|
94
|
+
};
|
package/src/main/main-view.ts
CHANGED
|
@@ -2,20 +2,35 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
import {convertSequence, undefinedInputSequence, isValidSequence} from '../structures-works/sequence-codes-tools';
|
|
5
|
-
import {map
|
|
5
|
+
import {map} from '../structures-works/map';
|
|
6
|
+
import {MODIFICATIONS} from '../structures-works/const';
|
|
6
7
|
import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
|
|
7
8
|
import $ from 'cash-dom';
|
|
8
9
|
import {download} from '../helpers';
|
|
9
10
|
|
|
10
|
-
const defaultInput = 'fAmCmGmAmCpsmU';
|
|
11
|
-
const sequenceWasCopied = 'Copied';
|
|
11
|
+
const defaultInput = 'fAmCmGmAmCpsmU'; // todo: capitalize constants
|
|
12
|
+
const sequenceWasCopied = 'Copied'; // todo: wrap hardcoded literals into constants
|
|
12
13
|
const tooltipSequence = 'Copy sequence';
|
|
13
14
|
|
|
14
|
-
export function mainView() {
|
|
15
|
-
|
|
15
|
+
export async function mainView(): Promise<HTMLDivElement> {
|
|
16
|
+
const monomersLibAddress = 'System:AppData/SequenceTranslator/helmLib.json';
|
|
17
|
+
async function updateTableAndMolecule(sequence: string, inputFormat: string): Promise<void> {
|
|
16
18
|
moleculeSvgDiv.innerHTML = '';
|
|
17
19
|
outputTableDiv.innerHTML = '';
|
|
18
20
|
const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
|
|
21
|
+
let errorsExist = false;
|
|
22
|
+
|
|
23
|
+
// external helm-like monomers library
|
|
24
|
+
const fileExists = await grok.dapi.files.exists(monomersLibAddress);
|
|
25
|
+
if (!fileExists) {
|
|
26
|
+
// todo: improve behaviour in this case
|
|
27
|
+
grok.shell.warning('Please, provide the file with monomers library as System:AppData/SequenceTranslator/helmLib.json');
|
|
28
|
+
pi.close();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const monomersLib = await grok.dapi.files.readAsText(monomersLibAddress);
|
|
33
|
+
|
|
19
34
|
try {
|
|
20
35
|
sequence = sequence.replace(/\s/g, '');
|
|
21
36
|
const output = isValidSequence(sequence, null);
|
|
@@ -31,16 +46,16 @@ export function mainView() {
|
|
|
31
46
|
tableRows.push({
|
|
32
47
|
'key': key,
|
|
33
48
|
'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
ui.divH([
|
|
50
|
+
ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
|
|
51
|
+
ui.tooltip.bind(
|
|
52
|
+
ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
|
|
53
|
+
'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
|
|
54
|
+
'. See tables with valid codes on the right',
|
|
55
|
+
),
|
|
56
|
+
]) : //@ts-ignore
|
|
57
|
+
ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
|
|
58
|
+
.then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
|
|
44
59
|
});
|
|
45
60
|
}
|
|
46
61
|
|
|
@@ -55,8 +70,11 @@ export function mainView() {
|
|
|
55
70
|
const canvas = ui.canvas(300, 170);
|
|
56
71
|
canvas.addEventListener('click', () => {
|
|
57
72
|
const canv = ui.canvas($(window).width(), $(window).height());
|
|
58
|
-
const mol = sequenceToMolV3000(
|
|
59
|
-
|
|
73
|
+
const mol = sequenceToMolV3000(
|
|
74
|
+
inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
75
|
+
output.synthesizer![0],
|
|
76
|
+
);
|
|
77
|
+
console.log(mol);
|
|
60
78
|
// @ts-ignore
|
|
61
79
|
OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
62
80
|
ui.dialog('Molecule: ' + inputSequenceField.value)
|
|
@@ -66,7 +84,7 @@ export function mainView() {
|
|
|
66
84
|
$(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
|
|
67
85
|
$(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
|
|
68
86
|
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
69
|
-
|
|
87
|
+
output.synthesizer![0]);
|
|
70
88
|
// @ts-ignore
|
|
71
89
|
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
72
90
|
moleculeSvgDiv.append(canvas);
|
|
@@ -151,9 +169,11 @@ export function mainView() {
|
|
|
151
169
|
$(codesTablesDiv).hide(),
|
|
152
170
|
);
|
|
153
171
|
|
|
154
|
-
const downloadMolFileIcon = ui.iconFA('download', () => {
|
|
172
|
+
const downloadMolFileIcon = ui.iconFA('download', async () => {
|
|
155
173
|
const clearSequence = inputSequenceField.value.replace(/\s/g, '');
|
|
156
|
-
const
|
|
174
|
+
const monomersLib = await grok.dapi.files.readAsText(monomersLibAddress);
|
|
175
|
+
const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
|
|
176
|
+
inputFormatChoiceInput.value!);
|
|
157
177
|
download(clearSequence + '.mol', encodeURIComponent(result));
|
|
158
178
|
}, 'Save .mol file');
|
|
159
179
|
|
package/src/package.ts
CHANGED
|
@@ -5,13 +5,31 @@ import {autostartOligoSdFileSubscription} from './autostart/registration';
|
|
|
5
5
|
import {defineAxolabsPattern} from './axolabs/define-pattern';
|
|
6
6
|
import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
|
|
7
7
|
import {mainView} from './main/main-view';
|
|
8
|
+
import {IMonomerLib, MonomerWorks, readLibrary} from '@datagrok-libraries/bio';
|
|
8
9
|
|
|
9
10
|
export const _package = new DG.Package();
|
|
10
11
|
|
|
12
|
+
const LIB_PATH = 'System:AppData/SequenceTranslator';
|
|
13
|
+
|
|
14
|
+
let monomerLib: IMonomerLib | null = null;
|
|
15
|
+
export let monomerWorks: MonomerWorks | null = null;
|
|
16
|
+
|
|
17
|
+
export function getMonomerWorks() {
|
|
18
|
+
return monomerWorks;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function getMonomerLib() {
|
|
22
|
+
return monomerLib;
|
|
23
|
+
};
|
|
11
24
|
|
|
12
25
|
//name: Sequence Translator
|
|
13
26
|
//tags: app
|
|
14
|
-
export function sequenceTranslator(): void {
|
|
27
|
+
export async function sequenceTranslator(): Promise<void> {
|
|
28
|
+
monomerLib = await readLibrary(LIB_PATH, 'helmLib.json');
|
|
29
|
+
|
|
30
|
+
if (monomerWorks == null)
|
|
31
|
+
monomerWorks = new MonomerWorks(monomerLib);
|
|
32
|
+
|
|
15
33
|
const windows = grok.shell.windows;
|
|
16
34
|
windows.showProperties = false;
|
|
17
35
|
windows.showToolbox = false;
|
|
@@ -20,7 +38,7 @@ export function sequenceTranslator(): void {
|
|
|
20
38
|
const v = grok.shell.newView('Sequence Translator', []);
|
|
21
39
|
v.box = true;
|
|
22
40
|
v.append(ui.tabControl({
|
|
23
|
-
'MAIN': mainView(),
|
|
41
|
+
'MAIN': await mainView(),
|
|
24
42
|
'AXOLABS': defineAxolabsPattern(),
|
|
25
43
|
'SDF': saveSenseAntiSense(),
|
|
26
44
|
}));
|