@datagrok/peptides 1.3.0 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "author": {
5
5
  "name": "Volodymyr Dyma",
6
6
  "email": "vdyma@datagrok.ai"
@@ -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,43 @@ 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
-
443
455
  return matrixDf as DG.DataFrame;
444
456
  }
445
457
 
458
+ calculateClusterStatistics(): DG.DataFrame {
459
+ const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
460
+ const statsDf = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
461
+ const clustersCol = statsDf.getCol(C.COLUMNS_NAMES.CLUSTERS);
462
+ const statsDfCols = statsDf.columns;
463
+ const mdCol= statsDfCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
464
+ const pValCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
465
+ const countCol = statsDfCols.addNewInt(C.COLUMNS_NAMES.COUNT);
466
+ const ratioCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
467
+ const activityList: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
468
+
469
+ for (let rowIdx = 0; rowIdx < clustersCol.length; ++rowIdx) {
470
+ const cluster = clustersCol.get(rowIdx);
471
+ const mask = DG.BitSet.create(activityList.length, (bitIdx) => originalClustersCol.get(bitIdx) === cluster);
472
+ const stats = getStats(activityList, mask);
473
+
474
+ mdCol.set(rowIdx, stats.meanDifference);
475
+ pValCol.set(rowIdx, stats.pValue);
476
+ countCol.set(rowIdx, stats.count);
477
+ ratioCol.set(rowIdx, stats.ratio);
478
+ }
479
+ return statsDf;
480
+ }
481
+
446
482
  setCategoryOrder(matrixDf: DG.DataFrame): void {
447
483
  let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
448
484
  if (this.getViewer().bidirectionalAnalysis) {
449
- const mdCol = this.statsDf.getCol(sortArgument);
485
+ const mdCol = this.monomerPositionStatsDf.getCol(sortArgument);
450
486
  sortArgument = 'Absolute Mean difference';
451
- const absMDCol = this.statsDf.columns.addNewFloat(sortArgument);
487
+ const absMDCol = this.monomerPositionStatsDf.columns.addNewFloat(sortArgument);
452
488
  absMDCol.init((i) => Math.abs(mdCol.get(i)));
453
489
  }
454
490
 
455
- const aarWeightsDf = this.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
491
+ const aarWeightsDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
456
492
  .aggregate();
457
493
  const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
458
494
  const getWeight = (aar: string): number => aarWeightsDf
@@ -469,7 +505,7 @@ export class PeptidesModel {
469
505
  // TODO: aquire ALL of the positions
470
506
  const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.POSITION,
471
507
  'Count', 'Ratio', C.COLUMNS_NAMES.P_VALUE];
472
- let sequenceDf = this.statsDf.groupBy(columns)
508
+ let sequenceDf = this.monomerPositionStatsDf.groupBy(columns)
473
509
  .where('pValue <= 0.1')
474
510
  .aggregate();
475
511
 
@@ -505,8 +541,8 @@ export class PeptidesModel {
505
541
  pValGridCol.name = 'P-value';
506
542
 
507
543
  // Setting Monomer column renderer
508
- setAARRenderer(mutationCliffsDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mutationCliffsGrid);
509
- setAARRenderer(mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mostPotentResiduesGrid);
544
+ CR.setAARRenderer(mutationCliffsDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mutationCliffsGrid);
545
+ CR.setAARRenderer(mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mostPotentResiduesGrid);
510
546
 
511
547
  return [mutationCliffsGrid, mostPotentResiduesGrid];
512
548
  }
@@ -514,28 +550,75 @@ export class PeptidesModel {
514
550
  createLogoSummaryGrid(): DG.Grid {
515
551
  const summaryTable = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
516
552
  const summaryTableLength = summaryTable.rowCount;
517
- const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
518
553
  const clustersCol: DG.Column<number> = summaryTable.getCol(C.COLUMNS_NAMES.CLUSTERS);
519
- clustersCol.name = 'Clusters';
554
+ const membersCol: DG.Column<number> = summaryTable.columns.addNewInt('Members');
555
+ const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
520
556
  const tempDfList: DG.DataFrame[] = new Array(summaryTableLength);
521
557
  const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
558
+ const peptideCol: DG.Column<string> = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
522
559
 
523
560
  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;
561
+ const indexes: number[] = [];
562
+ for (let j = 0; j < originalClustersCol.length; ++j) {
563
+ if (originalClustersCol.get(j) === clustersCol.get(index))
564
+ indexes.push(j);
565
+ }
566
+ const tCol = DG.Column.string('peptides', indexes.length);
567
+ tCol.init((i) => peptideCol.get(indexes[i]));
568
+
569
+ for (const tag of peptideCol.tags)
570
+ tCol.setTag(tag[0], tag[1]);
571
+
572
+ const dfSlice = DG.DataFrame.fromColumns([tCol]);
573
+ tempDfList[index] = dfSlice;
529
574
  webLogoCol.set(index, index.toString());
575
+ membersCol.set(index, dfSlice.rowCount);
530
576
  }
531
577
  webLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
532
578
 
533
579
  const grid = summaryTable.plot.grid();
580
+ const gridClustersCol = grid.col(C.COLUMNS_NAMES.CLUSTERS)!;
581
+ gridClustersCol.name = 'Clusters';
582
+ gridClustersCol.visible = true;
534
583
  grid.columns.rowHeader!.visible = false;
535
584
  grid.props.rowHeight = 55;
536
585
  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);
586
+ if (cell.isTableCell && cell.tableColumn?.name === 'WebLogo') {
587
+ tempDfList[parseInt(cell.cell.value)].plot.fromType('WebLogo', {maxHeight: 50})
588
+ .then((viewer) => cell.element = viewer.root);
589
+ }
590
+ });
591
+ grid.root.addEventListener('click', (ev) => {
592
+ const cell = grid.hitTest(ev.offsetX, ev.offsetY);
593
+ if (!cell || !cell.isTableCell)
594
+ return;
595
+
596
+ const cluster = clustersCol.get(cell.tableRowIndex!)!;
597
+ summaryTable.currentRowIdx = -1;
598
+ if (ev.shiftKey)
599
+ this.modifyClusterSelection(cluster);
600
+ else
601
+ this.initClusterSelection(cluster);
602
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
603
+ });
604
+ grid.onCellRender.subscribe((gridCellArgs) => {
605
+ const gc = gridCellArgs.cell;
606
+ if (gc.tableColumn?.name !== C.COLUMNS_NAMES.CLUSTERS || gc.isColHeader)
607
+ return;
608
+ const canvasContext = gridCellArgs.g;
609
+ const bound = gridCellArgs.bounds;
610
+ canvasContext.save();
611
+ canvasContext.beginPath();
612
+ canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
613
+ canvasContext.clip();
614
+ CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.logoSummarySelection, bound);
615
+ gridCellArgs.preventDefault();
616
+ canvasContext.restore();
617
+ });
618
+ grid.onCellTooltip((cell, x, y) => {
619
+ if (!cell.isColHeader && cell.tableColumn?.name === C.COLUMNS_NAMES.CLUSTERS)
620
+ this.showTooltipCluster(cell.cell.value, x, y);
621
+ return true;
539
622
  });
