@datagrok/peptides 1.0.0 → 1.1.0

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.
Files changed (38) hide show
  1. package/dist/package-test.js +9821 -5237
  2. package/dist/package.js +9737 -4734
  3. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +450 -360
  4. package/files/aligned.csv +648 -648
  5. package/files/aligned_2.csv +541 -10275
  6. package/files/aligned_3.csv +335 -0
  7. package/helm/JSDraw/Pistoia.HELM.js +27 -0
  8. package/package.json +24 -16
  9. package/src/__jest__/remote.test.ts +33 -15
  10. package/src/__jest__/test-node.ts +3 -2
  11. package/src/model.ts +416 -521
  12. package/src/package-test.ts +0 -2
  13. package/src/package.ts +7 -126
  14. package/src/tests/core.ts +60 -18
  15. package/src/tests/peptide-space-test.ts +7 -7
  16. package/src/tests/utils.ts +3 -19
  17. package/src/utils/cell-renderer.ts +140 -262
  18. package/src/utils/constants.ts +7 -4
  19. package/src/utils/filtering-statistics.ts +21 -53
  20. package/src/utils/misc.ts +80 -16
  21. package/src/utils/peptide-similarity-space.ts +1 -1
  22. package/src/utils/types.ts +7 -5
  23. package/src/viewers/peptide-space-viewer.ts +18 -20
  24. package/src/viewers/sar-viewer.ts +33 -22
  25. package/src/widgets/analyze-peptides.ts +34 -10
  26. package/src/widgets/distribution.ts +169 -60
  27. package/src/widgets/manual-alignment.ts +5 -4
  28. package/src/widgets/subst-table.ts +6 -2
  29. package/{test-Peptides-69a4761f6044-40ac3a0c.html → test-Peptides-eb4783c07294-f4162403.html} +43 -22
  30. package/detectors.js +0 -9
  31. package/src/monomer-library.ts +0 -193
  32. package/src/tests/msa-tests.ts +0 -27
  33. package/src/utils/chem-palette.ts +0 -280
  34. package/src/utils/multiple-sequence-alignment.ts +0 -106
  35. package/src/utils/multivariate-analysis.ts +0 -76
  36. package/src/viewers/stacked-barchart-viewer.ts +0 -339
  37. package/src/widgets/multiple-sequence-alignment.ts +0 -9
  38. package/src/widgets/peptide-molecule.ts +0 -82
