@datagrok/peptides 1.14.1 → 1.15.1

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 (51) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/196.js +3 -0
  3. package/dist/196.js.LICENSE.txt +51 -0
  4. package/dist/209.js +2 -2
  5. package/dist/361.js +2 -0
  6. package/dist/381.js +2 -0
  7. package/dist/436.js +2 -0
  8. package/dist/694.js +2 -0
  9. package/dist/831.js +2 -0
  10. package/dist/868.js +2 -0
  11. package/dist/package-test.js +3 -2
  12. package/dist/package-test.js.LICENSE.txt +51 -0
  13. package/dist/package.js +3 -2
  14. package/dist/package.js.LICENSE.txt +51 -0
  15. package/files/help/logo-summary-table.md +23 -0
  16. package/files/help/monomer-position.md +31 -0
  17. package/files/help/most-potent-residues.md +17 -0
  18. package/files/icons/ribbon/logo-summary-viewer.svg +8 -0
  19. package/files/icons/ribbon/peptide-sar-vertical-viewer.svg +10 -0
  20. package/files/icons/ribbon/peptide-sar-viewer.svg +11 -0
  21. package/files/tests/100k.d42 +0 -0
  22. package/files/tests/200k.d42 +0 -0
  23. package/files/tests/50k.d42 +0 -0
  24. package/files/tests/{aligned_5k.d42 → 5k.d42} +0 -0
  25. package/package.json +5 -5
  26. package/src/model.ts +85 -175
  27. package/src/package-test.ts +7 -6
  28. package/src/tests/benchmarks.ts +95 -0
  29. package/src/tests/core.ts +4 -75
  30. package/src/tests/{algorithms.ts → misc.ts} +3 -16
  31. package/src/tests/model.ts +3 -14
  32. package/src/tests/table-view.ts +4 -14
  33. package/src/tests/viewers.ts +7 -1
  34. package/src/tests/widgets.ts +9 -1
  35. package/src/utils/algorithms.ts +166 -16
  36. package/src/utils/cell-renderer.ts +12 -7
  37. package/src/utils/constants.ts +2 -0
  38. package/src/utils/misc.ts +2 -1
  39. package/src/utils/parallel-mutation-cliffs.ts +106 -0
  40. package/src/utils/types.ts +1 -1
  41. package/src/viewers/logo-summary.ts +9 -6
  42. package/src/viewers/sar-viewer.ts +28 -14
  43. package/src/widgets/mutation-cliffs.ts +2 -2
  44. package/src/widgets/peptides.ts +15 -5
  45. package/src/widgets/selection.ts +9 -0
  46. package/src/widgets/settings.ts +3 -8
  47. package/src/workers/mutation-cliffs-worker.ts +77 -0
  48. package/dist/521.js +0 -2
  49. package/dist/677.js +0 -2
  50. package/dist/729.js +0 -2
  51. package/dist/959.js +0 -2
package/src/utils/misc.ts CHANGED
@@ -36,7 +36,7 @@ export function scaleActivity(activityCol: DG.Column<number>, scaling: C.SCALING
36
36
  const val = activityColData[i];
37
37
  return val === DG.FLOAT_NULL || val === DG.INT_NULL ? val : formula(val);
38
38
  });
39
-
39
+ scaledCol.setTag(C.TAGS.ANALYSIS_COL, `${true}`);
40
40
  return scaledCol;
41
41
  }
42
42
 
@@ -131,4 +131,5 @@ export function setGridProps(grid: DG.Grid): void {
131
131
  grid.props.showRowHeader = false;
132
132
  grid.props.showCurrentRowIndicator = false;
133
133
  grid.root.style.width = '100%';
134
+ grid.root.style.maxWidth = '100%';
134
135
  }
