@datagrok/peptides 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "author": {
5
5
  "name": "Volodymyr Dyma",
6
6
  "email": "vdyma@datagrok.ai"
@@ -12,10 +12,10 @@
12
12
  "directory": "packages/Peptides"
13
13
  },
14
14
  "dependencies": {
15
- "@datagrok-libraries/bio": "^3.3.0",
15
+ "@datagrok-libraries/bio": "^5.0.0",
16
16
  "@datagrok-libraries/ml": "^2.0.1",
17
17
  "@datagrok-libraries/statistics": "^0.1.6",
18
- "@datagrok-libraries/utils": "^0.4.1",
18
+ "@datagrok-libraries/utils": "^1.10.1",
19
19
  "cash-dom": "latest",
20
20
  "datagrok-api": "^1.6.0",
21
21
  "file-loader": "^6.2.0",
@@ -43,6 +43,10 @@
43
43
  "@types/node-fetch": "^2.6.2",
44
44
  "node-fetch": "^2.6.7"
45
45
  },
46
+ "grokDependencies": {
47
+ "@datagrok/bio": "latest",
48
+ "@datagrok/helm": "latest"
49
+ },
46
50
  "scripts": {
47
51
  "link-api": "npm link datagrok-api",
48
52
  "link-utils": "npm link @datagrok-libraries/utils",
package/src/model.ts CHANGED
@@ -5,13 +5,12 @@ import * as DG from 'datagrok-api/dg';
5
5
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
6
6
 
7
7
  import * as rxjs from 'rxjs';
8
- import $ from 'cash-dom';
9
8
 
10
9
  import * as C from './utils/constants';
11
10
  import * as type from './utils/types';
12
11
  import {calculateBarsData, getTypedArrayConstructor, isGridCellInvalid, scaleActivity} from './utils/misc';
13
12
  import {MutationCliffsViewer, SARViewerBase, MostPotentResiduesViewer} from './viewers/sar-viewer';
14
- import {renderBarchart, renderMutationCliffCell, setAARRenderer, renderInvaraintMapCell} from './utils/cell-renderer';
13
+ import * as CR from './utils/cell-renderer';
15
14
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
16
15
  import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
17
16
  import {getStats, Stats} from './utils/statistics';
@@ -35,9 +34,11 @@ export class PeptidesModel {
35
34
  df: DG.DataFrame;
36
35
  splitCol!: DG.Column<boolean>;
37
36
  edf: DG.DataFrame | null = null;
38
- statsDf!: DG.DataFrame;
37
+ monomerPositionStatsDf!: DG.DataFrame;
38
+ clusterStatsDf!: DG.DataFrame;
39
39
  _mutationCliffsSelection: type.PositionToAARList = {};
40
40
  _invariantMapSelection: type.PositionToAARList = {};
41
+ _logoSummarySelection: number[] = [];
41
42
  substitutionsInfo: type.SubstitutionsInfo = new Map();
42
43
  isInitialized = false;
43
44
  currentView!: DG.TableView;
@@ -86,7 +87,6 @@ export class PeptidesModel {
86
87
  this.df.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
87
88
  this.fireBitsetChanged();
88
89
  this.invalidateGrids();
89
- this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
90
90
  }
91
91
 
92
92
  get invariantMapSelection(): type.PositionToAARList {
@@ -100,6 +100,17 @@ export class PeptidesModel {
100
100
  this.invalidateGrids();
101
101
  }
102
102
 
103
+ get logoSummarySelection(): number[] {
104
+ this._logoSummarySelection ??= JSON.parse(this.df.tags[C.TAGS.CLUSTER_SELECTION] || '[]');
105
+ return this._logoSummarySelection;
106
+ }
107
+ set logoSummarySelection(selection: number[]) {
108
+ this._logoSummarySelection = selection;
109
+ this.df.tags[C.TAGS.CLUSTER_SELECTION] = JSON.stringify(selection);
110
+ this.fireBitsetChanged();
111
+ this.invalidateGrids();
112
+ }
113
+
103
114
  get usedProperties(): {[propName: string]: string | number | boolean} {
104
115
  this._usedProperties = JSON.parse(this.df.tags['sarProperties'] ?? '{}');
105
116
  return this._usedProperties;
@@ -171,7 +182,8 @@ export class PeptidesModel {
171
182
  }
172
183
 
173
184
  updateDefault(): void {
174
- const proprtyChanged = this.isPropertyChanged(this.mutationCliffsViewer) || this.isPropertyChanged(this.mostPotentResiduesViewer);
185
+ const proprtyChanged =
186
+ this.isPropertyChanged(this.mutationCliffsViewer) || this.isPropertyChanged(this.mostPotentResiduesViewer);
175
187
  if ((this.sourceGrid && !this._isUpdating && proprtyChanged) || !this.isInitialized) {
176
188
  this.isInitialized = true;
177
189
  this._isUpdating = true;
@@ -193,7 +205,7 @@ export class PeptidesModel {
193
205
  throw new Error(`Source grid is not initialized`);
194
206
 
195
207
  //Split the aligned sequence into separate AARs
196
- const col: DG.Column<string> = this.df.columns.bySemType(C.SEM_TYPES.MACROMOLECULE)!;
208
+ const col = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
197
209
  const alphabet = col.tags['alphabet'];
198
210
  const splitSeqDf = splitAlignedSequences(col);
199
211
 
@@ -204,12 +216,7 @@ export class PeptidesModel {
204
216
  const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
205
217
  splitSeqDf.columns.add(activityCol);
206
218
 
207
- this.joinDataFrames(positionColumns, splitSeqDf);
208
-
209
- for (const dfCol of this.df.columns) {
210
- if (positionColumns.includes(dfCol.name))
211
- setAARRenderer(dfCol, alphabet, this.sourceGrid);
212
- }
219
+ this.joinDataFrames(positionColumns, splitSeqDf, alphabet);
213
220
 
214
221
  this.sortSourceGrid();
215
222
 
@@ -223,11 +230,11 @@ export class PeptidesModel {
223
230
  matrixDf = matrixDf.unpivot([], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER);
224
231
 
225
232
  //statistics for specific AAR at a specific position
226
- this.statsDf = this.calculateStatistics(matrixDf);
233
+ this.monomerPositionStatsDf = this.calculateMonomerPositionStatistics(matrixDf);
227
234
 
228
235
  // SAR matrix table
229
236
  //pivot a table to make it matrix-like
230
- matrixDf = this.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
237
+ matrixDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
231
238
  .pivot(C.COLUMNS_NAMES.POSITION)
232
239
  .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
233
240
  .aggregate();
@@ -244,8 +251,10 @@ export class PeptidesModel {
244
251
  [this.mutationCliffsGrid, this.mostPotentResiduesGrid] =
245
252
  this.createGrids(matrixDf, sequenceDf, positionColumns, alphabet);
246
253
 
247
- if (this.df.getTag(C.TAGS.CLUSTERS))
254
+ if (this.df.getTag(C.TAGS.CLUSTERS)) {
255
+ this.clusterStatsDf = this.calculateClusterStatistics();
248
256
  this.logoSummaryGrid = this.createLogoSummaryGrid();
257
+ }
249
258
 
250
259
  // init invariant map & mutation cliffs selections
251
260
  this.initSelections(positionColumns);
@@ -360,15 +369,22 @@ export class PeptidesModel {
360
369
  }
361
370
  this.invariantMapSelection = tempInvariantMapSelection;
362
371
  this.mutationCliffsSelection = mutationCliffsSelection;
372
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
363
373
  }
364
374
 
365
- joinDataFrames(positionColumns: string[], splitSeqDf: DG.DataFrame): void {
375
+ joinDataFrames(positionColumns: string[], splitSeqDf: DG.DataFrame, alphabet: string): void {
366
376
  // append splitSeqDf columns to source table and make sure columns are not added more than once
367
377
  const name = this.df.name;
368
- const dfColsSet = new Set(this.df.columns.names());
369
- if (!positionColumns.every((col: string) => dfColsSet.has(col))) {
370
- this.df.join(splitSeqDf, [C.COLUMNS_NAMES.ACTIVITY], [C.COLUMNS_NAMES.ACTIVITY],
371
- this.df.columns.names(), positionColumns, 'inner', true);
378
+ for (const colName of positionColumns) {
379
+ const col = this.df.col(colName);
380
+ const newCol = splitSeqDf.getCol(colName);
381
+ if (col === null)
382
+ this.df.columns.add(newCol);
383
+ else {
384
+ this.df.columns.remove(colName);
385
+ this.df.columns.add(newCol);
386
+ }
387
+ CR.setAARRenderer(newCol, alphabet, this.sourceGrid);
372
388
  }
373
389
  this.df.name = name;
374
390
  this.currentView.name = name;
@@ -393,8 +409,8 @@ export class PeptidesModel {
393
409
  }
394
410
 
395
411
  createScaledCol(activityScaling: string, splitSeqDf: DG.DataFrame): void {
396
- const [scaledDf, newColName] =
397
- scaleActivity(activityScaling, this.df, this.df.tags[C.COLUMNS_NAMES.ACTIVITY]);
412
+ const [scaledDf, _, newColName] =
413
+ scaleActivity(activityScaling, this.df.getCol(C.COLUMNS_NAMES.ACTIVITY));
398
414
  //TODO: make another func
399
415
  const scaledCol = scaledDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
400
416
  scaledCol.semType = C.SEM_TYPES.ACTIVITY_SCALED;
@@ -410,7 +426,7 @@ export class PeptidesModel {
410
426
  this.sourceGrid.columns.setOrder([newColName]);
411
427
  }
412
428
 
413
- calculateStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
429
+ calculateMonomerPositionStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
414
430
  matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER]).aggregate();
415
431
 
416
432
  //calculate p-values based on t-test
@@ -427,7 +443,7 @@ export class PeptidesModel {
427
443
  for (let i = 0; i < matrixDf.rowCount; i++) {
428
444
  const position: string = posCol.get(i);
429
445
  const aar: string = aarCol.get(i);
430
- const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) == aar);
446
+ const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) === aar);
431
447
  const stats = getStats(activityCol, mask);
432
448
 
433
449
  mdCol.set(i, stats.meanDifference);
@@ -436,23 +452,45 @@ export class PeptidesModel {
436
452
  ratioCol.set(i, stats.ratio);
437
453
  }
438
454
 
439
- const countThreshold = 4;
440
- matrixDf = matrixDf.rows.match(`${C.COLUMNS_NAMES.COUNT} >= ${countThreshold}`).toDataFrame();
441
- matrixDf = matrixDf.rows.match(`${C.COLUMNS_NAMES.COUNT} <= ${sourceDfLen - countThreshold}`).toDataFrame();
442
-
455
+ const monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
456
+ matrixDf = matrixDf.clone(DG.BitSet.create(matrixDf.rowCount, (i) => monomerCol.get(i) ? true : false));
443
457
  return matrixDf as DG.DataFrame;
444
458
  }
445
459
 
460
+ calculateClusterStatistics(): DG.DataFrame {
461
+ const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
462
+ const statsDf = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
463
+ const clustersCol = statsDf.getCol(C.COLUMNS_NAMES.CLUSTERS);
464
+ const statsDfCols = statsDf.columns;
465
+ const mdCol= statsDfCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
466
+ const pValCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
467
+ const countCol = statsDfCols.addNewInt(C.COLUMNS_NAMES.COUNT);
468
+ const ratioCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
469
+ const activityList: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
470
+
471
+ for (let rowIdx = 0; rowIdx < clustersCol.length; ++rowIdx) {
472
+ const cluster = clustersCol.get(rowIdx);
473
+ const mask = DG.BitSet.create(activityList.length, (bitIdx) => originalClustersCol.get(bitIdx) === cluster);
474
+ const stats = getStats(activityList, mask);
475
+
476
+ mdCol.set(rowIdx, stats.meanDifference);
477
+ pValCol.set(rowIdx, stats.pValue);
478
+ countCol.set(rowIdx, stats.count);
479
+ ratioCol.set(rowIdx, stats.ratio);
480
+ }
481
+ return statsDf;
482
+ }
483
+
446
484
  setCategoryOrder(matrixDf: DG.DataFrame): void {
447
485
  let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
448
486
  if (this.getViewer().bidirectionalAnalysis) {
449
- const mdCol = this.statsDf.getCol(sortArgument);
487
+ const mdCol = this.monomerPositionStatsDf.getCol(sortArgument);
450
488
  sortArgument = 'Absolute Mean difference';
451
- const absMDCol = this.statsDf.columns.addNewFloat(sortArgument);
489
+ const absMDCol = this.monomerPositionStatsDf.columns.addNewFloat(sortArgument);
452
490
  absMDCol.init((i) => Math.abs(mdCol.get(i)));
453
491
  }
454
492
 
455
- const aarWeightsDf = this.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
493
+ const aarWeightsDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
456
494
  .aggregate();
457
495
  const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
458
496
  const getWeight = (aar: string): number => aarWeightsDf
@@ -469,7 +507,7 @@ export class PeptidesModel {
469
507
  // TODO: aquire ALL of the positions
470
508
  const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.POSITION,
471
509
  'Count', 'Ratio', C.COLUMNS_NAMES.P_VALUE];
472
- let sequenceDf = this.statsDf.groupBy(columns)
510
+ let sequenceDf = this.monomerPositionStatsDf.groupBy(columns)
473
511
  .where('pValue <= 0.1')
474
512
  .aggregate();
475
513
 
@@ -505,8 +543,8 @@ export class PeptidesModel {
505
543
  pValGridCol.name = 'P-value';
506
544
 
507
545
  // Setting Monomer column renderer
508
- setAARRenderer(mutationCliffsDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mutationCliffsGrid);
509
- setAARRenderer(mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mostPotentResiduesGrid);
546
+ CR.setAARRenderer(mutationCliffsDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mutationCliffsGrid);
547
+ CR.setAARRenderer(mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mostPotentResiduesGrid);
510
548
 
511
549
  return [mutationCliffsGrid, mostPotentResiduesGrid];
512
550
  }
@@ -514,28 +552,75 @@ export class PeptidesModel {
514
552
  createLogoSummaryGrid(): DG.Grid {
515
553
  const summaryTable = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
516
554
  const summaryTableLength = summaryTable.rowCount;
517
- const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
518
555
  const clustersCol: DG.Column<number> = summaryTable.getCol(C.COLUMNS_NAMES.CLUSTERS);
519
- clustersCol.name = 'Clusters';
556
+ const membersCol: DG.Column<number> = summaryTable.columns.addNewInt('Members');
557
+ const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
520
558
  const tempDfList: DG.DataFrame[] = new Array(summaryTableLength);
521
559
  const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
560
+ const peptideCol: DG.Column<string> = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
522
561
 
523
562
  for (let index = 0; index < summaryTableLength; ++index) {
524
- // const tempDf: DG.DataFrame = this.df.rows.match(`${C.COLUMNS_NAMES.CLUSTERS} = ${clustersCol.get(index)}`).toDataFrame();
525
- const bs = DG.BitSet.create(this.df.rowCount);
526
- bs.init((i) => clustersCol.get(index) === originalClustersCol.get(i));
527
- const tempDf = this.df.clone(bs, [C.COLUMNS_NAMES.ALIGNED_SEQUENCE]);
528
- tempDfList[index] = tempDf;
563
+ const indexes: number[] = [];
564
+ for (let j = 0; j < originalClustersCol.length; ++j) {
565
+ if (originalClustersCol.get(j) === clustersCol.get(index))
566
+ indexes.push(j);
567
+ }
568
+ const tCol = DG.Column.string('peptides', indexes.length);
569
+ tCol.init((i) => peptideCol.get(indexes[i]));
570
+
571
+ for (const tag of peptideCol.tags)
572
+ tCol.setTag(tag[0], tag[1]);
573
+
574
+ const dfSlice = DG.DataFrame.fromColumns([tCol]);
575
+ tempDfList[index] = dfSlice;
529
576
  webLogoCol.set(index, index.toString());
577
+ membersCol.set(index, dfSlice.rowCount);
530
578
  }
531
579
  webLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
532
580
 
533
581
  const grid = summaryTable.plot.grid();
582
+ const gridClustersCol = grid.col(C.COLUMNS_NAMES.CLUSTERS)!;
583
+ gridClustersCol.name = 'Clusters';
584
+ gridClustersCol.visible = true;
534
585
  grid.columns.rowHeader!.visible = false;
535
586
  grid.props.rowHeight = 55;
536
587
  grid.onCellPrepare((cell) => {
537
- if (cell.isTableCell && cell.tableColumn?.name === 'WebLogo')
538
- tempDfList[parseInt(cell.cell.value)].plot.fromType('WebLogo').then((viewer) => cell.element = viewer.root);
588
+ if (cell.isTableCell && cell.tableColumn?.name === 'WebLogo') {
589
+ tempDfList[parseInt(cell.cell.value)].plot.fromType('WebLogo', {maxHeight: 50})
590
+ .then((viewer) => cell.element = viewer.root);
591
+ }
592
+ });
593
+ grid.root.addEventListener('click', (ev) => {
594
+ const cell = grid.hitTest(ev.offsetX, ev.offsetY);
595
+ if (!cell || !cell.isTableCell)
596
+ return;
597
+
598
+ const cluster = clustersCol.get(cell.tableRowIndex!)!;
599
+ summaryTable.currentRowIdx = -1;
600
+ if (ev.shiftKey)
601
+ this.modifyClusterSelection(cluster);
602
+ else
603
+ this.initClusterSelection(cluster);
604
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
605
+ });
606
+ grid.onCellRender.subscribe((gridCellArgs) => {
607
+ const gc = gridCellArgs.cell;
608
+ if (gc.tableColumn?.name !== C.COLUMNS_NAMES.CLUSTERS || gc.isColHeader)
609
+ return;
610
+ const canvasContext = gridCellArgs.g;
611
+ const bound = gridCellArgs.bounds;
612
+ canvasContext.save();
613
+ canvasContext.beginPath();
614
+ canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
615
+ canvasContext.clip();
616
+ CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.logoSummarySelection, bound);
617
+ gridCellArgs.preventDefault();
618
+ canvasContext.restore();
619
+ });
620
+ grid.onCellTooltip((cell, x, y) => {
621
+ if (!cell.isColHeader && cell.tableColumn?.name === C.COLUMNS_NAMES.CLUSTERS)
622
+ this.showTooltipCluster(cell.cell.value, x, y);
623
+ return true;
539
624
  });
540
625
  const webLogoGridCol = grid.columns.byName('WebLogo')!;
541
626
  webLogoGridCol.cellType = 'html';
@@ -544,6 +629,21 @@ export class PeptidesModel {
544
629
  return grid;
545
630
  }
546
631
 
632
+ modifyClusterSelection(cluster: number): void {
633
+ const tempSelection = this.logoSummarySelection;
634
+ const idx = tempSelection.indexOf(cluster);
635
+ if (idx !== -1)
636
+ tempSelection.splice(idx, 1);
637
+ else
638
+ tempSelection.push(cluster);
639
+
640
+ this.logoSummarySelection = tempSelection;
641
+ }
642
+
643
+ initClusterSelection(cluster: number): void {
644
+ this.logoSummarySelection = [cluster];
645
+ }
646
+
547
647
  setBarChartInteraction(): void {
548
648
  const eventAction = (ev: MouseEvent): void => {
549
649
  const cell = this.sourceGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -578,8 +678,9 @@ export class PeptidesModel {
578
678
  const monomer = barPart.monomer;
579
679
  const position = barPart.position;
580
680
  if (ev.type === 'click') {
581
- ev.shiftKey ? this.modifyCurrentSelection(monomer, position, true) :
582
- this.initCurrentSelection(monomer, position, true);
681
+ ev.shiftKey ? this.modifyMonomerPositionSelection(monomer, position, true) :
682
+ this.initMonomerPositionSelection(monomer, position, true);
683
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
583
684
  } else {
584
685
  const bar = `${monomer}:${position}`;
585
686
  if (this.cachedBarchartTooltip.bar == bar)
@@ -590,7 +691,7 @@ export class PeptidesModel {
590
691
  }
591
692
 
592
693
  setCellRenderers(renderColNames: string[]): void {
593
- const mdCol = this.statsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
694
+ const mdCol = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
594
695
  //decompose into two different renering funcs
595
696
  const renderCell = (args: DG.GridCellRenderArgs): void => {
596
697
  const canvasContext = args.g;
@@ -622,14 +723,16 @@ export class PeptidesModel {
622
723
 
623
724
  const viewer = this.getViewer();
624
725
  if (this.isInvariantMap) {
625
- const value: number = this.statsDf
726
+ const value: number = this.monomerPositionStatsDf
626
727
  .groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.COUNT])
627
728
  .where(`${C.COLUMNS_NAMES.POSITION} = ${currentPosition} and ${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`)
628
729
  .aggregate().get(C.COLUMNS_NAMES.COUNT, 0);
629
- renderInvaraintMapCell(canvasContext, currentAAR, currentPosition, this.invariantMapSelection, value, bound);
730
+ CR.renderInvaraintMapCell(
731
+ canvasContext, currentAAR, currentPosition, this.invariantMapSelection, value, bound);
630
732
  } else {
631
- renderMutationCliffCell(canvasContext, currentAAR, currentPosition, this.statsDf,
632
- viewer.bidirectionalAnalysis, mdCol, bound, cellValue, this.mutationCliffsSelection, this.substitutionsInfo);
733
+ CR.renderMutationCliffCell(
734
+ canvasContext, currentAAR, currentPosition, this.monomerPositionStatsDf, viewer.bidirectionalAnalysis,
735
+ mdCol, bound, cellValue, this.mutationCliffsSelection, this.substitutionsInfo);
633
736
  }
634
737
  }
635
738
  args.preventDefault();
@@ -651,7 +754,7 @@ export class PeptidesModel {
651
754
  context.clip();
652
755
 
653
756
  if (gcArgs.cell.isColHeader && col?.semType == C.SEM_TYPES.MONOMER) {
654
- const barBounds = renderBarchart(context, col, this.barData[col.name], bounds, this.df.filter.trueCount);
757
+ const barBounds = CR.renderBarchart(context, col, this.barData[col.name], bounds, this.df.filter.trueCount);
655
758
  this.barsBounds[col.name] = barBounds;
656
759
  gcArgs.preventDefault();
657
760
  }
@@ -708,8 +811,9 @@ export class PeptidesModel {
708
811
  }
709
812
 
710
813
  showTooltipAt(aar: string, position: string, x: number, y: number): HTMLDivElement | null {
711
- const currentStatsDf = this.statsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
814
+ const currentStatsDf = this.monomerPositionStatsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
712
815
  const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
816
+ //TODO: use bitset instead of splitCol
713
817
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
714
818
  const currentPosCol = this.df.getCol(position);
715
819
  splitCol.init((i) => currentPosCol.get(i) == aar);
@@ -730,15 +834,40 @@ export class PeptidesModel {
730
834
  return tooltip;
731
835
  }
732
836
 
837
+ showTooltipCluster(cluster: number, x: number, y: number): HTMLDivElement | null {
838
+ const currentStatsDf = this.clusterStatsDf.rows.match({clusters: cluster}).toDataFrame();
839
+ const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
840
+ //TODO: use bitset instead of splitCol
841
+ const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
842
+ const currentClusterCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
843
+ splitCol.init((i) => currentClusterCol.get(i) == cluster);
844
+ const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
845
+ const stats: Stats = {
846
+ count: currentStatsDf.get(C.COLUMNS_NAMES.COUNT, 0),
847
+ ratio: currentStatsDf.get(C.COLUMNS_NAMES.RATIO, 0),
848
+ pValue: currentStatsDf.get(C.COLUMNS_NAMES.P_VALUE, 0),
849
+ meanDifference: currentStatsDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, 0),
850
+ };
851
+ if (!stats.count)
852
+ return null;
853
+
854
+ const tooltip = getDistributionAndStats(distributionTable, stats, `Cluster: ${cluster}`, 'Other', true);
855
+
856
+ ui.tooltip.show(tooltip, x, y);
857
+
858
+ return tooltip;
859
+ }
860
+
733
861
  setInteractionCallback(): void {
734
862
  const mutationCliffsDf = this.mutationCliffsGrid.dataFrame;
735
863
  const mostPotentResiduesDf = this.mostPotentResiduesGrid.dataFrame;
736
- // const invariantMapDf = this.invariantMapGrid.dataFrame;
737
864
 
738
865
  const chooseAction =
739
- (aar: string, position: string, isShiftPressed: boolean, isInvariantMapSelection: boolean = true): void =>
740
- isShiftPressed ? this.modifyCurrentSelection(aar, position, isInvariantMapSelection) :
741
- this.initCurrentSelection(aar, position, isInvariantMapSelection);
866
+ (aar: string, position: string, isShiftPressed: boolean, isInvariantMapSelection: boolean = true): void => {
867
+ isShiftPressed ? this.modifyMonomerPositionSelection(aar, position, isInvariantMapSelection) :
868
+ this.initMonomerPositionSelection(aar, position, isInvariantMapSelection);
869
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
870
+ };
742
871
 
743
872
  this.mutationCliffsGrid.root.addEventListener('click', (ev) => {
744
873
  const gridCell = this.mutationCliffsGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -758,7 +887,7 @@ export class PeptidesModel {
758
887
  const tableRowIdx = gridCell!.tableRowIndex!;
759
888
  const position = mostPotentResiduesDf.get(C.COLUMNS_NAMES.POSITION, tableRowIdx);
760
889
  const aar = mostPotentResiduesDf.get(C.COLUMNS_NAMES.MONOMER, tableRowIdx);
761
- chooseAction(aar, position, ev.shiftKey);
890
+ chooseAction(aar, position, ev.shiftKey, false);
762
891
  });
763
892
 
764
893
  const cellChanged = (table: DG.DataFrame): void => {
@@ -772,17 +901,14 @@ export class PeptidesModel {
772
901
  this.mostPotentResiduesGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(mostPotentResiduesDf));
773
902
  }
774
903
 
775
- modifyCurrentSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
904
+ modifyMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
776
905
  const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
777
- if (!tempSelection.hasOwnProperty(position))
778
- tempSelection[position] = [aar];
779
- else {
780
- const tempSelectionAt = tempSelection[position];
781
- const aarIndex = tempSelectionAt.indexOf(aar);
782
- aarIndex == -1 ? tempSelectionAt.push(aar) :
783
- tempSelectionAt.length == 1 ? delete tempSelection[position] :
784
- tempSelectionAt.splice(aarIndex, 1);
785
- }
906
+ const tempSelectionAt = tempSelection[position];
907
+ const aarIndex = tempSelectionAt.indexOf(aar);
908
+ if (aarIndex === -1)
909
+ tempSelectionAt.push(aar);
910
+ else
911
+ tempSelectionAt.splice(aarIndex, 1);
786
912
 
787
913
  if (isInvariantMapSelection)
788
914
  this.invariantMapSelection = tempSelection;
@@ -790,8 +916,10 @@ export class PeptidesModel {
790
916
  this.mutationCliffsSelection = tempSelection;
791
917
  }
792
918
 
793
- initCurrentSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
794
- const tempSelection: type.PositionToAARList = {};
919
+ initMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
920
+ const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
921
+ for (const key of Object.keys(tempSelection))
922
+ tempSelection[key] = [];
795
923
  tempSelection[position] = [aar];
796
924
 
797
925
  if (isInvariantMapSelection)
@@ -803,8 +931,8 @@ export class PeptidesModel {
803
931
  invalidateGrids(): void {
804
932
  this.mutationCliffsGrid.invalidate();
805
933
  this.mostPotentResiduesGrid.invalidate();
934
+ this.logoSummaryGrid?.invalidate();
806
935
  this.sourceGrid?.invalidate();
807
- //TODO: this.peptideSpaceGrid.invalidate();
808
936
  }
809
937
 
810
938
  setBitsetCallback(): void {
@@ -812,6 +940,7 @@ export class PeptidesModel {
812
940
  return;
813
941
  const selection = this.df.selection;
814
942
  const filter = this.df.filter;
943
+ const clusterCol = this.df.col(C.COLUMNS_NAMES.CLUSTERS);
815
944
 
816
945
  const changeSelectionBitset = (currentBitset: DG.BitSet): void => {
817
946
  const edfSelection = this.edf?.selection;
@@ -830,11 +959,6 @@ export class PeptidesModel {
830
959
  };
831
960
 
832
961
  const positionList = Object.keys(this.mutationCliffsSelection);
833
- if (positionList.length == 0) {
834
- currentBitset.init(() => false, false);
835
- updateEdfSelection();
836
- return;
837
- }
838
962
 
839
963
  //TODO: move out
840
964
  const getBitAt = (i: number): boolean => {
@@ -843,6 +967,8 @@ export class PeptidesModel {
843
967
  if (this._mutationCliffsSelection[position].includes(positionCol.get(i)!))
844
968
  return true;
845
969
  }
970
+ if (this._logoSummarySelection.includes(clusterCol?.get(i)!))
971
+ return true;
846
972
  return false;
847
973
  };
848
974
  currentBitset.init(getBitAt, false);
@@ -885,10 +1011,9 @@ export class PeptidesModel {
885
1011
  for (let i = 0; i < colNum; ++i) {
886
1012
  const col = girdCols.byIndex(i)!;
887
1013
  const colName = col.name;
888
- if (grid == this.mostPotentResiduesGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER)
889
- col.width = 50;
890
- else
891
- col.width = gridProps.rowHeight + 10;
1014
+ col.width =
1015
+ grid == this.mostPotentResiduesGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER ? 50 :
1016
+ gridProps.rowHeight + 10;
892
1017
  }
893
1018
  }
894
1019
 
@@ -951,7 +1076,9 @@ export class PeptidesModel {
951
1076
  return;
952
1077
 
953
1078
  this.df.tags[C.PEPTIDES_ANALYSIS] = 'true';
954
- this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!.name = this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED];
1079
+ const scaledGridCol = this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!;
1080
+ scaledGridCol.name = this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED];
1081
+ scaledGridCol.format = '#.000';
955
1082
  this.sourceGrid.columns.setOrder([this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED]]);
956
1083
  this.sourceGrid.props.allowColSelection = false;
957
1084
 
@@ -966,10 +1093,9 @@ export class PeptidesModel {
966
1093
  };
967
1094
 
968
1095
  for (let i = 0; i < this.sourceGrid.columns.length; i++) {
969
- const aarCol = this.sourceGrid.columns.byIndex(i);
970
- if (aarCol && aarCol.name && aarCol.column?.semType !== C.SEM_TYPES.MONOMER &&
971
- aarCol.name !== this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED])
972
- aarCol.visible = false;
1096
+ const currentCol = this.sourceGrid.columns.byIndex(i);
1097
+ if (currentCol?.column?.getTag(C.TAGS.VISIBLE) === '0')
1098
+ currentCol.visible = false;
973
1099
  }
974
1100
 
975
1101
  const options = {scaling: this.df.tags['scaling']};
@@ -986,14 +1112,6 @@ export class PeptidesModel {
986
1112
  dockManager.dock(logoSummary, DG.DOCK_TYPE.RIGHT, null, 'Logo Summary Table');
987
1113
  }
988
1114
 
989
- // TODO: completely remove this viewer?
990
- // if (this.df.rowCount <= 10000) {
991
- // const peptideSpaceViewerOptions = {method: 'UMAP', measure: 'Levenshtein', cyclesCount: 100};
992
- // const peptideSpaceViewer =
993
- // await this.df.plot.fromType('peptide-space-viewer', peptideSpaceViewerOptions) as PeptideSpaceViewer;
994
- // dockManager.dock(peptideSpaceViewer, DG.DOCK_TYPE.RIGHT, null, 'Peptide Space Viewer');
995
- // }
996
-
997
1115
  this.updateDefault();
998
1116
 
999
1117
  const mcNode =
package/src/package.ts CHANGED
@@ -5,7 +5,7 @@ import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import * as C from './utils/constants';
7
7
 
8
- import {analyzePeptidesWidget} from './widgets/analyze-peptides';
8
+ import {analyzePeptidesWidget} from './widgets/peptides';
9
9
  import {PeptideSimilaritySpaceWidget} from './utils/peptide-similarity-space';
10
10
  import {manualAlignmentWidget} from './widgets/manual-alignment';
11
11
  import {MutationCliffsViewer, MostPotentResiduesViewer} from './viewers/sar-viewer';
@@ -155,7 +155,8 @@ export function getPeptidesStructure(col: DG.Column): DG.Widget {
155
155
 
156
156
  function getOrDefine(dataframe?: DG.DataFrame, column?: DG.Column | null): [DG.DataFrame, DG.Column] {
157
157
  dataframe ??= grok.shell.t;
158
- column ??= dataframe.columns.bySemType(C.SEM_TYPES.MACROMOLECULE);
158
+ // column ??= dataframe.columns.bySemType(C.SEM_TYPES.MACROMOLECULE);
159
+ column ??= dataframe.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
159
160
  if (column === null)
160
161
  throw new Error('Table does not contain aligned sequence columns');
161
162