@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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.0.18",
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
- const notationInput = ui.choiceInput('Notation', units, notations);
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
- // hide the separator input for non-SEPARATOR target notations
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
- toggleSeparator();
39
+ const inputs = units === NOTATION.HELM ? ui.divV([editHelmLink]) :
40
+ units === NOTATION.SEPARATOR ? ui.inputs([substructureInput, separatorInput]) :
41
+ ui.inputs([substructureInput]);
30
42
 
31
- notationInput.onChanged(() => {
32
- toggleSeparator();
33
- });
43
+ updateDivInnerHTML(inputsDiv, inputs);
34
44
 
35
45
  ui.dialog('Substructure search')
36
- .add(ui.inputs([
37
- substructureInput,
38
- notationInput,
39
- separatorInput
46
+ .add(ui.divV([
47
+ ui.divText(`Notation: ${units}`),
48
+ inputsDiv
40
49
  ]))
41
- .onOK(() => {
42
- let substructure = substructureInput.value;
43
- if (notationInput.value !== NOTATION.FASTA && separatorInput.value !== separator)
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
- const matches = substructureSearch(substructure, col);
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 substructureSearch(substructure: string, col: DG.Column): DG.BitSet {
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, separator);
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
+ }