@@ -0,0 +1,106 @@
1
+
2
+ import * as type from './types';
3
+
4
+ export type ParallelMutationReturnType = {
5
+ // monomers1: string[],
6
+ // monomers2: string[],
7
+ pos: string[],
8
+ seq1Idxs: Uint32Array,
9
+ seq2Idxs: Uint32Array,
10
+ }
11
+ export class ParallelMutationCliffs {
12
+ private _workers: Worker[];
13
+ private _workerCount: number;
14
+
15
+ constructor() {
16
+ const threadCount = navigator.hardwareConcurrency;
17
+ this._workerCount = Math.max(threadCount - 2, 1);
18
+ this._workers = new Array(this._workerCount).fill(null)
19
+ .map(() => new Worker(new URL('../workers/mutation-cliffs-worker', import.meta.url)));
20
+ }
21
+
22
+ public async calc(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
23
+ settings: type.PeptidesSettings = {},
24
+ targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {},
25
+ ): Promise<type.MutationCliffs> {
26
+ const substitutionsInfo: type.MutationCliffs = new Map();
27
+ try {
28
+ const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
29
+
30
+ const len = activityArray.length;
31
+ const promises = new Array<Promise<ParallelMutationReturnType>>(this._workerCount);
32
+ const matSize = len * (len - 1) / 2; // size of reduced upper triangular matrix
33
+ this._workerCount = Math.min(this._workerCount, matSize);
34
+ const chunkSize = matSize / this._workerCount;
35
+ // monomerInfoArray[m].cat and targetCol can contain some function from datagrok-api,
36
+ //which the worker can't serialize and fails. so we need to remove it
37
+ monomerInfoArray.forEach((monomerInfo) => {
38
+ monomerInfo.cat = monomerInfo.cat?.slice();
39
+ });
40
+ targetOptions.targetCol?.cat && (targetOptions.targetCol.cat = targetOptions.targetCol.cat.slice());
41
+ for (let idx = 0; idx < this._workerCount; idx++) {
42
+ promises[idx] = new Promise((resolveWorker, rejectWorker) => {
43
+ const startIdx = Math.floor(idx * chunkSize);
44
+ const endIdx = idx === this._workerCount - 1 ? matSize : Math.floor((idx + 1) * chunkSize);
45
+ this._workers[idx].postMessage(
46
+ {startIdx, endIdx, activityArray, monomerInfoArray, settings, currentTargetIdx, targetOptions});
47
+ this._workers[idx].onmessage = ({data: {pos, seq1Idxs, seq2Idxs, error}}): void => {
48
+ if (error) {
49
+ this._workers[idx]?.terminate();
50
+ rejectWorker(error);
51
+ } else {
52
+ this._workers[idx].terminate();
53
+ resolveWorker({pos, seq1Idxs, seq2Idxs});
54
+ }
55
+ };
56
+ });
57
+ }
58
+
59
+ const results = await Promise.all(promises);
60
+ const monomerPositionsMap = new Map<string, number>();
61
+ monomerInfoArray.forEach((monomerInfo, i) => {
62
+ monomerPositionsMap.set(monomerInfo.name, i);
63
+ });
64
+ results.filter(Boolean).forEach((result) => {
65
+ for (let i = 0; i< result.pos.length; i++) {
66
+ //getting monomers from monomerInfoArray by position
67
+ const monomerPos = monomerPositionsMap.get(result.pos[i])!;
68
+ const monomer1Cat = monomerInfoArray[monomerPos].rawData[result.seq1Idxs[i]];
69
+ const monomer1 = monomerInfoArray[monomerPos].cat![monomer1Cat];
70
+ const monomer2Cat = monomerInfoArray[monomerPos].rawData[result.seq2Idxs[i]];
71
+ const monomer2 = monomerInfoArray[monomerPos].cat![monomer2Cat];
72
+
73
+ // filling map
74
+ if (!substitutionsInfo.has(monomer1))
75
+ substitutionsInfo.set(monomer1, new Map());
76
+ if (!substitutionsInfo.has(monomer2))
77
+ substitutionsInfo.set(monomer2, new Map());
78
+ const position1Map = substitutionsInfo.get(monomer1)!;
79
+ const position2Map = substitutionsInfo.get(monomer2)!;
80
+ if (!position1Map.has(result.pos[i]))
81
+ position1Map.set(result.pos[i], new Map());
82
+ if (!position2Map.has(result.pos[i]))
83
+ position2Map.set(result.pos[i], new Map());
84
+ const indexes1Map = position1Map.get(result.pos[i])!;
85
+ const indexes2Map = position2Map.get(result.pos[i])!;
86
+ if (!indexes1Map.has(result.seq1Idxs[i]))
87
+ indexes1Map.set(result.seq1Idxs[i], []);
88
+ if (!indexes2Map.has(result.seq2Idxs[i]))
89
+ indexes2Map.set(result.seq2Idxs[i], []);
90
+ const indexes1 = indexes1Map.get(result.seq1Idxs[i])!;
91
+ const indexes2 = indexes2Map.get(result.seq2Idxs[i])!;
92
+ (indexes1 as number[]).push(result.seq2Idxs[i]);
93
+ (indexes2 as number[]).push(result.seq1Idxs[i]);
94
+ }
95
+ });
96
+ } catch (error) {
97
+ this.terminate();
98
+ console.error(error);
99
+ }
100
+ return substitutionsInfo;
101
+ }
102
+
103
+ public terminate(): void {
104
+ this._workers?.forEach((worker) => worker?.terminate());
105
+ }
106
+ }
@@ -18,7 +18,6 @@ export type PeptidesSettings = {
18
18
  clustersColumnName?: string,
19
19
  targetColumnName?: string,
20
20
  scaling?: SCALING_METHODS,
21
- isBidirectional?: boolean,
22
21
  showMonomerPosition?: boolean,
23
22
  showMostPotentResidues?: boolean,
24
23
  showLogoSummaryTable?: boolean,
@@ -39,6 +38,7 @@ export type DrawOptions = {
39
38
  marginHorizontal?: number,
40
39
  headerStyle?: string,
41
40
  textHeight?: number,
41
+ selectionWidth?: number,
42
42
  };
43
43
 
44
44
  export type StatsInfo = {
@@ -13,6 +13,7 @@ import {getActivityDistribution, getDistributionLegend, getStatsTableMap} from '
13
13
  import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
14
14
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
15
15
  import {SelectionItem} from '../utils/types';
16
+ import {_package} from '../package';
16
17
 
17
18
  const getAggregatedColName = (aggF: string, colName: string): string => `${aggF}(${colName})`;
18
19
 
@@ -213,7 +214,6 @@ export class LogoSummaryTable extends DG.JsViewer {
213
214
  this.viewerGrid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
214
215
  this.updateFilter();
215
216
  const gridClustersCol = this.viewerGrid.col(C.LST_COLUMN_NAMES.CLUSTER)!;
216
- // gridClustersCol.column!.name = C.LST_COLUMN_NAMES.CLUSTER;
217
217
  gridClustersCol.visible = true;
218
218
  this.viewerGrid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
219
219
  C.LST_COLUMN_NAMES.WEB_LOGO, C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
@@ -223,7 +223,7 @@ export class LogoSummaryTable extends DG.JsViewer {
223
223
 
224
224
  const webLogoCache = new DG.LruCache<number, DG.Viewer & IWebLogoViewer>();
225
225
  const distCache = new DG.LruCache<number, DG.Viewer<DG.IHistogramLookSettings>>();
226
- const maxSequenceLen = this.model.splitSeqDf.columns.length;
226
+ const maxSequenceLen = this.model.positionColumns.toArray().length;
227
227
  const webLogoGridCol = this.viewerGrid.columns.byName(C.LST_COLUMN_NAMES.WEB_LOGO)!;
228
228
  webLogoGridCol.cellType = 'html';
229
229
  webLogoGridCol.width = 350;
@@ -269,7 +269,7 @@ export class LogoSummaryTable extends DG.JsViewer {
269
269
  const webLogoTable = this.createWebLogoDf(pepCol, clusterBitSet);
270
270
  viewer = await webLogoTable.plot
271
271
  .fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
272
- maxHeight: 1000, minHeight: height, positionWidth: positionWidth});
272
+ maxHeight: 1000, minHeight: height, positionWidth: positionWidth, showPositionLabels: false});
273
273
  webLogoCache.set(currentRowIdx, viewer);
274
274
  }
