@datagrok/peptides 0.8.8 → 0.8.12

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 (42) hide show
  1. package/.eslintrc.json +2 -1
  2. package/dist/package-test.js +22626 -0
  3. package/dist/package.js +21429 -0
  4. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +8840 -0
  5. package/jest.config.js +33 -0
  6. package/package.json +70 -62
  7. package/src/__jest__/remote.test.ts +50 -0
  8. package/src/__jest__/test-node.ts +96 -0
  9. package/src/model.ts +977 -92
  10. package/src/monomer-library.ts +18 -12
  11. package/src/package-test.ts +6 -5
  12. package/src/package.ts +83 -68
  13. package/src/peptides.ts +298 -142
  14. package/src/styles.css +8 -0
  15. package/src/tests/peptide-space-test.ts +1 -1
  16. package/src/tests/peptides-tests.ts +20 -81
  17. package/src/tests/utils.ts +4 -9
  18. package/src/utils/SAR-multiple-filter.ts +439 -0
  19. package/src/utils/SAR-multiple-selection.ts +177 -0
  20. package/src/utils/cell-renderer.ts +124 -97
  21. package/src/utils/chem-palette.ts +98 -166
  22. package/src/utils/constants.ts +56 -0
  23. package/src/utils/filtering-statistics.ts +62 -0
  24. package/src/utils/multiple-sequence-alignment.ts +33 -2
  25. package/src/utils/multivariate-analysis.ts +79 -0
  26. package/src/utils/peptide-similarity-space.ts +24 -53
  27. package/src/utils/types.ts +10 -0
  28. package/src/viewers/logo-viewer.ts +7 -5
  29. package/src/viewers/peptide-space-viewer.ts +121 -0
  30. package/src/viewers/sar-viewer.ts +118 -342
  31. package/src/viewers/stacked-barchart-viewer.ts +322 -369
  32. package/src/widgets/analyze-peptides.ts +50 -29
  33. package/src/widgets/distribution.ts +61 -0
  34. package/src/widgets/manual-alignment.ts +7 -4
  35. package/src/widgets/multiple-sequence-alignment.ts +9 -0
  36. package/src/widgets/peptide-molecule.ts +8 -6
  37. package/src/widgets/subst-table.ts +73 -0
  38. package/src/workers/dimensionality-reducer.ts +1 -1
  39. package/test-Peptides-414a1874a71a-2f1c6575.html +256 -0
  40. package/src/describe.ts +0 -535
  41. package/src/utils/split-aligned.ts +0 -72
  42. package/src/viewers/subst-viewer.ts +0 -285
