@datagrok/bio 2.12.0 → 2.12.2
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/CHANGELOG.md +13 -1
- package/dist/36.js +1 -1
- package/dist/36.js.map +1 -1
- package/dist/709.js +1 -1
- package/dist/709.js.map +1 -1
- package/dist/package-test.js +3 -3
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/files/tests/libraries/HELMmonomerSchema.json +1 -1
- package/package.json +3 -3
- package/src/analysis/sequence-space.ts +7 -24
- package/src/package.ts +4 -7
- package/src/tests/substructure-filters-tests.ts +143 -1
- package/src/utils/poly-tool/ui.ts +46 -135
- package/src/widgets/bio-substructure-filter-types.ts +19 -45
- package/src/widgets/bio-substructure-filter.ts +42 -20
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"id": {
|
|
23
23
|
"description": "Unique ID for the monomer. There is no meaning associated with this ID value.",
|
|
24
|
-
"type": "integer"
|
|
24
|
+
"type": ["string", "integer"]
|
|
25
25
|
},
|
|
26
26
|
"rgroups": {
|
|
27
27
|
"description": "An array of the monomer R groups and required information.",
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Aleksandr Tanas",
|
|
6
6
|
"email": "atanas@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.12.
|
|
8
|
+
"version": "2.12.2",
|
|
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",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"@biowasm/aioli": "^3.1.0",
|
|
37
37
|
"@datagrok-libraries/bio": "5.40.0",
|
|
38
38
|
"@datagrok-libraries/chem-meta": "^1.2.3",
|
|
39
|
-
"@datagrok-libraries/ml": "^6.4.
|
|
39
|
+
"@datagrok-libraries/ml": "^6.4.13",
|
|
40
40
|
"@datagrok-libraries/tutorials": "^1.3.12",
|
|
41
41
|
"ajv": "^8.12.0",
|
|
42
42
|
"ajv-errors": "^3.0.0",
|
|
43
|
-
"@datagrok-libraries/utils": "^4.
|
|
43
|
+
"@datagrok-libraries/utils": "^4.2.0",
|
|
44
44
|
"@datagrok-libraries/math": "^1.0.7",
|
|
45
45
|
"cash-dom": "^8.0.0",
|
|
46
46
|
"css-loader": "^6.7.3",
|
|
@@ -20,7 +20,7 @@ export async function getEncodedSeqSpaceCol(
|
|
|
20
20
|
const rowCount = seqCol.length;
|
|
21
21
|
const sh = SeqHandler.forColumn(seqCol);
|
|
22
22
|
const encList = Array<string>(rowCount);
|
|
23
|
-
let charCodeCounter =
|
|
23
|
+
let charCodeCounter = 1; // start at 1, 0 is reserved for null.
|
|
24
24
|
const charCodeMap = new Map<string, string>();
|
|
25
25
|
const seqColCats = seqCol.categories;
|
|
26
26
|
const seqColRawData = seqCol.getRawData();
|
|
@@ -28,7 +28,7 @@ export async function getEncodedSeqSpaceCol(
|
|
|
28
28
|
const catI = seqColRawData[rowIdx];
|
|
29
29
|
const seq = seqColCats[catI];
|
|
30
30
|
if (seq === null || seqCol.isNone(rowIdx)) {
|
|
31
|
-
|
|
31
|
+
//@ts-ignore
|
|
32
32
|
encList[rowIdx] = null;
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
@@ -44,30 +44,13 @@ export async function getEncodedSeqSpaceCol(
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
let options = {} as mmDistanceFunctionArgs;
|
|
47
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
similarityMetric === MmDistanceFunctionsNames.MONOMER_CHEMICAL_DISTANCE ||
|
|
49
|
+
similarityMetric === MmDistanceFunctionsNames.NEEDLEMANN_WUNSCH
|
|
50
|
+
) {
|
|
48
51
|
const monomers = Array.from(charCodeMap.keys());
|
|
49
52
|
const monomerRes = await getMonomerSubstitutionMatrix(monomers, fingerprintType);
|
|
50
|
-
|
|
51
|
-
monomerRes.scoringMatrix.forEach((row, i) => {
|
|
52
|
-
row.forEach((val, j) => {
|
|
53
|
-
monomerRes.scoringMatrix[i][j] = 1 - val;
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
const monomerHashToMatrixMap: { [_: string]: number } = {};
|
|
57
|
-
Object.entries(monomerRes.alphabetIndexes).forEach(([key, value]) => {
|
|
58
|
-
monomerHashToMatrixMap[charCodeMap.get(key)!] = value;
|
|
59
|
-
});
|
|
60
|
-
// sets distance function args in place.
|
|
61
|
-
options = {scoringMatrix: monomerRes.scoringMatrix, alphabetIndexes: monomerHashToMatrixMap};
|
|
62
|
-
} else if (similarityMetric === MmDistanceFunctionsNames.NEEDLEMANN_WUNSCH) {
|
|
63
|
-
const monomers = Array.from(charCodeMap.keys());
|
|
64
|
-
const monomerRes = await getMonomerSubstitutionMatrix(monomers, fingerprintType);
|
|
65
|
-
// the susbstitution matrix contains similarity, but we need distances
|
|
66
|
-
// monomerRes.scoringMatrix.forEach((row, i) => {
|
|
67
|
-
// row.forEach((val, j) => {
|
|
68
|
-
// monomerRes.scoringMatrix[i][j] = 1 - val;
|
|
69
|
-
// });
|
|
70
|
-
// });
|
|
53
|
+
|
|
71
54
|
const monomerHashToMatrixMap: { [_: string]: number } = {};
|
|
72
55
|
Object.entries(monomerRes.alphabetIndexes).forEach(([key, value]) => {
|
|
73
56
|
monomerHashToMatrixMap[charCodeMap.get(key)!] = value;
|
package/src/package.ts
CHANGED
|
@@ -59,7 +59,7 @@ import {BioPackage, BioPackageProperties} from './package-types';
|
|
|
59
59
|
import {getCompositionAnalysisWidget} from './widgets/composition-analysis-widget';
|
|
60
60
|
import {MacromoleculeColumnWidget} from './utils/macromolecule-column-widget';
|
|
61
61
|
import {addCopyMenuUI} from './utils/context-menu';
|
|
62
|
-
import {
|
|
62
|
+
import {getPolyToolDialog} from './utils/poly-tool/ui';
|
|
63
63
|
import {PolyToolCsvLibHandler} from './utils/poly-tool/csv-to-json-monomer-lib-converter';
|
|
64
64
|
import {_setPeptideColumn} from './utils/poly-tool/utils';
|
|
65
65
|
import {getRegionDo} from './utils/get-region';
|
|
@@ -471,9 +471,8 @@ export async function activityCliffs(table: DG.DataFrame, molecules: DG.Column<s
|
|
|
471
471
|
})
|
|
472
472
|
.onCancel(() => { resolve(undefined); })
|
|
473
473
|
.show();
|
|
474
|
-
} else
|
|
474
|
+
} else
|
|
475
475
|
runCliffs().then((res) => resolve(res)).catch((err) => reject(err));
|
|
476
|
-
}
|
|
477
476
|
}).catch((err: any) => {
|
|
478
477
|
const [errMsg, errStack] = errInfo(err);
|
|
479
478
|
_package.logger.error(errMsg, undefined, errStack);
|
|
@@ -647,9 +646,8 @@ export async function compositionAnalysis(): Promise<void> {
|
|
|
647
646
|
await handler(col);
|
|
648
647
|
})
|
|
649
648
|
.show();
|
|
650
|
-
} else
|
|
649
|
+
} else
|
|
651
650
|
col = colList[0];
|
|
652
|
-
}
|
|
653
651
|
|
|
654
652
|
if (!col)
|
|
655
653
|
return;
|
|
@@ -690,10 +688,9 @@ export function convertDialog() {
|
|
|
690
688
|
//name: polyTool
|
|
691
689
|
//description: Perform cyclization of polymers
|
|
692
690
|
export async function polyTool(): Promise<void> {
|
|
693
|
-
const polytool = new PolyTool();
|
|
694
691
|
let dialog: DG.Dialog;
|
|
695
692
|
try {
|
|
696
|
-
dialog = await
|
|
693
|
+
dialog = await getPolyToolDialog();
|
|
697
694
|
dialog.show();
|
|
698
695
|
} catch (err: any) {
|
|
699
696
|
grok.shell.warning('To run PolyTool, open a dataframe with macromolecules');
|
|
@@ -2,6 +2,10 @@ 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
|
|
|
5
|
+
import $ from 'cash-dom';
|
|
6
|
+
import wu from 'wu';
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
import {after, before, category, test, expect, delay, testEvent, awaitCheck} from '@datagrok-libraries/utils/src/test';
|
|
6
10
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
7
11
|
import {
|
|
@@ -19,7 +23,7 @@ import {HelmBioFilter} from '../widgets/bio-substructure-filter-helm';
|
|
|
19
23
|
import {_package} from '../package-test';
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
category('
|
|
26
|
+
category('bio-substructure-filters', async () => {
|
|
23
27
|
let monomerLibHelper: IMonomerLibHelper;
|
|
24
28
|
/** Backup actual user's monomer libraries settings */
|
|
25
29
|
let userLibSettings: UserLibSettings;
|
|
@@ -305,6 +309,144 @@ category('substructureFilters', async () => {
|
|
|
305
309
|
await Promise.all([f1.awaitRendered(), f2.awaitRendered()]);
|
|
306
310
|
await awaitGrid(view.grid);
|
|
307
311
|
});
|
|
312
|
+
|
|
313
|
+
// two seq columns
|
|
314
|
+
|
|
315
|
+
const twoColumnsCsv: string = `id,seq1,seq2,trueSeq1,trueSeq2
|
|
316
|
+
0,CGGCTACGGC,ATTGCATTCG,0,1,
|
|
317
|
+
1,CGGCTGCCGC,ATAGCATTCG,1,1,
|
|
318
|
+
2,CGGCTGCGCC,AATGCATACG,1,0,
|
|
319
|
+
3,CGGCTGCATT,TTTGCATTCG,1,1,
|
|
320
|
+
4,CGGCTGCATT,AAAGCATACG,1,0,
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
test('two-columns-fasta', async () => {
|
|
324
|
+
const df = DG.DataFrame.fromCsv(twoColumnsCsv);
|
|
325
|
+
await grok.data.detectSemanticTypes(df);
|
|
326
|
+
const view = grok.shell.addTableView(df);
|
|
327
|
+
|
|
328
|
+
const fSeq1ColName: string = 'seq1';
|
|
329
|
+
const fSeq1SubStr: string = 'CGGCTG';
|
|
330
|
+
const fSeq1Trues: number[] = df.getCol('trueSeq1').toList();
|
|
331
|
+
|
|
332
|
+
const fSeq2ColName: string = 'seq2';
|
|
333
|
+
const fSeq2SubStr: string = 'GCATT';
|
|
334
|
+
const fSeq2Trues: number[] = df.getCol('trueSeq2').toList();
|
|
335
|
+
|
|
336
|
+
//const seq2Filter = new BioSubstructureFilter();
|
|
337
|
+
const filterList: any[] = [
|
|
338
|
+
{type: 'Bio:bioSubstructureFilter', columnName: fSeq1ColName},
|
|
339
|
+
{type: 'Bio:bioSubstructureFilter', columnName: fSeq2ColName},
|
|
340
|
+
];
|
|
341
|
+
const fg = (await df.plot.fromType(DG.VIEWER.FILTERS,
|
|
342
|
+
{filters: filterList})) as DG.FilterGroup;
|
|
343
|
+
view.dockManager.dock(fg, DG.DOCK_TYPE.LEFT);
|
|
344
|
+
await delay(100);
|
|
345
|
+
await awaitGrid(view.grid);
|
|
346
|
+
|
|
347
|
+
const seq1Filter = fg.filters[0] as BioSubstructureFilter;
|
|
348
|
+
const seq2Filter = fg.filters[1] as BioSubstructureFilter;
|
|
349
|
+
expect(seq1Filter.column!.name, fSeq1ColName);
|
|
350
|
+
expect(seq2Filter.column!.name, fSeq2ColName);
|
|
351
|
+
|
|
352
|
+
const seq1Bf = seq1Filter.bioFilter as FastaBioFilter;
|
|
353
|
+
const seq2Bf = seq2Filter.bioFilter as FastaBioFilter;
|
|
354
|
+
|
|
355
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
356
|
+
seq1Bf.props = new BioFilterProps(fSeq1SubStr);
|
|
357
|
+
}, 1000);
|
|
358
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
359
|
+
seq2Bf.props = new BioFilterProps('');
|
|
360
|
+
}, 1000, 'testEvent onRowsFiltered on seq1');
|
|
361
|
+
expect(df.filter.trueCount, fSeq1Trues.filter((v) => v === 1).length);
|
|
362
|
+
expect(df.filter.toBinaryString(), fSeq1Trues.map((v) => v.toString()).join(''));
|
|
363
|
+
|
|
364
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
365
|
+
seq1Bf.props = new BioFilterProps('');
|
|
366
|
+
}, 1000);
|
|
367
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
368
|
+
seq2Bf.props = new BioFilterProps(fSeq2SubStr);
|
|
369
|
+
}, 1000, 'testEvent onRowsFiltered on seq2');
|
|
370
|
+
expect(df.filter.trueCount, fSeq2Trues.filter((v) => v === 1).length);
|
|
371
|
+
expect(df.filter.toBinaryString(), fSeq2Trues.map((v) => v.toString()).join(''));
|
|
372
|
+
|
|
373
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
374
|
+
seq1Bf.props = new BioFilterProps('');
|
|
375
|
+
}, 1000);
|
|
376
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
377
|
+
seq2Bf.props = new BioFilterProps('');
|
|
378
|
+
}, 1000, 'testEvent onRowsFiltered on neither');
|
|
379
|
+
expect(df.filter.trueCount, df.rowCount);
|
|
380
|
+
|
|
381
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
382
|
+
seq1Bf.props = new BioFilterProps(fSeq1SubStr);
|
|
383
|
+
}, 5000);
|
|
384
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
385
|
+
seq2Bf.props = new BioFilterProps(fSeq2SubStr);
|
|
386
|
+
}, 5000, 'testEvent onRowsFiltered on both');
|
|
387
|
+
const bothTrues: number[] = wu.count(0).take(df.rowCount)
|
|
388
|
+
.map((rowI) => fSeq1Trues[rowI] * fSeq2Trues[rowI]).toArray();
|
|
389
|
+
expect(df.filter.trueCount, bothTrues.filter((v) => v === 1).length);
|
|
390
|
+
expect(df.filter.toBinaryString(), bothTrues.map((v) => v.toString()).join(''));
|
|
391
|
+
|
|
392
|
+
await Promise.all([seq1Filter.awaitRendered(), seq2Filter.awaitRendered(), awaitGrid(view.grid)]);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// -- reset --
|
|
396
|
+
|
|
397
|
+
test('reset-fasta', async () => {
|
|
398
|
+
const df = await readDataframe('tests/filter_FASTA.csv');
|
|
399
|
+
await grok.data.detectSemanticTypes(df);
|
|
400
|
+
const view = grok.shell.addTableView(df);
|
|
401
|
+
|
|
402
|
+
const fSeqColName: string = 'fasta';
|
|
403
|
+
const fSubStr: string = 'MD';
|
|
404
|
+
const fTrueCount: number = 3;
|
|
405
|
+
|
|
406
|
+
const filterList = [{type: 'Bio:bioSubstructureFilter', columnName: fSeqColName}];
|
|
407
|
+
const fg = (await df.plot.fromType(DG.VIEWER.FILTERS,
|
|
408
|
+
{filters: filterList})) as DG.FilterGroup;
|
|
409
|
+
view.dockManager.dock(fg, DG.DOCK_TYPE.LEFT);
|
|
410
|
+
await delay(100);
|
|
411
|
+
await awaitGrid(view.grid);
|
|
412
|
+
|
|
413
|
+
const seqFilter = fg.filters[0] as BioSubstructureFilter;
|
|
414
|
+
const seqBf = seqFilter.bioFilter as FastaBioFilter;
|
|
415
|
+
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
416
|
+
seqBf.props = new BioFilterProps(fSubStr);
|
|
417
|
+
}, 1000, 'testEvent onRowsFiltered');
|
|
418
|
+
expect(df.filter.trueCount, fTrueCount);
|
|
419
|
+
expect(seqBf.props.substructure, fSubStr);
|
|
420
|
+
expect(seqBf.substructureInput.value, fSubStr);
|
|
421
|
+
|
|
422
|
+
const fgResetIconEl: HTMLElement = $(fg.root).find('i[name="icon-arrow-rotate-left"]')[0] as HTMLElement;
|
|
423
|
+
fgResetIconEl.click();
|
|
424
|
+
await delay(100);
|
|
425
|
+
await awaitGrid(view.grid);
|
|
426
|
+
expect(seqBf.props.substructure, '');
|
|
427
|
+
expect(seqBf.substructureInput.value, '');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('reopen', async () => {
|
|
431
|
+
const df = await _package.files.readCsv('tests/filter_FASTA.csv');
|
|
432
|
+
const view = grok.shell.addTableView(df);
|
|
433
|
+
|
|
434
|
+
const filterList = [{type: 'Bio:bioSubstructureFilter', columnName: 'fasta'}];
|
|
435
|
+
|
|
436
|
+
const fg1 = (await df.plot.fromType(DG.VIEWER.FILTERS,
|
|
437
|
+
{filters: filterList})) as DG.FilterGroup;
|
|
438
|
+
const fg1Dn = view.dockManager.dock(fg1, DG.DOCK_TYPE.LEFT);
|
|
439
|
+
await delay(100);
|
|
440
|
+
await awaitGrid(view.grid);
|
|
441
|
+
fg1.close();
|
|
442
|
+
await awaitGrid(view.grid);
|
|
443
|
+
|
|
444
|
+
const fg2 = (await df.plot.fromType(DG.VIEWER.FILTERS,
|
|
445
|
+
{filters: filterList})) as DG.FilterGroup;
|
|
446
|
+
const fg2Dn = view.dockManager.dock(fg2, DG.DOCK_TYPE.LEFT);
|
|
447
|
+
await delay(100);
|
|
448
|
+
await awaitGrid(view.grid);
|
|
449
|
+
});
|
|
308
450
|
});
|
|
309
451
|
|
|
310
452
|
async function createFilter(colName: string, df: DG.DataFrame): Promise<BioSubstructureFilter> {
|
|
@@ -5,144 +5,55 @@ import * as DG from 'datagrok-api/dg';
|
|
|
5
5
|
|
|
6
6
|
import {addTransformedColumn} from './transformation';
|
|
7
7
|
import {RULES_PATH, RULES_STORAGE_NAME} from './transformation';
|
|
8
|
+
import {ActiveFiles} from '@datagrok-libraries/utils/src/settings/active-files-base';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function getAllAvailableRuleFiles(): Promise<string[]> {
|
|
15
|
-
const list = await grok.dapi.files.list(RULES_PATH);
|
|
16
|
-
const paths = list.map((fileInfo) => {
|
|
17
|
-
return fileInfo.fullPath;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return paths;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function getUserRulesSettings(): Promise<UserRuleSettings> {
|
|
24
|
-
const resStr: string = await grok.dapi.userDataStorage.getValue(RULES_STORAGE_NAME, 'Settings', true);
|
|
25
|
-
const res = resStr ? JSON.parse(resStr) : {included: [], enotIncludedxplicit: []};
|
|
26
|
-
|
|
27
|
-
res.included = res.included instanceof Array ? res.included : [];
|
|
28
|
-
res.notIncluded = res.notIncluded instanceof Array ? res.notIncluded : [];
|
|
29
|
-
|
|
30
|
-
return res!;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function setUserLibSettings(value: UserRuleSettings): Promise<void> {
|
|
34
|
-
await grok.dapi.userDataStorage.postValue(RULES_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class PolyTool {
|
|
38
|
-
ruleFiles: string[];
|
|
39
|
-
userRuleSettings: UserRuleSettings;
|
|
40
|
-
ruleFilesInputs: HTMLDivElement;// DG.InputBase<boolean | null>[];
|
|
41
|
-
dialog: DG.Dialog;
|
|
42
|
-
|
|
43
|
-
constructor() {
|
|
44
|
-
this.ruleFiles = [];
|
|
45
|
-
this.userRuleSettings = {included: [], notIncluded: []};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private updateRulesSelectionStatus(ruleFileName: string, isSelected: boolean): void {
|
|
49
|
-
const isRuleFileSelected = this.userRuleSettings.included.includes(ruleFileName);
|
|
50
|
-
|
|
51
|
-
if (!isRuleFileSelected && isSelected) {
|
|
52
|
-
this.userRuleSettings.included.push(ruleFileName);
|
|
53
|
-
this.userRuleSettings.included = this.userRuleSettings.included.sort();
|
|
54
|
-
|
|
55
|
-
const index = this.userRuleSettings.notIncluded.indexOf(ruleFileName);
|
|
56
|
-
if (index > -1)
|
|
57
|
-
this.userRuleSettings.notIncluded.splice(index, 1);
|
|
58
|
-
} else {
|
|
59
|
-
const index = this.userRuleSettings.included.indexOf(ruleFileName);
|
|
60
|
-
if (index > -1)
|
|
61
|
-
this.userRuleSettings.included.splice(index, 1);
|
|
62
|
-
|
|
63
|
-
this.userRuleSettings.notIncluded.push(ruleFileName);
|
|
64
|
-
this.userRuleSettings.notIncluded = this.userRuleSettings.notIncluded.sort();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
setUserLibSettings(this.userRuleSettings);
|
|
10
|
+
class RuleInputs extends ActiveFiles {
|
|
11
|
+
constructor(path: string, userStorageName: string, ext: string ) {
|
|
12
|
+
super(path, userStorageName, ext);
|
|
68
13
|
}
|
|
14
|
+
}
|
|
69
15
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
16
|
+
export async function getPolyToolDialog(): Promise<DG.Dialog> {
|
|
17
|
+
const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
|
|
18
|
+
if (!targetColumns)
|
|
19
|
+
throw new Error('No dataframe with macromolecule columns open');
|
|
20
|
+
|
|
21
|
+
const targetColumnInput = ui.columnInput(
|
|
22
|
+
'Column', grok.shell.t, targetColumns[0], null,
|
|
23
|
+
{filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const generateHelmChoiceInput = ui.boolInput('Get HELM', true);
|
|
27
|
+
ui.tooltip.bind(generateHelmChoiceInput.root, 'Add HELM column');
|
|
28
|
+
|
|
29
|
+
const chiralityEngineInput = ui.boolInput('Chirality engine', false);
|
|
30
|
+
const ruleInputs = new RuleInputs(RULES_PATH, RULES_STORAGE_NAME, '.csv');
|
|
31
|
+
const rulesForm = await ruleInputs.getForm();
|
|
32
|
+
|
|
33
|
+
const div = ui.div([
|
|
34
|
+
targetColumnInput,
|
|
35
|
+
generateHelmChoiceInput,
|
|
36
|
+
chiralityEngineInput,
|
|
37
|
+
'Rules used',
|
|
38
|
+
rulesForm
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const dialog = ui.dialog('Poly Tool')
|
|
42
|
+
.add(div)
|
|
43
|
+
.onOK(async () => {
|
|
44
|
+
const molCol = targetColumnInput.value;
|
|
45
|
+
if (!molCol) {
|
|
46
|
+
grok.shell.warning('No marcomolecule column chosen!');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const files = await ruleInputs.getActive();
|
|
51
|
+
|
|
52
|
+
addTransformedColumn(molCol!,
|
|
53
|
+
generateHelmChoiceInput.value!,
|
|
54
|
+
files,
|
|
55
|
+
chiralityEngineInput.value!);
|
|
86
56
|
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private async getRuleFilesBlock(): Promise<DG.InputBase<boolean | null>[]> {
|
|
90
|
-
this.ruleFiles = await getAllAvailableRuleFiles();
|
|
91
|
-
this.userRuleSettings = await getUserRulesSettings();
|
|
92
|
-
const cBoxes: DG.InputBase<boolean | null>[] = [];
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < this.ruleFiles.length; i++) {
|
|
95
|
-
const ruleFileName = this.ruleFiles[i];
|
|
96
|
-
const isRuleFileSelected = this.userRuleSettings.included.includes(ruleFileName);
|
|
97
|
-
const cb = ui.boolInput(
|
|
98
|
-
ruleFileName.replace(RULES_PATH, ''),
|
|
99
|
-
isRuleFileSelected,
|
|
100
|
-
(isSelected: boolean) => this.updateRulesSelectionStatus(ruleFileName, isSelected)
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
cBoxes.push(cb);
|
|
104
|
-
}
|
|
105
|
-
return cBoxes;
|
|
106
|
-
}
|
|
107
57
|
|
|
108
|
-
|
|
109
|
-
const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
|
|
110
|
-
if (!targetColumns)
|
|
111
|
-
throw new Error('No dataframe with macromolecule columns open');
|
|
112
|
-
|
|
113
|
-
const targetColumnInput = ui.columnInput(
|
|
114
|
-
'Column', grok.shell.t, targetColumns[0], null,
|
|
115
|
-
{filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE}
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const generateHelmChoiceInput = ui.boolInput('Get HELM', true);
|
|
119
|
-
ui.tooltip.bind(generateHelmChoiceInput.root, 'Add HELM column');
|
|
120
|
-
|
|
121
|
-
const addButton = this.getAddButton();
|
|
122
|
-
this.ruleFilesInputs = ui.div(await this.getRuleFilesBlock());
|
|
123
|
-
//const rulesFiles = ui.div(this.ruleFilesInputs);
|
|
124
|
-
const chiralityEngineInput = ui.boolInput('Chirality engine', false);
|
|
125
|
-
|
|
126
|
-
const div = ui.div([
|
|
127
|
-
targetColumnInput,
|
|
128
|
-
generateHelmChoiceInput,
|
|
129
|
-
chiralityEngineInput,
|
|
130
|
-
'Rules used',
|
|
131
|
-
this.ruleFilesInputs,
|
|
132
|
-
addButton
|
|
133
|
-
]);
|
|
134
|
-
|
|
135
|
-
this.dialog = ui.dialog('Poly Tool')
|
|
136
|
-
.add(div)
|
|
137
|
-
.onOK(async () => {
|
|
138
|
-
const molCol = targetColumnInput.value;
|
|
139
|
-
if (!molCol) {
|
|
140
|
-
grok.shell.warning('No marcomolecule column chosen!');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
addTransformedColumn(molCol!, generateHelmChoiceInput.value!, this.userRuleSettings.included, chiralityEngineInput.value!);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return this.dialog;
|
|
147
|
-
}
|
|
58
|
+
return dialog;
|
|
148
59
|
}
|
|
@@ -6,50 +6,24 @@ import {Observable, Subject, Unsubscribable} from 'rxjs';
|
|
|
6
6
|
import {_package} from '../package';
|
|
7
7
|
|
|
8
8
|
export interface IFilterProps {
|
|
9
|
-
get onChanged(): Observable<void>;
|
|
10
|
-
|
|
11
|
-
save(): object;
|
|
12
|
-
apply(propsObj: object): void;
|
|
13
9
|
}
|
|
14
10
|
|
|
15
11
|
/** Fasta and Helm */
|
|
16
12
|
export class BioFilterProps implements IFilterProps {
|
|
17
|
-
private _onChanged: Subject<void> = new Subject<void>();
|
|
18
|
-
|
|
19
|
-
get onChanged(): Observable<void> { return this._onChanged; }
|
|
20
|
-
|
|
21
13
|
constructor(
|
|
22
|
-
public substructure: string
|
|
14
|
+
public readonly substructure: string,
|
|
15
|
+
/** Pass false from an inheritors constructor, at the end set true. */ protected readOnly: boolean = true,
|
|
23
16
|
) {
|
|
24
17
|
return new Proxy(this, {
|
|
25
18
|
set: (target: any, key: string | symbol, value: any) => {
|
|
26
19
|
_package.logger.debug(`BioFilterProps.set ${key.toString()}( '${value}' )`);
|
|
20
|
+
if (this.readOnly)
|
|
21
|
+
throw new Error('Properties are immutable.');
|
|
27
22
|
target[key] = value;
|
|
28
|
-
this._onChanged.next();
|
|
29
23
|
return true;
|
|
30
24
|
}
|
|
31
25
|
});
|
|
32
26
|
}
|
|
33
|
-
|
|
34
|
-
save(): object {
|
|
35
|
-
const propsObj = {};
|
|
36
|
-
for (const [key, value] of Object.entries(this)) {
|
|
37
|
-
if (key !== '_onChanged') {
|
|
38
|
-
// @ts-ignore
|
|
39
|
-
propsObj[key] = this[key];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return propsObj;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
apply(propsObj: object) {
|
|
46
|
-
for (const [key, value] of Object.entries(this)) {
|
|
47
|
-
if (key !== '_onChanged') {
|
|
48
|
-
// @ts-ignore
|
|
49
|
-
this[key] = propsObj[key];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
27
|
}
|
|
54
28
|
|
|
55
29
|
export interface IBioFilter {
|
|
@@ -58,6 +32,9 @@ export interface IBioFilter {
|
|
|
58
32
|
get props(): IFilterProps;
|
|
59
33
|
set props(value: IFilterProps);
|
|
60
34
|
|
|
35
|
+
applyProps(props: IFilterProps): void;
|
|
36
|
+
saveProps(): IFilterProps;
|
|
37
|
+
|
|
61
38
|
get onChanged(): Observable<void>;
|
|
62
39
|
get filterPanel(): HTMLElement;
|
|
63
40
|
get filterSummary(): string;
|
|
@@ -79,7 +56,6 @@ export abstract class BioFilterBase<TProps extends BioFilterProps> implements IB
|
|
|
79
56
|
|
|
80
57
|
private _props: TProps;
|
|
81
58
|
protected _propsChanging: boolean = false;
|
|
82
|
-
private _propsOnChangedSub: Unsubscribable | null = null;
|
|
83
59
|
|
|
84
60
|
abstract get type(): string;
|
|
85
61
|
|
|
@@ -91,33 +67,31 @@ export abstract class BioFilterBase<TProps extends BioFilterProps> implements IB
|
|
|
91
67
|
set props(value: TProps) {
|
|
92
68
|
this._propsChanging = true;
|
|
93
69
|
try {
|
|
94
|
-
if (this._propsOnChangedSub) {
|
|
95
|
-
this._propsOnChangedSub.unsubscribe();
|
|
96
|
-
this._propsOnChangedSub = null;
|
|
97
|
-
}
|
|
98
70
|
this._props = value;
|
|
99
71
|
this.applyProps();
|
|
100
72
|
this.onChanged.next();
|
|
101
|
-
this._propsOnChangedSub = this._props.onChanged
|
|
102
|
-
.subscribe(() => {
|
|
103
|
-
this.onChanged.next();
|
|
104
|
-
});
|
|
105
73
|
} finally {
|
|
106
74
|
this._propsChanging = false;
|
|
107
75
|
}
|
|
108
76
|
};
|
|
109
77
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
78
|
+
saveProps(): IFilterProps {
|
|
79
|
+
const propsObj = {};
|
|
80
|
+
for (const [key, value] of Object.entries(this.props)) {
|
|
81
|
+
if (key !== '_onChanged') {
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
propsObj[key] = this.props[key];
|
|
84
|
+
}
|
|
116
85
|
}
|
|
86
|
+
return propsObj;
|
|
117
87
|
}
|
|
118
88
|
|
|
119
89
|
abstract applyProps(): void;
|
|
120
90
|
|
|
91
|
+
abstract attach(): Promise<void>;
|
|
92
|
+
|
|
93
|
+
async detach(): Promise<void> { }
|
|
94
|
+
|
|
121
95
|
get filterSummary(): string { return this.props.substructure; };
|
|
122
96
|
|
|
123
97
|
get isFiltering(): boolean { return this.props.substructure !== ''; }
|