275
275
  gridCell.element = viewer.root;
@@ -302,7 +302,7 @@ export class LogoSummaryTable extends DG.JsViewer {
302
302
  }
303
303
  });
304
304
  this.viewerGrid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
305
- this.viewerGrid.onCurrentCellChanged.subscribe((gridCell) => {
305
+ DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell) => {
306
306
  if (!gridCell.isTableCell)
307
307
  return;
308
308
 
@@ -328,6 +328,10 @@ export class LogoSummaryTable extends DG.JsViewer {
328
328
  const selection = this.getCluster(gridCell);
329
329
  this.model.modifyClusterSelection(selection, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
330
330
  this.viewerGrid.invalidate();
331
+
332
+ _package.files.readAsText('help/logo-summary-table.md').then((text) => {
333
+ grok.shell.windows.help.showHelp(ui.markdown(text));
334
+ }).catch((e) => grok.log.error(e));
331
335
  });
332
336
  this.viewerGrid.onCellTooltip((gridCell, x, y) => {
333
337
  if (!gridCell.isTableCell) {
@@ -347,7 +351,6 @@ export class LogoSummaryTable extends DG.JsViewer {
347
351
  gridProps.allowRowSelection = false;
348
352
  gridProps.allowBlockSelection = false;
349
353
  gridProps.allowColSelection = false;
350
- gridProps.showRowHeader = false;
351
354
  gridProps.showCurrentRowIndicator = false;
352
355
 
353
356
  return this.viewerGrid;
@@ -381,7 +384,7 @@ export class LogoSummaryTable extends DG.JsViewer {
381
384
 
382
385
  this.bitsets.push(currentSelection.clone());
383
386
 
384
- const newClusterName = viewerDfCols.getUnusedName('New Cluster');
387
+ const newClusterName = this.model.df.columns.getUnusedName('New Cluster');
385
388
  const aggregatedValues: {[colName: string]: number} = {};
386
389
  const aggColsEntries = Object.entries(this.model.settings.columns ?? {});
387
390
  for (const [colName, aggFn] of aggColsEntries) {
@@ -9,6 +9,7 @@ import {PeptidesModel, PositionStats, VIEWER_TYPE} from '../model';
9
9
  import wu from 'wu';
10
10
  import {SelectionItem} from '../utils/types';
11
11
  import {Stats} from '../utils/statistics';
12
+ import {_package} from '../package';
12
13
 
13
14
  export enum SELECTION_MODE {
14
15
  MUTATION_CLIFFS = 'Mutation Cliffs',
@@ -26,7 +27,7 @@ export class MonomerPosition extends DG.JsViewer {
26
27
  _titleHost = ui.divText(SELECTION_MODE.MUTATION_CLIFFS, {id: 'pep-viewer-title'});
27
28
  _viewerGrid!: DG.Grid;
28
29
  _model!: PeptidesModel;
29
- colorCol: string;
30
+ color: string;
30
31
  aggregation: string;
31
32
  target: string;
32
33
  keyPressed: boolean = false;
@@ -36,7 +37,7 @@ export class MonomerPosition extends DG.JsViewer {
36
37
  super();
37
38
  this.target = this.string(MONOMER_POSITION_PROPERTIES.TARGET, null,
38
39
  {category: SELECTION_MODE.MUTATION_CLIFFS, choices: []});
39
- this.colorCol = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
40
+ this.color = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
40
41
  {category: SELECTION_MODE.INVARIANT_MAP,
41
42
  choices: wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name)});
42
43
  this.aggregation = this.string(MONOMER_POSITION_PROPERTIES.AGGREGATION, DG.AGG.AVG,
@@ -81,14 +82,14 @@ export class MonomerPosition extends DG.JsViewer {
81
82
  onPropertyChanged(property: DG.Property): void {
82
83
  super.onPropertyChanged(property);
83
84
  if (property.name === MONOMER_POSITION_PROPERTIES.TARGET)
84
- this.model.updateMutationCliffs();
85
+ this.model.updateMutationCliffs().then(() => this.render(true));
85
86
 
86
- this.render();
87
+ this.render(true);
87
88
  }
88
89
 
89
90
  createMonomerPositionDf(): DG.DataFrame {
90
91
  const uniqueMonomers = new Set<string>();
91
- const splitSeqCols = this.model.splitSeqDf.columns;
92
+ const splitSeqCols = this.model.positionColumns.toArray();
92
93
  for (const col of splitSeqCols) {
93
94
  const colCat = col.categories;
94
95
  for (const cat of colCat) {
@@ -111,12 +112,11 @@ export class MonomerPosition extends DG.JsViewer {
111
112
  const monomerPositionDf = this.createMonomerPositionDf();
112
113
  this.viewerGrid = monomerPositionDf.plot.grid();
113
114
  this.viewerGrid.sort([C.COLUMNS_NAMES.MONOMER]);
114
- this.viewerGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...this.model.splitSeqDf.columns.names()]);
115
+ this.viewerGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...this.model.positionColumns.toArray().map((col) => col.name)]);
115
116
  const monomerCol = monomerPositionDf.getCol(C.COLUMNS_NAMES.MONOMER);
116
117
  CR.setMonomerRenderer(monomerCol, this.model.alphabet);
117
118
  this.viewerGrid.onCellRender.subscribe((args: DG.GridCellRenderArgs) => renderCell(args, this.model,
118
- this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.colorCol),
119
- this.aggregation as DG.AggregationType));
119
+ this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.color), this.aggregation as DG.AGG));
120
120
 
121
121
  this.viewerGrid.onCellTooltip((gridCell: DG.GridCell, x: number, y: number) => {
122
122
  if (!gridCell.isTableCell) {
@@ -128,7 +128,7 @@ export class MonomerPosition extends DG.JsViewer {
128
128
  return this.model.showTooltip(monomerPosition, x, y, true);
129
129
  });
130
130
  this.viewerGrid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
131
- this.viewerGrid.onCurrentCellChanged.subscribe((gridCell: DG.GridCell) => {
131
+ DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
132
132
  try {
133
133
  if (!this.keyPressed)
134
134
  return;
@@ -166,11 +166,19 @@ export class MonomerPosition extends DG.JsViewer {
166
166
  this.model.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
167
167
 
168
168
  this.viewerGrid.invalidate();
169
+
170
+ this.showHelp();
169
171
  });
170
172
 
171
173
  setViewerGridProps(this.viewerGrid, false);
172
174
  }
173
175
 
176
+ showHelp(): void {
177
+ _package.files.readAsText('help/monomer-position.md').then((text) => {
178
+ grok.shell.windows.help.showHelp(ui.markdown(text));
179
+ }).catch((e) => grok.log.error(e));
180
+ }
181
+
174
182
  getMonomerPosition(gridCell: DG.GridCell): SelectionItem {
175
183
  return {monomerOrCluster: gridCell.cell.dataFrame.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!) as string,
176
184
  positionOrClusterType: gridCell!.tableColumn!.name};
@@ -186,6 +194,7 @@ export class MonomerPosition extends DG.JsViewer {
186
194
  invariantMapMode.value = false;
187
195
  mutationCliffsMode.value = true;
188
196
  this.mode = SELECTION_MODE.MUTATION_CLIFFS;
197
+ this.showHelp();
189
198
  });
190
199
  mutationCliffsMode.setTooltip('Statistically significant changes in activity');
191
200
  const invariantMapMode = ui.boolInput(SELECTION_MODE.INVARIANT_MAP, this.mode === SELECTION_MODE.INVARIANT_MAP);
@@ -193,6 +202,7 @@ export class MonomerPosition extends DG.JsViewer {
193
202
  mutationCliffsMode.value = false;
194
203
  invariantMapMode.value = true;
195
204
  this.mode = SELECTION_MODE.INVARIANT_MAP;
205
+ this.showHelp();
196
206
  });
197
207
  invariantMapMode.setTooltip('Number of sequences having monomer-position');
198
208
  const setDefaultProperties = (input: DG.InputBase): void => {
@@ -351,13 +361,13 @@ export class MostPotentResidues extends DG.JsViewer {
351
361
  return false;
352
362
  return this.model.showTooltip(monomerPosition, x, y, true);
353
363
  });
354
- this.viewerGrid.onCurrentCellChanged.subscribe((gridCell: DG.GridCell) => {
364
+ DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
355
365
  try {
356
366
  if ((this.keyPressed && mostPotentResiduesDf.currentCol.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE) || !this.keyPressed)
357
367
  return;
358
368
  const monomerPosition = this.getMonomerPosition(gridCell);
359
369
  if (this.currentGridRowIdx !== null) {
360
- const previousMonomerPosition = this.getMonomerPosition(this.viewerGrid.cell(C.COLUMNS_NAMES.MEAN_DIFFERENCE, this.currentGridRowIdx));
370
+ const previousMonomerPosition = this.getMonomerPosition(this.viewerGrid.cell('Diff', this.currentGridRowIdx));
361
371
  this.model.modifyMutationCliffsSelection(previousMonomerPosition, {shiftPressed: true, ctrlPressed: true}, false);
362
372
  }
363
373
  if (this.model.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType)?.size)
@@ -380,6 +390,10 @@ export class MostPotentResidues extends DG.JsViewer {
380
390
  return;
381
391
  this.model.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
382
392
  this.viewerGrid.invalidate();
393
+
394
+ _package.files.readAsText('help/most-potent-residues.md').then((text) => {
395
+ grok.shell.windows.help.showHelp(ui.markdown(text));
396
+ }).catch((e) => grok.log.error(e));
383
397
  });
384
398
  const mdCol: DG.GridColumn = this.viewerGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
385
399
  mdCol.name = 'Diff';
@@ -405,8 +419,8 @@ export class MostPotentResidues extends DG.JsViewer {
405
419
  }
406
420
 
407
421
  function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvariantMap?: boolean,
408
- colorCol?: DG.Column<number>, colorAgg?: DG.AggregationType, renderNums?: boolean): void {
409
- const renderColNames = [...model.splitSeqDf.columns.names(), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
422
+ colorCol?: DG.Column<number>, colorAgg?: DG.AGG, renderNums?: boolean): void {
423
+ const renderColNames = [...model.positionColumns.toArray().map((col) => col.name), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
410
424
  const canvasContext = args.g;
411
425
  const bound = args.bounds;
412
426
 
@@ -464,7 +478,7 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
464
478
  canvasContext, currentMonomer, currentPosition, model.invariantMapSelection, value, bound, color);
465
479
  } else {
466
480
  CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition, model.monomerPositionStats, bound,
467
- model.mutationCliffsSelection, model.mutationCliffs, model.settings.isBidirectional, renderNums);
481
+ model.mutationCliffsSelection, model.mutationCliffs, renderNums);
468
482
  }
469
483
  args.preventDefault();
470
484
  canvasContext.restore();
@@ -147,11 +147,11 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
147
147
 
148
148
  const gridCols = model.analysisView.grid.columns;
149
149
  const originalGridColCount = gridCols.length;
150
- const positionColumns = model.splitSeqDf.columns;
150
+ const positionColumns = model.positionColumns.toArray().map((col) => col.name);
151
151
  const columnNames: string[] = [];
152
152
  for (let colIdx = 1; colIdx < originalGridColCount; colIdx++) {
153
153
  const gridCol = gridCols.byIndex(colIdx);
154
- if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.contains(gridCol.name)))
154
+ if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.includes(gridCol.name)))
155
155
  columnNames.push(gridCol!.name);
156
156
  }
157
157
 
@@ -122,7 +122,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): {h
122
122
  bitsetChanged.unsubscribe();
123
123
  if (sequencesCol) {
124
124
  const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
125
- scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, targetColumnChoice.value);
125
+ scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, targetColumnChoice.value, {addSequenceSpace: true});
126
126
  return model !== null;
127
127
  }
128
128
  return false;
@@ -155,9 +155,11 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): {h
155
155
  return {host: mainHost, callback: startAnalysisCallback};
156
156
  }