@@ -3,394 +3,170 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
- import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
+ import {PeptidesController} from '../peptides';
7
+ import * as C from '../utils/constants';
8
+
9
+ let IS_PROPERTY_CHANGING = false;
10
+
11
+ export class SARViewerBase extends DG.JsViewer {
12
+ tempName!: string;
13
+ viewerGrid!: DG.Grid;
14
+ sourceGrid!: DG.Grid;
15
+ controller!: PeptidesController;
16
+ scaling: string;
17
+ // filterMode: boolean;
18
+ bidirectionalAnalysis: boolean;
19
+ // grouping: boolean;
20
+ showSubstitution: boolean;
21
+ maxSubstitutions: number;
22
+ activityLimit: number;
23
+ _titleHost = ui.divText('SAR Viewer', {id: 'pep-viewer-title'});
24
+ initialized = false;
25
+ isPropertyChanging: boolean = false;
7
26
 
8
- import {model} from '../model';
9
-
10
- /**
11
- * Structure-activity relationship viewer.
12
- *
13
- * @export
14
- * @class SARViewer
15
- * @extends {DG.JsViewer}
16
- */
17
- export class SARViewer extends DG.JsViewer {
18
- protected viewerGrid: DG.Grid | null;
19
- protected sourceGrid: DG.Grid | null;
20
- protected activityColumnName: string;
21
- protected scaling: string;
22
- protected bidirectionalAnalysis: boolean;
23
- protected filterMode: boolean;
24
- protected statsDf: DG.DataFrame | null;
25
- protected initialized: boolean;
26
- protected viewGridInitialized: boolean;
27
- protected aminoAcidResidue;
28
- protected _initialBitset: DG.BitSet | null;
29
- protected viewerVGrid: DG.Grid | null;
30
- protected currentBitset: DG.BitSet | null;
31
- grouping: boolean;
32
- groupMapping: StringDictionary | null;
33
- // protected pValueThreshold: number;
34
- // protected amountOfBestAARs: number;
35
- // duplicatesHandingMethod: string;
36
-
37
- /**
38
- * Creates an instance of SARViewer.
39
- *
40
- * @memberof SARViewer
41
- */
42
27
  constructor() {
43
28
  super();
44
29
 
45
- this.viewerGrid = null;
46
- this.viewerVGrid = null;
47
- this.statsDf = null;
48
- this.groupMapping = null;
49
- this.initialized = false;
50
- this.aminoAcidResidue = 'AAR';
51
- this._initialBitset = null;
52
- this.viewGridInitialized = false;
53
- this.currentBitset = null;
54
-
55
- //TODO: find a way to restrict activityColumnName to accept only numerical columns (double even better)
56
- this.activityColumnName = this.string('activityColumnName');
57
30
  this.scaling = this.string('scaling', 'none', {choices: ['none', 'lg', '-lg']});
58
- this.filterMode = this.bool('filterMode', false);
31
+ // this.filterMode = this.bool('filterMode', false);
59
32
  this.bidirectionalAnalysis = this.bool('bidirectionalAnalysis', false);
60
- this.grouping = this.bool('grouping', false);
61
- // this.pValueThreshold = this.float('pValueThreshold', 0.1);
62
- // this.amountOfBestAARs = this.int('amountOfBestAAR', 1);
63
- // this.duplicatesHandingMethod = this.string('duplicatesHandlingMethod', 'median', {choices: ['median']});
33
+ // this.grouping = this.bool('grouping', false);
64
34
 
65
- this.sourceGrid = null;
35
+ this.showSubstitution = this.bool('showSubstitution', false);
36
+ this.maxSubstitutions = this.int('maxSubstitutions', 1);
37
+ this.activityLimit = this.float('activityLimit', 2);
66
38
  }
67
39
 
68
- /**
69
- * Initializes SARViewer.
70
- *
71
- * @memberof SARViewer
72
- */
73
- init() {
74
- this._initialBitset = this.dataFrame!.filter.clone();
75
- this.currentBitset = this._initialBitset.clone();
76
- this.initialized = true;
77
- this.subs.push(model.statsDf$.subscribe((data) => this.statsDf = data));
78
- this.subs.push(model.viewerGrid$.subscribe((data) => {
79
- this.viewerGrid = data;
80
- this.render();
81
- }));
82
- this.subs.push(model.viewerVGrid$.subscribe((data) => this.viewerVGrid = data));
83
- this.subs.push(model.groupMapping$.subscribe((data) => this.groupMapping = data));
40
+ async onTableAttached() {
41
+ super.onTableAttached();
42
+ this.dataFrame.temp[this.tempName] ??= this;
43
+ this.sourceGrid = this.view?.grid ?? (grok.shell.v as DG.TableView).grid;
44
+ this.controller = await PeptidesController.getInstance(this.dataFrame);
45
+ this.controller.init(this.dataFrame);
46
+ await this.requestDataUpdate();
47
+
48
+ // this.subs.push(this.controller.onGroupMappingChanged.subscribe(() => {this.render(true);}));
84
49
  }
85
50
 
86
- /**
87
- * Function that is executed when the table is attached.
88
- *
89
- * @memberof SARViewer
90
- */
91
- onTableAttached() {
92
- this.sourceGrid = this.view.grid;
93
- this.sourceGrid?.dataFrame?.setTag('dataType', 'peptides');
94
- this.render();
51
+ detach() {this.subs.forEach((sub) => sub.unsubscribe());}
52
+
53
+ render(refreshOnly = false) {
54
+ if (!this.initialized)
55
+ return;
56
+ if (!refreshOnly) {
57
+ $(this.root).empty();
58
+ const viewerRoot = this.viewerGrid.root;
59
+ viewerRoot.style.width = 'auto';
60
+ this.root.appendChild(ui.divV([this._titleHost, viewerRoot]));
61
+ }
62
+ this.viewerGrid?.invalidate();
95
63
  }
96
64
 
97
- /**
98
- * Function that is executed when the viewer is detached from the table.
99
- *
100
- * @memberof SARViewer
101
- */
102
- detach() {
103
- this.subs.forEach((sub) => sub.unsubscribe());
65
+ async requestDataUpdate() {
66
+ await this.controller.updateData(this.scaling, this.sourceGrid, this.bidirectionalAnalysis,
67
+ this.activityLimit, this.maxSubstitutions, this.showSubstitution);
104
68
  }
105
69
 
106
- /**
107
- * Function that is executed when the property is changed.
108
- *
109
- * @param {DG.Property} property New property.
110
- * @memberof SARViewer
111
- */
112
- onPropertyChanged(property: DG.Property) {
70
+ async onPropertyChanged(property: DG.Property) {
113
71
  super.onPropertyChanged(property);
114
-
115
- if (!this.initialized) {
116
- this.init();
72
+ this.dataFrame.tags[property.name] = `${property.get(this)}`;
73
+ if (!this.initialized || IS_PROPERTY_CHANGING)
117
74
  return;
118
- }
75
+
76
+ const propName = property.name;
119
77
 
120
- if (property.name === 'scaling' && typeof this.dataFrame !== 'undefined') {
121
- const minActivity = DG.Stats.fromColumn(
122
- this.dataFrame!.col(this.activityColumnName)!,
123
- this._initialBitset,
124
- ).min;
78
+ if (propName === 'scaling' && typeof this.dataFrame !== 'undefined') {
79
+ const minActivity = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY).stats.min;
125
80
  if (minActivity && minActivity <= 0 && this.scaling !== 'none') {
126
81
  grok.shell.warning(`Could not apply ${this.scaling}: ` +
127
- `activity column ${this.activityColumnName} contains zero or negative values, falling back to 'none'.`);
82
+ `activity column ${C.COLUMNS_NAMES.ACTIVITY} contains zero or negative values, falling back to 'none'.`);
128
83
  property.set(this, 'none');
129
84
  return;
130
85
  }
131
86
  }
132
87
 
133
- this.render();
134
- }
135
-
136
- /**
137
- * Viewer render function.
138
- *
139
- * @param {boolean} [computeData=true] Recalculate data.
140
- * @memberof SARViewer
141
- */
142
- async render(computeData = true) {
143
- if (!this.initialized)
88
+ if (!this.showSubstitution && ['maxSubstitutions', 'activityLimit'].includes(propName))
144
89
  return;
145
90
 
146
- //TODO: optimize. Don't calculate everything again if only view changes
147
- if (computeData) {
148
- if (typeof this.dataFrame !== 'undefined' && this.activityColumnName && this.sourceGrid) {
149
- await model?.updateData(
150
- this.dataFrame!,
151
- this.activityColumnName,
152
- this.scaling,
153
- this.sourceGrid,
154
- this.bidirectionalAnalysis,
155
- this._initialBitset,
156
- this.grouping,
157
- );
158
-
159
- if (this.viewerGrid !== null && this.viewerVGrid !== null) {
160
- $(this.root).empty();
161
- this.root.appendChild(this.viewerGrid.root);
162
- this.viewerGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
163
- this.currentBitset = applyBitset(
164
- this.dataFrame!, this.viewerGrid!, this.aminoAcidResidue,
165
- this.groupMapping!, this._initialBitset!, this.filterMode,
166
- ) ?? this.currentBitset;
167
- syncGridsFunc(false, this.viewerGrid!, this.viewerVGrid!, this.aminoAcidResidue);
168
- });
169
- this.viewerVGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
170
- syncGridsFunc(true, this.viewerGrid!, this.viewerVGrid!, this.aminoAcidResidue);
171
- });
172
- this.dataFrame!.onRowsFiltering.subscribe((_) => {
173
- sourceFilteringFunc(this.filterMode, this.dataFrame!, this.currentBitset!, this._initialBitset!);
174
- });
175
- grok.events.onAccordionConstructed.subscribe((accordion: DG.Accordion) => {
176
- accordionFunc(
177
- accordion, this.viewerGrid!, this.aminoAcidResidue,
178
- this._initialBitset!, this.activityColumnName, this.statsDf!,
179
- );
180
- });
181
- }
182
- }
183
- }
184
- //fixes viewers not rendering immediately after analyze.
185
- this.viewerGrid?.invalidate();
91
+ await this.requestDataUpdate();
92
+ this.render(true);
186
93
  }
187
94
  }