540
623
  const webLogoGridCol = grid.columns.byName('WebLogo')!;
541
624
  webLogoGridCol.cellType = 'html';
@@ -544,6 +627,21 @@ export class PeptidesModel {
544
627
  return grid;
545
628
  }
546
629
 
630
+ modifyClusterSelection(cluster: number): void {
631
+ const tempSelection = this.logoSummarySelection;
632
+ const idx = tempSelection.indexOf(cluster);
633
+ if (idx !== -1)
634
+ tempSelection.splice(idx, 1);
635
+ else
636
+ tempSelection.push(cluster);
637
+
638
+ this.logoSummarySelection = tempSelection;
639
+ }
640
+
641
+ initClusterSelection(cluster: number): void {
642
+ this.logoSummarySelection = [cluster];
643
+ }
644
+
547
645
  setBarChartInteraction(): void {
548
646
  const eventAction = (ev: MouseEvent): void => {
549
647
  const cell = this.sourceGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -578,8 +676,9 @@ export class PeptidesModel {
578
676
  const monomer = barPart.monomer;
579
677
  const position = barPart.position;
580
678
  if (ev.type === 'click') {
581
- ev.shiftKey ? this.modifyCurrentSelection(monomer, position, true) :
582
- this.initCurrentSelection(monomer, position, true);
679
+ ev.shiftKey ? this.modifyMonomerPositionSelection(monomer, position, true) :
680
+ this.initMonomerPositionSelection(monomer, position, true);
681
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
583
682
  } else {
584
683
  const bar = `${monomer}:${position}`;
585
684
  if (this.cachedBarchartTooltip.bar == bar)
@@ -590,7 +689,7 @@ export class PeptidesModel {
590
689
  }
591
690
 
592
691
  setCellRenderers(renderColNames: string[]): void {
593
- const mdCol = this.statsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
692
+ const mdCol = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
594
693
  //decompose into two different renering funcs
595
694
  const renderCell = (args: DG.GridCellRenderArgs): void => {
596
695
  const canvasContext = args.g;
@@ -622,14 +721,16 @@ export class PeptidesModel {
622
721
 
623
722
  const viewer = this.getViewer();
624
723
  if (this.isInvariantMap) {
625
- const value: number = this.statsDf
724
+ const value: number = this.monomerPositionStatsDf
626
725
  .groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.COUNT])
627
726
  .where(`${C.COLUMNS_NAMES.POSITION} = ${currentPosition} and ${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`)
628
727
  .aggregate().get(C.COLUMNS_NAMES.COUNT, 0);
629
- renderInvaraintMapCell(canvasContext, currentAAR, currentPosition, this.invariantMapSelection, value, bound);
728
+ CR.renderInvaraintMapCell(
729
+ canvasContext, currentAAR, currentPosition, this.invariantMapSelection, value, bound);
630
730
  } else {
631
- renderMutationCliffCell(canvasContext, currentAAR, currentPosition, this.statsDf,
632
- viewer.bidirectionalAnalysis, mdCol, bound, cellValue, this.mutationCliffsSelection, this.substitutionsInfo);
731
+ CR.renderMutationCliffCell(
732
+ canvasContext, currentAAR, currentPosition, this.monomerPositionStatsDf, viewer.bidirectionalAnalysis,
733
+ mdCol, bound, cellValue, this.mutationCliffsSelection, this.substitutionsInfo);
633
734
  }
634
735
  }
635
736
  args.preventDefault();
@@ -651,7 +752,7 @@ export class PeptidesModel {
651
752
  context.clip();
652
753
 
653
754
  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);
755
+ const barBounds = CR.renderBarchart(context, col, this.barData[col.name], bounds, this.df.filter.trueCount);
655
756
  this.barsBounds[col.name] = barBounds;
656
757
  gcArgs.preventDefault();
657
758
  }
@@ -708,8 +809,9 @@ export class PeptidesModel {
708
809
  }
709
810
 
710
811
  showTooltipAt(aar: string, position: string, x: number, y: number): HTMLDivElement | null {
711
- const currentStatsDf = this.statsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
812
+ const currentStatsDf = this.monomerPositionStatsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
712
813
  const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
814
+ //TODO: use bitset instead of splitCol
713
815
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
714
816
  const currentPosCol = this.df.getCol(position);
715
817
  splitCol.init((i) => currentPosCol.get(i) == aar);
@@ -730,15 +832,40 @@ export class PeptidesModel {
730
832
  return tooltip;
731
833
  }
732
834
 
835
+ showTooltipCluster(cluster: number, x: number, y: number): HTMLDivElement | null {
836
+ const currentStatsDf = this.clusterStatsDf.rows.match({clusters: cluster}).toDataFrame();
837
+ const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
838
+ //TODO: use bitset instead of splitCol
839
+ const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
840
+ const currentClusterCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
841
+ splitCol.init((i) => currentClusterCol.get(i) == cluster);
842
+ const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
843
+ const stats: Stats = {
844
+ count: currentStatsDf.get(C.COLUMNS_NAMES.COUNT, 0),
845
+ ratio: currentStatsDf.get(C.COLUMNS_NAMES.RATIO, 0),
846
+ pValue: currentStatsDf.get(C.COLUMNS_NAMES.P_VALUE, 0),
847
+ meanDifference: currentStatsDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, 0),
848
+ };
849
+ if (!stats.count)
850
+ return null;
851
+
852
+ const tooltip = getDistributionAndStats(distributionTable, stats, `Cluster: ${cluster}`, 'Other', true);
853
+
854
+ ui.tooltip.show(tooltip, x, y);
855
+
856
+ return tooltip;
857
+ }
858
+
733
859
  setInteractionCallback(): void {
734
860
  const mutationCliffsDf = this.mutationCliffsGrid.dataFrame;
735
861
  const mostPotentResiduesDf = this.mostPotentResiduesGrid.dataFrame;
736
- // const invariantMapDf = this.invariantMapGrid.dataFrame;
737
862
 
738
863
  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);
864
+ (aar: string, position: string, isShiftPressed: boolean, isInvariantMapSelection: boolean = true): void => {
865
+ isShiftPressed ? this.modifyMonomerPositionSelection(aar, position, isInvariantMapSelection) :
866
+ this.initMonomerPositionSelection(aar, position, isInvariantMapSelection);
867
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
868
+ };
742
869
 
743
870
  this.mutationCliffsGrid.root.addEventListener('click', (ev) => {
744
871
  const gridCell = this.mutationCliffsGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -758,7 +885,7 @@ export class PeptidesModel {
758
885
  const tableRowIdx = gridCell!.tableRowIndex!;
759
886
  const position = mostPotentResiduesDf.get(C.COLUMNS_NAMES.POSITION, tableRowIdx);
760
887
  const aar = mostPotentResiduesDf.get(C.COLUMNS_NAMES.MONOMER, tableRowIdx);
761
- chooseAction(aar, position, ev.shiftKey);
888
+ chooseAction(aar, position, ev.shiftKey, false);
762
889
  });
763
890
 
764
891
  const cellChanged = (table: DG.DataFrame): void => {
@@ -772,17 +899,14 @@ export class PeptidesModel {
772
899
  this.mostPotentResiduesGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(mostPotentResiduesDf));
773
900
  }
774
901
 
775
- modifyCurrentSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
902
+ modifyMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
776
903
  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
- }
904
+ const tempSelectionAt = tempSelection[position];
905
+ const aarIndex = tempSelectionAt.indexOf(aar);
906
+ if (aarIndex === -1)
907
+ tempSelectionAt.push(aar);
908
+ else
909
+ tempSelectionAt.splice(aarIndex, 1);
786
910
 
787
911
  if (isInvariantMapSelection)
788
912
  this.invariantMapSelection = tempSelection;
@@ -790,8 +914,8 @@ export class PeptidesModel {
790
914
  this.mutationCliffsSelection = tempSelection;
791
915
  }
792
916
 
793
- initCurrentSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
794
- const tempSelection: type.PositionToAARList = {};
917
+ initMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
918
+ const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
795
919
  tempSelection[position] = [aar];
796
920
 
797
921
  if (isInvariantMapSelection)
@@ -803,8 +927,8 @@ export class PeptidesModel {
803
927
  invalidateGrids(): void {
804
928
  this.mutationCliffsGrid.invalidate();
805
929
  this.mostPotentResiduesGrid.invalidate();
930
+ this.logoSummaryGrid?.invalidate();
806
931
  this.sourceGrid?.invalidate();
807
- //TODO: this.peptideSpaceGrid.invalidate();
808
932
  }
809
933
 
810
934
  setBitsetCallback(): void {
@@ -812,6 +936,7 @@ export class PeptidesModel {
812
936
  return;
813
937
  const selection = this.df.selection;
814
938
  const filter = this.df.filter;
939
+ const clusterCol = this.df.col(C.COLUMNS_NAMES.CLUSTERS);
815
940
 
816
941
  const changeSelectionBitset = (currentBitset: DG.BitSet): void => {
817
942
  const edfSelection = this.edf?.selection;
@@ -830,11 +955,6 @@ export class PeptidesModel {
830
955
  };
831
956
 
832
957
  const positionList = Object.keys(this.mutationCliffsSelection);
833
- if (positionList.length == 0) {
834
- currentBitset.init(() => false, false);
835
- updateEdfSelection();
836
- return;
837
- }
838
958
 
839
959
  //TODO: move out
840
960
  const getBitAt = (i: number): boolean => {
@@ -843,6 +963,8 @@ export class PeptidesModel {
843
963
  if (this._mutationCliffsSelection[position].includes(positionCol.get(i)!))
844
964
  return true;
845
965
  }
966
+ if (this._logoSummarySelection.includes(clusterCol?.get(i)!))
967
+ return true;
846
968
  return false;
847
969
  };
848
970
  currentBitset.init(getBitAt, false);
@@ -885,10 +1007,9 @@ export class PeptidesModel {
885
1007
  for (let i = 0; i < colNum; ++i) {
886
1008
  const col = girdCols.byIndex(i)!;
887
1009
  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;
1010
+ col.width =
1011
+ grid == this.mostPotentResiduesGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER ? 50 :
1012
+ gridProps.rowHeight + 10;
892
1013
  }
893
1014
  }
894
1015
 
@@ -951,7 +1072,9 @@ export class PeptidesModel {
951
1072
  return;
952
1073
 
953
1074
  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];
1075
+ const scaledGridCol = this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!;
1076
+ scaledGridCol.name = this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED];
1077
+ scaledGridCol.format = '#.000';
955
1078
  this.sourceGrid.columns.setOrder([this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED]]);
956
1079
  this.sourceGrid.props.allowColSelection = false;
957
1080
 
@@ -966,10 +1089,9 @@ export class PeptidesModel {
966
1089
  };
967
1090
 
968
1091
  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;
1092
+ const currentCol = this.sourceGrid.columns.byIndex(i);
1093
+ if (currentCol?.column?.getTag(C.TAGS.VISIBLE) === '0')
1094
+ currentCol.visible = false;
973
1095
  }
974
1096
 
975
1097
  const options = {scaling: this.df.tags['scaling']};
@@ -986,14 +1108,6 @@ export class PeptidesModel {
986
1108
  dockManager.dock(logoSummary, DG.DOCK_TYPE.RIGHT, null, 'Logo Summary Table');
987
1109
  }
988
1110
 
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
1111
  this.updateDefault();
998
1112
 
999
1113
  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