157
157
 
158
+ type AnalysisOptions = {addSequenceSpace?: boolean};
159
+
158
160
  export async function startAnalysis(activityColumn: DG.Column<number>, peptidesCol: DG.Column<string>,
159
161
  clustersColumn: DG.Column | null, currentDf: DG.DataFrame, scaledCol: DG.Column<number>, scaling: C.SCALING_METHODS,
160
- targetColumn: DG.Column<string> | null = null): Promise<PeptidesModel | null> {
162
+ targetColumn: DG.Column<string> | null = null, options: AnalysisOptions = {}): Promise<PeptidesModel | null> {
161
163
  const progress = DG.TaskBarProgressIndicator.create('Loading SAR...');
162
164
  let model = null;
163
165
  if (activityColumn.type === DG.COLUMN_TYPE.FLOAT || activityColumn.type === DG.COLUMN_TYPE.INT) {
@@ -165,15 +167,19 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
165
167
  const newDf = DG.DataFrame.create(currentDf.rowCount);
166
168
  const newDfCols = newDf.columns;
167
169
  newDfCols.add(scaledCol);
168
- for (const col of currentDf.columns)
169
- newDfCols.add(col);
170
+ for (const col of currentDf.columns) {
171
+ if (col.getTag(C.TAGS.ANALYSIS_COL) !== `${true}`) {
172
+ if (col.name === scaledCol.name)
173
+ col.name = currentDf.columns.getUnusedName(col.name);
174
+ newDfCols.add(col);
175
+ }
176
+ }
170
177
 
171
178
  newDf.name = 'Peptides analysis';
172
179
  const settings: type.PeptidesSettings = {
173
180
  sequenceColumnName: peptidesCol.name,
174
181
  activityColumnName: activityColumn.name,
175
182
  scaling: scaling,
176
- isBidirectional: false,
177
183
  columns: {},
178
184
  maxMutations: 1,
179
185
  minActivityDelta: 0,
@@ -212,6 +218,10 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
212
218
  await model.addLogoSummaryTable();
213
219
  await model.addMonomerPosition();
214
220
  await model.addMostPotentResidues();
221
+
222
+ // FIXME: enable by default for tests
223
+ if (options.addSequenceSpace ?? false)
224
+ model.addSequenceSpace();
215
225
  } else
216
226
  grok.shell.error('The activity column must be of numeric type!');
217
227
  progress.close();
@@ -33,6 +33,7 @@ export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): H
33
33
  }