@@ -1,106 +0,0 @@
1
- import * as DG from 'datagrok-api/dg';
2
-
3
- //@ts-ignore
4
- import Aioli from '@biowasm/aioli';
5
-
6
- import {AlignedSequenceEncoder} from '@datagrok-libraries/bio/src/sequence-encoder';
7
- import * as C from './constants';
8
-
9
- /**
10
- * Converts array of sequences into simple fasta string.
11
- *
12
- * @param {string[]} sequences Input list of sequences.
13
- * @return {string} Fasta-formatted string.
14
- */
15
- function _stringsToFasta(sequences: string[]): string {
16
- return sequences.reduce((a, v, i) => a + `>sample${i + 1}\n${v}\n`, '');
17
- }
18
-
19
- /**
20
- * Extracts array of sequences from simple fasta string.
21
- *
22
- * @param {string} fasta Fasta-formatted string.
23
- * @return {string[]} Output list of sequences.
24
- */
25
- function _fastaToStrings(fasta: string): string[] {
26
- return fasta.replace(/>sample\d+(\r\n|\r|\n)/g, '').split('\n');
27
- }
28
-
29
- /**
30
- * Converts aligned sequence to semantic type format.
31
- *
32
- * @param {string} seq Source sequence.
33
- * @return {string} Formatted sequence.
34
- */
35
- function _castAligned(seq: string): string {
36
- let delimited = '';
37
-
38
- for (const char of seq)
39
- delimited += char == '-' ? char : `-${char}`;
40
-
41
- return `NH2${delimited}-COOH`;
42
- }
43
-
44
- /**
45
- * Formats a batch of sequences to correspond the semantic type.
46
- *
47
- * @param {string[]} alignment List of aligned sequences.
48
- * @return {string[]} Formatted sequences.
49
- */
50
- function _stringsToAligned(alignment: string[]): string[] {
51
- const nItems = alignment.length;
52
- const aligned = new Array<string>(nItems);
53
-
54
- for (let i = 0; i < nItems; ++i)
55
- aligned[i] = _castAligned(alignment[i]);
56
-
57
- return aligned;
58
- }
59
-
60
- /**
61
- * Runs Aioli environment with kalign tool.
62
- *
63
- * @param {DG.Column} col Column with sequences.
64
- * @param {boolean} isAligned Whether the column is aligned.
65
- * @return {Promise<DG.Column>} Aligned sequences.
66
- */
67
- export async function runKalign(col: DG.Column, isAligned = false) : Promise<DG.Column> {
68
- let sequences = col.toList();
69
-
70
- if (isAligned)
71
- sequences = sequences.map((v: string, _) => AlignedSequenceEncoder.clean(v).replace(/\-/g, ''));
72
-
73
- const fasta = _stringsToFasta(sequences);
74
- const CLI = await new Aioli({
75
- tool: 'kalign',
76
- version: '3.3.1',
77
- reinit: true,
78
- });
79
-
80
- console.log(['fasta.length =', fasta.length]);
81
-
82
- await CLI.fs.writeFile('input.fa', fasta);
83
- const output = await CLI.exec('kalign input.fa -f fasta -o result.fasta');
84
- const buf = await CLI.cat('result.fasta');
85
-
86
- console.warn(output);
87
-
88
- const aligned = _fastaToStrings(buf).slice(0, sequences.length);
89
- const alignedCol = DG.Column.fromStrings(`(${col.name})msa`, _stringsToAligned(aligned));
90
- alignedCol.semType = C.SEM_TYPES.ALIGNED_SEQUENCE;
91
- return alignedCol;
92
- }
93
-
94
- export async function testMSAEnoughMemory(col: DG.Column): Promise<void> {
95
- const sequencesCount = col.length;
96
- const delta = sequencesCount/100;
97
-
98
- for (let i = delta; i < sequencesCount; i += delta) {
99
- try {
100
- await runKalign(DG.Column.fromStrings(col.name, col.toList().slice(0, Math.round(i))));
101
- console.log(`runKalign succeeded on ${i}`);
102
- } catch (error) {
103
- console.log(`runKalign failed on ${i} with '${error}'`);
104
- }
105
- }
106
- }
@@ -1,76 +0,0 @@
1
- import * as grok from 'datagrok-api/grok';
2
- import * as DG from 'datagrok-api/dg';
3
-
4
- import * as C from './constants';
5
-
6
- import {AlignedSequenceEncoder} from '@datagrok-libraries/bio/src/sequence-encoder';
7
-
8
- export async function callMVA(
9
- currentDf: DG.DataFrame,
10
- options: {[name: string]: string},
11
- sequencesCol: DG.Column,
12
- ): Promise<void> {
13
- const activityCol = await _scaleColumn(currentDf.getCol(options['activityColumnName']), options['scaling']);
14
- const encDf = _encodeSequences(sequencesCol);
15
-
16
- //TODO: is it correct? Is it needed?
17
- _insertColumns(
18
- currentDf,
19
- [DG.Column.fromList('double', C.COLUMNS_NAMES.ACTIVITY_SCALED, activityCol.toList())],
20
- );
21
- _insertColumns(currentDf, encDf.columns.toList());
22
-
23
- const res = await grok.functions.call('MultivariateAnalysis', {
24
- table: currentDf,
25
- features: encDf.columns.names(),
26
- prediction: C.COLUMNS_NAMES.ACTIVITY_SCALED,
27
- components: 10,
28
- showScores: true,
29
- showRegresCoefs: true,
30
- });
31
- console.log(res);
32
- }
33
-
34
- /**
35
- * Encodes a series of sequences into a certain scale.
36
- *
37
- * @param {string[]} sequencesCol Column containing the sequences.
38
- * @return {DG.DataFrame} The data frame with seqences encoded.
39
- */
40
- function _encodeSequences(sequencesCol: DG.Column): DG.DataFrame {
41
- const nRows = sequencesCol.length;
42
- const nCols = AlignedSequenceEncoder.clean(sequencesCol.get(0)).length;
43
- const enc = new AlignedSequenceEncoder('WimleyWhite');
44
- const positions = new Array(nCols).fill(0).map((_) => new Float32Array(nRows));
45
-
46
- for (let j = 0; j < nRows; ++j) {
47
- const s = AlignedSequenceEncoder.clean(sequencesCol.get(j));
48
- for (let i = 0; i < nCols; ++i)
49
- positions[i][j] = enc.encodeLettter(s[i]);
50
- }
51
- return DG.DataFrame.fromColumns(positions.map(
52
- (v, i) => DG.Column.fromFloat32Array((i+1).toString(), v),
53
- ));
54
- }
55
-
56
- async function _scaleColumn(column: DG.Column, method: string): Promise<DG.Column> {
57
- if (method == 'none')
58
- return column;
59
-
60
-
61
- const formula = (method.startsWith('-') ? '0-' : '')+'Log10(${'+column.name+'})';
62
- const newCol = await column.applyFormula(formula);
63
-
64
- if (newCol == null)
65
- throw new Error('Column formula returned unexpected null.');
66
-
67
- return newCol;
68
- }
69
-
70
- function _insertColumns(targetDf: DG.DataFrame, columns: DG.Column[]): DG.DataFrame {
71
- for (const col of columns)
72
- targetDf.columns.add(col);
73
-
74
- return targetDf;
75
- }
76
-
@@ -1,339 +0,0 @@
1
- import * as DG from 'datagrok-api/dg';
2
- import * as rxjs from 'rxjs';
3
- import * as ui from 'datagrok-api/ui';
4
- import {MonomerLibrary} from '../monomer-library';
5
-
6
- import * as C from '../utils/constants';
7
- import * as type from '../utils/types';
8
- import {PeptidesModel} from '../model';
9
-
10
- export function addViewerToHeader(grid: DG.Grid, barchart: StackedBarChart): void {
11
- if (grid.temp['containsBarchart'])
12
- return;
13
-
14
- const eventAction = (ev: MouseEvent): void => {
15
- const cell = grid.hitTest(ev.offsetX, ev.offsetY);
16
- if (cell?.isColHeader && cell.tableColumn?.semType == C.SEM_TYPES.AMINO_ACIDS) {
17
- const newBarPart = barchart.findAARandPosition(cell, ev);
18
- barchart._currentBarPart = newBarPart;
19
- barchart.requestAction(ev, newBarPart);
20
- barchart.computeData();
21
- }
22
- };
23
-
24
- // The following events makes the barchart interactive
25
- rxjs.fromEvent<MouseEvent>(grid.overlay, 'mousemove').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
26
- rxjs.fromEvent<MouseEvent>(grid.overlay, 'click').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
27
- rxjs.fromEvent<MouseEvent>(grid.overlay, 'mouseout').subscribe(() => barchart.unhighlight());
28
-
29
- barchart.tableCanvas = grid.canvas;
30
-
31
- //Setting grid options
32
- grid.setOptions({'colHeaderHeight': 130});
33
-
34
- grid.onCellTooltip((cell, x, y) => {
35
- if (
36
- cell.tableColumn &&
37
- [C.SEM_TYPES.AMINO_ACIDS, C.SEM_TYPES.ALIGNED_SEQUENCE].includes(cell.tableColumn.semType as C.SEM_TYPES)
38
- ) {
39
- if (!cell.isColHeader) {
40
- const monomerLib = cell.cell.dataFrame.temp[MonomerLibrary.id];
41
- PeptidesModel.chemPalette.showTooltip(cell, x, y, monomerLib);
42
- } else if (barchart._currentBarPart) {
43
- let elements: HTMLElement[] = [];
44
- elements = elements.concat([ui.divText(barchart._currentBarPart.aaName)]);
45
- ui.tooltip.show(ui.divV(elements), x, y);
46
- }
47
- }
48
- return true;
49
- });
50
-
51
- grid.onCellRender.subscribe((args) => {
52
- const context = args.g;
53
- const boundX = args.bounds.x;
54
- const boundY = args.bounds.y;
55
- const boundWidth = args.bounds.width;
56
- const boundHeight = args.bounds.height;
57
- const cell = args.cell;
58
- context.save();
59
- context.beginPath();
60
- context.rect(boundX, boundY, boundWidth, boundHeight);
61
- context.clip();
62
-
63
- if (cell.isColHeader && barchart.aminoColumnNames.includes(cell.gridColumn.name)) {
64
- barchart.renderBarToCanvas(context, cell, boundX, boundY, boundWidth, boundHeight);
65
- args.preventDefault();
66
- }
67
- context.restore();
68
- });
69
-
70
- grid.temp['containsBarchart'] = true;
71
- //FIXME: for some reason barchat didn't show when running analysis. This fixes it, but it's bad. Find a way to fix
72
- // the problem
73
- barchart.unhighlight();
74
- }
75
-
76
- export class StackedBarChart extends DG.JsViewer {
77
- dataEmptyAA: string;
78
- _currentBarPart: type.BarChart.BarPart | null = null;
79
- tableCanvas: HTMLCanvasElement | undefined;
80
- aminoColumnNames: string[] = [];
81
- ord: { [Key: string]: number; } = {};
82
- aminoColumnIndices: {[Key: string]: number} = {};
83
- aggregatedFilterTables: type.DataFrameDict = {};
84
- max = 0;
85
- barStats: {[Key: string]: type.BarChart.BarStatsObject[]} = {};
86
- selected: type.BarChart.BarPart[] = [];
87
- aggregatedSelectedTables: type.DataFrameDict = {};
88
- model!: PeptidesModel;
89
-
90
- constructor() {
91
- super();
92
- this.dataEmptyAA = this.string('dataEmptyAA', '-');
93
- }
94
-
95
- init(): void {
96
- const groups: {[key: string]: string[]} = {
97
- 'yellow': ['C', 'U'],
98
- 'red': ['G', 'P'],
99
- 'all_green': ['A', 'V', 'I', 'L', 'M', 'F', 'Y', 'W'],
100
- 'light_blue': ['R', 'H', 'K'],
101
- 'dark_blue': ['D', 'E'],
102
- 'orange': ['S', 'T', 'N', 'Q'],
103
- };
104
- let i = 0;
105
-
106
- for (const value of Object.values(groups)) {
107
- for (const obj of value)
108
- this.ord[obj] = i++;
109
- }
110
-
111
- this.aminoColumnNames = [];
112
- }
113
-
114
- // Stream subscriptions
115
- async onTableAttached(): Promise<void> {
116
- this.init();
117
- this.model = await PeptidesModel.getInstance(this.dataFrame);
118
- // this.controller.init(this.dataFrame);
119
- if (this.dataFrame) {
120
- this.subs.push(DG.debounce(this.dataFrame.selection.onChanged, 50).subscribe((_) => this.computeData()));
121
- this.subs.push(DG.debounce(this.dataFrame.filter.onChanged, 50).subscribe((_) => this.computeData()));
122
- this.subs.push(DG.debounce(this.dataFrame.onValuesChanged, 50).subscribe(() => this.computeData()));
123
- }
124
- }
125
-
126
- // Cancel subscriptions when the viewer is detached
127
- detach(): void {
128
- this.subs.forEach((sub) => sub.unsubscribe());
129
- }
130
-
131
- computeData(): void {
132
- this.aminoColumnNames = [];
133
- this.aminoColumnIndices = {};
134
-
135
- this.dataFrame.columns.names().forEach((name: string) => {
136
- if (this.dataFrame.getCol(name).semType === C.SEM_TYPES.AMINO_ACIDS &&
137
- !this.dataFrame.getCol(name).categories.includes('COOH') &&
138
- !this.dataFrame.getCol(name).categories.includes('NH2')) {
139
- this.aminoColumnIndices[name] = this.aminoColumnNames.length + 1;
140
- this.aminoColumnNames.push(name);
141
- }
142
- });
143
-
144
- this.aggregatedFilterTables = {};
145
- this.aggregatedSelectedTables = {};
146
- //TODO: optimize it, why store so many tables?
147
- this.aminoColumnNames.forEach((name) => {
148
- this.aggregatedFilterTables[name] = this.dataFrame
149
- .groupBy([name])
150
- .whereRowMask(this.dataFrame.filter)
151
- .add('count', name, `${name}_count`)
152
- .aggregate();
153
-
154
- this.aggregatedSelectedTables[name] = this.dataFrame
155
- .groupBy([name])
156
- .whereRowMask(this.dataFrame.selection)
157
- .add('count', name, `${name}_count`)
158
- .aggregate();
159
- });
160
-
161
- this.barStats = {};
162
-
163
- for (const [name, df] of Object.entries(this.aggregatedFilterTables)) {
164
- const colData: {'name': string, 'count': number, 'selectedCount': number}[] = [];
165
- const aminoCol = df.getCol(name);
166
- const aminoCountCol = df.getCol(`${name}_count`);
167
- this.barStats[name] = colData;
168
-
169
- for (let i = 0; i < df.rowCount; i++) {
170
- const amino = aminoCol.get(i);
171
- const aminoCount = aminoCountCol.get(i);
172
- const aminoObj = {'name': amino, 'count': aminoCount, 'selectedCount': 0};
173
- const aggSelectedAminoCol = this.aggregatedSelectedTables[name].getCol(`${name}`);
174
- const aggSelectedCountCol = this.aggregatedSelectedTables[name].getCol(`${name}_count`);
175
-
176
- if (!amino || amino === this.dataEmptyAA)
177
- continue;
178
-
179
- colData.push(aminoObj);
180
-
181
- for (let j = 0; j < aggSelectedCountCol.length; j++) {
182
- const selectedAmino = aggSelectedAminoCol.get(j);
183
- const curAmino = (selectedAmino);
184
- if (curAmino == amino) {
185
- aminoObj['selectedCount'] = aggSelectedCountCol.get(j);
186
- break;
187
- }
188
- }
189
- }
190
-
191
- colData.sort((o1, o2) => this.ord[o2['name']] - this.ord[o1['name']]);
192
- }
193
-
194
- this.max = this.dataFrame.filter.trueCount;
195
- }
196
-
197
- renderBarToCanvas(g: CanvasRenderingContext2D, cell: DG.GridCell, x: number, y: number, w: number, h: number): void {
198
- const name = cell.tableColumn!.name;
199
- const colNameSize = g.measureText(name).width;
200
- const barData = this.barStats[name];
201
- const margin = 0.2;
202
- const innerMargin = 0.02;
203
- const selectLineRatio = 0.1;
204
- let sum = 0;
205
-
206
- barData.forEach((obj) => {
207
- sum += obj['count'];
208
- });
209
-
210
- x = x + w * margin;
211
- y = y + h * margin / 4;
212
- w = w - w * margin * 2;
213
- h = h - h * margin;
214
- const barWidth = w - 10;
215
- g.fillStyle = 'black';
216
- g.textBaseline = 'top';
217
- g.font = `${h * margin / 2}px`;
218
- g.fillText(name, x + (w - colNameSize) / 2, y + h + h * margin / 4);
219
-
220
- barData.forEach((obj) => {
221
- const sBarHeight = h * obj['count'] / this.max;
222
- const gapSize = sBarHeight * innerMargin;
223
- const verticalShift = (this.max - sum) / this.max;
224
- const [color, aarOuter] = PeptidesModel.chemPalette.getColorAAPivot(obj['name']);
225
- const textSize = g.measureText(aarOuter);
226
- const fontSize = 11;
227
- const leftMargin = (w - (aarOuter.length > 1 ? fontSize : textSize.width - 8)) / 2;
228
- const subBartHeight = sBarHeight - gapSize;
229
- const yStart = h * verticalShift + gapSize / 2;
230
- const xStart = (w - barWidth) / 2;
231
- const absX = x + leftMargin;
232
- const absY = y + yStart + subBartHeight / 2 + (aarOuter.length == 1 ? + 4 : 0);
233
- const eps = 0.1;
234
-
235
- g.strokeStyle = color;
236
- g.fillStyle = color;
237
- if (textSize.width <= subBartHeight) {
238
- const origTransform = g.getTransform();
239
-
240
- if (color != PeptidesModel.chemPalette.undefinedColor) {
241
- g.fillRect(x + xStart, y + yStart, barWidth, subBartHeight);
242
- g.fillStyle = 'black';
243
- } else
244
- g.strokeRect(x + xStart + 0.5, y + yStart, barWidth - 1, subBartHeight);
245
-
246
- g.font = `${fontSize}px monospace`;
247
- g.textAlign = 'center';
248
- g.textBaseline = 'bottom';
249
-
250
- if (aarOuter.length > 1) {
251
- g.translate(absX, absY);
252
- g.rotate(Math.PI / 2);
253
- g.translate(-absX, -absY);
254
- }
255
-
256
- g.fillText(aarOuter, absX, absY);
257
- g.setTransform(origTransform);
258
- } else
259
- g.fillRect(x + xStart, y + yStart, barWidth, subBartHeight);
260
-
261
- if (obj['selectedCount'] > eps) {
262
- g.fillStyle = 'rgb(255,165,0)';
263
- g.fillRect(
264
- x + xStart - w * selectLineRatio * 2,
265
- y + yStart,
266
- barWidth * selectLineRatio,
267
- h * obj['selectedCount'] / this.max - gapSize,
268
- );
269
- }
270
-
271
- sum -= obj['count'];
272
- });
273
- }
274
-
275
- findAARandPosition(cell: DG.GridCell, mouseEvent: MouseEvent): {colName: string, aaName: string} | null {
276
- if (!cell.tableColumn?.name || !this.aminoColumnNames.includes(cell.tableColumn.name))
277
- return null;
278
-
279
- const offsetX = mouseEvent.offsetX;
280
- const offsetY = mouseEvent.offsetY;
281
- const colName = cell.tableColumn?.name;
282
- const innerMargin = 0.02;
283
- const margin = 0.2;
284
- const bound = cell.bounds;
285
- const height = 130;
286
- const x = bound.x + bound.width * margin;
287
- const y = height * margin / 4;
288
- const w = bound.width - bound.width * margin * 2;
289
- const h = height - height * margin;
290
- const barData = this.barStats[colName];
291
- const barWidth = w - 10;
292
- let sum = 0;
293
-
294
- barData.forEach((obj) => {
295
- sum += obj['count'];
296
- });
297
-
298
- const xStart = x + (w - barWidth) / 2;
299
- for (const obj of barData) {
300
- const sBarHeight = h * obj['count'] / this.max;
301
- const gapSize = sBarHeight * innerMargin;
302
- const verticalShift = (this.max - sum) / this.max;
303
- const subBartHeight = sBarHeight - gapSize;
304
- const yStart = y + h * verticalShift + gapSize / 2;
305
-
306
- const isIntersectingX = offsetX >= xStart && offsetX <= xStart + barWidth;
307
- const isIntersectingY = offsetY >= yStart && offsetY <= yStart + subBartHeight;
308
-
309
- if (isIntersectingX && isIntersectingY)
310
- return {'colName': colName, 'aaName': obj['name']};
311
-
312
- sum -= obj['count'];
313
- }
314
-
315
- return null;
316
- }
317
-
318
- unhighlight(): void {
319
- ui.tooltip.hide();
320
- this.computeData();
321
- }
322
-
323
- /** Requests highlight/select/filter action based on currentBarPart */
324
- requestAction(event: MouseEvent, barPart: {colName: string, aaName: string} | null): void {
325
- if (!barPart)
326
- return;
327
- const aar = barPart['aaName'];
328
- const position = barPart['colName'];
329
- if (event.type === 'click') {
330
- event.shiftKey ? this.model.modifyCurrentSelection(aar, position) :
331
- this.model.initCurrentSelection(aar, position);
332
- } else {
333
- ui.tooltip.showRowGroup(this.dataFrame, (i) => {
334
- const currentAAR = this.dataFrame.get(position, i);
335
- return currentAAR === aar;
336
- }, event.offsetX, event.offsetY);
337
- }
338
- }
339
- }
@@ -1,9 +0,0 @@
1
- import * as DG from 'datagrok-api/dg';
2
- import {runKalign} from '../utils/multiple-sequence-alignment';
3
-
4
- export async function msaWidget(col: DG.Column): Promise<DG.DataFrame> {
5
- const msaCol = await runKalign(col, true);
6
- const table = col.dataFrame;
7
- table.columns.add(msaCol);
8
- return table;
9
- }
@@ -1,82 +0,0 @@
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 C from '../utils/constants';
5
- import {PeptidesModel} from '../model';
6
-
7
- /**
8
- * 3D representation widget of peptide molecule.
9
- *
10
- * @export
11
- * @param {string} pep Peptide string.
12
- * @return {Promise<DG.Widget>} Widget.
13
- */
14
- export async function peptideMoleculeWidget(pep: string, currentTable: DG.DataFrame): Promise<DG.Widget> {
15
- const pi = DG.TaskBarProgressIndicator.create('Creating NGL view');
16
- const separator = currentTable.columns.bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE)!.tags[C.TAGS.SEPARATOR];
17
-
18
- let widgetHost;
19
- let smiles = '';
20
- let molfileStr = '';
21
- try {
22
- try {
23
- const params = {table: currentTable};
24
- const result = await grok.functions.call('Customerextensions:getPeptideStructure', params) as string[];
25
- if (result.length !== 0) {
26
- smiles = result[0];
27
- molfileStr = result[1];
28
- throw new Error(`Found structure in DB`);
29
- }
30
-
31
- smiles = getMolecule(pep, separator);
32
- if (smiles == '')
33
- throw new Error('Couldn\'t get smiles');
34
-
35
- molfileStr = (await grok.functions.call('Peptides:SmiTo3D', {smiles})) as string;
36
- } catch (e) {
37
- console.warn(e);
38
- }
39
-
40
- try {
41
- molfileStr = molfileStr.replaceAll('\\n', '\n');
42
- const stringBlob = new Blob([molfileStr], {type: 'text/plain'});
43
- const nglHost = ui.div([], {classes: 'd4-ngl-viewer', id: 'ngl-3d-host'});
44
-
45
- //@ts-ignore
46
- const stage = new NGL.Stage(nglHost, {backgroundColor: 'white'});
47
- //@ts-ignore
48
- stage.loadFile(stringBlob, {ext: 'sdf'}).then(function(comp: NGL.StructureComponent) {
49
- stage.setSize(300, 300);
50
- comp.addRepresentation('ball+stick');
51
- comp.autoView();
52
- });
53
- const sketch = grok.chem.svgMol(molfileStr);
54
- const panel = ui.divH([sketch]);
55
-
56
- widgetHost = ui.div([panel, nglHost]);
57
- } catch (e) {
58
- widgetHost = ui.divText('Couldn\'t get peptide structure');
59
- }
60
- } catch (e) {
61
- widgetHost = ui.divText('Couldn\'t get peptide structure');
62
- }
63
- pi.close();
64
- return new DG.Widget(widgetHost);
65
- }
66
-
67
- export function getMolecule(pep: string, separator: string): string {
68
- const split = pep.split(separator);
69
- const mols = [];
70
- const chemPalette = PeptidesModel.chemPalette;
71
- for (let i = 1; i < split.length - 1; i++) {
72
- if (split[i] in chemPalette.AASmiles) {
73
- const aar = chemPalette.AASmiles[split[i]];
74
- mols[i] = aar.substring(0, aar.length - 1);
75
- } else if (!split[i] || split[i] == '-')
76
- mols[i] = '';
77
- else
78
- return '';
79
- }
80
-
81
- return mols.join('') + 'O';
82
- }