@datagrok/bio 2.16.3 → 2.16.5
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 +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/284.js +1 -1
- package/dist/284.js.map +1 -1
- package/dist/980.js +1 -1
- package/dist/980.js.map +1 -1
- package/dist/package-test.js +6 -6
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/files/tests/placer_MSA.csv +5 -0
- package/package.json +3 -3
- package/src/global.d.ts +13 -0
- package/src/package.ts +1 -1
- package/src/tests/helm-tests.ts +46 -20
- package/src/tests/renderers-monomer-placer-tests.ts +57 -2
- package/src/utils/helm-to-molfile/converter/connection-list.ts +5 -5
- package/src/utils/helm-to-molfile/converter/helm.ts +19 -22
- package/src/utils/helm-to-molfile/converter/monomer-wrapper.ts +1 -2
- package/src/utils/monomer-lib/monomer-lib-base.ts +1 -1
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +66 -32
- package/src/viewers/web-logo-viewer.ts +19 -21
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
MSA,Activity
|
|
2
|
+
meI/hHis/Aca/N/T/dE/Thr_PO3H2/Aca//Phe_4Me,5.307510973968128
|
|
3
|
+
meI/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2/Aca//Phe_4Me,5.723876853431544
|
|
4
|
+
Lys_Boc/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2/Aca//Phe_4Me,5.185811246022437
|
|
5
|
+
meI/hHis/Aca/Cys_SEt/T/dK/Thr_PO3H2///Phe_4Me,6.223502390804369
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Aleksandr Tanas",
|
|
6
6
|
"email": "atanas@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.16.
|
|
8
|
+
"version": "2.16.5",
|
|
9
9
|
"description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@biowasm/aioli": "^3.1.0",
|
|
40
|
-
"@datagrok-libraries/bio": "^5.45.
|
|
40
|
+
"@datagrok-libraries/bio": "^5.45.3",
|
|
41
41
|
"@datagrok-libraries/chem-meta": "^1.2.7",
|
|
42
|
-
"@datagrok-libraries/math": "^1.2.
|
|
42
|
+
"@datagrok-libraries/math": "^1.2.2",
|
|
43
43
|
"@datagrok-libraries/ml": "^6.7.4",
|
|
44
44
|
"@datagrok-libraries/tutorials": "^1.4.3",
|
|
45
45
|
"@datagrok-libraries/utils": "^4.3.6",
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as grokNamespace from 'datagrok-api/grok';
|
|
2
|
+
import * as uiNamespace from 'datagrok-api/ui';
|
|
3
|
+
import * as DGNamespace from 'datagrok-api/dg';
|
|
4
|
+
import * as rxjsNamespace from 'rxjs';
|
|
5
|
+
import $Namespace from 'cash-dom';
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
const grok: typeof grokNamespace;
|
|
9
|
+
const ui: typeof uiNamespace;
|
|
10
|
+
const DG: typeof DGNamespace;
|
|
11
|
+
const rjxs: typeof rxjsNamespace;
|
|
12
|
+
const $: typeof $Namespace;
|
|
13
|
+
}
|
package/src/package.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/sr
|
|
|
26
26
|
import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
27
27
|
import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
|
|
28
28
|
import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
|
|
29
|
+
import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
29
30
|
|
|
30
31
|
import {getMacromoleculeColumns} from './utils/ui-utils';
|
|
31
32
|
import {MacromoleculeDifferenceCellRenderer, MacromoleculeSequenceCellRenderer,} from './utils/cell-renderer';
|
|
@@ -69,7 +70,6 @@ import {getMolColumnFromHelm} from './utils/helm-to-molfile/utils';
|
|
|
69
70
|
import {MonomerManager} from './utils/monomer-lib/monomer-manager/monomer-manager';
|
|
70
71
|
import {calculateScoresWithEmptyValues} from './utils/calculate-scores';
|
|
71
72
|
import {SeqHelper} from './utils/seq-helper/seq-helper';
|
|
72
|
-
import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
73
73
|
|
|
74
74
|
export const _package = new BioPackage(/*{debug: true}/**/);
|
|
75
75
|
|
package/src/tests/helm-tests.ts
CHANGED
|
@@ -12,58 +12,75 @@ category('helm', () => {
|
|
|
12
12
|
'single-linear': {
|
|
13
13
|
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T.[meI]}$$$$'},
|
|
14
14
|
tgt: {
|
|
15
|
-
simplePolymers: [6],
|
|
15
|
+
simplePolymers: [6], connections: [],
|
|
16
16
|
bondedRGroups: [1, 2, 2, 2, 2, 1],
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
'single-cyclized-C-2-2': {
|
|
20
|
-
src: {helm: 'PEPTIDE1{R.F.C.Y.G.H.[GGaz].C.T.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-8
|
|
20
|
+
src: {helm: 'PEPTIDE1{R.F.C.Y.G.H.[GGaz].C.T.[meI]}$PEPTIDE1,PEPTIDE1,3:R3-8:R3$$$'},
|
|
21
21
|
tgt: {
|
|
22
|
-
simplePolymers: [10],
|
|
22
|
+
simplePolymers: [10], connections: [[['PEPTIDE1', 3, 'R3'], ['PEPTIDE1', 8, 'R3']]],
|
|
23
23
|
bondedRGroups: [1, 2, 3, 2, 2, 2, 2, 3, 2, 1],
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
'single-cyclized-C-1-1': {
|
|
27
|
-
src: {helm: 'PEPTIDE1{F.C.Y.G.H.[GGaz].C.[meI]}$PEPTIDE1,PEPTIDE1,2:R3-7
|
|
27
|
+
src: {helm: 'PEPTIDE1{F.C.Y.G.H.[GGaz].C.[meI]}$PEPTIDE1,PEPTIDE1,2:R3-7:R3$$$'},
|
|
28
28
|
tgt: {
|
|
29
|
-
simplePolymers: [8],
|
|
29
|
+
simplePolymers: [8], connections: [[['PEPTIDE1', 2, 'R3'], ['PEPTIDE1', 7, 'R3']]],
|
|
30
30
|
bondedRGroups: [1, 3, 2, 2, 2, 1, 3, 1],
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
'single-cyclized-C-0-0': {
|
|
34
|
-
src: {helm: 'PEPTIDE1{C.Y.G.H.[GGaz].C}$PEPTIDE1,PEPTIDE1,
|
|
34
|
+
src: {helm: 'PEPTIDE1{C.Y.G.H.[GGaz].C}$PEPTIDE1,PEPTIDE1,1:R3-6:R3$$$'},
|
|
35
35
|
tgt: {
|
|
36
|
-
simplePolymers: [6],
|
|
36
|
+
simplePolymers: [6], connections: [[['PEPTIDE1', 1, 'R3'], ['PEPTIDE1', 6, 'R3']]],
|
|
37
37
|
bondedRGroups: [2, 2, 2, 2, 2, 2],
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
-
'two-separated-1': {
|
|
40
|
+
'two-separated-5-1': {
|
|
41
41
|
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$$$$'},
|
|
42
42
|
tgt: {
|
|
43
|
-
simplePolymers: [5, 1],
|
|
44
|
-
bondedRGroups: [1, 2, 2, 2, 1,
|
|
43
|
+
simplePolymers: [5, 1], connections: [],
|
|
44
|
+
bondedRGroups: [1, 2, 2, 2, 1, 0],
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
-
'two-separated-
|
|
47
|
+
'two-separated-1-5': {
|
|
48
|
+
src: {helm: 'PEPTIDE1{[meI]}|PEPTIDE2{R.F.Y.[GGaz].T}$$$$'},
|
|
49
|
+
tgt: {
|
|
50
|
+
simplePolymers: [1, 5], connections: [],
|
|
51
|
+
bondedRGroups: [0, 1, 2, 2, 2, 1],
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
'two-separated-4-2': {
|
|
48
55
|
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$$$$'},
|
|
49
56
|
tgt: {
|
|
50
|
-
simplePolymers: [4, 2],
|
|
57
|
+
simplePolymers: [4, 2], connections: [],
|
|
51
58
|
bondedRGroups: [1, 2, 2, 1, 1, 1],
|
|
52
59
|
}
|
|
53
60
|
},
|
|
54
61
|
'two-connected-1': {
|
|
55
|
-
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE2,5:R2-1
|
|
62
|
+
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz].T}|PEPTIDE2{[meI]}$PEPTIDE1,PEPTIDE2,5:R2-1:R1$$$'},
|
|
56
63
|
tgt: {
|
|
57
|
-
simplePolymers: [5, 1],
|
|
64
|
+
simplePolymers: [5, 1], connections: [[['PEPTIDE1', 5, 'R2'], ['PEPTIDE2', 1, 'R1']]],
|
|
58
65
|
bondedRGroups: [1, 2, 2, 2, 2, 1],
|
|
59
66
|
}
|
|
60
67
|
},
|
|
61
68
|
'two-connected-2': {
|
|
62
|
-
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$PEPTIDE1,PEPTIDE2,4:R2-1
|
|
69
|
+
src: {helm: 'PEPTIDE1{R.F.Y.[GGaz]}|PEPTIDE2{T.[meI]}$PEPTIDE1,PEPTIDE2,4:R2-1:R1$$$'},
|
|
63
70
|
tgt: {
|
|
64
|
-
simplePolymers: [4, 2],
|
|
71
|
+
simplePolymers: [4, 2], connections: [[['PEPTIDE1', 4, 'R2'], ['PEPTIDE2', 1, 'R1']]],
|
|
65
72
|
bondedRGroups: [1, 2, 2, 2, 2, 1],
|
|
66
73
|
}
|
|
74
|
+
},
|
|
75
|
+
'two-cyclized-1-9': {
|
|
76
|
+
src: {helm: 'PEPTIDE1{[meI]}|PEPTIDE2{R.F.[GGaz].T.G.H.F.Y.P}$PEPTIDE2,PEPTIDE2,3:R3-9:R2|PEPTIDE2,PEPTIDE1,3:R4-1:R1$$$V2.0'},
|
|
77
|
+
tgt: {
|
|
78
|
+
simplePolymers: [1, 9],
|
|
79
|
+
connections: [
|
|
80
|
+
[['PEPTIDE2', 3, 'R3'], ['PEPTIDE2', 9, 'R2']],
|
|
81
|
+
[['PEPTIDE2', 3, 'R4'], ['PEPTIDE1', 1, 'R1']]],
|
|
82
|
+
bondedRGroups: [1, 1, 2, 4, 2, 2, 2, 2, 2, 1],
|
|
83
|
+
}
|
|
67
84
|
}
|
|
68
85
|
};
|
|
69
86
|
|
|
@@ -74,12 +91,21 @@ category('helm', () => {
|
|
|
74
91
|
const simplePolymers = resHelm.simplePolymers
|
|
75
92
|
.map((sp) => sp.monomers.length);
|
|
76
93
|
const totalMonomerCount = simplePolymers.reduce((a, b) => a + b, 0);
|
|
77
|
-
|
|
94
|
+
expectArray(simplePolymers, tgt.simplePolymers);
|
|
95
|
+
|
|
96
|
+
const connections = resHelm.connectionList.getConnectionData()
|
|
97
|
+
.map((cdi) => {
|
|
98
|
+
return [
|
|
99
|
+
[cdi[0].polymerId, cdi[0].bond.monomerIdx + 1, `R${cdi[0].bond.rGroupId}`],
|
|
100
|
+
[cdi[1].polymerId, cdi[1].bond.monomerIdx + 1, `R${cdi[1].bond.rGroupId}`]];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expectArray(connections, tgt.connections);
|
|
78
104
|
|
|
79
|
-
const bondedRGroups = wu.count(0).take(resHelm.bondedRGroupsMap.
|
|
80
|
-
.map((i) => resHelm.bondedRGroupsMap.
|
|
105
|
+
const bondedRGroups = wu.count(0).take(resHelm.bondedRGroupsMap.length)
|
|
106
|
+
.map((i) => resHelm.bondedRGroupsMap[i].length).toArray();
|
|
81
107
|
expect(totalMonomerCount, bondedRGroups.length);
|
|
82
108
|
// expectArray(bondedRGroups, tgt.bondedRGroups);
|
|
83
|
-
}
|
|
109
|
+
});
|
|
84
110
|
}
|
|
85
111
|
});
|
|
@@ -4,10 +4,9 @@ import * as ui from 'datagrok-api/ui';
|
|
|
4
4
|
|
|
5
5
|
import wu from 'wu';
|
|
6
6
|
|
|
7
|
-
import {after, before, category, expect, test} from '@datagrok-libraries/utils/src/test';
|
|
7
|
+
import {after, before, category, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
|
|
8
8
|
import {MonomerPlacer, hitBounds} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
|
|
9
9
|
import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
10
|
-
import {getSeqHelper, ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
11
10
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
12
11
|
import {
|
|
13
12
|
getUserLibSettings, setUserLibSettings
|
|
@@ -164,4 +163,60 @@ id3,QHIRE--LT
|
|
|
164
163
|
expect(res, testData.tgt);
|
|
165
164
|
});
|
|
166
165
|
}
|
|
166
|
+
|
|
167
|
+
const lengthsTests = {
|
|
168
|
+
mono1: {
|
|
169
|
+
src: {
|
|
170
|
+
csv: 'seq' + '\n' +
|
|
171
|
+
'm1/m2/m3/m4/m5/m6/m7/m8/m9' + '\n' +
|
|
172
|
+
'n1/m2/n3/m4/n5/m6/n7/m8/n9' + '\n' +
|
|
173
|
+
'm1/n2/m3/n4/m5/n6/m7/n8/m9' + '\n',
|
|
174
|
+
},
|
|
175
|
+
tgt: {
|
|
176
|
+
lengths: [5, 31, 57, 83, 109, 135, 161, 187, 213, 239],
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
monoWithGaps: {
|
|
180
|
+
src: {
|
|
181
|
+
csv: 'seq' + '\n' +
|
|
182
|
+
'm1/m2/m3/m4/m5/m6//m8/m9' + '\n' +
|
|
183
|
+
'n1/m2/n3/m4/n5/m6//m8/n9' + '\n' +
|
|
184
|
+
'm1/n2/m3/n4/m5/n6/m7/n8/m9' + '\n',
|
|
185
|
+
},
|
|
186
|
+
tgt: {
|
|
187
|
+
lengths: [5, 31, 57, 83, 109, 135, 161, 187, 213, 239],
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
monoWithGapColumn: {
|
|
191
|
+
src: {
|
|
192
|
+
csv: 'seq' + '\n' +
|
|
193
|
+
'm1/m2/m3/m4/m5/m6//m8/m9' + '\n' +
|
|
194
|
+
'n1/m2/n3/m4/n5/m6//m8/n9' + '\n' +
|
|
195
|
+
'm1/n2/m3/n4/m5///n8/m9' + '\n',
|
|
196
|
+
},
|
|
197
|
+
tgt: {lengths: [5, 31, 57, 83, 109, 135, 161, 180, 206, 232],}
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
for (const [testName, testData] of Object.entries(lengthsTests)) {
|
|
202
|
+
test(`getCellMonomerLengths-${testName}`, async () => {
|
|
203
|
+
const df: DG.DataFrame = DG.DataFrame.fromCsv(testData.src.csv);
|
|
204
|
+
await grok.data.detectSemanticTypes(df);
|
|
205
|
+
const seqCol = df.getCol('seq');
|
|
206
|
+
|
|
207
|
+
const monLengthLimit: number = 3;
|
|
208
|
+
const charWidth: number = 7;
|
|
209
|
+
const sepWidth: number = 12;
|
|
210
|
+
const colTemp = new MonomerPlacer(null, seqCol, _package.logger, monLengthLimit, () => {
|
|
211
|
+
return {
|
|
212
|
+
monomerCharWidth: charWidth,
|
|
213
|
+
separatorWidth: sepWidth,
|
|
214
|
+
monomerToShort: monomerToShort,
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
await colTemp.init();
|
|
218
|
+
const resLengths = colTemp.getCellMonomerLengths(0, 1000)[1];
|
|
219
|
+
expectArray(resLengths, testData.tgt.lengths);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
167
222
|
});
|
|
@@ -4,12 +4,12 @@ import {Bond} from './types';
|
|
|
4
4
|
|
|
5
5
|
export class ConnectionList {
|
|
6
6
|
constructor(connectionList: string) {
|
|
7
|
-
const splitted = connectionList.split(HELM_ITEM_SEPARATOR);
|
|
7
|
+
const splitted = connectionList.split(HELM_ITEM_SEPARATOR).filter((ci) => ci);
|
|
8
8
|
splitted.forEach((connectionItem: string) => this.validateConnectionItem(connectionItem));
|
|
9
9
|
this.connectionItems = splitted;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
public connectionItems: string[];
|
|
13
13
|
|
|
14
14
|
private validateConnectionItem(connectionItem: string): void {
|
|
15
15
|
const allowedType = `(${HELM_POLYMER_TYPE.PEPTIDE}|${HELM_POLYMER_TYPE.RNA})`;
|
|
@@ -18,10 +18,10 @@ export class ConnectionList {
|
|
|
18
18
|
throw new Error(`Cannot parse connection item from ${connectionItem}`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
getConnectionData(): {polymerId: string, bond: Bond}[][] {
|
|
22
|
-
const result: {polymerId: string, bond: Bond}[][] = [];
|
|
21
|
+
getConnectionData(): { polymerId: string, bond: Bond }[][] {
|
|
22
|
+
const result: { polymerId: string, bond: Bond }[][] = [];
|
|
23
23
|
this.connectionItems.forEach((connectionItem: string) => {
|
|
24
|
-
const pair: {polymerId: string, bond: Bond}[] = [];
|
|
24
|
+
const pair: { polymerId: string, bond: Bond }[] = [];
|
|
25
25
|
const splitted = connectionItem.split(',');
|
|
26
26
|
splitted[2].split('-').forEach((item, idx) => {
|
|
27
27
|
const polymerId = splitted[idx];
|
|
@@ -10,8 +10,7 @@ export class Helm {
|
|
|
10
10
|
const simplePolymers = helmSections[0].split(HELM_ITEM_SEPARATOR);
|
|
11
11
|
this.simplePolymers = simplePolymers
|
|
12
12
|
.map((item) => new SimplePolymer(item));
|
|
13
|
-
|
|
14
|
-
this.connectionList = new ConnectionList(helmSections[1]);
|
|
13
|
+
this.connectionList = new ConnectionList(helmSections[1]);
|
|
15
14
|
this.bondData = this.getBondData();
|
|
16
15
|
|
|
17
16
|
this.bondedRGroupsMap = this.getBondedRGroupsMap();
|
|
@@ -22,25 +21,25 @@ export class Helm {
|
|
|
22
21
|
readonly bondData: Bond[][];
|
|
23
22
|
|
|
24
23
|
public readonly simplePolymers: SimplePolymer[];
|
|
25
|
-
public readonly connectionList
|
|
24
|
+
public readonly connectionList: ConnectionList;
|
|
26
25
|
|
|
27
26
|
/** Maps global monomer index to r-group ids (starting from 1) participating
|
|
28
27
|
* in connection */
|
|
29
|
-
readonly bondedRGroupsMap:
|
|
28
|
+
readonly bondedRGroupsMap: number[][];
|
|
30
29
|
|
|
31
|
-
private getBondedRGroupsMap():
|
|
32
|
-
const
|
|
30
|
+
private getBondedRGroupsMap(): number[][] {
|
|
31
|
+
const monomerCount = this.simplePolymers.map((sp) => sp.monomers.length)
|
|
32
|
+
.reduce((a, b) => a + b, 0);
|
|
33
|
+
const bondedRGroupsList: number[][] = Array.from({length: monomerCount}, () => []);
|
|
33
34
|
this.bondData.forEach((bond) => {
|
|
34
35
|
bond.forEach((bondPart) => {
|
|
35
36
|
const monomerIdx = bondPart.monomerIdx;
|
|
36
37
|
const rGroupId = bondPart.rGroupId;
|
|
37
|
-
|
|
38
|
-
bondedRGroupsMap.set(monomerIdx, []);
|
|
39
|
-
bondedRGroupsMap.get(monomerIdx)!.push(rGroupId);
|
|
38
|
+
bondedRGroupsList[monomerIdx].push(rGroupId);
|
|
40
39
|
});
|
|
41
40
|
});
|
|
42
41
|
|
|
43
|
-
return
|
|
42
|
+
return bondedRGroupsList;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
toString() {
|
|
@@ -93,19 +92,17 @@ export class Helm {
|
|
|
93
92
|
this.shiftBondMonomerIds(shift, bondData);
|
|
94
93
|
result.push(...bondData);
|
|
95
94
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
data.push(bond);
|
|
105
|
-
});
|
|
106
|
-
result.push(data);
|
|
95
|
+
const connectionData = this.connectionList.getConnectionData();
|
|
96
|
+
connectionData.forEach((connection) => {
|
|
97
|
+
const data: Bond[] = [];
|
|
98
|
+
connection.forEach((connectionItem) => {
|
|
99
|
+
const shift = shifts[connectionItem.polymerId];
|
|
100
|
+
const bond = connectionItem.bond;
|
|
101
|
+
bond.monomerIdx += shift;
|
|
102
|
+
data.push(bond);
|
|
107
103
|
});
|
|
108
|
-
|
|
104
|
+
result.push(data);
|
|
105
|
+
});
|
|
109
106
|
return result;
|
|
110
107
|
}
|
|
111
108
|
}
|
|
@@ -28,8 +28,7 @@ export class MonomerWrapper {
|
|
|
28
28
|
this.molfileWrapper = MolfileWrapperFactory.getInstance(molfile, monomerSymbol);
|
|
29
29
|
this.capGroupElements = this.getCapGroupElements(libraryMonomerObject);
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
this.removeRGroups(helm.bondedRGroupsMap.get(monomerIdx)!);
|
|
31
|
+
this.removeRGroups(helm.bondedRGroupsMap[monomerIdx]!);
|
|
33
32
|
this.capRemainingRGroups();
|
|
34
33
|
|
|
35
34
|
this.shiftCoordinates(shift);
|
|
@@ -161,12 +161,12 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
161
161
|
getTooltip(biotype: HelmType, monomerSymbol: string): HTMLElement {
|
|
162
162
|
const polymerType = helmTypeToPolymerType(biotype);
|
|
163
163
|
const res = ui.div([], {classes: 'ui-form ui-tooltip'});
|
|
164
|
+
const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
|
|
164
165
|
const monomer = this.getMonomer(polymerType, monomerSymbol);
|
|
165
166
|
if (monomer) {
|
|
166
167
|
// Symbol & Name
|
|
167
168
|
const symbol = monomer[REQ.SYMBOL];
|
|
168
169
|
const _name = monomer[REQ.NAME];
|
|
169
|
-
const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
|
|
170
170
|
|
|
171
171
|
const htmlColor = wem.backgroundcolor;
|
|
172
172
|
res.append(ui.divH([
|
|
@@ -16,7 +16,7 @@ import {MonomerLibManager} from '../lib-manager';
|
|
|
16
16
|
import {LIB_PATH} from '../consts';
|
|
17
17
|
|
|
18
18
|
import '../../../../css/monomer-manager.css';
|
|
19
|
-
import {
|
|
19
|
+
import {MONOMER_RENDERER_TAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer';
|
|
20
20
|
|
|
21
21
|
// columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
|
|
22
22
|
export enum MONOMER_DF_COLUMN_NAMES {
|
|
@@ -51,7 +51,6 @@ export const MONOMER_DF_COLUMNS = {
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
export class MonomerManager implements IMonomerManager {
|
|
54
|
-
|
|
55
54
|
private adjustColWidths() {
|
|
56
55
|
setTimeout(() => {
|
|
57
56
|
if (this.tv?.grid) {
|
|
@@ -87,7 +86,6 @@ export class MonomerManager implements IMonomerManager {
|
|
|
87
86
|
}, 500);
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
|
-
|
|
91
89
|
}, () => this.tv?.dataFrame);
|
|
92
90
|
}
|
|
93
91
|
|
|
@@ -203,6 +201,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
203
201
|
}
|
|
204
202
|
})
|
|
205
203
|
);
|
|
204
|
+
this.tv.grid && (this.tv.grid.props.allowEdit = false); // disable editing
|
|
206
205
|
return this.tv;
|
|
207
206
|
}
|
|
208
207
|
|
|
@@ -239,6 +238,10 @@ export class MonomerManager implements IMonomerManager {
|
|
|
239
238
|
});
|
|
240
239
|
ribbons = ribbons.filter((r) => r.length > 0);
|
|
241
240
|
|
|
241
|
+
const newMonomerButton = ui.icons.add(() => {
|
|
242
|
+
this._newMonomerForm.setEmptyMonomer();
|
|
243
|
+
}, 'Add New Monomer');
|
|
244
|
+
|
|
242
245
|
const editButton = ui.icons.edit(() => {
|
|
243
246
|
if ((this.tv?.dataFrame?.currentRowIdx ?? -1) < 0) return;
|
|
244
247
|
this.cloneMonomer(this.tv!.dataFrame.rows.get(this.tv!.dataFrame.currentRowIdx));
|
|
@@ -277,19 +280,19 @@ export class MonomerManager implements IMonomerManager {
|
|
|
277
280
|
DG.Utils.download(libName!, lib!, 'text/plain');
|
|
278
281
|
}, 'Download Monomer Library');
|
|
279
282
|
|
|
280
|
-
ribbons.push([editButton, deleteButton, downloadButton]);
|
|
283
|
+
ribbons.push([newMonomerButton, editButton, deleteButton, downloadButton]);
|
|
281
284
|
this.tv.setRibbonPanels(ribbons);
|
|
282
285
|
|
|
283
286
|
|
|
284
287
|
this.tv.name = MonomerManager.VIEW_NAME;
|
|
285
288
|
this.libInput = ui.input.choice('Monomer Library', {value: libName, items: availableMonLibs, nullable: false, onValueChanged: async () => {
|
|
286
|
-
|
|
287
|
-
|
|
289
|
+
try {
|
|
290
|
+
const df = await this.getMonomersDf(this.libInput.value!);
|
|
288
291
|
this.tv!.dataFrame = df;
|
|
289
292
|
this.adjustColWidths();
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
} catch (e) {
|
|
294
|
+
console.error(e);
|
|
295
|
+
}
|
|
293
296
|
}});
|
|
294
297
|
this.libInput.addOptions(ui.icons.add(() => { this.createNewLibDialog(); }, 'Create new monomer library...'));
|
|
295
298
|
const monForm = this._newMonomerForm.form;
|
|
@@ -370,6 +373,7 @@ export class MonomerManager implements IMonomerManager {
|
|
|
370
373
|
df.col(rgName)!.semType = DG.SEMTYPE.MOLECULE;
|
|
371
374
|
});
|
|
372
375
|
df.currentRowIdx = -1;
|
|
376
|
+
// eslint-disable-next-line rxjs/no-ignored-subscription
|
|
373
377
|
df.onCurrentRowChanged.subscribe((_) => {
|
|
374
378
|
try {
|
|
375
379
|
if (df.currentRowIdx === -1 || this._newMonomerForm.molChanged)
|
|
@@ -541,6 +545,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
541
545
|
await this.saveMonomer();
|
|
542
546
|
});
|
|
543
547
|
// this.saveButton.style.pointerEvents = 'revert';
|
|
548
|
+
// eslint-disable-next-line rxjs/no-async-subscribe
|
|
544
549
|
this.molSketcher.subs.push(this.molSketcher.onChanged.subscribe(async () => {
|
|
545
550
|
if (!this.triggerMolChange) {
|
|
546
551
|
this.triggerMolChange = true;
|
|
@@ -551,6 +556,9 @@ class MonomerForm implements INewMonomerForm {
|
|
|
551
556
|
let smiles = this.molSketcher.getSmiles();
|
|
552
557
|
if (!smiles) {
|
|
553
558
|
this.rgroupsGrid.items = [];
|
|
559
|
+
this.rgroupsGrid.render();
|
|
560
|
+
this.saveValidationResult = 'Monomer molecule is required';
|
|
561
|
+
this.invalidateSaveButton();
|
|
554
562
|
return;
|
|
555
563
|
}
|
|
556
564
|
smiles = getCorrectedSmiles([], smiles);
|
|
@@ -559,6 +567,8 @@ class MonomerForm implements INewMonomerForm {
|
|
|
559
567
|
if (rGroupMatches.length === 0) {
|
|
560
568
|
this.rgroupsGrid.items = [];
|
|
561
569
|
this.rgroupsGrid.render();
|
|
570
|
+
this.saveValidationResult = 'At least one R-group is required';
|
|
571
|
+
this.invalidateSaveButton();
|
|
562
572
|
return;
|
|
563
573
|
}
|
|
564
574
|
const rGroupNums = rGroupMatches.map((match) => Number.parseInt(match[0].match(/[1-9]/g)![0]));
|
|
@@ -606,6 +616,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
606
616
|
[HELM_RGROUP_FIELDS.LABEL]: 'Label',
|
|
607
617
|
},
|
|
608
618
|
});
|
|
619
|
+
// eslint-disable-next-line rxjs/no-ignored-subscription
|
|
609
620
|
this.rgroupsGrid.onItemChanged.subscribe(() => this.onMonomerInputChanged());
|
|
610
621
|
|
|
611
622
|
this.rgroupsGridRoot = ui.divV([this.rgroupsGrid.root]);
|
|
@@ -633,17 +644,19 @@ class MonomerForm implements INewMonomerForm {
|
|
|
633
644
|
'Meta': ui.divV([this.metaGrid.root]),
|
|
634
645
|
'Colors': this.colorsEditor.form,
|
|
635
646
|
}, false);
|
|
647
|
+
}
|
|
636
648
|
|
|
649
|
+
invalidateSaveButton() {
|
|
650
|
+
if (this.saveValidationResult)
|
|
651
|
+
this.saveButton.classList.add('d4-disabled');
|
|
652
|
+
else
|
|
653
|
+
this.saveButton.classList.remove('d4-disabled');
|
|
637
654
|
}
|
|
638
655
|
|
|
639
656
|
onMonomerInputChanged() {
|
|
640
657
|
setTimeout(() => {
|
|
641
658
|
this.saveValidationResult = this.validateInputs();
|
|
642
|
-
|
|
643
|
-
this.saveButton.classList.add('d4-disabled');
|
|
644
|
-
else
|
|
645
|
-
this.saveButton.classList.remove('d4-disabled');
|
|
646
|
-
|
|
659
|
+
this.invalidateSaveButton();
|
|
647
660
|
const monomerExists = this.polymerTypeInput.value && this.polymerTypeInput.value &&
|
|
648
661
|
!!this.getMonomerLib()?.getMonomer(this.polymerTypeInput.value as PolymerType, this.monomerSymbolInput.value);
|
|
649
662
|
|
|
@@ -651,6 +664,27 @@ class MonomerForm implements INewMonomerForm {
|
|
|
651
664
|
}, 200);
|
|
652
665
|
}
|
|
653
666
|
|
|
667
|
+
setEmptyMonomer() {
|
|
668
|
+
this.triggerMolChange = false;
|
|
669
|
+
this.molSketcher.setSmiles('');
|
|
670
|
+
// leave polymer and monomer type as is
|
|
671
|
+
this.monomerSymbolInput.value = '';
|
|
672
|
+
this.monomerNameInput.value = '';
|
|
673
|
+
this.monomerIdInput.value = null;
|
|
674
|
+
this.monomerNaturalAnalogInput.value = null;
|
|
675
|
+
this.rgroupsGrid.items = [];
|
|
676
|
+
this.metaGrid.items = [];
|
|
677
|
+
this.rgroupsGrid.render();
|
|
678
|
+
this.metaGrid.render();
|
|
679
|
+
this.rgroupsGridRoot.style.display = 'none';
|
|
680
|
+
this.onMonomerInputChanged();
|
|
681
|
+
this.colorsEditor.colors = {
|
|
682
|
+
line: '#000000',
|
|
683
|
+
background: '#000000',
|
|
684
|
+
text: '#000000',
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
654
688
|
setMonomer(monomer: Monomer) {
|
|
655
689
|
this.triggerMolChange = false;
|
|
656
690
|
this.molSketcher.setSmiles(monomer.smiles);
|
|
@@ -661,7 +695,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
661
695
|
this.monomerIdInput.value = monomer.id;
|
|
662
696
|
this.monomerNaturalAnalogInput.value = monomer.naturalAnalog ?? null;
|
|
663
697
|
this.rgroupsGrid.items = resolveRGroupInfo(monomer.rgroups);
|
|
664
|
-
this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k,
|
|
698
|
+
this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, _v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
|
|
665
699
|
return {Property: k, Value: v};
|
|
666
700
|
});
|
|
667
701
|
this.rgroupsGrid.render();
|
|
@@ -681,7 +715,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
681
715
|
} catch (e) {
|
|
682
716
|
console.error(e);
|
|
683
717
|
}
|
|
684
|
-
|
|
718
|
+
|
|
685
719
|
this.colorsEditor.colors = {
|
|
686
720
|
line: colorsObj.line ?? '#000000',
|
|
687
721
|
background: colorsObj.background ?? '#000000',
|
|
@@ -693,7 +727,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
693
727
|
const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
|
|
694
728
|
rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
|
|
695
729
|
if (!this.molSketcher.getSmiles()) return 'Monomer Molecule field is required';
|
|
696
|
-
for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput
|
|
730
|
+
for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput]) {
|
|
697
731
|
if (i.value == null || i.value === '')
|
|
698
732
|
return `${i.caption} field is required`;
|
|
699
733
|
}
|
|
@@ -703,17 +737,18 @@ class MonomerForm implements INewMonomerForm {
|
|
|
703
737
|
if (!rgroupError) {
|
|
704
738
|
outerFor:
|
|
705
739
|
for (const item of this.rgroupsGrid.items) {
|
|
706
|
-
for (const [k, v] of Object.entries(item))
|
|
707
|
-
if (!v){
|
|
740
|
+
for (const [k, v] of Object.entries(item)) {
|
|
741
|
+
if (!v) {
|
|
708
742
|
rgroupError = `R-group ${k} field is required for ${item[HELM_RGROUP_FIELDS.LABEL]}`;
|
|
709
743
|
break outerFor;
|
|
710
|
-
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
711
746
|
}
|
|
712
747
|
}
|
|
713
748
|
|
|
714
|
-
if (!rgroupError && this.rgroupsGrid.hasErrors())
|
|
749
|
+
if (!rgroupError && this.rgroupsGrid.hasErrors())
|
|
715
750
|
rgroupError = 'R-group fields contain errors';
|
|
716
|
-
|
|
751
|
+
|
|
717
752
|
if (rgroupError) {
|
|
718
753
|
rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
|
|
719
754
|
return rgroupError;
|
|
@@ -730,7 +765,6 @@ class MonomerForm implements INewMonomerForm {
|
|
|
730
765
|
}
|
|
731
766
|
|
|
732
767
|
get form() {
|
|
733
|
-
|
|
734
768
|
this.inputsTabControl.root.classList.add('monomer-manager-form-tab-control');
|
|
735
769
|
this.inputsTabControl.header.style.marginBottom = '10px';
|
|
736
770
|
const saveB = ui.buttonsInput([this.saveButton]);
|
|
@@ -826,11 +860,11 @@ class MonomerForm implements INewMonomerForm {
|
|
|
826
860
|
try {
|
|
827
861
|
// first remove the existing monomer with that symbol
|
|
828
862
|
const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
|
|
829
|
-
if (monomerIdx >= 0)
|
|
863
|
+
if (monomerIdx >= 0)
|
|
830
864
|
libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
|
|
831
|
-
|
|
865
|
+
else
|
|
832
866
|
libJSON.push({...monomer, lib: undefined, wem: undefined});
|
|
833
|
-
|
|
867
|
+
|
|
834
868
|
await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
|
|
835
869
|
await (await MonomerLibManager.getInstance()).loadLibraries(true);
|
|
836
870
|
await this.refreshTable(monomer.symbol);
|
|
@@ -839,6 +873,7 @@ class MonomerForm implements INewMonomerForm {
|
|
|
839
873
|
grok.shell.error('Error saving monomer');
|
|
840
874
|
console.error(e);
|
|
841
875
|
}
|
|
876
|
+
this.onMonomerInputChanged();
|
|
842
877
|
};
|
|
843
878
|
let infoTable: HTMLDivElement | null = null;
|
|
844
879
|
let promptMessage = '';
|
|
@@ -882,9 +917,9 @@ class MonomerForm implements INewMonomerForm {
|
|
|
882
917
|
meta[item['Property']] = item['Value'];
|
|
883
918
|
});
|
|
884
919
|
const addingItem = this.metaGrid.addingItem;
|
|
885
|
-
if (addingItem && addingItem['Property'] && addingItem['Value'])
|
|
920
|
+
if (addingItem && addingItem['Property'] && addingItem['Value'])
|
|
886
921
|
meta[addingItem['Property']] = addingItem['Value'];
|
|
887
|
-
|
|
922
|
+
|
|
888
923
|
//console.log(this.metaGrid.addingItem);
|
|
889
924
|
if (this.colorsEditor.colors.line !== '#000000' || this.colorsEditor.colors.background !== '#000000' || this.colorsEditor.colors.text !== '#000000')
|
|
890
925
|
meta.colors = {default: this.colorsEditor.colors};
|
|
@@ -975,7 +1010,7 @@ function getCorrectedMolBlock(molBlock: string) {
|
|
|
975
1010
|
|
|
976
1011
|
if (rgpLineIdx === -1) {
|
|
977
1012
|
// number of r groups has 3 empty slots before it, atom numbers have 4 empty slots before them
|
|
978
|
-
|
|
1013
|
+
const rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3, ' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
|
|
979
1014
|
const mEndIdx = lines.findIndex((line) => line.startsWith('M') && line.includes('END'));
|
|
980
1015
|
lines.splice(mEndIdx, 0, rgpLine);
|
|
981
1016
|
}
|
|
@@ -1042,9 +1077,8 @@ class ColorsEditor {
|
|
|
1042
1077
|
};
|
|
1043
1078
|
|
|
1044
1079
|
this._colors = colsHex;
|
|
1045
|
-
for (const key in this._colorInputs)
|
|
1080
|
+
for (const key in this._colorInputs)
|
|
1046
1081
|
this._colorInputs[key as keyof ColorsEditor['_colors']].value = colsHex[key as keyof ColorsEditor['_colors']];
|
|
1047
|
-
}
|
|
1048
1082
|
}
|
|
1049
1083
|
|
|
1050
1084
|
get colorsMetaFormat() {
|
|
@@ -1054,4 +1088,4 @@ class ColorsEditor {
|
|
|
1054
1088
|
get form() {
|
|
1055
1089
|
return ui.form(Object.values(this._colorInputs));
|
|
1056
1090
|
}
|
|
1057
|
-
}
|
|
1091
|
+
}
|