34
34
  const grid = newTable.plot.grid();
35
35
  grid.props.showRowHeader = false;
36
+ grid.root.style.maxWidth = '100%';
36
37
 
37
38
  DG.debounce(ui.onSizeChanged(grid.root), 50).subscribe((_) => {
38
39
  const panel = grid.root.parentElement;
@@ -49,5 +50,13 @@ export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): H
49
50
 
50
51
  const gridHost = ui.box(grid.root);
51
52
  gridHost.style.marginLeft = '0px';
53
+ setTimeout(() => {
54
+ for (let gridColIdx = 1; gridColIdx < sourceGrid.columns.length; gridColIdx++) {
55
+ const gridCol = sourceGrid.columns.byIndex(gridColIdx)!;
56
+ if (!gridCol.visible)
57
+ continue;
58
+ grid.col(gridCol.name)!.width = gridCol.width;
59
+ }
60
+ }, 500);
52
61
  return gridHost;
53
62
  }
@@ -22,7 +22,6 @@ export enum SETTINGS_PANES {
22
22
  export enum GENERAL_INPUTS {
23
23
  ACTIVITY = 'Activity',
24
24
  ACTIVITY_SCALING = 'Activity scaling',
25
- BIDIRECTIONAL_ANALYSIS = 'Bidirectional analysis',
26
25
  }
27
26
 
28
27
  export enum VIEWERS_INPUTS {
@@ -51,7 +50,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
51
50
  const accordion = ui.accordion();
52
51
  const settings = model.settings;
53
52
  const currentScaling = settings.scaling ?? C.SCALING_METHODS.NONE;
54
- const currentBidirectional = settings.isBidirectional ?? false;
53
+ // const currentBidirectional = settings.isBidirectional ?? false;
55
54
  const currentMaxMutations = settings.maxMutations ?? 1;
56
55
  const currentMinActivityDelta = settings.minActivityDelta ?? 0;
57
56
  const currentColumns = settings.columns ?? {};
@@ -69,13 +68,9 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
69
68
  ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, currentScaling, Object.values(C.SCALING_METHODS),
70
69
  () => result.scaling = activityScaling.value as C.SCALING_METHODS) as DG.InputBase<C.SCALING_METHODS>;
71
70
  activityScaling.setTooltip('Activity column transformation method');
72
- const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, currentBidirectional,
73
- () => result.isBidirectional = bidirectionalAnalysis.value) as DG.InputBase<boolean>;
74
- bidirectionalAnalysis.setTooltip('Distinguish between positive and negative mean activity difference in ' +
75
- 'Monomer-Position and Most Potent Residues viewers');
76
71
 
