@datagrok/peptides 1.3.8 → 1.4.0

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/src/model.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
2
  import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
- import * as bio from '@datagrok-libraries/bio';
5
4
 
6
5
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
7
6
 
@@ -10,15 +9,17 @@ import * as rxjs from 'rxjs';
10
9
 
11
10
  import * as C from './utils/constants';
12
11
  import * as type from './utils/types';
13
- import {calculateBarsData, getTypedArrayConstructor, isGridCellInvalid, scaleActivity} from './utils/misc';
14
- import {MutationCliffsViewer, SARViewerBase, MostPotentResiduesViewer} from './viewers/sar-viewer';
12
+ import {calculateSelected, isGridCellInvalid, scaleActivity} from './utils/misc';
13
+ import {MutationCliffsViewer, MostPotentResiduesViewer} from './viewers/sar-viewer';
15
14
  import * as CR from './utils/cell-renderer';
16
15
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
17
16
  import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
18
17
  import {getStats, Stats} from './utils/statistics';
19
18
  import {LogoSummary} from './viewers/logo-summary';
20
19
  import {getSettingsDialog} from './widgets/settings';
21
- import {getMoomerWorks} from './package';
20
+ import {getMonomerWorks} from './package';
21
+ import * as bio from '@datagrok-libraries/bio';
22
+ import {findMutations} from './utils/algorithms';
22
23
 
