@datagrok/bio 2.16.4 → 2.16.6
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 +23 -1
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/package.json +2 -2
- package/src/package.ts +2 -1
- package/src/tests/monomer-libraries-tests.ts +1 -0
- package/src/tests/seq-handler-get-helm-tests.ts +2 -2
- package/src/utils/helm-to-molfile/converter/converter.ts +12 -33
- package/src/utils/helm-to-molfile/converter/simple-polymer.ts +4 -2
- package/src/utils/monomer-lib/monomer-lib-base.ts +4 -0
- package/src/utils/monomer-lib/monomer-lib.ts +3 -5
- package/src/utils/monomer-lib/monomer-manager/monomer-manager.ts +66 -32
- package/src/utils/seq-helper/seq-handler.ts +7 -9
- package/src/utils/seq-helper/seq-helper.ts +1 -1
- package/src/viewers/web-logo-viewer.ts +19 -21
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.6",
|
|
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,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@biowasm/aioli": "^3.1.0",
|
|
40
|
-
"@datagrok-libraries/bio": "^5.45.
|
|
40
|
+
"@datagrok-libraries/bio": "^5.45.4",
|
|
41
41
|
"@datagrok-libraries/chem-meta": "^1.2.7",
|
|
42
42
|
"@datagrok-libraries/math": "^1.2.2",
|
|
43
43
|
"@datagrok-libraries/ml": "^6.7.4",
|
package/src/package.ts
CHANGED
|
@@ -27,6 +27,7 @@ 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
29
|
import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
30
|
+
import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
|
|
30
31
|
|
|
31
32
|
import {getMacromoleculeColumns} from './utils/ui-utils';
|
|
32
33
|
import {MacromoleculeDifferenceCellRenderer, MacromoleculeSequenceCellRenderer,} from './utils/cell-renderer';
|
|
@@ -630,7 +631,7 @@ export async function toAtomicLevel(
|
|
|
630
631
|
const pi = DG.TaskBarProgressIndicator.create('Converting to atomic level ...');
|
|
631
632
|
try {
|
|
632
633
|
await initBioPromise;
|
|
633
|
-
const monomerLib = _package.monomerLib;
|
|
634
|
+
const monomerLib = seqCol.temp[MmcrTemps.overriddenLibrary] ?? _package.monomerLib;
|
|
634
635
|
const seqHelper = _package.seqHelper;
|
|
635
636
|
const rdKitModule = _package.rdKitModule;
|
|
636
637
|
await sequenceToMolfile(table, seqCol, nonlinear, highlight, monomerLib, seqHelper, rdKitModule);
|
|
@@ -85,6 +85,7 @@ category('monomerLibraries', () => {
|
|
|
85
85
|
|
|
86
86
|
const overriddenMonomerLib = monomerLib.override({[overMon.polymerType]: {[overMon.symbol]: overMon}}, 'test');
|
|
87
87
|
const resOverMon = overriddenMonomerLib.getMonomer(overMon.polymerType, overMon.symbol);
|
|
88
|
+
if (resOverMon) resOverMon.lib = undefined; // cleanup to prevent infinite recursive comparison
|
|
88
89
|
expectObject(resOverMon as any, overMon);
|
|
89
90
|
});
|
|
90
91
|
});
|
|
@@ -81,8 +81,8 @@ category('SeqHandler: getHelm', () => {
|
|
|
81
81
|
await grok.data.detectSemanticTypes(df);
|
|
82
82
|
|
|
83
83
|
const sh = seqHelper.getSeqHandler(seqCol);
|
|
84
|
-
const
|
|
85
|
-
const resHelm =
|
|
84
|
+
const resMValue = await sh.getValue(0);
|
|
85
|
+
const resHelm = resMValue.helm;
|
|
86
86
|
expect(resHelm, tgtHelm);
|
|
87
87
|
}
|
|
88
88
|
});
|
|
@@ -94,53 +94,33 @@ export class HelmToMolfileConverter {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
public convertToMolfileV3KColumn(
|
|
98
|
-
helmCol: DG.Column<string>
|
|
99
|
-
): DG.Column<string> {
|
|
97
|
+
public convertToMolfileV3KColumn(helmCol: DG.Column<string>): DG.Column<string> {
|
|
100
98
|
const df = helmCol.dataFrame;
|
|
101
|
-
const
|
|
102
|
-
const molfileList = polymerGraphColumn.toList().map(
|
|
103
|
-
(pseudoMolfile: string, idx: number) => {
|
|
104
|
-
const helm = helmCol.get(idx);
|
|
105
|
-
if (!helm) return '';
|
|
106
|
-
|
|
107
|
-
let resMolfileWithMap: MolfileWithMap;
|
|
108
|
-
try {
|
|
109
|
-
resMolfileWithMap = this.getPolymerMolfile(helm, pseudoMolfile);
|
|
110
|
-
} catch (err: any) {
|
|
111
|
-
const [errMsg, errStack] = errInfo(err);
|
|
112
|
-
_package.logger.error(errMsg, undefined, errStack);
|
|
113
|
-
resMolfileWithMap = MolfileWithMap.createEmpty();
|
|
114
|
-
}
|
|
115
|
-
return resMolfileWithMap.molfile;
|
|
116
|
-
});
|
|
99
|
+
const molfileList = this.convertToMolfileV3K(helmCol.toList()).map((mwm) => mwm.molfile);
|
|
117
100
|
const molColName = getUnusedColName(df, `molfileV2K(${helmCol.name})`);
|
|
118
101
|
const molfileColumn = DG.Column.fromList('string', molColName, molfileList);
|
|
119
102
|
return molfileColumn;
|
|
120
103
|
}
|
|
121
104
|
|
|
122
105
|
/** Gets list of monomer molfiles */
|
|
123
|
-
public convertToMolfileV3K(
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (let rowIdx = 0; rowIdx < rowCount; ++rowIdx) {
|
|
128
|
-
const helm = helmCol.get(rowIdx);
|
|
106
|
+
public convertToMolfileV3K(helmList: string[]): MolfileWithMap[] {
|
|
107
|
+
const resList: MolfileWithMap[] = new Array<MolfileWithMap>(helmList.length);
|
|
108
|
+
for (let i = 0; i < helmList.length; ++i) {
|
|
109
|
+
const helm = helmList[i];
|
|
129
110
|
if (!helm) {
|
|
130
|
-
resList[
|
|
111
|
+
resList[i] = MolfileWithMap.createEmpty();
|
|
131
112
|
continue;
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
const pseudoMolfile = polymerGraphColumn.get(rowIdx)!;
|
|
135
115
|
let resMolfile: MolfileWithMap;
|
|
136
116
|
try {
|
|
137
|
-
resMolfile = this.getPolymerMolfile(helm
|
|
117
|
+
resMolfile = this.getPolymerMolfile(helm);
|
|
138
118
|
} catch (err: any) {
|
|
139
119
|
const [errMsg, errStack] = errInfo(err);
|
|
140
120
|
_package.logger.error(errMsg, undefined, errStack);
|
|
141
121
|
resMolfile = MolfileWithMap.createEmpty();
|
|
142
122
|
}
|
|
143
|
-
resList[
|
|
123
|
+
resList[i] = resMolfile;
|
|
144
124
|
}
|
|
145
125
|
return resList;
|
|
146
126
|
}
|
|
@@ -152,16 +132,15 @@ export class HelmToMolfileConverter {
|
|
|
152
132
|
return molfileCol;
|
|
153
133
|
}
|
|
154
134
|
|
|
155
|
-
private getPolymerMolfile(
|
|
156
|
-
helm: string, polymerGraph: string
|
|
157
|
-
): MolfileWithMap {
|
|
135
|
+
private getPolymerMolfile(helm: string): MolfileWithMap {
|
|
158
136
|
const woGapsRes = this.helmHelper.removeGaps(helm);
|
|
159
137
|
const woGapsHelm = woGapsRes.resHelm;
|
|
160
138
|
const woGapsReverseMap = new Map<number, number>();
|
|
161
139
|
for (const [orgPosIdx, woGapsPosIdx] of (woGapsRes.monomerMap?.entries() ?? [])) {
|
|
162
140
|
woGapsReverseMap.set(woGapsPosIdx, orgPosIdx);
|
|
163
141
|
}
|
|
164
|
-
const
|
|
142
|
+
const pseudoMolfile = this.helmHelper.getMolfiles([woGapsHelm])[0];
|
|
143
|
+
const globalPositionHandler = new GlobalMonomerPositionHandler(pseudoMolfile);
|
|
165
144
|
const woGapsPolymer = new Polymer(woGapsHelm, this.rdKitModule, this.monomerLib);
|
|
166
145
|
globalPositionHandler.monomerSymbols.forEach((monomerSymbol: string, monomerIdx: number) => {
|
|
167
146
|
const shift = globalPositionHandler.getMonomerShifts(monomerIdx);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {HELM_MONOMER_TYPE, HELM_POLYMER_TYPE} from '@datagrok-libraries/bio/src/utils/const';
|
|
2
|
+
import {cleanupHelmSymbol} from '@datagrok-libraries/bio/src/helm/utils';
|
|
3
|
+
|
|
2
4
|
import {Bond} from './types';
|
|
3
5
|
|
|
4
6
|
/** Wrapper over simple polymer substring of HELM, like RNA1{d(A)p} */
|
|
@@ -41,7 +43,7 @@ export class SimplePolymer {
|
|
|
41
43
|
return id;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
private getMonomerSymbolsAndTypes(): {monomers: string[], monomerTypes: HELM_MONOMER_TYPE[]} {
|
|
46
|
+
private getMonomerSymbolsAndTypes(): { monomers: string[], monomerTypes: HELM_MONOMER_TYPE[] } {
|
|
45
47
|
const helmWrapperRegex = new RegExp(`${this.polymerType}${this.idx}{|}`, 'g');
|
|
46
48
|
const monomerGroups = this.simplePolymer.replace(helmWrapperRegex, '').split('.');
|
|
47
49
|
const monomerList: string[] = [];
|
|
@@ -51,7 +53,7 @@ export class SimplePolymer {
|
|
|
51
53
|
// monomerList.push(...splitted);
|
|
52
54
|
// WARNING: only the groups of the form r(A)p, as in RNA, are supported
|
|
53
55
|
|
|
54
|
-
monomerList.push(monomerGroup);
|
|
56
|
+
monomerList.push(cleanupHelmSymbol(monomerGroup));
|
|
55
57
|
// const monomerTypes = splitted.map(
|
|
56
58
|
// (_, idx) => (idx % 2 === 0) ? HELM_MONOMER_TYPE.BACKBONE : HELM_MONOMER_TYPE.BRANCH
|
|
57
59
|
// );
|
|
@@ -46,6 +46,10 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
46
46
|
) {
|
|
47
47
|
this._isEmpty = !this._monomers || Object.keys(this._monomers).length === 0 ||
|
|
48
48
|
Object.entries(this._monomers).every(([_, ptMonomers]) => Object.keys(ptMonomers).length === 0);
|
|
49
|
+
for (const [_monomerType, monomersOfType] of Object.entries(this._monomers)) {
|
|
50
|
+
for (const [_monomerSymbol, monomer] of Object.entries(monomersOfType))
|
|
51
|
+
monomer.lib = this;
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
getMonomerSymbolsByType(polymerType: PolymerType): string[] {
|
|
@@ -43,10 +43,6 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
|
|
|
43
43
|
public readonly error: string | undefined = undefined,
|
|
44
44
|
) {
|
|
45
45
|
super(monomers, source);
|
|
46
|
-
for (const [_monomerType, monomersOfType] of Object.entries(this._monomers)) {
|
|
47
|
-
for (const [_monomerSymbol, monomer] of Object.entries(monomersOfType))
|
|
48
|
-
monomer.lib = this;
|
|
49
|
-
}
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
toJSON(): Monomer[] {
|
|
@@ -237,8 +233,10 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
|
|
|
237
233
|
return resStr;
|
|
238
234
|
}
|
|
239
235
|
|
|
236
|
+
static overrideCounter: number = 0;
|
|
237
|
+
|
|
240
238
|
override(data: MonomerLibData, source: string): IMonomerLibBase {
|
|
241
|
-
return new OverriddenMonomerLib(data,
|
|
239
|
+
return new OverriddenMonomerLib(data, `override: ${++MonomerLib.overrideCounter}, ${source}`, this);
|
|
242
240
|
}
|
|
243
241
|
}
|
|
244
242
|
|
|
@@ -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
|
+
}
|
|
@@ -14,7 +14,7 @@ import {GAP_SYMBOL, GapOriginals} from '@datagrok-libraries/bio/src/utils/macrom
|
|
|
14
14
|
import {CellRendererBackBase, GridCellRendererTemp} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
|
|
15
15
|
import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
16
16
|
import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
|
|
17
|
-
import {ISeqHandler, ConvertFunc, JoinerFunc, SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
17
|
+
import {ISeqHandler, ConvertFunc, JoinerFunc, SeqTemps, MacromoleculeValueBase} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
18
18
|
|
|
19
19
|
import {SeqHelper} from './seq-helper';
|
|
20
20
|
|
|
@@ -241,18 +241,16 @@ export class SeqHandler implements ISeqHandler {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/** Any Macromolecule can be represented on Helm format. The reverse is not always possible. */
|
|
244
|
-
public async
|
|
244
|
+
public async getValue(rowIdx: number, options?: any): Promise<MacromoleculeValueBase> {
|
|
245
245
|
const seq: string = this.column.get(rowIdx);
|
|
246
|
-
let
|
|
246
|
+
let resHelm: string;
|
|
247
247
|
if (this.notationProvider)
|
|
248
|
-
|
|
248
|
+
resHelm = await this.notationProvider.getHelm(seq, options);
|
|
249
249
|
else {
|
|
250
|
-
|
|
251
|
-
resHelmSV = DG.SemanticValue.fromValueType(resHelm, DG.SEMTYPE.MACROMOLECULE, NOTATION.HELM);
|
|
252
|
-
// TODO: set tags from column
|
|
250
|
+
resHelm = this.convertToHelm(seq);
|
|
253
251
|
}
|
|
254
|
-
|
|
255
|
-
return
|
|
252
|
+
const resMValue = new MacromoleculeValueBase(resHelm, this, rowIdx);
|
|
253
|
+
return resMValue;
|
|
256
254
|
}
|
|
257
255
|
|
|
258
256
|
private _stats: SeqColStats | null = null;
|
|
@@ -56,7 +56,7 @@ export class SeqHelper implements ISeqHelper {
|
|
|
56
56
|
|
|
57
57
|
//#region From HelmToMolfileConverter.convertToRdKitBeautifiedMolfileColumn
|
|
58
58
|
|
|
59
|
-
const molfilesV3K = converter.convertToMolfileV3K(helmCol
|
|
59
|
+
const molfilesV3K = converter.convertToMolfileV3K(helmCol.toList());
|
|
60
60
|
|
|
61
61
|
const beautifiedMolList: (RDMol | null)[] = molfilesV3K.map((item) => {
|
|
62
62
|
const molfile = item.molfile;
|
|
@@ -395,14 +395,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
395
395
|
|
|
396
396
|
// -- Data --
|
|
397
397
|
this.sequenceColumnName = this.string(PROPS.sequenceColumnName, defaults.sequenceColumnName,
|
|
398
|
-
{category: PROPS_CATS.DATA});
|
|
398
|
+
{category: PROPS_CATS.DATA, semType: DG.SEMTYPE.MACROMOLECULE});
|
|
399
399
|
const aggExcludeList = [DG.AGG.KEY, DG.AGG.PIVOT, DG.AGG.MISSING_VALUE_COUNT, DG.AGG.SKEW, DG.AGG.KURT,
|
|
400
400
|
DG.AGG.SELECTED_ROWS_COUNT];
|
|
401
401
|
const aggChoices = Object.values(DG.AGG).filter((agg) => !aggExcludeList.includes(agg));
|
|
402
402
|
this.valueAggrType = this.string(PROPS.valueAggrType, defaults.valueAggrType,
|
|
403
403
|
{category: PROPS_CATS.DATA, choices: aggChoices}) as DG.AggregationType;
|
|
404
404
|
this.valueColumnName = this.string(PROPS.valueColumnName, defaults.valueColumnName,
|
|
405
|
-
{category: PROPS_CATS.DATA});
|
|
405
|
+
{category: PROPS_CATS.DATA, columnTypeFilter: 'numerical'});
|
|
406
406
|
this.startPositionName = this.string(PROPS.startPositionName, defaults.startPositionName,
|
|
407
407
|
{category: PROPS_CATS.DATA});
|
|
408
408
|
this.endPositionName = this.string(PROPS.endPositionName, defaults.endPositionName,
|
|
@@ -1022,20 +1022,18 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1022
1022
|
|
|
1023
1023
|
// 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
|
|
1024
1024
|
const dfRowCount = this.dataFrame.rowCount;
|
|
1025
|
-
|
|
1025
|
+
const filterIndexes = dfFilter.getSelectedIndexes();
|
|
1026
1026
|
for (let jPos = 0; jPos < length; ++jPos) {
|
|
1027
1027
|
// Here we want to build lists of values for every monomer in position jPos
|
|
1028
|
-
for (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
pmi.value = ++pmi.rowCount;
|
|
1038
|
-
}
|
|
1028
|
+
for (const rowI of filterIndexes) {
|
|
1029
|
+
const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
|
|
1030
|
+
const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
|
|
1031
|
+
this.seqHandler.defaultGapOriginal;
|
|
1032
|
+
const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
|
|
1033
|
+
const pi = this.positions[jPos];
|
|
1034
|
+
const pmi = pi.getFreq(cm);
|
|
1035
|
+
++pi.sumRowCount;
|
|
1036
|
+
pmi.value = ++pmi.rowCount;
|
|
1039
1037
|
}
|
|
1040
1038
|
if (this.valueAggrType === DG.AGG.TOTAL_COUNT) continue;
|
|
1041
1039
|
|
|
@@ -1048,13 +1046,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1048
1046
|
} catch { valueCol = null; }
|
|
1049
1047
|
if (!valueCol) continue; // fallback to TOTAL_COUNT
|
|
1050
1048
|
|
|
1051
|
-
for (
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1049
|
+
for (const rowI of filterIndexes) {
|
|
1050
|
+
const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
|
|
1051
|
+
const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
|
|
1052
|
+
this.seqHandler.defaultGapOriginal;
|
|
1053
|
+
const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
|
|
1054
|
+
const value: number | null = valueCol.get(rowI);
|
|
1055
|
+
this.positions[jPos].getFreq(cm).push(value);
|
|
1058
1056
|
}
|
|
1059
1057
|
this.positions[jPos].aggregate(this.valueAggrType);
|
|
1060
1058
|
}
|