@datagrok/bio 2.0.17 → 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 +933 -622
- package/dist/package.js +771 -460
- package/package.json +9 -9
- package/src/analysis/sequence-activity-cliffs.ts +10 -10
- package/src/analysis/sequence-similarity-viewer.ts +4 -3
- package/src/calculations/monomerLevelMols.ts +5 -3
- package/src/package.ts +20 -14
- package/src/substructure-search/substructure-search.ts +51 -22
- package/src/tests/WebLogo-positions-test.ts +47 -47
- package/src/tests/WebLogo-test.ts +14 -14
- package/src/tests/fasta-export-tests.ts +2 -2
- package/src/tests/splitters-test.ts +7 -6
- package/src/utils/cell-renderer.ts +28 -31
- package/src/utils/convert.ts +10 -11
- package/src/utils/save-as-fasta.ts +5 -5
- package/src/utils/utils.ts +7 -5
- package/src/viewers/vd-regions-viewer.ts +12 -15
- package/src/widgets/bio-substructure-filter.ts +151 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {after, before, category, test, expect, expectArray, expectObject} from '@datagrok-libraries/utils/src/test';
|
|
2
|
-
|
|
3
1
|
import * as grok from 'datagrok-api/grok';
|
|
4
2
|
import * as ui from 'datagrok-api/ui';
|
|
5
3
|
import * as DG from 'datagrok-api/dg';
|
|
6
|
-
import
|
|
7
|
-
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
5
|
+
|
|
6
|
+
import {after, before, category, test, expect, expectArray, expectObject} from '@datagrok-libraries/utils/src/test';
|
|
8
7
|
import * as C from '../utils/constants';
|
|
8
|
+
import {splitToMonomers, _package, getHelmMonomers} from '../package';
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
category('splitters', () => {
|
|
11
12
|
let tvList: DG.TableView[];
|
|
@@ -121,13 +122,13 @@ PEPTIDE1{hHis.Aca.Cys_SEt}$$$,5.72388
|
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
export async function _testFastaSplitter(src: string, tgt: string[]) {
|
|
124
|
-
const res: string[] =
|
|
125
|
+
const res: string[] = bio.splitterAsFasta(src);
|
|
125
126
|
console.debug(`Bio: tests: splitters: src=${JSON.stringify(src)}, res=${JSON.stringify(res)} .`);
|
|
126
127
|
expectArray(res, tgt);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
export async function _testHelmSplitter(src: string, tgt: string[]) {
|
|
130
|
-
const res: string[] =
|
|
131
|
+
const res: string[] = bio.splitterAsHelm(src);
|
|
131
132
|
console.debug(`Bio: tests: splitters: src=${JSON.stringify(src)}, res=${JSON.stringify(res)} .`);
|
|
132
133
|
expectArray(res, tgt);
|
|
133
134
|
}
|
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as DG from 'datagrok-api/dg';
|
|
3
|
-
import {AminoacidsPalettes} from '@datagrok-libraries/bio/src/aminoacids';
|
|
4
|
-
import {NucleotidesPalettes} from '@datagrok-libraries/bio/src/nucleotides';
|
|
5
|
-
import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
|
|
6
|
-
import {SplitterFunc, WebLogo} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
7
|
-
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
8
2
|
import * as ui from 'datagrok-api/ui';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
5
|
+
import * as C from './constants';
|
|
11
6
|
|
|
12
7
|
const undefinedColor = 'rgb(100,100,100)';
|
|
13
|
-
const monomerToShortFunction: (amino: string, maxLengthOfMonomer: number) => string =
|
|
8
|
+
const monomerToShortFunction: (amino: string, maxLengthOfMonomer: number) => string = bio.monomerToShort;
|
|
14
9
|
|
|
15
10
|
|
|
16
|
-
function getPaletteByType(paletteType: string): SeqPalette {
|
|
11
|
+
function getPaletteByType(paletteType: string): bio.SeqPalette {
|
|
17
12
|
switch (paletteType) {
|
|
18
13
|
case 'PT':
|
|
19
|
-
return AminoacidsPalettes.GrokGroups;
|
|
14
|
+
return bio.AminoacidsPalettes.GrokGroups;
|
|
20
15
|
case 'NT':
|
|
21
|
-
return NucleotidesPalettes.Chromatogram;
|
|
16
|
+
return bio.NucleotidesPalettes.Chromatogram;
|
|
22
17
|
case 'DNA':
|
|
23
|
-
return NucleotidesPalettes.Chromatogram;
|
|
18
|
+
return bio.NucleotidesPalettes.Chromatogram;
|
|
24
19
|
case 'RNA':
|
|
25
|
-
return NucleotidesPalettes.Chromatogram;
|
|
20
|
+
return bio.NucleotidesPalettes.Chromatogram;
|
|
26
21
|
// other
|
|
27
22
|
default:
|
|
28
|
-
return UnknownSeqPalettes.Color;
|
|
23
|
+
return bio.UnknownSeqPalettes.Color;
|
|
29
24
|
}
|
|
30
25
|
}
|
|
31
26
|
|
|
@@ -66,7 +61,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
|
|
69
|
-
if (gridCell.cell.column.getTag(UnitsHandler.TAGS.aligned) !== 'SEQ.MSA')
|
|
64
|
+
if (gridCell.cell.column.getTag(bio.UnitsHandler.TAGS.aligned) !== 'SEQ.MSA')
|
|
70
65
|
return;
|
|
71
66
|
|
|
72
67
|
const maxLengthWordsSum = gridCell.cell.column.temp['bio-sum-maxLengthWords'];
|
|
@@ -94,7 +89,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
94
89
|
}
|
|
95
90
|
left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
|
|
96
91
|
const separator = gridCell.cell.column.getTag('separator') ?? '';
|
|
97
|
-
const splitterFunc: SplitterFunc =
|
|
92
|
+
const splitterFunc: bio.SplitterFunc = bio.getSplitter('separator', separator);
|
|
98
93
|
const subParts: string[] = splitterFunc(gridCell.cell.value);
|
|
99
94
|
(((subParts[left]?.length ?? 0) > 0)) ?
|
|
100
95
|
ui.tooltip.show(ui.div(subParts[left]), e.x + 16, e.y + 16) : ui.tooltip.hide();
|
|
@@ -135,7 +130,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
135
130
|
|
|
136
131
|
const separator = gridCell.cell.column.getTag('separator') ?? '';
|
|
137
132
|
const splitLimit = gridCell.bounds.width / 5;
|
|
138
|
-
const splitterFunc: SplitterFunc =
|
|
133
|
+
const splitterFunc: bio.SplitterFunc = bio.getSplitter(units, separator, splitLimit);
|
|
139
134
|
const referenceSequence: string[] = splitterFunc(((gridCell.cell.column?.temp['reference-sequence'] != null) && (gridCell.cell.column?.temp['reference-sequence'] != ''))
|
|
140
135
|
? gridCell.cell.column.temp['reference-sequence'] : gridCell.cell.column.temp['current-word'] ?? '');
|
|
141
136
|
const monomerWidth = (gridCell.cell.column?.temp['monomer-width'] != null) ? gridCell.cell.column.temp['monomer-width'] : 'short';
|
|
@@ -188,15 +183,15 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
188
183
|
const subParts: string[] = splitterFunc(cell.value);
|
|
189
184
|
let x1 = x;
|
|
190
185
|
let color = undefinedColor;
|
|
191
|
-
let drawStyle = DrawStyle.classic;
|
|
186
|
+
let drawStyle = bio.DrawStyle.classic;
|
|
192
187
|
if (gridCell.cell.column.getTag('aligned').includes('MSA') && gridCell.cell.column.getTag('units') === 'separator')
|
|
193
|
-
drawStyle = DrawStyle.MSA;
|
|
188
|
+
drawStyle = bio.DrawStyle.MSA;
|
|
194
189
|
|
|
195
190
|
subParts.every((amino, index) => {
|
|
196
191
|
color = palette.get(amino);
|
|
197
192
|
g.fillStyle = undefinedColor;
|
|
198
193
|
let last = index === subParts.length - 1;
|
|
199
|
-
x1 = printLeftOrCentered(x1, y, w, h, g, amino, color, 0, true, 1.0, separator, last, drawStyle, maxLengthWords, index, gridCell, referenceSequence, maxLengthOfMonomer);
|
|
194
|
+
x1 = bio.printLeftOrCentered(x1, y, w, h, g, amino, color, 0, true, 1.0, separator, last, drawStyle, maxLengthWords, index, gridCell, referenceSequence, maxLengthOfMonomer);
|
|
200
195
|
return x1 - minDistanceRenderer - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCell.bounds.x) <= gridCell.bounds.width;
|
|
201
196
|
});
|
|
202
197
|
|
|
@@ -233,7 +228,9 @@ export class MonomerCellRenderer extends DG.GridCellRenderer {
|
|
|
233
228
|
g.textAlign = 'center';
|
|
234
229
|
|
|
235
230
|
const palette = getPaletteByType(gridCell.cell.column.getTag(C.TAGS.ALPHABET));
|
|
236
|
-
const s: string = gridCell.cell.value
|
|
231
|
+
const s: string = gridCell.cell.value;
|
|
232
|
+
if (!s)
|
|
233
|
+
return;
|
|
237
234
|
const color = palette.get(s);
|
|
238
235
|
|
|
239
236
|
g.fillStyle = color;
|
|
@@ -273,10 +270,10 @@ export class MacromoleculeDifferenceCellRenderer extends DG.GridCellRenderer {
|
|
|
273
270
|
w = getUpdatedWidth(grid, g, x, w);
|
|
274
271
|
//TODO: can this be replaced/merged with splitSequence?
|
|
275
272
|
const [s1, s2] = s.split('#');
|
|
276
|
-
const splitter =
|
|
273
|
+
const splitter = bio.getSplitter(units, separator);
|
|
277
274
|
const subParts1 = splitter(s1);
|
|
278
275
|
const subParts2 = splitter(s2);
|
|
279
|
-
drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, units
|
|
276
|
+
drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, units);
|
|
280
277
|
}
|
|
281
278
|
}
|
|
282
279
|
|
|
@@ -315,7 +312,7 @@ export function drawMoleculeDifferenceOnCanvas(
|
|
|
315
312
|
g.font = '12px monospace';
|
|
316
313
|
g.textBaseline = 'top';
|
|
317
314
|
|
|
318
|
-
let palette: SeqPalette = UnknownSeqPalettes.Color;
|
|
315
|
+
let palette: bio.SeqPalette = bio.UnknownSeqPalettes.Color;
|
|
319
316
|
if (units != 'HELM')
|
|
320
317
|
palette = getPaletteByType(units.substring(units.length - 2));
|
|
321
318
|
|
|
@@ -327,12 +324,12 @@ export function drawMoleculeDifferenceOnCanvas(
|
|
|
327
324
|
|
|
328
325
|
if (amino1 != amino2) {
|
|
329
326
|
const color2 = palette.get(amino2);
|
|
330
|
-
const subX0 = printLeftOrCentered(updatedX, updatedY - vShift, w, h, g, amino1, color1, 0, true);
|
|
331
|
-
const subX1 = printLeftOrCentered(updatedX, updatedY + vShift, w, h, g, amino2, color2, 0, true);
|
|
327
|
+
const subX0 = bio.printLeftOrCentered(updatedX, updatedY - vShift, w, h, g, amino1, color1, 0, true);
|
|
328
|
+
const subX1 = bio.printLeftOrCentered(updatedX, updatedY + vShift, w, h, g, amino2, color2, 0, true);
|
|
332
329
|
updatedX = Math.max(subX1, subX0);
|
|
333
330
|
if (molDifferences)
|
|
334
331
|
molDifferences[i] = createDifferenceCanvas(amino1, amino2, color1, color2, updatedY, vShift, h);
|
|
335
|
-
} else { updatedX = printLeftOrCentered(updatedX, updatedY, w, h, g, amino1, color1, 0, true, 0.5); }
|
|
332
|
+
} else { updatedX = bio.printLeftOrCentered(updatedX, updatedY, w, h, g, amino1, color1, 0, true, 0.5); }
|
|
336
333
|
updatedX += 4;
|
|
337
334
|
}
|
|
338
335
|
g.restore();
|
|
@@ -356,7 +353,7 @@ function createDifferenceCanvas(
|
|
|
356
353
|
canvas.width = width + 4;
|
|
357
354
|
context.font = '12px monospace';
|
|
358
355
|
context.textBaseline = 'top';
|
|
359
|
-
printLeftOrCentered(0, y - shift, width, h, context, amino1, color1, 0, true);
|
|
360
|
-
printLeftOrCentered(0, y + shift, width, h, context, amino2, color2, 0, true);
|
|
356
|
+
bio.printLeftOrCentered(0, y - shift, width, h, context, amino1, color1, 0, true);
|
|
357
|
+
bio.printLeftOrCentered(0, y + shift, width, h, context, amino2, color2, 0, true);
|
|
361
358
|
return canvas;
|
|
362
359
|
}
|
package/src/utils/convert.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as grok from 'datagrok-api/grok';
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
4
5
|
|
|
5
6
|
import $ from 'cash-dom';
|
|
6
7
|
import {Subscription} from 'rxjs';
|
|
7
|
-
import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
|
|
8
|
-
import {NOTATION, UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
let convertDialog: DG.Dialog | null = null;
|
|
@@ -17,13 +16,13 @@ let convertDialogSubs: Subscription[] = [];
|
|
|
17
16
|
* @param {DG.column} col Column with 'Macromolecule' semantic type
|
|
18
17
|
*/
|
|
19
18
|
export function convert(col: DG.Column): void {
|
|
20
|
-
const converter = new NotationConverter(col);
|
|
21
|
-
const currentNotation: NOTATION = converter.notation;
|
|
19
|
+
const converter = new bio.NotationConverter(col);
|
|
20
|
+
const currentNotation: bio.NOTATION = converter.notation;
|
|
22
21
|
//TODO: read all notations
|
|
23
22
|
const notations = [
|
|
24
|
-
NOTATION.FASTA,
|
|
25
|
-
NOTATION.SEPARATOR,
|
|
26
|
-
NOTATION.HELM
|
|
23
|
+
bio.NOTATION.FASTA,
|
|
24
|
+
bio.NOTATION.SEPARATOR,
|
|
25
|
+
bio.NOTATION.HELM
|
|
27
26
|
];
|
|
28
27
|
const separatorArray = ['-', '.', '/'];
|
|
29
28
|
const filteredNotations = notations.filter((e) => e !== currentNotation);
|
|
@@ -33,7 +32,7 @@ export function convert(col: DG.Column): void {
|
|
|
33
32
|
|
|
34
33
|
// hide the separator input for non-SEPARATOR target notations
|
|
35
34
|
const toggleSeparator = () => {
|
|
36
|
-
if (targetNotationInput.value !== NOTATION.SEPARATOR)
|
|
35
|
+
if (targetNotationInput.value !== bio.NOTATION.SEPARATOR)
|
|
37
36
|
$(separatorInput.root).hide();
|
|
38
37
|
else
|
|
39
38
|
$(separatorInput.root).show();
|
|
@@ -64,7 +63,7 @@ export function convert(col: DG.Column): void {
|
|
|
64
63
|
separatorInput.root
|
|
65
64
|
]))
|
|
66
65
|
.onOK(async () => {
|
|
67
|
-
const targetNotation = targetNotationInput.value as NOTATION;
|
|
66
|
+
const targetNotation = targetNotationInput.value as bio.NOTATION;
|
|
68
67
|
const separator: string | null = separatorInput.value;
|
|
69
68
|
|
|
70
69
|
await convertDo(col, targetNotation, separator);
|
|
@@ -81,9 +80,9 @@ export function convert(col: DG.Column): void {
|
|
|
81
80
|
|
|
82
81
|
/** Creates a new column with converted sequences and detects its semantic type */
|
|
83
82
|
export async function convertDo(
|
|
84
|
-
srcCol: DG.Column, targetNotation: NOTATION, separator: string | null
|
|
83
|
+
srcCol: DG.Column, targetNotation: bio.NOTATION, separator: string | null
|
|
85
84
|
): Promise<DG.Column> {
|
|
86
|
-
const converter = new NotationConverter(srcCol);
|
|
85
|
+
const converter = new bio.NotationConverter(srcCol);
|
|
87
86
|
const newColumn = converter.convert(targetNotation, separator);
|
|
88
87
|
srcCol.dataFrame.columns.add(newColumn);
|
|
89
88
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as grok from 'datagrok-api/grok';
|
|
4
|
-
import
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
5
|
+
|
|
5
6
|
import wu from 'wu';
|
|
6
|
-
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
7
7
|
|
|
8
8
|
const FASTA_LINE_WIDTH = 60;
|
|
9
9
|
|
|
@@ -27,7 +27,7 @@ export function saveAsFastaUI() {
|
|
|
27
27
|
.filter((gc: DG.GridColumn) => {
|
|
28
28
|
const col: DG.Column | null = gc.column;
|
|
29
29
|
if (col && col.semType === DG.SEMTYPE.MACROMOLECULE) {
|
|
30
|
-
const uh = new UnitsHandler(col);
|
|
30
|
+
const uh = new bio.UnitsHandler(col);
|
|
31
31
|
return uh.isFasta();
|
|
32
32
|
}
|
|
33
33
|
return false;
|
|
@@ -69,7 +69,7 @@ export function saveAsFastaUI() {
|
|
|
69
69
|
export function saveAsFastaDo(
|
|
70
70
|
idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n'
|
|
71
71
|
): string {
|
|
72
|
-
const splitter: SplitterFunc =
|
|
72
|
+
const splitter: bio.SplitterFunc = bio.splitterAsFasta;
|
|
73
73
|
|
|
74
74
|
const fastaLines: string[] = [];
|
|
75
75
|
|
|
@@ -91,7 +91,7 @@ export function saveAsFastaDo(
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/* split sequence for monomers to prevent wrapping monomer partially */
|
|
94
|
-
export function wrapSequence(seq: string, splitter: SplitterFunc, lineWidth: number = FASTA_LINE_WIDTH): string[] {
|
|
94
|
+
export function wrapSequence(seq: string, splitter: bio.SplitterFunc, lineWidth: number = FASTA_LINE_WIDTH): string[] {
|
|
95
95
|
const seqMonomerList = splitter(seq);
|
|
96
96
|
let seqPos: number = 0;
|
|
97
97
|
const seqLength: number = seqMonomerList.length;
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import {WebLogo, SplitterFunc} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
3
1
|
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
CAP_GROUP_NAME, CAP_GROUP_SMILES, jsonSdfMonomerLibDict, MONOMER_ENCODE_MAX, MONOMER_ENCODE_MIN, MONOMER_SYMBOL,
|
|
6
8
|
RGROUP_ALTER_ID, RGROUP_FIELD, RGROUP_LABEL, SDF_MONOMER_NAME
|
|
@@ -18,7 +20,7 @@ export function encodeMonomers(col: DG.Column): DG.Column | null {
|
|
|
18
20
|
const monomerSymbolDict: { [key: string]: number } = {};
|
|
19
21
|
const units = col.tags[DG.TAGS.UNITS];
|
|
20
22
|
const sep = col.getTag(UnitsHandler.TAGS.separator);
|
|
21
|
-
const splitterFunc: SplitterFunc =
|
|
23
|
+
const splitterFunc: bio.SplitterFunc = bio.getSplitter(units, sep);
|
|
22
24
|
const encodedStringArray = [];
|
|
23
25
|
for (let i = 0; i < col.length; ++i) {
|
|
24
26
|
let encodedMonomerStr = '';
|
|
@@ -42,7 +44,7 @@ export function encodeMonomers(col: DG.Column): DG.Column | null {
|
|
|
42
44
|
export function getMolfilesFromSeq(col: DG.Column, monomersLibObject: any[]): any[][] | null {
|
|
43
45
|
const units = col.tags[DG.TAGS.UNITS];
|
|
44
46
|
const sep = col.getTag('separator');
|
|
45
|
-
const splitterFunc: SplitterFunc =
|
|
47
|
+
const splitterFunc: bio.SplitterFunc = bio.getSplitter(units, sep);
|
|
46
48
|
const monomersDict = createMomomersMolDict(monomersLibObject);
|
|
47
49
|
const molFiles = [];
|
|
48
50
|
for (let i = 0; i < col.length; ++i) {
|
|
@@ -66,7 +68,7 @@ export function getMolfilesFromSeq(col: DG.Column, monomersLibObject: any[]): an
|
|
|
66
68
|
export function getMolfilesFromSingleSeq(cell: DG.Cell, monomersLibObject: any[]): any[][] | null {
|
|
67
69
|
const units = cell.column.tags[DG.TAGS.UNITS];
|
|
68
70
|
const sep = cell.column!.getTag('separator');
|
|
69
|
-
const splitterFunc: SplitterFunc =
|
|
71
|
+
const splitterFunc: bio.SplitterFunc = bio.getSplitter(units, sep);
|
|
70
72
|
const monomersDict = createMomomersMolDict(monomersLibObject);
|
|
71
73
|
const molFiles = [];
|
|
72
74
|
const macroMolecule = cell.value;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import * as ui from 'datagrok-api/ui';
|
|
2
2
|
import * as grok from 'datagrok-api/grok';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
import {IVdRegionsViewer} from '@datagrok-libraries/bio/src/viewers/vd-regions-viewer';
|
|
7
|
-
import {WebLogo} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
8
|
-
|
|
9
|
-
const vrt = VdRegionType;
|
|
6
|
+
const vrt = bio.VdRegionType;
|
|
10
7
|
|
|
11
8
|
// Positions of regions for numbering schemes
|
|
12
9
|
// http://www.bioinf.org.uk/abs/info.html
|
|
@@ -37,7 +34,7 @@ const vrt = VdRegionType;
|
|
|
37
34
|
/** Viewer with tabs based on description of chain regions.
|
|
38
35
|
* Used to define regions of an immunoglobulin LC.
|
|
39
36
|
*/
|
|
40
|
-
export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
37
|
+
export class VdRegionsViewer extends DG.JsViewer implements bio.IVdRegionsViewer {
|
|
41
38
|
// private regionsDf: DG.DataFrame;
|
|
42
39
|
private regionsFg: DG.FilterGroup | null = null;
|
|
43
40
|
// private regionsTV: DG.TableView;
|
|
@@ -46,7 +43,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
46
43
|
private isOpened: boolean = false;
|
|
47
44
|
private panelNode: DG.DockNode | null = null;
|
|
48
45
|
|
|
49
|
-
public regions: VdRegion[] = [];
|
|
46
|
+
public regions: bio.VdRegion[] = [];
|
|
50
47
|
public regionTypes: string[];
|
|
51
48
|
public chains: string[];
|
|
52
49
|
public sequenceColumnNamePostfix: string;
|
|
@@ -60,7 +57,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
// TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
|
|
63
|
-
public async setDf(value: DG.DataFrame, regions: VdRegion[]) {
|
|
60
|
+
public async setDf(value: DG.DataFrame, regions: bio.VdRegion[]) {
|
|
64
61
|
console.debug('VdRegionsViewer.setDf()');
|
|
65
62
|
await this.destroyView();
|
|
66
63
|
this.regions = regions;
|
|
@@ -177,7 +174,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
177
174
|
//#region -- View --
|
|
178
175
|
private host: HTMLElement | null = null;
|
|
179
176
|
private mainLayout: HTMLTableElement | null = null;
|
|
180
|
-
private logos: { [chain: string]: WebLogo }[] = [];
|
|
177
|
+
private logos: { [chain: string]: bio.WebLogo }[] = [];
|
|
181
178
|
|
|
182
179
|
private async destroyView(): Promise<void> {
|
|
183
180
|
// TODO: Unsubscribe from and remove all view elements
|
|
@@ -197,16 +194,16 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
197
194
|
const colNames: { [chain: string]: string } = Object.assign({},
|
|
198
195
|
...this.chains.map((chain) => ({[chain]: `${chain} ${this.sequenceColumnNamePostfix}`})));
|
|
199
196
|
|
|
200
|
-
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
197
|
+
const regionsFiltered: bio.VdRegion[] = this.regions.filter((r: bio.VdRegion) => this.regionTypes.includes(r.type));
|
|
201
198
|
|
|
202
199
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
203
200
|
|
|
204
201
|
this.logos = [];
|
|
205
202
|
|
|
206
203
|
for (let orderI = 0; orderI < orderList.length; orderI++) {
|
|
207
|
-
const regionChains: { [chain: string]: WebLogo } = {};
|
|
204
|
+
const regionChains: { [chain: string]: bio.WebLogo } = {};
|
|
208
205
|
for (const chain of this.chains) {
|
|
209
|
-
const region: VdRegion | undefined = regionsFiltered
|
|
206
|
+
const region: bio.VdRegion | undefined = regionsFiltered
|
|
210
207
|
.find((r) => r.order == orderList[orderI] && r.chain == chain);
|
|
211
208
|
regionChains[chain] = (await this.dataFrame.plot.fromType('WebLogo', {
|
|
212
209
|
sequenceColumnName: colNames[chain],
|
|
@@ -215,7 +212,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
215
212
|
fixWidth: true,
|
|
216
213
|
skipEmptyPositions: this.skipEmptyPositions,
|
|
217
214
|
positionWidth: this.positionWidth,
|
|
218
|
-
})) as unknown as WebLogo;
|
|
215
|
+
})) as unknown as bio.WebLogo;
|
|
219
216
|
}
|
|
220
217
|
// WebLogo creation fires onRootSizeChanged event even before control being added to this.logos
|
|
221
218
|
this.logos[orderI] = regionChains;
|
|
@@ -240,7 +237,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
240
237
|
})] : []),
|
|
241
238
|
// List with controls for regions
|
|
242
239
|
...[...Array(orderList.length).keys()].map((orderI) => {
|
|
243
|
-
const wl: WebLogo = this.logos[orderI][chain];
|
|
240
|
+
const wl: bio.WebLogo = this.logos[orderI][chain];
|
|
244
241
|
wl.root.style.height = '100%';
|
|
245
242
|
|
|
246
243
|
const resDiv = ui.div([wl.root]/*`${chain} ${regionsFiltered[rI]}`*/, {
|
|
@@ -257,7 +254,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
257
254
|
},
|
|
258
255
|
['', ...[...Array(orderList.length).keys()].map(
|
|
259
256
|
(orderI: number) => regionsFiltered.find(
|
|
260
|
-
(r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
|
|
257
|
+
(r: bio.VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
|
|
261
258
|
)!.name || 'Name')]
|
|
262
259
|
);
|
|
263
260
|
this.mainLayout.className = 'mlb-vd-regions-viewer-table2';
|
|
@@ -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
|
+
}
|