@datagrok/bio 2.0.18 → 2.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-test.js +247 -27
- package/dist/package.js +247 -27
- package/package.json +1 -1
- package/src/package.ts +12 -1
- package/src/substructure-search/substructure-search.ts +51 -22
- package/src/utils/cell-renderer.ts +4 -2
- package/src/widgets/bio-substructure-filter.ts +151 -0
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.0.
|
|
8
|
+
"version": "2.0.19",
|
|
9
9
|
"description": "Bio is a [package](https://datagrok.ai/help/develop/develop#packages) for the [Datagrok](https://datagrok.ai) platform",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
package/src/package.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {SequenceSimilarityViewer} from './analysis/sequence-similarity-viewer';
|
|
|
31
31
|
import {SequenceDiversityViewer} from './analysis/sequence-diversity-viewer';
|
|
32
32
|
import {substructureSearchDialog} from './substructure-search/substructure-search';
|
|
33
33
|
import {saveAsFastaUI} from './utils/save-as-fasta';
|
|
34
|
+
import {BioSubstructureFilter} from './widgets/bio-substructure-filter';
|
|
34
35
|
|
|
35
36
|
//tags: init
|
|
36
37
|
export async function initBio() {
|
|
@@ -517,4 +518,14 @@ export function bioSubstructureSearch(col: DG.Column): void {
|
|
|
517
518
|
//tags: fileExporter
|
|
518
519
|
export function saveAsFasta() {
|
|
519
520
|
saveAsFastaUI();
|
|
520
|
-
}
|
|
521
|
+
}
|
|
522
|
+
//name: BioSubstructureFilter
|
|
523
|
+
//description: Substructure filter for linear macromolecules
|
|
524
|
+
//tags: filter
|
|
525
|
+
//output: filter result
|
|
526
|
+
//meta.semType: Macromolecule
|
|
527
|
+
export function bioSubstructureFilter(): BioSubstructureFilter {
|
|
528
|
+
return new BioSubstructureFilter();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
@@ -3,6 +3,9 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
5
5
|
import * as C from '../utils/constants';
|
|
6
|
+
import {getMonomericMols} from '../calculations/monomerLevelMols';
|
|
7
|
+
import {BitSet} from 'datagrok-api/dg';
|
|
8
|
+
import {updateDivInnerHTML} from '../utils/ui-utils';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Searches substructure in each row of Macromolecule column
|
|
@@ -12,48 +15,58 @@ import * as C from '../utils/constants';
|
|
|
12
15
|
export function substructureSearchDialog(col: DG.Column): void {
|
|
13
16
|
const units = col.getTag(DG.TAGS.UNITS);
|
|
14
17
|
const separator = col.getTag(C.TAGS.SEPARATOR);
|
|
15
|
-
const notations = [NOTATION.FASTA, NOTATION.SEPARATOR];
|
|
18
|
+
// const notations = [NOTATION.FASTA, NOTATION.SEPARATOR, NOTATION.HELM];
|
|
16
19
|
|
|
17
20
|
const substructureInput = ui.textInput('Substructure', '');
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
const editHelmLink = ui.link('Edit helm', async () => {
|
|
23
|
+
updateDivInnerHTML(inputsDiv, grid.root);
|
|
24
|
+
await ui.tools.waitForElementInDom(grid.root);
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
grid.cell('substr_helm', 0).element.children[0].dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
27
|
+
}, 100);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const df = DG.DataFrame.create(1);
|
|
31
|
+
df.columns.addNewString('substr_helm').init((i) => '');
|
|
32
|
+
df.col('substr_helm')!.semType = col.semType;
|
|
33
|
+
df.col('substr_helm')!.setTag(DG.TAGS.UNITS, NOTATION.HELM);
|
|
34
|
+
const grid = df.plot.grid();
|
|
19
35
|
const separatorInput = ui.textInput('Separator', separator);
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
const toggleSeparator = () => {
|
|
23
|
-
if (notationInput.value !== NOTATION.SEPARATOR)
|
|
24
|
-
separatorInput.root.hidden = true;
|
|
25
|
-
else
|
|
26
|
-
separatorInput.root.hidden = false;
|
|
27
|
-
};
|
|
37
|
+
const inputsDiv = ui.div();
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
const inputs = units === NOTATION.HELM ? ui.divV([editHelmLink]) :
|
|
40
|
+
units === NOTATION.SEPARATOR ? ui.inputs([substructureInput, separatorInput]) :
|
|
41
|
+
ui.inputs([substructureInput]);
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
toggleSeparator();
|
|
33
|
-
});
|
|
43
|
+
updateDivInnerHTML(inputsDiv, inputs);
|
|
34
44
|
|
|
35
45
|
ui.dialog('Substructure search')
|
|
36
|
-
.add(ui.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
separatorInput
|
|
46
|
+
.add(ui.divV([
|
|
47
|
+
ui.divText(`Notation: ${units}`),
|
|
48
|
+
inputsDiv
|
|
40
49
|
]))
|
|
41
|
-
.onOK(() => {
|
|
42
|
-
let substructure = substructureInput.value;
|
|
43
|
-
if (
|
|
50
|
+
.onOK(async () => {
|
|
51
|
+
let substructure = units === NOTATION.HELM ? df.get('substr_helm', 0) : substructureInput.value;
|
|
52
|
+
if (units === NOTATION.SEPARATOR && separatorInput.value !== separator && separatorInput.value !== '')
|
|
44
53
|
substructure = substructure.replaceAll(separatorInput.value, separator);
|
|
45
54
|
const matchesColName = `Matches: ${substructure}`;
|
|
46
55
|
const colExists = col.dataFrame.columns.names()
|
|
47
56
|
.filter((it) => it.toLocaleLowerCase() === matchesColName.toLocaleLowerCase()).length > 0;
|
|
48
57
|
if (!colExists) {
|
|
49
|
-
|
|
58
|
+
let matches: BitSet;
|
|
59
|
+
if (units === NOTATION.HELM)
|
|
60
|
+
matches = await helmSubstructureSearch(substructure, col);
|
|
61
|
+
else
|
|
62
|
+
matches = linearSubstructureSearch(substructure, col);
|
|
50
63
|
col.dataFrame.columns.add(DG.Column.fromBitSet(matchesColName, matches));
|
|
51
64
|
} else { grok.shell.warning(`Search ${substructure} is already performed`); }
|
|
52
65
|
})
|
|
53
66
|
.show();
|
|
54
67
|
}
|
|
55
68
|
|
|
56
|
-
export function
|
|
69
|
+
export function linearSubstructureSearch(substructure: string, col: DG.Column): DG.BitSet {
|
|
57
70
|
const lowerCaseSubstr = substructure.toLowerCase();
|
|
58
71
|
const resultArray = DG.BitSet.create(col.length);
|
|
59
72
|
for (let i = 0; i < col.length; i++) {
|
|
@@ -63,3 +76,19 @@ export function substructureSearch(substructure: string, col: DG.Column): DG.Bit
|
|
|
63
76
|
}
|
|
64
77
|
return resultArray;
|
|
65
78
|
}
|
|
79
|
+
|
|
80
|
+
async function helmSubstructureSearch(substructure: string, col: DG.Column): Promise<BitSet> {
|
|
81
|
+
const helmColWithSubstructure = DG.Column.string('helm', col.length + 1)
|
|
82
|
+
.init((i) => i === col.length ? substructure : col.get(i));
|
|
83
|
+
helmColWithSubstructure.setTag(DG.TAGS.UNITS, NOTATION.HELM);
|
|
84
|
+
const monomericMolsCol = await getMonomericMols(helmColWithSubstructure, true);
|
|
85
|
+
const molSubstructure = monomericMolsCol.get(col.length);
|
|
86
|
+
const monomericMolsDf = DG.DataFrame.fromColumns([monomericMolsCol]);
|
|
87
|
+
monomericMolsDf.rows.removeAt(col.length);
|
|
88
|
+
const matchesCol = await grok.functions.call('Chem:searchSubstructure', {
|
|
89
|
+
molStringsColumn: monomericMolsDf.columns.byIndex(0),
|
|
90
|
+
molString: molSubstructure,
|
|
91
|
+
molBlockFailover: '',
|
|
92
|
+
});
|
|
93
|
+
return matchesCol.get(0);
|
|
94
|
+
}
|
|
@@ -228,7 +228,9 @@ export class MonomerCellRenderer extends DG.GridCellRenderer {
|
|
|
228
228
|
g.textAlign = 'center';
|
|
229
229
|
|
|
230
230
|
const palette = getPaletteByType(gridCell.cell.column.getTag(C.TAGS.ALPHABET));
|
|
231
|
-
const s: string = gridCell.cell.value
|
|
231
|
+
const s: string = gridCell.cell.value;
|
|
232
|
+
if (!s)
|
|
233
|
+
return;
|
|
232
234
|
const color = palette.get(s);
|
|
233
235
|
|
|
234
236
|
g.fillStyle = color;
|
|
@@ -271,7 +273,7 @@ export class MacromoleculeDifferenceCellRenderer extends DG.GridCellRenderer {
|
|
|
271
273
|
const splitter = bio.getSplitter(units, separator);
|
|
272
274
|
const subParts1 = splitter(s1);
|
|
273
275
|
const subParts2 = splitter(s2);
|
|
274
|
-
drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, units
|
|
276
|
+
drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, units);
|
|
275
277
|
}
|
|
276
278
|
}
|
|
277
279
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macromolecules substructure filter that uses Datagrok's collaborative filtering.
|
|
3
|
+
* 1. On onRowsFiltering event, only FILTER OUT rows that do not satisfy this filter's criteria
|
|
4
|
+
* 2. Call dataFrame.rows.requestFilter when filtering criteria changes.
|
|
5
|
+
* */
|
|
6
|
+
|
|
7
|
+
import * as ui from 'datagrok-api/ui';
|
|
8
|
+
import * as DG from 'datagrok-api/dg';
|
|
9
|
+
import wu from 'wu';
|
|
10
|
+
import {linearSubstructureSearch} from '../substructure-search/substructure-search';
|
|
11
|
+
import {Subject, Subscription} from 'rxjs';
|
|
12
|
+
import {NOTATION} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
13
|
+
import * as C from '../utils/constants';
|
|
14
|
+
|
|
15
|
+
export class BioSubstructureFilter extends DG.Filter {
|
|
16
|
+
bioFilter: FastaFilter | SeparatorFilter | null = null;
|
|
17
|
+
bitset: DG.BitSet | null = null;
|
|
18
|
+
loader: HTMLDivElement = ui.loader();
|
|
19
|
+
onBioFilterChangedSubs?: Subscription;
|
|
20
|
+
|
|
21
|
+
get calculating(): boolean { return this.loader.style.display == 'initial'; }
|
|
22
|
+
set calculating(value: boolean) { this.loader.style.display = value ? 'initial' : 'none'; }
|
|
23
|
+
|
|
24
|
+
get filterSummary(): string {
|
|
25
|
+
return this.bioFilter!.substructure;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get isFiltering(): boolean {
|
|
29
|
+
return super.isFiltering && this.bioFilter!.substructure !== '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get isReadyToApplyFilter(): boolean {
|
|
33
|
+
return !this.calculating && this.bitset != null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super();
|
|
38
|
+
this.root = ui.divV([]);
|
|
39
|
+
this.calculating = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
attach(dataFrame: DG.DataFrame): void {
|
|
43
|
+
super.attach(dataFrame);
|
|
44
|
+
this.column = dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
45
|
+
this.columnName = this.column?.name;
|
|
46
|
+
const notation = this.column?.getTag(DG.TAGS.UNITS);
|
|
47
|
+
this.bioFilter = notation === NOTATION.FASTA ?
|
|
48
|
+
new FastaFilter() : new SeparatorFilter(this.column!.getTag(C.TAGS.SEPARATOR));
|
|
49
|
+
this.root.appendChild(this.bioFilter!.filterPanel);
|
|
50
|
+
this.root.appendChild(this.loader);
|
|
51
|
+
|
|
52
|
+
this.onBioFilterChangedSubs?.unsubscribe();
|
|
53
|
+
const onChangedEvent: any = this.bioFilter.onChanged;
|
|
54
|
+
this.onBioFilterChangedSubs = onChangedEvent.subscribe(async (_: any) => await this._onInputChanged());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
detach() {
|
|
58
|
+
super.detach();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
applyFilter(): void {
|
|
62
|
+
if (this.bitset && !this.isDetached)
|
|
63
|
+
this.dataFrame?.filter.and(this.bitset);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Override to save filter state. */
|
|
67
|
+
saveState(): any {
|
|
68
|
+
const state = super.saveState();
|
|
69
|
+
state.bioSubstructure = this.bioFilter?.substructure;
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Override to load filter state. */
|
|
74
|
+
applyState(state: any): void {
|
|
75
|
+
super.applyState(state);
|
|
76
|
+
if (state.bioSubstructure)
|
|
77
|
+
this.bioFilter!.substructureInput.value = state.bioSubstructure;
|
|
78
|
+
|
|
79
|
+
const that = this;
|
|
80
|
+
if (state.bioSubstructure)
|
|
81
|
+
setTimeout(function() { that._onInputChanged(); }, 1000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Performs the actual filtering
|
|
86
|
+
* When the results are ready, triggers `rows.requestFilter`, which in turn triggers `applyFilter`
|
|
87
|
+
* that would simply apply the bitset synchronously.
|
|
88
|
+
*/
|
|
89
|
+
async _onInputChanged(): Promise<void> {
|
|
90
|
+
if (!this.isFiltering) {
|
|
91
|
+
this.bitset = null;
|
|
92
|
+
this.dataFrame?.rows.requestFilter();
|
|
93
|
+
} else if (wu(this.dataFrame!.rows.filters).has(`${this.columnName}: ${this.filterSummary}`)) {
|
|
94
|
+
// some other filter is already filtering for the exact same thing
|
|
95
|
+
return;
|
|
96
|
+
} else {
|
|
97
|
+
this.calculating = true;
|
|
98
|
+
try {
|
|
99
|
+
this.bitset = linearSubstructureSearch(this.bioFilter!.substructure, this.column!);
|
|
100
|
+
this.calculating = false;
|
|
101
|
+
this.dataFrame?.rows.requestFilter();
|
|
102
|
+
} finally {
|
|
103
|
+
this.calculating = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class FastaFilter {
|
|
110
|
+
onChanged: Subject<any> = new Subject<any>();
|
|
111
|
+
substructureInput: DG.InputBase<string> = ui.stringInput('', '', () => {
|
|
112
|
+
this.onChanged.next();
|
|
113
|
+
}, {placeholder: 'Substructure'});
|
|
114
|
+
|
|
115
|
+
constructor() {
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get filterPanel() {
|
|
119
|
+
return this.substructureInput.root;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get substructure() {
|
|
123
|
+
return this.substructureInput.value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class SeparatorFilter extends FastaFilter {
|
|
128
|
+
separatorInput: DG.InputBase<string> = ui.stringInput('', '', () => {
|
|
129
|
+
this.onChanged.next();
|
|
130
|
+
}, {placeholder: 'Separator'});
|
|
131
|
+
colSeparator = '';
|
|
132
|
+
|
|
133
|
+
constructor(separator: string) {
|
|
134
|
+
super();
|
|
135
|
+
this.colSeparator = separator;
|
|
136
|
+
this.separatorInput.value = separator;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get filterPanel() {
|
|
140
|
+
return ui.divV([
|
|
141
|
+
this.substructureInput.root,
|
|
142
|
+
this.separatorInput.root
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get substructure() {
|
|
147
|
+
return this.separatorInput.value && this.separatorInput.value !== this.colSeparator ?
|
|
148
|
+
this.substructureInput.value.replaceAll(this.separatorInput.value, this.colSeparator):
|
|
149
|
+
this.substructureInput.value;
|
|
150
|
+
}
|
|
151
|
+
}
|