@datagrok/helm 2.1.8 → 2.1.9
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/.eslintrc.json +3 -2
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +2 -2
- package/src/cell-renderer.ts +18 -18
- package/src/tests/helm-tests.ts +42 -5
- package/src/utils.ts +60 -47
- package/tsconfig.json +1 -1
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/helm",
|
|
3
3
|
"friendlyName": "Helm",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.9",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Oleksandra Serhiienko",
|
|
7
7
|
"email": "oserhiienko@datagrok.ai"
|
|
8
8
|
},
|
|
9
|
-
"description": "Provides support for HELM notation (importing, detecting, rendering, conversion).
|
|
9
|
+
"description": "Provides support for HELM notation (importing, detecting, rendering, conversion).",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@datagrok-libraries/bio": "^5.22.1",
|
|
12
12
|
"@datagrok-libraries/utils": "^1.19.1",
|
package/src/cell-renderer.ts
CHANGED
|
@@ -37,27 +37,27 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
37
37
|
} else if (argsX > maxLengthWordsSum[mid + 1]) {
|
|
38
38
|
left = mid + 1;
|
|
39
39
|
}
|
|
40
|
-
if (left == right)
|
|
40
|
+
if (left == right)
|
|
41
41
|
found = true;
|
|
42
|
-
}
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
|
|
46
45
|
const monomers = findMonomers(gridCell.cell.value);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const s: string = gridCell.cell.value ?? '';
|
|
47
|
+
const subParts: string[] = parseHelm(s);
|
|
48
|
+
const allParts: string[] = getParts(subParts, s);
|
|
49
|
+
const tooltipMessage: HTMLElement[] = [];
|
|
51
50
|
for (let i = 0; i < allParts.length; ++i) {
|
|
52
|
-
if (monomers.has(allParts[i]))
|
|
51
|
+
if (monomers.has(allParts[i])) {
|
|
53
52
|
tooltipMessage[i] = ui.divV([
|
|
54
53
|
ui.divText(`Monomer ${allParts[i]} not found.`),
|
|
55
54
|
ui.divText('Open the Context Panel, then expand Manage Libraries')
|
|
56
55
|
]);
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
|
-
(((tooltipMessage[left]?.childNodes.length ?? 0) > 0))
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
(((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
|
|
59
|
+
ui.tooltip.show(ui.div(tooltipMessage[left]), e.x + 16, e.y + 16) :
|
|
60
|
+
ui.tooltip.hide();
|
|
61
61
|
} catch (err: any) {
|
|
62
62
|
const errMsg: string = errorToConsole(err);
|
|
63
63
|
console.error('Helm: HelmCellRenderer.onMouseMove() error:\n' + errMsg);
|
|
@@ -71,8 +71,8 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
71
71
|
const undefinedColor = 'rgb(100,100,100)';
|
|
72
72
|
const grayColor = '#808080';
|
|
73
73
|
const monomers = findMonomers(gridCell.cell.value);
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const s: string = gridCell.cell.value ?? '';
|
|
75
|
+
const subParts: string[] = parseHelm(s);
|
|
76
76
|
if (monomers.size == 0 && grid) {
|
|
77
77
|
const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
|
|
78
78
|
host.setAttribute('dataformat', 'helm');
|
|
@@ -96,20 +96,20 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
|
|
|
96
96
|
g.font = '12px monospace';
|
|
97
97
|
g.textBaseline = 'top';
|
|
98
98
|
let x1 = x;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const maxLengthWords: any = {};
|
|
100
|
+
const maxLengthWordSum: any = {};
|
|
101
|
+
const allParts: string[] = getParts(subParts, s);
|
|
102
102
|
for (let i = 0; i < allParts.length; ++i) {
|
|
103
103
|
maxLengthWords[i] = allParts[i].length * 7;
|
|
104
|
-
|
|
104
|
+
const color = monomers.has(allParts[i]) ? 'red' : grayColor;
|
|
105
105
|
g.fillStyle = undefinedColor;
|
|
106
106
|
x1 = printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
maxLengthWordSum[0] = maxLengthWords[0];
|
|
110
|
-
for (let i = 1; i < allParts.length; i++)
|
|
110
|
+
for (let i = 1; i < allParts.length; i++)
|
|
111
111
|
maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
gridCell.cell.column.temp = {
|
|
114
114
|
'helm-sum-maxLengthWords': maxLengthWordSum,
|
|
115
115
|
'helm-maxLengthWords': maxLengthWords
|
package/src/tests/helm-tests.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {after, before, category, delay, expect, test} from '@datagrok-libraries/utils/src/test';
|
|
1
|
+
import {after, before, category, delay, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
|
|
2
2
|
//import {findMonomers, helmToFasta, helmToPeptide, helmToRNA, initHelm} from '../package';
|
|
3
3
|
import {_package} from '../package-test';
|
|
4
4
|
import {parseHelm} from '../utils';
|
|
@@ -18,7 +18,9 @@ category('Helm', () => {
|
|
|
18
18
|
// expect(await helmToRNA('RNA1{R(U)P.R(T)P.R(G)P.R(C)P.R(A)}$$$$'), 'UTGCA');
|
|
19
19
|
// expect(await helmToRNA('RNA1{P.R(U).P.R(T)}$$$$'), 'UT');
|
|
20
20
|
// // eslint-disable-next-line max-len
|
|
21
|
-
// expect(
|
|
21
|
+
// expect(
|
|
22
|
+
// await helmToRNA('RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$'), 'UT AA'
|
|
23
|
+
// );
|
|
22
24
|
// });
|
|
23
25
|
//
|
|
24
26
|
// test('helmToPeptide', async () => {
|
|
@@ -46,7 +48,32 @@ category('Helm', () => {
|
|
|
46
48
|
// expect(col.tags[DG.TAGS.UNITS], 'HELM');
|
|
47
49
|
// });
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
const complexMonomers = [
|
|
52
|
+
// tests parsing of complex monomemer in square brackets like [L-hArg(Et,Et)]
|
|
53
|
+
'PEPTIDE1{[L-hArg(Et,Et)].E.F.G}|PEPTIDE2{C.E}$PEPTIDE1,PEPTIDE2,2:R3-1:R1$$$V2.0',
|
|
54
|
+
// tests parsing of complex monomer and one as a side chain as well
|
|
55
|
+
'PEPTIDE1{[L-hArg(Et,Et)]([L-hArg(Et,Et)]).E.F.G}$$$$',
|
|
56
|
+
// tests parsing of complex monomer and one as a side chain as well
|
|
57
|
+
'RNA1{[dhp]([m1A])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
58
|
+
// tests complex monomer as side chain
|
|
59
|
+
'RNA1{r([mo6pur])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
60
|
+
// test complex monomer, side chain and linker
|
|
61
|
+
'RNA1{[afhna]([mo6pur])[36SS].r(T)p}$$$$V2.0'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const complexMonomersExpected = [
|
|
65
|
+
['L-hArg(Et,Et)', 'E', 'F', 'G', 'C', 'E'],
|
|
66
|
+
['L-hArg(Et,Et)', 'L-hArg(Et,Et)', 'E', 'F', 'G'],
|
|
67
|
+
['dhp', 'm1A', 'p', 'r', 'T', 'p', 'r', 'T', 'p', 'r', 'G', 'p'],
|
|
68
|
+
['r', 'mo6pur', 'p', 'r', 'T', 'p', 'r', 'T', 'p', 'r', 'G', 'p'],
|
|
69
|
+
['afhna', 'mo6pur', '36SS', 'r', 'T', 'p']
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const complexTestNames = [
|
|
73
|
+
'comples-monomer', 'complex-monomer+side-chain', 'complex-monomer+side-chain-RNA',
|
|
74
|
+
'complex-side-chain', 'complex+side-chain+linker'
|
|
75
|
+
];
|
|
76
|
+
test('parseHelm', async () => {
|
|
50
77
|
const expectedResults = [
|
|
51
78
|
['meI', 'hHis', 'Aca', 'N', 'T', 'dK', 'Thr_PO3H2', 'D-Tyr_Et', 'Aze', 'dV', 'E', 'Phe_4Me'],
|
|
52
79
|
['A', 'C', 'T', 'G', 'W', 'E', 'Q'],
|
|
@@ -55,14 +82,24 @@ category('Helm', () => {
|
|
|
55
82
|
];
|
|
56
83
|
const helmStrings = [
|
|
57
84
|
'PEPTIDE1{meI.hHis.Aca.N.T.dK.Thr_PO3H2.Aca.D-Tyr_Et.Aze.dV.E.N.dV.Phe_4Me}$$$',
|
|
85
|
+
// eslint-disable-next-line max-len
|
|
58
86
|
'PEPTIDE1{A.C.T.G.C.T.W.G.T.W.E.C.W.C.Q.W}|PEPTIDE2{A.C.T.G.C.T.W.G.T.W.E.Q}$PEPTIDE1,PEPTIDE1,5:R3-14:R3|PEPTIDE2,PEPTIDE1,2:R3-12:R3$$$',
|
|
59
87
|
'RNA1{R(U)P.R(T)P}|RNA2{P.R(A)P.R(A)}$RNA1,RNA2,2:pair-6:pair|RNA1,RNA2,5:pair-3:pair$$$',
|
|
60
88
|
'PEPTIDE1{A.*.G.C}$$$$V2.0'
|
|
61
89
|
];
|
|
62
90
|
for (let idx = 0; idx < helmStrings.length; ++idx) {
|
|
63
|
-
|
|
64
|
-
|
|
91
|
+
const monomerSet = new Set(parseHelm(helmStrings[idx]));
|
|
92
|
+
const monomerArray = Array.from(monomerSet);
|
|
65
93
|
expect(JSON.stringify(monomerArray), JSON.stringify(expectedResults[idx]));
|
|
66
94
|
}
|
|
67
95
|
});
|
|
96
|
+
complexMonomers.forEach((helm, i) => {
|
|
97
|
+
test(complexTestNames[i], async () => {
|
|
98
|
+
const monomersArray = parseHelm(helm);
|
|
99
|
+
monomersArray.sort();
|
|
100
|
+
const expected = complexMonomersExpected[i];
|
|
101
|
+
expected.sort();
|
|
102
|
+
expectArray(monomersArray, expected);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
68
105
|
});
|
package/src/utils.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import { RGROUP_CAP_GROUP_NAME, RGROUP_CAP_GROUP_SMILES, jsonSdfMonomerLibDict, MONOMER_SYMBOL, RGROUP_ALTER_ID, RGROUPS, RGROUP_LABEL, SDF_MONOMER_NAME } from "./constants";
|
|
3
2
|
|
|
4
3
|
export function getParts(subParts: string[], s: string): string[] {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const j = 0;
|
|
5
|
+
const allParts: string[] = [];
|
|
7
6
|
for (let k = 0; k < subParts.length; ++k) {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const indexOfMonomer = s.indexOf(subParts[k]);
|
|
8
|
+
const helmBeforeMonomer = s.slice(j, indexOfMonomer);
|
|
10
9
|
allParts.push(helmBeforeMonomer);
|
|
11
10
|
allParts.push(subParts[k]);
|
|
12
11
|
s = s.substring(indexOfMonomer + subParts[k].length);
|
|
@@ -16,32 +15,44 @@ export function getParts(subParts: string[], s: string): string[] {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export function parseHelm(s: string) {
|
|
19
|
-
|
|
18
|
+
const sections = split(s, '$');
|
|
20
19
|
s = sections[0];
|
|
21
|
-
|
|
20
|
+
const monomers = [];
|
|
22
21
|
//@ts-ignore
|
|
23
22
|
if (!scil.Utils.isNullOrEmpty(s)) {
|
|
24
|
-
|
|
25
|
-
for (
|
|
26
|
-
|
|
23
|
+
const seqs = split(s, '|');
|
|
24
|
+
for (let i = 0; i < seqs.length; ++i) {
|
|
25
|
+
const e = detachAnnotation(seqs[i]);
|
|
27
26
|
s = e.str;
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
let p = s.indexOf('{');
|
|
30
29
|
|
|
31
30
|
s = s.substring(p + 1);
|
|
32
31
|
p = s.indexOf('}');
|
|
33
32
|
s = s.substring(0, p);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
for (
|
|
37
|
-
if (monomer
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
const ss = split(s, '.');
|
|
35
|
+
for (const monomer of ss) {
|
|
36
|
+
if (!monomer || monomer === '') continue;
|
|
37
|
+
if (monomer.startsWith('[') && monomer.includes(']')) {
|
|
38
|
+
const element = monomer.substring(1, monomer.indexOf(']'));
|
|
39
|
+
monomers.push(element);
|
|
40
|
+
const residue = monomer.substring(monomer.indexOf(']') + 1);
|
|
41
|
+
ss.push(residue);
|
|
42
|
+
} else if (monomer.includes('[') && monomer.endsWith(']')) {
|
|
43
|
+
const element = monomer.substring(monomer.lastIndexOf('[') + 1, monomer.length - 1);
|
|
44
|
+
monomers.push(element);
|
|
45
|
+
const residue = monomer.substring(0, monomer.lastIndexOf('['));
|
|
46
|
+
ss.push(residue);
|
|
47
|
+
} else if (monomer.includes('(') && monomer.includes(')')) {
|
|
48
|
+
// here we only want to split the string at first '(' and last ')'
|
|
49
|
+
// because entries like [L-hArg(Et,Et)]([L-hArg(Et,Et)]) where L-hArg(Et,Et) is a single monomer
|
|
50
|
+
const firstPiece = monomer.substring(0, monomer.indexOf('('));
|
|
51
|
+
const thirdPiece = monomer.substring(monomer.lastIndexOf(')') + 1);
|
|
52
|
+
const secondPiece = monomer.substring(firstPiece.length + 1, monomer.length - thirdPiece.length - 1);
|
|
53
|
+
const elements =[firstPiece, secondPiece, thirdPiece];
|
|
54
|
+
for (const el of elements)
|
|
55
|
+
ss.push(el);
|
|
45
56
|
} else {
|
|
46
57
|
monomers.push(monomer);
|
|
47
58
|
}
|
|
@@ -50,32 +61,33 @@ export function parseHelm(s: string) {
|
|
|
50
61
|
}
|
|
51
62
|
return monomers;
|
|
52
63
|
}
|
|
53
|
-
|
|
64
|
+
/* this function returns names of monomers that are NOT in the monomer library */
|
|
54
65
|
export function findMonomers(helmString: string) {
|
|
55
66
|
//@ts-ignore
|
|
56
67
|
const types = Object.keys(org.helm.webeditor.monomerTypeList());
|
|
57
68
|
const monomers: any = [];
|
|
58
|
-
const
|
|
59
|
-
for (
|
|
69
|
+
const monomerNames: any = [];
|
|
70
|
+
for (let i = 0; i < types.length; i++) {
|
|
60
71
|
//@ts-ignore
|
|
72
|
+
// eslint-disable-next-line new-cap
|
|
61
73
|
monomers.push(new scil.helm.Monomers.getMonomerSet(types[i]));
|
|
62
|
-
Object.keys(monomers[i]).forEach(k => {
|
|
63
|
-
|
|
74
|
+
Object.keys(monomers[i]).forEach((k) => {
|
|
75
|
+
monomerNames.push(monomers[i][k].id);
|
|
64
76
|
});
|
|
65
77
|
}
|
|
66
|
-
const
|
|
67
|
-
return new Set(
|
|
78
|
+
const splitString = parseHelm(helmString);
|
|
79
|
+
return new Set(splitString.filter((val) => !monomerNames.includes(val)));
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
function split(s: string, sep: string) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (
|
|
78
|
-
|
|
83
|
+
const ret = [];
|
|
84
|
+
let frag = '';
|
|
85
|
+
let parentheses = 0;
|
|
86
|
+
let bracket = 0;
|
|
87
|
+
let braces = 0;
|
|
88
|
+
let quote = 0;
|
|
89
|
+
for (let i = 0; i < s.length; ++i) {
|
|
90
|
+
let c = s.substring(i, i + 1);
|
|
79
91
|
if (c == sep && bracket == 0 && parentheses == 0 && braces == 0 && quote == 0) {
|
|
80
92
|
ret.push(frag);
|
|
81
93
|
frag = '';
|
|
@@ -84,7 +96,7 @@ function split(s: string, sep: string) {
|
|
|
84
96
|
if (quote > 0) {
|
|
85
97
|
if (c == '\\' && i + 1 < s.length) {
|
|
86
98
|
++i;
|
|
87
|
-
|
|
99
|
+
const c2 = s.substring(i, i + 1);
|
|
88
100
|
frag += c2;
|
|
89
101
|
c += c2;
|
|
90
102
|
}
|
|
@@ -92,18 +104,19 @@ function split(s: string, sep: string) {
|
|
|
92
104
|
if (c == '\"') {
|
|
93
105
|
if (!(i > 0 && s.substring(i - 1, i) == '\\'))
|
|
94
106
|
quote = quote == 0 ? 1 : 0;
|
|
95
|
-
} else if (c == '[')
|
|
107
|
+
} else if (c == '[') {
|
|
96
108
|
++bracket;
|
|
97
|
-
else if (c == ']')
|
|
109
|
+
} else if (c == ']') {
|
|
98
110
|
--bracket;
|
|
99
|
-
else if (c == '(')
|
|
111
|
+
} else if (c == '(') {
|
|
100
112
|
++parentheses;
|
|
101
|
-
else if (c == ')')
|
|
113
|
+
} else if (c == ')') {
|
|
102
114
|
--parentheses;
|
|
103
|
-
else if (c == '{')
|
|
115
|
+
} else if (c == '{') {
|
|
104
116
|
++braces;
|
|
105
|
-
else if (c == '}')
|
|
117
|
+
} else if (c == '}') {
|
|
106
118
|
--braces;
|
|
119
|
+
}
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
ret.push(frag);
|
|
@@ -111,19 +124,19 @@ function split(s: string, sep: string) {
|
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
function detachAnnotation(s: string) {
|
|
114
|
-
|
|
127
|
+
const ret = _detachAppendix(s, '\"');
|
|
115
128
|
if (ret.tag != null)
|
|
116
129
|
return ret;
|
|
117
130
|
|
|
118
|
-
|
|
131
|
+
const r = _detachAppendix(s, '\'');
|
|
119
132
|
return {tag: ret.tag, repeat: r.tag, str: r.str};
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
function _detachAppendix(s: string, c: string) {
|
|
123
|
-
|
|
136
|
+
let tag = null;
|
|
124
137
|
//@ts-ignore
|
|
125
138
|
if (scil.Utils.endswith(s, c)) {
|
|
126
|
-
|
|
139
|
+
let p = s.length - 1;
|
|
127
140
|
while (p > 0) {
|
|
128
141
|
p = s.lastIndexOf(c, p - 1);
|
|
129
142
|
if (p <= 0 || s.substring(p - 1, p) != '\\')
|
package/tsconfig.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// "incremental": true, /* Enable incremental compilation */
|
|
7
7
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
|
8
8
|
"module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
|
9
|
-
"lib": ["
|
|
9
|
+
"lib": ["ES2022", "dom"], /* Specify library files to be included in the compilation. */
|
|
10
10
|
"allowJs": true, /* Allow javascript files to be compiled. */
|
|
11
11
|
// "checkJs": true, /* Report errors in .js files. */
|
|
12
12
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|