77
- accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling, bidirectionalAnalysis]), true);
78
- inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling, bidirectionalAnalysis];
72
+ accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling]), true);
73
+ inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling];
79
74
 
80
75
  // Viewers pane options
81
76
  /* FIXME: combinations of adding and deleting viewers are not working properly
@@ -0,0 +1,77 @@
1
+
2
+ onmessage = async (event): Promise<void> => {
3
+ const {startIdx, endIdx, activityArray, monomerInfoArray, settings, currentTargetIdx, targetOptions} = event.data;
4
+ // const monomers1: string[] = [];
5
+ // const monomers2: string[] = [];
6
+ const pos: string[] = [];
7
+ const seq1Idxs: number[] = [];
8
+ const seq2Idxs: number[] = [];
9
+ const chunkSize = endIdx - startIdx;
10
+ //const mi = startRow;
11
+ //const mj = startCol;
12
+ let cnt = 0;
13
+ const startRow = activityArray.length - 2 - Math.floor(
14
+ Math.sqrt(-8 * startIdx + 4 * activityArray.length * (activityArray.length - 1) - 7) / 2 - 0.5);
15
+ const startCol = startIdx - activityArray.length * startRow + Math.floor((startRow + 1) * (startRow + 2) / 2);
16
+ let seq1Idx = startRow;
17
+ let seq2Idx = startCol;
18
+ const tempData = new Array(monomerInfoArray.length);
19
+ while (cnt < chunkSize) {
20
+ if (!(currentTargetIdx !== -1 && (targetOptions.targetCol?.rawData[seq1Idx] !== currentTargetIdx ||
21
+ targetOptions.targetCol?.rawData[seq2Idx] !== currentTargetIdx))) {
22
+ let substCounter = 0;
23
+ const activityValSeq1 = activityArray[seq1Idx];
24
+ const activityValSeq2 = activityArray[seq2Idx];
25
+ const delta = activityValSeq1 - activityValSeq2;
26
+ if (Math.abs(delta) >= settings.minActivityDelta) {
27
+ let substCounterFlag = false;
28
+ let tempDataIdx = 0;
29
+ for (const monomerInfo of monomerInfoArray) {
30
+ const seq1categoryIdx = monomerInfo.rawData[seq1Idx];
31
+ const seq2categoryIdx = monomerInfo.rawData[seq2Idx];
32
+ if (seq1categoryIdx === seq2categoryIdx)
33
+ continue;
34
+
35
+ substCounter++;
36
+ substCounterFlag = substCounter > settings.maxMutations;
37
+ if (substCounterFlag)
38
+ break;
39
+
40
+ tempData[tempDataIdx++] = {
41
+ pos: monomerInfo.name,
42
+ // seq1monomer: monomerInfo.cat![seq1categoryIdx],
43
+ // seq2monomer: monomerInfo.cat![seq2categoryIdx],
44
+ seq1Idx: seq1Idx,
45
+ seq2Idx: seq2Idx,
46
+ };
47
+ }
48
+ if (!(substCounterFlag || substCounter === 0)) {
49
+ for (let i = 0; i < tempDataIdx; i++) {
50
+ const tempDataElement = tempData[i];
51
+ const position = tempDataElement.pos;
52
+ // const seq1monomer = tempDataElement.seq1monomer;
53
+ // const seq2monomer = tempDataElement.seq2monomer;
54
+ // monomers1.push(seq1monomer);
55
+ // monomers2.push(seq2monomer);
56
+ pos.push(position);
57
+ seq1Idxs.push(seq1Idx);
58
+ seq2Idxs.push(seq2Idx);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ cnt++;
64
+ seq2Idx++;
65
+ if (seq2Idx === activityArray.length) {
66
+ seq1Idx++;
67
+ seq2Idx = seq1Idx + 1;
68
+ }
69
+ }
70
+ postMessage({
71
+ // monomers1: monomers1,
72
+ // monomers2: monomers2,
73
+ pos: pos,
74
+ seq1Idxs: new Uint32Array(seq1Idxs),
75
+ seq2Idxs: new Uint32Array(seq2Idxs),
76
+ });
77
+ };