@datagrok/peptides 0.8.9 → 0.8.13

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