23
24
  export class PeptidesModel {
24
25
  static modelName = 'peptidesModel';
@@ -26,6 +27,7 @@ export class PeptidesModel {
26
27
  mutationCliffsGridSubject = new rxjs.Subject<DG.Grid>();
27
28
  mostPotentResiduesGridSubject = new rxjs.Subject<DG.Grid>();
28
29
  logoSummaryGridSubject = new rxjs.Subject<DG.Grid>();
30
+ settingsSubject = new rxjs.Subject();
29
31
 
30
32
  _isUpdating: boolean = false;
31
33
  isBitsetChangedInitialized = false;
@@ -33,7 +35,7 @@ export class PeptidesModel {
33
35
 
34
36
  mutationCliffsGrid!: DG.Grid;
35
37
  mostPotentResiduesGrid!: DG.Grid;
36
- logoSummaryGrid!: DG.Grid;
38
+ // logoSummaryGrid!: DG.Grid;
37
39
  sourceGrid!: DG.Grid;
38
40
  df: DG.DataFrame;
39
41
  splitCol!: DG.Column<boolean>;
@@ -51,15 +53,23 @@ export class PeptidesModel {
51
53
  isChangingEdfBitset = false;
52
54
 
53
55
  monomerMap: { [key: string]: { molfile: string, fullName: string } } = {};
54
- barData: type.MonomerDfStats = {};
55
- barsBounds: { [position: string]: type.BarCoordinates } = {};
56
- cachedBarchartTooltip: { bar: string, tooltip: null | HTMLDivElement } = {bar: '', tooltip: null};
56
+ monomerLib: bio.IMonomerLib | null = null; // To get monomers from lib(s)
57
+ monomerWorks: bio.MonomerWorks | null = null; // To get processed monomers
57
58
 
58
59
  _settings!: type.PeptidesSettings;
59
60
  isRibbonSet = false;
60
61
 
62
+ cp: bio.SeqPalette;
63
+ initBitset: DG.BitSet;
64
+ isInvariantMapTrigger: boolean = false;
65
+ headerSelectedMonomers: type.MonomerSelectionStats = {};
66
+ webLogoBounds: {[positon: string]: {[monomer: string]: DG.Rect}} = {};
67
+ cachedWebLogoTooltip: {bar: string; tooltip: HTMLDivElement | null;} = {bar: '', tooltip: null};
68
+
61
69
  private constructor(dataFrame: DG.DataFrame) {
62
70
  this.df = dataFrame;
71
+ this.initBitset = this.df.filter.clone();
72
+ this.cp = bio.pickUpPalette(this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE));
63
73
  }
64
74
 
65
75
  static async getInstance(dataFrame: DG.DataFrame): Promise<PeptidesModel> {
@@ -80,6 +90,10 @@ export class PeptidesModel {
80
90
  return this.logoSummaryGridSubject.asObservable();
81
91
  }
82
92
 
93
+ get onSettingsChanged(): rxjs.Observable<unknown> {
94
+ return this.settingsSubject.asObservable();
95
+ }
96
+
83
97
  get mutationCliffsSelection(): type.PositionToAARList {
84
98
  this._mutationCliffsSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
85
99
  return this._mutationCliffsSelection;
@@ -100,7 +114,9 @@ export class PeptidesModel {
100
114
  set invariantMapSelection(selection: type.PositionToAARList) {
101
115
  this._invariantMapSelection = selection;
102
116
  this.df.tags[C.TAGS.FILTER] = JSON.stringify(selection);
117
+ this.isInvariantMapTrigger = true;
103
118
  this.df.filter.fireChanged();
119
+ this.isInvariantMapTrigger = false;
104
120
  this.invalidateGrids();
105
121
  }
106
122
 
@@ -166,6 +182,7 @@ export class PeptidesModel {
166
182
  this.df.setTag('settings', JSON.stringify(this._settings));
167
183
  //TODO: update only needed components
168
184
  this.updateDefault();
185
+ this.settingsSubject.next();
169
186
  }
170
187
 
171
188
  createAccordion(): DG.Accordion {
@@ -180,14 +197,14 @@ export class PeptidesModel {
180
197
 
181
198
  updateDefault(): void {
182
199
  if ((this.sourceGrid && !this._isUpdating) || !this.isInitialized) {
183
- this.isInitialized = true;
200
+ // this.isInitialized = true;
184
201
  this._isUpdating = true;
185
202
  this.initializeViewersComponents();
186
203
  //FIXME: modify during the initializeViewersComponents stages
187
204
  this.mutationCliffsGridSubject.next(this.mutationCliffsGrid);
188
205
  this.mostPotentResiduesGridSubject.next(this.mostPotentResiduesGrid);
189
- if (this.df.getTag(C.TAGS.CLUSTERS))
190
- this.logoSummaryGridSubject.next(this.logoSummaryGrid);
206
+ // if (this.df.getTag(C.TAGS.CLUSTERS))
207
+ // this.logoSummaryGridSubject.next(this.logoSummaryGrid);
191
208
 
192
209
  this.fireBitsetChanged();
193
210
  this.invalidateGrids();
@@ -203,8 +220,8 @@ export class PeptidesModel {
203
220
  const col = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
204
221
  const alphabet = col.tags['alphabet'];
205
222
  const splitSeqDf = splitAlignedSequences(col);
206
-
207
- this.barData = calculateBarsData(splitSeqDf.columns.toList(), this.df.selection);
223
+ for (const col of splitSeqDf.columns)
224
+ col.name = `p${col.name}`;
208
225
 
209
226
  const positionColumns = splitSeqDf.columns.names();
210
227
 
@@ -227,6 +244,8 @@ export class PeptidesModel {
227
244
 
228
245
  // SAR matrix table
229
246
  //pivot a table to make it matrix-like
247
+ const monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
248
+ matrixDf = matrixDf.clone(DG.BitSet.create(matrixDf.rowCount, (i) => monomerCol.get(i) ? true : false));
230
249
  matrixDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
231
250
  .pivot(C.COLUMNS_NAMES.POSITION)
232
251
  .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
@@ -234,123 +253,41 @@ export class PeptidesModel {
234
253
  matrixDf.name = 'SAR';
235
254
 
236
255
  // Setting category order
237
- this.setCategoryOrder(matrixDf);
256
+ // this.setCategoryOrder(matrixDf);
238
257
 
239
258
  // SAR vertical table (naive, choose best Mean difference from pVals <= 0.01)
240
259
  const sequenceDf = this.createVerticalTable();
241
260
 
242
- this.calcSubstitutions();
261
+ const scaledActivityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
262
+ const monomerColumns = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER);
263
+ this.substitutionsInfo = findMutations(scaledActivityCol, monomerColumns, this.settings);
243
264
 
244
265
  [this.mutationCliffsGrid, this.mostPotentResiduesGrid] =
245
266
  this.createGrids(matrixDf, sequenceDf, positionColumns, alphabet);
246
267
 
247
268
  if (this.df.getTag(C.TAGS.CLUSTERS)) {
248
269
  this.clusterStatsDf = this.calculateClusterStatistics();
249
- this.logoSummaryGrid = this.createLogoSummaryGrid();
270
+ // this.logoSummaryGrid = this.createLogoSummaryGrid();
250
271
  }
251
272
 
252
273
  // init invariant map & mutation cliffs selections
253
274
  this.initSelections(positionColumns);
254
275
 
255
- positionColumns.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
276
+ // positionColumns.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
256
277
 
257
- this.setBarChartInteraction();
278
+ this.setWebLogoInteraction();
279
+ this.webLogoBounds = {};
258
280
 
259
- this.setCellRenderers(positionColumns);
281
+ this.setCellRenderers([...positionColumns, C.COLUMNS_NAMES.MEAN_DIFFERENCE]);
260
282
 
261
283
  // show all the statistics in a tooltip over cell
262
- this.setTooltips(positionColumns);
284
+ this.setTooltips([...positionColumns, C.COLUMNS_NAMES.MEAN_DIFFERENCE]);
263
285
 
264
286
  this.setInteractionCallback();
265
287
 
266
288
  this.setBitsetCallback();
267
289
 
268
- this.postProcessGrids();
269
- }
270
-
271
- //TODO: move out
272
- calcSubstitutions(): void {
273
- const activityValues: DG.Column<number> = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
274
- const columnList: DG.Column<string>[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER);
275
- const nCols = columnList.length;
276
- if (nCols == 0)
277
- throw new Error(`Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
278
-
279
- this.substitutionsInfo = new Map();
280
- const nRows = this.df.rowCount;
281
- for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
282
- for (let seq2Idx = seq1Idx + 1; seq2Idx < nRows; seq2Idx++) {
283
- let substCounter = 0;
284
- const activityValSeq1 = activityValues.get(seq1Idx)!;
285
- const activityValSeq2 = activityValues.get(seq2Idx)!;
286
- const delta = activityValSeq1 - activityValSeq2;
287
- if (Math.abs(delta) < (this.settings.minActivityDelta ?? 0))
288
- continue;
289
-
290
- let substCounterFlag = false;
291
- const tempData: { pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number }[] =
292
- [];
293
- for (const currentPosCol of columnList) {
294
- const seq1monomer = currentPosCol.get(seq1Idx)!;
295
- const seq2monomer = currentPosCol.get(seq2Idx)!;
296
- if (seq1monomer == seq2monomer)
297
- continue;
298
-
299
- substCounter++;
300
- substCounterFlag = substCounter > (this.settings.maxMutations ?? 1);
301
- if (substCounterFlag)
302
- break;
303
-
304
- tempData.push({
305
- pos: currentPosCol.name,
306
- seq1monomer: seq1monomer,
307
- seq2monomer: seq2monomer,
308
- seq1Idx: seq1Idx,
309
- seq2Idx: seq2Idx,
310
- });
311
- }
312
-
313
- if (substCounterFlag || substCounter == 0)
314
- continue;
315
-
316
- for (const tempDataElement of tempData) {
317
- const position = tempDataElement.pos;
318
-
319
- //Working with seq1monomer
320
- const seq1monomer = tempDataElement.seq1monomer;
321
- if (!this.substitutionsInfo.has(seq1monomer))
322
- this.substitutionsInfo.set(seq1monomer, new Map());
323
-
324
- let positionsMap = this.substitutionsInfo.get(seq1monomer)!;
325
- if (!positionsMap.has(position))
326
- positionsMap.set(position, new Map());
327
-
328
- let indexes = positionsMap.get(position)!;
329
-
330
- !indexes.has(seq1Idx) ? indexes.set(seq1Idx, [seq2Idx]) : (indexes.get(seq1Idx)! as number[]).push(seq2Idx);
331
-
332
- //Working with seq2monomer
333
- const seq2monomer = tempDataElement.seq2monomer;
334
- if (!this.substitutionsInfo.has(seq2monomer))
335
- this.substitutionsInfo.set(seq2monomer, new Map());
336
-
337
- positionsMap = this.substitutionsInfo.get(seq2monomer)!;
338
- if (!positionsMap.has(position))
339
- positionsMap.set(position, new Map());
340
-
341
- indexes = positionsMap.get(position)!;
342
- !indexes.has(seq2Idx) ? indexes.set(seq2Idx, [seq1Idx]) : (indexes.get(seq2Idx)! as number[]).push(seq1Idx);
343
- }
344
- }
345
- }
346
-
347
- const TypedArray = getTypedArrayConstructor(nRows);
348
- for (const positionMap of this.substitutionsInfo.values()) {
349
- for (const indexMap of positionMap.values()) {
350
- for (const [index, indexArray] of indexMap.entries())
351
- indexMap.set(index, new TypedArray(indexArray));
352
- }
353
- }
290
+ this.postProcessGrids(positionColumns);
354
291
  }
355
292
 
356
293
  initSelections(positionColumns: string[]): void {
@@ -362,7 +299,6 @@ export class PeptidesModel {
362
299
  }
363
300
  this.invariantMapSelection = tempInvariantMapSelection;
364
301
  this.mutationCliffsSelection = mutationCliffsSelection;
365
- this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
366
302
  }
367
303
 
368
304
  joinDataFrames(positionColumns: string[], splitSeqDf: DG.DataFrame, alphabet: string): void {
@@ -417,21 +353,23 @@ export class PeptidesModel {
417
353
  calculateMonomerPositionStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
418
354
  matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER]).aggregate();
419
355
 
356
+ let monomerCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
357
+ // monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
358
+
420
359
  //calculate p-values based on t-test
421
360
  const matrixCols = matrixDf.columns;
422
361
  const mdCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
423
362
  const pValCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
424
363
  const countCol = matrixCols.addNewInt(C.COLUMNS_NAMES.COUNT);
425
364
  const ratioCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
426
- const aarCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
427
- const posCol = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
428
- const activityCol: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
365
+ const posCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
366
+ const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
429
367
  const sourceDfLen = activityCol.length;
430
368
 
431
369
  for (let i = 0; i < matrixDf.rowCount; i++) {
432
- const position: string = posCol.get(i);
433
- const aar: string = aarCol.get(i);
434
- const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) === aar);
370
+ const position = posCol.get(i)!;
371
+ const monomer = monomerCol.get(i)!;
372
+ const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) === monomer);
435
373
  const stats = getStats(activityCol, mask);
436
374
 
437
375
  mdCol.set(i, stats.meanDifference);
@@ -440,8 +378,6 @@ export class PeptidesModel {
440
378
  ratioCol.set(i, stats.ratio);
441
379
  }
442
380
 
443
- const monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
444
- matrixDf = matrixDf.clone(DG.BitSet.create(matrixDf.rowCount, (i) => monomerCol.get(i) ? true : false));
445
381
  return matrixDf as DG.DataFrame;
446
382
  }
447
383
 
@@ -469,27 +405,27 @@ export class PeptidesModel {
469
405
  return statsDf;
470
406
  }
471
407
 
472
- setCategoryOrder(matrixDf: DG.DataFrame): void {
473
- let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
474
- if (this.settings.isBidirectional) {
475
- const mdCol = this.monomerPositionStatsDf.getCol(sortArgument);
476
- sortArgument = 'Absolute Mean difference';
477
- const absMDCol = this.monomerPositionStatsDf.columns.addNewFloat(sortArgument);
478
- absMDCol.init((i) => Math.abs(mdCol.get(i)));
479
- }
480
-
481
- const aarWeightsDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
482
- .aggregate();
483
- const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
484
- const getWeight = (aar: string): number => aarWeightsDf
485
- .groupBy(['weight'])
486
- .where(`${C.COLUMNS_NAMES.MONOMER} = ${aar}`)
487
- .aggregate()
488
- .get('weight', 0) as number;
489
- aarList.sort((first, second) => getWeight(second) - getWeight(first));
490
-
491
- matrixDf.getCol(C.COLUMNS_NAMES.MONOMER).setCategoryOrder(aarList);
492
- }
408
+ // setCategoryOrder(matrixDf: DG.DataFrame): void {
409
+ // let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
410
+ // if (this.settings.isBidirectional) {
411
+ // const mdCol = this.monomerPositionStatsDf.getCol(sortArgument);
412
+ // sortArgument = 'Absolute Mean difference';
413
+ // const absMDCol = this.monomerPositionStatsDf.columns.addNewFloat(sortArgument);
414
+ // absMDCol.init((i) => Math.abs(mdCol.get(i)));
415
+ // }
416
+
417
+ // const aarWeightsDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
418
+ // .aggregate();
419
+ // const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
420
+ // const getWeight = (aar: string): number => aarWeightsDf
421
+ // .groupBy(['weight'])
422
+ // .where(`${C.COLUMNS_NAMES.MONOMER} = ${aar}`)
423
+ // .aggregate()
424
+ // .get('weight', 0) as number;
425
+ // aarList.sort((first, second) => getWeight(second) - getWeight(first));
426
+
427
+ // matrixDf.getCol(C.COLUMNS_NAMES.MONOMER).setCategoryOrder(aarList);
428
+ // }
493
429
 
494
430
  createVerticalTable(): DG.DataFrame {
495
431
  // TODO: aquire ALL of the positions
@@ -537,86 +473,6 @@ export class PeptidesModel {
537
473
  return [mutationCliffsGrid, mostPotentResiduesGrid];
538
474
  }
539
475
 
540
- createLogoSummaryGrid(): DG.Grid {
541
- const summaryTable = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
542
- const summaryTableLength = summaryTable.rowCount;
543
- const clustersCol: DG.Column<number> = summaryTable.getCol(C.COLUMNS_NAMES.CLUSTERS);
544
- const membersCol: DG.Column<number> = summaryTable.columns.addNewInt('Members');
545
- const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
546
- const tempDfList: DG.DataFrame[] = new Array(summaryTableLength);
547
- const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
548
- const peptideCol: DG.Column<string> = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
549
-
550
- for (let index = 0; index < summaryTableLength; ++index) {
551
- const indexes: number[] = [];
552
- for (let j = 0; j < originalClustersCol.length; ++j) {
553
- if (originalClustersCol.get(j) === clustersCol.get(index))
554
- indexes.push(j);
555
- }
556
- const tCol = DG.Column.string('peptides', indexes.length);
557
- tCol.init((i) => peptideCol.get(indexes[i]));
558
-
559
- for (const tag of peptideCol.tags)
560
- tCol.setTag(tag[0], tag[1]);
561
-
562
- const dfSlice = DG.DataFrame.fromColumns([tCol]);
563
- tempDfList[index] = dfSlice;
564
- webLogoCol.set(index, index.toString());
565
- membersCol.set(index, dfSlice.rowCount);
566
- }
567
- webLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
568
-
569
- const grid = summaryTable.plot.grid();
570
- const gridClustersCol = grid.col(C.COLUMNS_NAMES.CLUSTERS)!;
571
- gridClustersCol.name = 'Clusters';
572
- gridClustersCol.visible = true;
573
- grid.columns.rowHeader!.visible = false;
574
- grid.props.rowHeight = 55;
575
- grid.onCellPrepare((cell) => {
576
- if (cell.isTableCell && cell.tableColumn?.name === 'WebLogo') {
577
- tempDfList[parseInt(cell.cell.value)].plot.fromType('WebLogo', {maxHeight: 50})
578
- .then((viewer) => cell.element = viewer.root);
579
- }
580
- });
581
- grid.root.addEventListener('click', (ev) => {
582
- const cell = grid.hitTest(ev.offsetX, ev.offsetY);
583
- if (!cell || !cell.isTableCell)
584
- return;
585
-
586
- const cluster = clustersCol.get(cell.tableRowIndex!)!;
587
- summaryTable.currentRowIdx = -1;
588
- if (ev.shiftKey)
589
- this.modifyClusterSelection(cluster);
590
- else
591
- this.initClusterSelection(cluster);
592
- this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
593
- });
594
- grid.onCellRender.subscribe((gridCellArgs) => {
595
- const gc = gridCellArgs.cell;
596
- if (gc.tableColumn?.name !== C.COLUMNS_NAMES.CLUSTERS || gc.isColHeader)
597
- return;
598
- const canvasContext = gridCellArgs.g;
599
- const bound = gridCellArgs.bounds;
600
- canvasContext.save();
601
- canvasContext.beginPath();
602
- canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
603
- canvasContext.clip();
604
- CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.logoSummarySelection, bound);
605
- gridCellArgs.preventDefault();
606
- canvasContext.restore();
607
- });
608
- grid.onCellTooltip((cell, x, y) => {
609
- if (!cell.isColHeader && cell.tableColumn?.name === C.COLUMNS_NAMES.CLUSTERS)
610
- this.showTooltipCluster(cell.cell.value, x, y);
611
- return true;
612
- });
613
- const webLogoGridCol = grid.columns.byName('WebLogo')!;
614
- webLogoGridCol.cellType = 'html';
615
- webLogoGridCol.width = 350;
616
-
617
- return grid;
618
- }
619
-
620
476
  modifyClusterSelection(cluster: number): void {
621
477
  const tempSelection = this.logoSummarySelection;
622
478
  const idx = tempSelection.indexOf(cluster);
@@ -632,7 +488,7 @@ export class PeptidesModel {
632
488
  this.logoSummarySelection = [cluster];
633
489
  }
634
490
 
635
- setBarChartInteraction(): void {
491
+ setWebLogoInteraction(): void {
636
492
  const eventAction = (ev: MouseEvent): void => {
637
493
  const cell = this.sourceGrid.hitTest(ev.offsetX, ev.offsetY);
638
494
  if (cell?.isColHeader && cell.tableColumn?.semType == C.SEM_TYPES.MONOMER) {
@@ -649,7 +505,7 @@ export class PeptidesModel {
649
505
  }
650
506
 
651
507
  findAARandPosition(cell: DG.GridCell, ev: MouseEvent): { monomer: string, position: string } | null {
652
- const barCoords = this.barsBounds[cell.tableColumn!.name];
508
+ const barCoords = this.webLogoBounds[cell.tableColumn!.name];
653
509
  for (const [monomer, coords] of Object.entries(barCoords)) {
654
510
  const isIntersectingX = ev.offsetX >= coords.x && ev.offsetX <= coords.x + coords.width;
655
511
  const isIntersectingY = ev.offsetY >= coords.y && ev.offsetY <= coords.y + coords.height;
@@ -666,15 +522,17 @@ export class PeptidesModel {
666
522
  const monomer = barPart.monomer;
667
523
  const position = barPart.position;
668
524
  if (ev.type === 'click') {
669
- ev.shiftKey ? this.modifyMonomerPositionSelection(monomer, position, true) :
670
- this.initMonomerPositionSelection(monomer, position, true);
671
- this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
525
+ ev.shiftKey ? this.modifyMonomerPositionSelection(monomer, position, false) :
526
+ this.initMonomerPositionSelection(monomer, position, false);
672
527
  } else {
673
- const bar = `${monomer}:${position}`;
674
- if (this.cachedBarchartTooltip.bar == bar)
675
- ui.tooltip.show(this.cachedBarchartTooltip.tooltip!, ev.clientX, ev.clientY);
528
+ const bar = `${position} = ${monomer}`;
529
+ if (this.cachedWebLogoTooltip.bar == bar)
530
+ ui.tooltip.show(this.cachedWebLogoTooltip.tooltip!, ev.clientX, ev.clientY);
676
531
  else
677
- this.cachedBarchartTooltip = {bar: bar, tooltip: this.showTooltipAt(monomer, position, ev.clientX, ev.clientY)};
532
+ this.cachedWebLogoTooltip = {bar: bar, tooltip: this.showTooltipAt(monomer, position, ev.clientX, ev.clientY)};
533
+
534
+ //TODO: how to unghighlight?
535
+ // this.df.rows.match(bar).highlight();
678
536
  }
679
537
  }
680
538
 
@@ -731,22 +589,40 @@ export class PeptidesModel {
731
589
 
732
590
  this.sourceGrid.setOptions({'colHeaderHeight': 130});
733
591
  this.sourceGrid.onCellRender.subscribe((gcArgs) => {
734
- const context = gcArgs.g;
592
+ const ctx = gcArgs.g;
735
593
  const bounds = gcArgs.bounds;
736
594
  const col = gcArgs.cell.tableColumn;
737
595
 
738
- context.save();
739
- context.beginPath();
740
- context.rect(bounds.x, bounds.y, bounds.width, bounds.height);
741
- context.clip();
596
+ ctx.save();
597
+ ctx.beginPath();
598
+ ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height);
599
+ ctx.clip();
742
600
 
743
601
  if (gcArgs.cell.isColHeader && col?.semType == C.SEM_TYPES.MONOMER) {
744
- const barBounds = CR.renderBarchart(context, col, this.barData[col.name], bounds, this.df.filter.trueCount);
745
- this.barsBounds[col.name] = barBounds;
602
+ const monomerStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MONOMER);
603
+ const positionStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.POSITION);
604
+ const rowMask = DG.BitSet.create(this.monomerPositionStatsDf.rowCount, (i) => positionStatsCol.get(i) === col.name);
605
+ //TODO: precalc on stats creation
606
+ const sortedStatsOrder = this.monomerPositionStatsDf.getSortedOrder([C.COLUMNS_NAMES.COUNT], [false], rowMask)
607
+ .sort((a, b) => {
608
+ if (monomerStatsCol.get(a) === '-' || monomerStatsCol.get(a) === '')
609
+ return -1;
610
+ else if (monomerStatsCol.get(b) === '-' || monomerStatsCol.get(b) === '')
611
+ return +1;
612
+ return 0;
613
+ });
614
+ const statsInfo: type.StatsInfo = {
615
+ countCol: this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.COUNT),
616
+ monomerCol: monomerStatsCol,
617
+ orderedIndexes: sortedStatsOrder,
618
+ };
619
+
620
+ this.webLogoBounds[col.name] =
621
+ CR.drawLogoInBounds(ctx, bounds, statsInfo, this.df.rowCount, this.cp, this.headerSelectedMonomers[col.name]);
746
622
  gcArgs.preventDefault();
747
623
  }
748
624
 
749
- context.restore();
625
+ ctx.restore();
750
626
  });
751
627
  }
752
628
 
@@ -787,7 +663,7 @@ export class PeptidesModel {
787
663
  const tooltipElements: HTMLDivElement[] = [];
788
664
  const monomerName = aar.toLowerCase();
789
665
 
790
- let mw = getMoomerWorks();
666
+ let mw = getMonomerWorks();
791
667
  let mol = mw?.getCappedRotatedMonomer('PEPTIDE', aar);
792
668
 
793
669
  if (mol) {
@@ -856,7 +732,6 @@ export class PeptidesModel {
856
732
  (aar: string, position: string, isShiftPressed: boolean, isInvariantMapSelection: boolean = true): void => {
857
733
  isShiftPressed ? this.modifyMonomerPositionSelection(aar, position, isInvariantMapSelection) :
858
734
  this.initMonomerPositionSelection(aar, position, isInvariantMapSelection);
859
- this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
860
735
  };
861
736
 
862
737
  this.mutationCliffsGrid.root.addEventListener('click', (ev) => {
@@ -921,7 +796,7 @@ export class PeptidesModel {
921
796
  invalidateGrids(): void {
922
797
  this.mutationCliffsGrid.invalidate();
923
798
  this.mostPotentResiduesGrid.invalidate();
924
- this.logoSummaryGrid?.invalidate();
799
+ // this.logoSummaryGrid?.invalidate();
925
800
  this.sourceGrid?.invalidate();
926
801
  }
927
802
 
@@ -967,18 +842,26 @@ export class PeptidesModel {
967
842
  };
968
843
 
969
844
  selection.onChanged.subscribe(() => changeSelectionBitset(selection));
845
+
970
846
  filter.onChanged.subscribe(() => {
971
847
  const positionList = Object.keys(this.invariantMapSelection);
972
- for (let index = 0; index < this.df.rowCount; ++index) {
848
+ const invariantMapBitset = DG.BitSet.create(filter.length, (index) => {
973
849
  let result = true;
974
850
  for (const position of positionList) {
975
851
  const aarList = this.invariantMapSelection[position];
976
852
  result &&= aarList.length === 0 || aarList.includes(this.df.get(position, index));
977
853
  if (!result)
978
- break;
854
+ return result;
979
855
  }
980
- filter.set(index, filter.get(index) && result, false);
981
- }
856
+ return result;
857
+ });
858
+
859
+ if (!this.isInvariantMapTrigger)
860
+ this.initBitset = filter.clone();
861
+
862
+ // filter.copyFrom(invariantMapBitset.and(this.initBitset), false);
863
+ const temp = invariantMapBitset.and(this.initBitset);
864
+ filter.init((i) => temp.get(i), false);
982
865
  });
983
866
  this.isBitsetChangedInitialized = true;
984
867
  }
@@ -987,11 +870,12 @@ export class PeptidesModel {
987
870
  this.isPeptideSpaceChangingBitset = isPeptideSpaceSource;
988
871
  this.df.selection.fireChanged();
989
872
  this.modifyOrCreateSplitCol();
873
+ this.headerSelectedMonomers = calculateSelected(this.df);
990
874
  grok.shell.o = this.createAccordion().root;
991
875
  this.isPeptideSpaceChangingBitset = false;
992
876
  }
993
877
 
994
- postProcessGrids(): void {
878
+ postProcessGrids(posCols: string[]): void {
995
879
  const mdCol: DG.GridColumn = this.mostPotentResiduesGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
996
880
  mdCol.name = 'Diff';
997
881
 
@@ -1019,8 +903,22 @@ export class PeptidesModel {
1019
903
 
1020
904
  setViewerGridProps(this.mutationCliffsGrid);
1021
905
  setViewerGridProps(this.mostPotentResiduesGrid);
1022
- if (this.df.getTag(C.TAGS.CLUSTERS))
1023
- setViewerGridProps(this.logoSummaryGrid);
906
+ // if (this.df.getTag(C.TAGS.CLUSTERS))
907
+ // setViewerGridProps(this.logoSummaryGrid);
908
+
909
+ for (let gcIndex = 0; gcIndex < this.sourceGrid.columns.length; ++gcIndex) {
910
+ const col = this.sourceGrid.columns.byIndex(gcIndex)!;
911
+ if (!col.column)
912
+ continue;
913
+
914
+ if (posCols.includes(col.name))
915
+ col.name = col.name.substring(1);
916
+
917
+ col.visible =
918
+ col.column?.semType === C.SEM_TYPES.MONOMER ||
919
+ col.column.name === C.COLUMNS_NAMES.ACTIVITY_SCALED ||
920
+ Object.keys(this.settings.columns ?? {}).includes(col.column.name ?? '');
921
+ }
1024
922
  }
1025
923
 
1026
924
  getSplitColValueAt(index: number, aar: string, position: string, aarLabel: string): string {
@@ -1040,19 +938,27 @@ export class PeptidesModel {
1040
938
  async init(): Promise<void> {
1041
939
  if (this.isInitialized)
1042
940
  return;
941
+ this.isInitialized = true;
942
+
943
+ // Don't find the dataset if the analysis started from button
944
+ if (this.df.getTag('newAnalysis') !== '1')
945
+ this.currentView = wu(grok.shell.tableViews).find(({dataFrame}) => dataFrame.tags[C.PEPTIDES_ANALYSIS] === '1')!;
946
+
947
+ this.currentView ??= grok.shell.addTableView(this.df);
1043
948
 
1044
- this.currentView = wu(grok.shell.tableViews).find(({dataFrame}) => dataFrame.tags[C.PEPTIDES_ANALYSIS] === 'true') ??
1045
- grok.shell.addTableView(this.df);
949
+ this.df.setTag('newAnalysis', '');
1046
950
  if (!this.isRibbonSet) {
1047
- this.currentView.setRibbonPanels([[ui.icons.settings(() => getSettingsDialog(this))]], false);
951
+ //TODO: don't pass model, pass parameters instead
952
+ const settingsButton = ui.bigButton('Settings', () => getSettingsDialog(this), 'Peptides analysis settings');
953
+ this.currentView.setRibbonPanels([[settingsButton]], false);
1048
954
  this.isRibbonSet = true;
1049
955
  }
1050
956
  grok.shell.v = this.currentView;
1051
957
  this.sourceGrid = this.currentView.grid;
1052
- if (this.df.tags[C.PEPTIDES_ANALYSIS] === 'true')
958
+ if (this.df.tags[C.PEPTIDES_ANALYSIS] === '1')
1053
959
  return;
1054
960
 
1055
- this.df.tags[C.PEPTIDES_ANALYSIS] = 'true';
961
+ this.df.tags[C.PEPTIDES_ANALYSIS] = '1';
1056
962
  const scaledGridCol = this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!;
1057
963
  scaledGridCol.name = scaledGridCol.column!.getTag('gridName');
1058
964
  scaledGridCol.format = '#.000';