188
95
 
189
96
  /**
190
- * Vertical structure activity relationship viewer.
191
- *
192
- * @export
193
- * @class SARViewerVertical
194
- * @extends {DG.JsViewer}
97
+ * Structure-activity relationship viewer.
195
98
  */
196
- export class SARViewerVertical extends DG.JsViewer {
197
- viewerVGrid: DG.Grid | null;
198
-
199
- /**
200
- * Creates an instance of SARViewerVertical.
201
- *
202
- * @memberof SARViewerVertical
203
- */
204
- constructor() {
205
- super();
99
+ export class SARViewer extends SARViewerBase {
100
+ _titleHost = ui.divText('Monomer-Positions', {id: 'pep-viewer-title'});
101
+ _name = 'Structure-Activity Relationship';
102
+ tempName = 'sarViewer';
206
103
 
207
- this.viewerVGrid = null;
208
- this.subs.push(model.viewerVGrid$.subscribe((data) => {
209
- this.viewerVGrid = data;
104
+ constructor() { super(); }
105
+
106
+ get name() {return this._name;}
107
+
108
+ async onTableAttached() {
109
+ await super.onTableAttached();
110
+ this.viewerGrid = this.controller.sarGrid;
111
+
112
+ this.subs.push(this.controller.onSARGridChanged.subscribe((data) => {
113
+ this.viewerGrid = data;
210
114
  this.render();
211
115
  }));
212
- }
213
116
 
214
- /**
215
- * Viewer render function.
216
- *
217
- * @memberof SARViewerVertical
218
- */
219
- render() {
220
- if (this.viewerVGrid) {
221
- $(this.root).empty();
222
- this.root.appendChild(this.viewerVGrid.root);
223
- }
224
- this.viewerVGrid?.invalidate();
117
+ this.initialized = true;
118
+ this.render();
225
119
  }
226
- }
227
120
 
228
- function syncGridsFunc(
229
- sourceVertical: boolean,
230
- viewerGrid: DG.Grid,
231
- viewerVGrid: DG.Grid,
232
- aminoAcidResidue: string,
233
- ) { //TODO: refactor, move
234
- if (viewerGrid && viewerGrid.dataFrame && viewerVGrid && viewerVGrid.dataFrame) {
235
- if (sourceVertical) {
236
- const dfCell = viewerVGrid.dataFrame.currentCell;
237
- if (dfCell.column === null || dfCell.column.name !== 'Mean difference')
238
- return;
121
+ isInitialized() { return this.controller?.sarGrid ?? false; }
239
122
 
240
- const otherColName: string = viewerVGrid.dataFrame.get('Position', dfCell.rowIndex);
241
- const otherRowName: string = viewerVGrid.dataFrame.get(aminoAcidResidue, dfCell.rowIndex);
242
- let otherRowIndex = -1;
243
- for (let i = 0; i < viewerGrid.dataFrame.rowCount; i++) {
244
- if (viewerGrid.dataFrame.get(aminoAcidResidue, i) === otherRowName) {
245
- otherRowIndex = i;
246
- break;
247
- }
248
- }
249
- if (otherRowIndex !== -1)
250
- viewerGrid.dataFrame.currentCell = viewerGrid.dataFrame.cell(otherRowIndex, otherColName);
251
- } else {
252
- const otherPos: string = viewerGrid.dataFrame.currentCol?.name;
253
- if (typeof otherPos === 'undefined' && otherPos !== aminoAcidResidue)
254
- return;
123
+ async onPropertyChanged(property: DG.Property): Promise<void> {
124
+ if (!this.isInitialized() || IS_PROPERTY_CHANGING)
125
+ return;
255
126
 
256
- const otherAAR: string =
257
- viewerGrid.dataFrame.get(aminoAcidResidue, viewerGrid.dataFrame.currentRowIdx);
258
- let otherRowIndex = -1;
259
- for (let i = 0; i < viewerVGrid.dataFrame.rowCount; i++) {
260
- if (
261
- viewerVGrid.dataFrame.get(aminoAcidResidue, i) === otherAAR &&
262
- viewerVGrid.dataFrame.get('Position', i) === otherPos
263
- ) {
264
- otherRowIndex = i;
265
- break;
266
- }
267
- }
268
- if (otherRowIndex !== -1)
269
- viewerVGrid.dataFrame.currentCell = viewerVGrid.dataFrame.cell(otherRowIndex, 'Mean difference');
270
- }
127
+ await super.onPropertyChanged(property);
128
+ IS_PROPERTY_CHANGING = true;
129
+ this.controller.syncProperties(true);
130
+ IS_PROPERTY_CHANGING = false;
271
131
  }
272
132
  }
273
133
 
274
- function sourceFilteringFunc(
275
- filterMode: boolean,
276
- dataFrame: DG.DataFrame,
277
- currentBitset: DG.BitSet,
278
- initialBitset: DG.BitSet,
279
- ) {
280
- if (filterMode) {
281
- dataFrame.selection.setAll(false, false);
282
- dataFrame.filter.copyFrom(currentBitset);
283
- } else {
284
- dataFrame.filter.copyFrom(initialBitset);
285
- dataFrame.selection.copyFrom(currentBitset);
134
+ /**
135
+ * Vertical structure activity relationship viewer.
136
+ */
137
+ export class SARViewerVertical extends SARViewerBase {
138
+ _name = 'Sequence-Activity relationship';
139
+ _titleHost = ui.divText('Most Potent Residues', {id: 'pep-viewer-title'});
140
+ tempName = 'sarViewerVertical';
141
+
142
+ constructor() {
143
+ super();
286
144
  }
287
- }
288
145
 
289
- function applyBitset(
290
- dataFrame: DG.DataFrame,
291
- viewerGrid: DG.Grid,
292
- aminoAcidResidue: string,
293
- groupMapping: StringDictionary,
294
- initialBitset: DG.BitSet,
295
- filterMode: boolean,
296
- ) {
297
- let currentBitset = null;
298
- if (
299
- viewerGrid.dataFrame &&
300
- viewerGrid.dataFrame.currentCell.value &&
301
- viewerGrid.dataFrame.currentCol.name !== aminoAcidResidue
302
- ) {
303
- const currentAAR: string =
304
- viewerGrid.dataFrame.get(aminoAcidResidue, viewerGrid.dataFrame.currentRowIdx);
305
- const currentPosition = viewerGrid.dataFrame.currentCol.name;
306
-
307
- const splitColName = '~splitCol';
308
- const otherLabel = 'Other';
309
- const aarLabel = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
310
-
311
- let splitCol = dataFrame.col(splitColName);
312
- if (!splitCol)
313
- splitCol = dataFrame.columns.addNew(splitColName, 'string');
314
-
315
-
316
- const isChosen = (i: number) => groupMapping[dataFrame!.get(currentPosition, i)] === currentAAR;
317
- splitCol!.init((i) => isChosen(i) ? aarLabel : otherLabel);
318
-
319
- //TODO: use column.compact
320
- currentBitset = DG.BitSet.create(dataFrame.rowCount, isChosen).and(initialBitset);
321
- sourceFilteringFunc(filterMode, dataFrame, currentBitset, initialBitset);
322
-
323
- const colorMap: {[index: string]: string | number} = {};
324
- colorMap[otherLabel] = DG.Color.blue;
325
- colorMap[aarLabel] = DG.Color.orange;
326
- // colorMap[currentAAR] = cp.getColor(currentAAR);
327
- dataFrame.getCol(splitColName).colors.setCategorical(colorMap);
146
+ get name() {return this._name;}
147
+
148
+ async onTableAttached() {
149
+ await super.onTableAttached();
150
+ this.viewerGrid = this.controller.sarVGrid;
151
+
152
+ this.subs.push(this.controller.onSARVGridChanged.subscribe((data) => {
153
+ this.viewerGrid = data;
154
+ this.render();
155
+ }));
156
+
157
+ this.initialized = true;
158
+ this.render();
328
159
  }
329
- return currentBitset;
330
- }
331
160
 
332
- function accordionFunc(
333
- accordion: DG.Accordion,
334
- viewerGrid: DG.Grid,
335
- aminoAcidResidue: string,
336
- initialBitset: DG.BitSet,
337
- activityColumnName: string,
338
- statsDf: DG.DataFrame,
339
- ) {
340
- if (accordion.context instanceof DG.RowGroup) {
341
- const originalDf: DG.DataFrame = DG.toJs(accordion.context.dataFrame);
342
- const viewerDf = viewerGrid.dataFrame;
343
-
344
- if (
345
- originalDf.getTag('dataType') === 'peptides' &&
346
- originalDf.col('~splitCol') &&
347
- viewerDf &&
348
- viewerDf.currentCol !== null
349
- ) {
350
- const currentAAR: string = viewerDf.get(
351
- aminoAcidResidue,
352
- viewerDf.currentRowIdx,
353
- );
354
- const currentPosition = viewerDf.currentCol.name;
355
-
356
- const labelStr = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
357
- const currentColor = DG.Color.toHtml(DG.Color.orange);
358
- const otherColor = DG.Color.toHtml(DG.Color.blue);
359
- const currentLabel = ui.label(labelStr, {style: {color: currentColor}});
360
- const otherLabel = ui.label('Other', {style: {color: otherColor}});
361
-
362
- const elements: (HTMLLabelElement | HTMLElement)[] = [currentLabel, otherLabel];
363
-
364
- const distPane = accordion.getPane('Distribution');
365
- if (distPane)
366
- accordion.removePane(distPane);
367
-
368
- accordion.addPane('Distribution', () => {
369
- const hist = originalDf.clone(initialBitset).plot.histogram({
370
- // const hist = originalDf.plot.histogram({
371
- filteringEnabled: false,
372
- valueColumnName: `${activityColumnName}Scaled`,
373
- splitColumnName: '~splitCol',
374
- legendVisibility: 'Never',
375
- showXAxis: true,
376
- showColumnSelector: false,
377
- showRangeSlider: false,
378
- }).root;
379
- hist.style.width = 'auto';
380
- elements.push(hist);
381
-
382
- const tableMap: StringDictionary = {'Statistics:': ''};
383
- for (const colName of new Set(['Count', 'pValue', 'Mean difference'])) {
384
- const query = `${aminoAcidResidue} = ${currentAAR} and Position = ${currentPosition}`;
385
- const textNum = statsDf.groupBy([colName]).where(query).aggregate().get(colName, 0);
386
- // const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
387
- const text = colName === 'Count' ? `${textNum}` : textNum < 0.01 ? '<0.01' : textNum.toFixed(2);
388
- tableMap[colName === 'pValue' ? 'p-value' : colName] = text;
389
- }
390
- elements.push(ui.tableFromMap(tableMap));
391
-
392
- return ui.divV(elements);
393
- }, true);
394
- }
161
+ isInitialized() { return this.controller?.sarVGrid ?? false; }
162
+
163
+ async onPropertyChanged(property: DG.Property): Promise<void> {
164
+ if (!this.isInitialized() || IS_PROPERTY_CHANGING)
165
+ return;
166
+
167
+ await super.onPropertyChanged(property);
168
+ IS_PROPERTY_CHANGING = true;
169
+ this.controller.syncProperties(false);
170
+ IS_PROPERTY_CHANGING = false;
395
171
  }
396
172
  }