@datagrok/peptides 1.12.0 → 1.13.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
@@ -3,9 +3,7 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
6
- import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
7
6
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
8
- import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
9
7
  import {pickUpPalette, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
8
  import {calculateScores, SCORE} from '@datagrok-libraries/bio/src/utils/macromolecule/scoring';
11
9
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
@@ -23,7 +21,7 @@ import $ from 'cash-dom';
23
21
  import * as C from './utils/constants';
24
22
  import * as type from './utils/types';
25
23
  import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary, prepareTableForHistogram} from './utils/misc';
26
- import {MONOMER_POSITION_PROPERTIES, MonomerPosition, MostPotentResidues} from './viewers/sar-viewer';
24
+ import {MONOMER_POSITION_PROPERTIES, MonomerPosition, MostPotentResidues, SELECTION_MODE} from './viewers/sar-viewer';
27
25
  import * as CR from './utils/cell-renderer';
28
26
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
29
27
  import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap} from './widgets/distribution';
@@ -41,8 +39,8 @@ export type SummaryStats = {
41
39
  minPValue: number, maxPValue: number,
42
40
  minRatio: number, maxRatio: number,
43
41
  };
44
- export type PositionStats = {[monomer: string]: Stats} & {general: SummaryStats};
45
- export type MonomerPositionStats = {[position: string]: PositionStats} & {general: SummaryStats};
42
+ export type PositionStats = {[monomer: string]: Stats | undefined} & {general: SummaryStats};
43
+ export type MonomerPositionStats = {[position: string]: PositionStats | undefined} & {general: SummaryStats};
46
44
  export type ClusterStats = {[cluster: string]: Stats};
47
45
  export enum CLUSTER_TYPE {
48
46
  ORIGINAL = 'original',
@@ -62,43 +60,34 @@ export const getAggregatedColName = (aggF: string, colName: string): string => `
62
60
  export class PeptidesModel {
63
61
  static modelName = 'peptidesModel';
64
62
 
65
- _isUpdating: boolean = false;
66
63
  isBitsetChangedInitialized = false;
67
- isCellChanging = false;
68
64
  isUserChangedSelection = true;
69
65
 
70
66
  df: DG.DataFrame;
71
- splitCol!: DG.Column<boolean>;
72
67
  _monomerPositionStats?: MonomerPositionStats;
73
68
  _clusterStats?: ClusterTypeStats;
74
- _mutationCliffsSelection!: type.PositionToAARList;
75
- _invariantMapSelection!: type.PositionToAARList;
76
- _clusterSelection!: string[];
69
+ _mutationCliffsSelection!: type.Selection;
70
+ _invariantMapSelection!: type.Selection;
71
+ _clusterSelection!: type.Selection;
77
72
  _mutationCliffs: type.MutationCliffs | null = null;
78
73
  isInitialized = false;
79
74
  _analysisView?: DG.TableView;
80
75
 
81
- monomerMap: {[key: string]: {molfile: string, fullName: string}} = {};
82
- monomerLib: IMonomerLib | null = null; // To get monomers from lib(s)
83
- monomerWorks: MonomerWorks | null = null; // To get processed monomers
84
-
85
76
  _settings!: type.PeptidesSettings;
86
77
  isRibbonSet = false;
87
78
 
88
79
  _cp?: SeqPalette;
89
- headerSelectedMonomers: type.MonomerSelectionStats = {};
80
+ headerSelectedMonomers: type.SelectionStats = {};
90
81
  webLogoBounds: {[positon: string]: {[monomer: string]: DG.Rect}} = {};
91
82
  cachedWebLogoTooltip: {bar: string, tooltip: HTMLDivElement | null} = {bar: '', tooltip: null};
92
- _monomerPositionDf?: DG.DataFrame;
93
83
  _alphabet?: string;
94
- _mostPotentResiduesDf?: DG.DataFrame;
95
- _matrixDf?: DG.DataFrame;
96
84
  _splitSeqDf?: DG.DataFrame;
97
- _distanceMatrix!: DistanceMatrix;
98
85
  _dm!: DistanceMatrix;
99
86
  _layoutEventInitialized = false;
100
87
 
101
88
  subs: rxjs.Subscription[] = [];
89
+ isHighlighting: boolean = false;
90
+ latestSelectionItem: (type.SelectionItem & {kind: SELECTION_MODE | 'Cluster'}) | null = null;
102
91
 
103
92
  private constructor(dataFrame: DG.DataFrame) {
104
93
  this.df = dataFrame;
@@ -118,15 +107,6 @@ export class PeptidesModel {
118
107
  return id;
119
108
  }
120
109
 
121
- get monomerPositionDf(): DG.DataFrame {
122
- this._monomerPositionDf ??= this.createMonomerPositionDf();
123
- return this._monomerPositionDf;
124
- }
125
-
126
- set monomerPositionDf(df: DG.DataFrame) {
127
- this._monomerPositionDf = df;
128
- }
129
-
130
110
  get monomerPositionStats(): MonomerPositionStats {
131
111
  this._monomerPositionStats ??= this.calculateMonomerPositionStatistics();
132
112
  return this._monomerPositionStats;
@@ -145,15 +125,6 @@ export class PeptidesModel {
145
125
  this._splitSeqDf = df;
146
126
  }
147
127
 
148
- get mostPotentResiduesDf(): DG.DataFrame {
149
- this._mostPotentResiduesDf ??= this.createMostPotentResiduesDf();
150
- return this._mostPotentResiduesDf;
151
- }
152
-
153
- set mostPotentResiduesDf(df: DG.DataFrame) {
154
- this._mostPotentResiduesDf = df;
155
- }
156
-
157
128
  get alphabet(): string {
158
129
  const col = this.df.getCol(this.settings.sequenceColumnName!);
159
130
  return col.getTag(bioTAGS.alphabet);
@@ -207,14 +178,19 @@ export class PeptidesModel {
207
178
  return this._analysisView;
208
179
  }
209
180
 
210
- get mutationCliffsSelection(): type.PositionToAARList {
211
- this._mutationCliffsSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
181
+ get mutationCliffsSelection(): type.Selection {
182
+ const tagSelection = this.df.getTag(C.TAGS.MUTATION_CLIFFS_SELECTION) ?? this.df.getTag(C.TAGS.SELECTION);
183
+ if (tagSelection === null)
184
+ this.initMutationCliffsSelection({notify: false});
185
+ this._mutationCliffsSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.MUTATION_CLIFFS_SELECTION) ?? this.df.getTag(C.TAGS.SELECTION)!);
212
186
  return this._mutationCliffsSelection;
213
187
  }
214
188
 
215
- set mutationCliffsSelection(selection: type.PositionToAARList) {
189
+ set mutationCliffsSelection(selection: type.Selection) {
216
190
  this._mutationCliffsSelection = selection;
217
- this.df.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
191
+ // TODO: Remove in 1.14.0
192
+ this.df.setTag(C.TAGS.SELECTION, JSON.stringify(selection));
193
+ this.df.setTag(C.TAGS.MUTATION_CLIFFS_SELECTION, JSON.stringify(selection));
218
194
  this.fireBitsetChanged();
219
195
 
220
196
  const mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
@@ -225,25 +201,45 @@ export class PeptidesModel {
225
201
  this.analysisView.grid.invalidate();
226
202
  }
227
203
 
228
- get invariantMapSelection(): type.PositionToAARList {
229
- this._invariantMapSelection ??=
230
- JSON.parse(this.df.tags[C.TAGS.INVARIANT_MAP_SELECTION] || this.df.tags[C.TAGS.FILTER] || '{}');
204
+ get invariantMapSelection(): type.Selection {
205
+ const tagSelection = this.df.getTag(C.TAGS.INVARIANT_MAP_SELECTION) ?? this.df.getTag(C.TAGS.FILTER);
206
+ if (tagSelection === null)
207
+ this.initInvariantMapSelection({notify: false});
208
+ this._invariantMapSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.INVARIANT_MAP_SELECTION) ?? this.df.getTag(C.TAGS.FILTER)!);
231
209
  return this._invariantMapSelection;
232
210
  }
233
211
 
234
- set invariantMapSelection(selection: type.PositionToAARList) {
212
+ set invariantMapSelection(selection: type.Selection) {
235
213
  this._invariantMapSelection = selection;
236
- this.df.tags[C.TAGS.INVARIANT_MAP_SELECTION] = JSON.stringify(selection);
214
+ this.df.setTag(C.TAGS.INVARIANT_MAP_SELECTION, JSON.stringify(selection));
215
+ // TODO: Remove in 1.14.0
216
+ this.df.setTag(C.TAGS.FILTER, JSON.stringify(selection));
237
217
  this.fireBitsetChanged();
238
218
  this.analysisView.grid.invalidate();
239
219
  }
240
220
 
241
- get clusterSelection(): string[] {
242
- this._clusterSelection ??= JSON.parse(this.df.tags[C.TAGS.CLUSTER_SELECTION] || '[]');
221
+ get clusterSelection(): type.Selection {
222
+ const tagSelection = this.df.getTag(C.TAGS.CLUSTER_SELECTION);
223
+ if (tagSelection === null)
224
+ this.initClusterSelection({notify: false});
225
+ this._clusterSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.CLUSTER_SELECTION)!);
226
+ // TODO: Remove in 1.14.0
227
+ if (Array.isArray(this._clusterSelection)) {
228
+ const newSelection: type.Selection = {};
229
+ newSelection[CLUSTER_TYPE.ORIGINAL] = [];
230
+ newSelection[CLUSTER_TYPE.CUSTOM] = [];
231
+ for (const cluster of this._clusterSelection) {
232
+ if (wu(this.customClusters).some((col) => col.name === cluster))
233
+ newSelection[CLUSTER_TYPE.CUSTOM].push(cluster);
234
+ else
235
+ newSelection[CLUSTER_TYPE.ORIGINAL].push(cluster);
236
+ }
237
+ this._clusterSelection = newSelection;
238
+ }
243
239
  return this._clusterSelection;
244
240
  }
245
241
 
246
- set clusterSelection(selection: string[]) {
242
+ set clusterSelection(selection: type.Selection) {
247
243
  this._clusterSelection = selection;
248
244
  this.df.tags[C.TAGS.CLUSTER_SELECTION] = JSON.stringify(selection);
249
245
  this.fireBitsetChanged();
@@ -256,30 +252,30 @@ export class PeptidesModel {
256
252
  }
257
253
 
258
254
  set splitByPos(flag: boolean) {
259
- const splitByAARFlag = (this.df.tags['distributionSplit'] || '00')[1];
260
- this.df.tags['distributionSplit'] = `${flag ? 1 : 0}${splitByAARFlag}`;
255
+ const splitByMonomerFlag = (this.df.tags['distributionSplit'] || '00')[1];
256
+ this.df.tags['distributionSplit'] = `${flag ? 1 : 0}${splitByMonomerFlag}`;
261
257
  }
262
258
 
263
- get splitByAAR(): boolean {
259
+ get splitByMonomer(): boolean {
264
260
  const splitByPosFlag = (this.df.tags['distributionSplit'] || '00')[1];
265
261
  return splitByPosFlag === '1' ? true : false;
266
262
  }
267
263
 
268
- set splitByAAR(flag: boolean) {
269
- const splitByAARFlag = (this.df.tags['distributionSplit'] || '00')[0];
270
- this.df.tags['distributionSplit'] = `${splitByAARFlag}${flag ? 1 : 0}`;
264
+ set splitByMonomer(flag: boolean) {
265
+ const splitByMonomerFlag = (this.df.tags['distributionSplit'] || '00')[0];
266
+ this.df.tags['distributionSplit'] = `${splitByMonomerFlag}${flag ? 1 : 0}`;
271
267
  }
272
268
 
273
269
  get isMonomerPositionSelectionEmpty(): boolean {
274
- for (const aarList of Object.values(this.mutationCliffsSelection)) {
275
- if (aarList.length !== 0)
270
+ for (const monomerList of Object.values(this.mutationCliffsSelection)) {
271
+ if (monomerList.length !== 0)
276
272
  return false;
277
273
  }
278
274
  return true;
279
275
  }
280
276
 
281
277
  get isClusterSelectionEmpty(): boolean {
282
- return this.clusterSelection.length === 0;
278
+ return (this.clusterSelection[CLUSTER_TYPE.ORIGINAL].length + this.clusterSelection[CLUSTER_TYPE.CUSTOM].length) === 0;
283
279
  }
284
280
 
285
281
  get customClusters(): Iterable<DG.Column<boolean>> {
@@ -337,8 +333,13 @@ export class PeptidesModel {
337
333
  break;
338
334
  case 'stats':
339
335
  this.monomerPositionStats = this.calculateMonomerPositionStatistics();
340
- this.mostPotentResiduesDf = this.createMostPotentResiduesDf();
341
336
  this.clusterStats = this.calculateClusterStatistics();
337
+ const mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition;
338
+ mpViewer.createMonomerPositionGrid();
339
+ mpViewer.render();
340
+ const mprViewer = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues;
341
+ mprViewer.createMostPotentResiduesGrid();
342
+ mprViewer.render();
342
343
  break;
343
344
  case 'grid':
344
345
  this.setGridProperties();
@@ -397,26 +398,6 @@ export class PeptidesModel {
397
398
  this._mutationCliffs = mutationCliffs;
398
399
  }
399
400
 
400
- createMonomerPositionDf(): DG.DataFrame {
401
- const uniqueMonomers = new Set<string>();
402
- const splitSeqCols = this.splitSeqDf.columns;
403
- for (const col of splitSeqCols) {
404
- const colCat = col.categories;
405
- for (const cat of colCat) {
406
- if (cat !== '')
407
- uniqueMonomers.add(cat);
408
- }
409
- }
410
-
411
- const monomerCol = DG.Column.fromStrings(C.COLUMNS_NAMES.MONOMER, Array.from(uniqueMonomers));
412
- const monomerPositionDf = DG.DataFrame.fromColumns([monomerCol]);
413
- monomerPositionDf.name = 'SAR';
414
- for (const col of splitSeqCols)
415
- monomerPositionDf.columns.addNewBool(col.name);
416
-
417
- return monomerPositionDf;
418
- }
419
-
420
401
  buildSplitSeqDf(): DG.DataFrame {
421
402
  const sequenceCol = this.df.getCol(this.settings.sequenceColumnName!);
422
403
  const splitSeqDf = splitAlignedSequences(sequenceCol);
@@ -465,8 +446,7 @@ export class PeptidesModel {
465
446
  };
466
447
  removeCluster.onmouseover =
467
448
  (ev): void => ui.tooltip.show('Removes currently selected custom cluster', ev.clientX + 5, ev.clientY + 5);
468
- removeCluster.style.visibility = trueModel.clusterSelection.length === 0 ||
469
- !wu(this.customClusters).some((c) => trueModel.clusterSelection.includes(c.name)) ? 'hidden' : 'visible';
449
+ removeCluster.style.visibility = trueModel.clusterSelection[CLUSTER_TYPE.CUSTOM].length === 0 ? 'hidden' : 'visible';
470
450
  return ui.divV([newView, newCluster, removeCluster]);
471
451
  });
472
452
  }
@@ -485,6 +465,7 @@ export class PeptidesModel {
485
465
 
486
466
  this.initInvariantMapSelection({notify: false});
487
467
  this.initMutationCliffsSelection({notify: false});
468
+ this.initClusterSelection({notify: false});
488
469
 
489
470
  this.setWebLogoInteraction();
490
471
  this.webLogoBounds = {};
@@ -498,33 +479,27 @@ export class PeptidesModel {
498
479
  this.setGridProperties();
499
480
  }
500
481
 
501
- initInvariantMapSelection(options: {cleanInit?: boolean, notify?: boolean} = {}): void {
502
- options.cleanInit ??= false;
482
+ initInvariantMapSelection(options: {notify?: boolean} = {}): void {
503
483
  options.notify ??= true;
504
484
 
505
- const tempFilter: type.PositionToAARList = this.invariantMapSelection;
485
+ const tempSelection: type.Selection = {};
506
486
  const positionColumns = this.splitSeqDf.columns.names();
507
- for (const pos of positionColumns) {
508
- if (options.cleanInit || !tempFilter.hasOwnProperty(pos))
509
- tempFilter[pos] = [];
510
- }
487
+ for (const pos of positionColumns)
488
+ tempSelection[pos] = [];
511
489
 
512
490
  if (options.notify)
513
- this.invariantMapSelection = tempFilter;
491
+ this.invariantMapSelection = tempSelection;
514
492
  else
515
- this._invariantMapSelection = tempFilter;
493
+ this._invariantMapSelection = tempSelection;
516
494
  }
517
495
 
518
- initMutationCliffsSelection(options: {cleanInit?: boolean, notify?: boolean} = {}): void {
519
- options.cleanInit ??= false;
496
+ initMutationCliffsSelection(options: {notify?: boolean} = {}): void {
520
497
  options.notify ??= true;
521
498
 
522
- const tempSelection: type.PositionToAARList = this.mutationCliffsSelection;
499
+ const tempSelection: type.Selection = {};
523
500
  const positionColumns = this.splitSeqDf.columns.names();
524
- for (const pos of positionColumns) {
525
- if (options.cleanInit || !tempSelection.hasOwnProperty(pos))
526
- tempSelection[pos] = [];
527
- }
501
+ for (const pos of positionColumns)
502
+ tempSelection[pos] = [];
528
503
 
529
504
  if (options.notify)
530
505
  this.mutationCliffsSelection = tempSelection;
@@ -546,7 +521,7 @@ export class PeptidesModel {
546
521
  const newColCat = newCol.categories;
547
522
  const newColData = newCol.getRawData();
548
523
  col = cols.addNew(newCol.name, newCol.type).init((i) => newColCat[newColData[i]]);
549
- CR.setAARRenderer(col, this.alphabet);
524
+ CR.setMonomerRenderer(col, this.alphabet);
550
525
  }
551
526
  this.df.name = name;
552
527
  }
@@ -569,7 +544,7 @@ export class PeptidesModel {
569
544
  for (const posCol of positionColumns) {
570
545
  const posColData = posCol.getRawData();
571
546
  const posColCateogries = posCol.categories;
572
- const currentPositionObject = {general: {}} as PositionStats & { general: SummaryStats };
547
+ const currentPositionObject = {general: {}} as PositionStats & {general: SummaryStats};
573
548
 
574
549
  for (let categoryIndex = 0; categoryIndex < posColCateogries.length; ++categoryIndex) {
575
550
  const monomer = posColCateogries[categoryIndex];
@@ -582,7 +557,9 @@ export class PeptidesModel {
582
557
  boolArray[i] = true;
583
558
  }
584
559
  const bitArray = BitArray.fromValues(boolArray);
585
- const stats = getStats(activityColData, bitArray);
560
+ const stats = bitArray.allFalse || bitArray.allTrue ?
561
+ {count: sourceDfLen, meanDifference: 0, ratio: 1.0, pValue: null, mask: bitArray} :
562
+ getStats(activityColData, bitArray);
586
563
  currentPositionObject[monomer] = stats;
587
564
  this.getSummaryStats(currentPositionObject.general, stats);
588
565
  }
@@ -616,15 +593,17 @@ export class PeptidesModel {
616
593
  if (genObj.minMeanDifference > possibleMinMeanDifference)
617
594
  genObj.minMeanDifference = possibleMinMeanDifference;
618
595
 
619
- const possibleMaxPValue = stats?.pValue ?? summaryStats!.maxPValue;
620
- genObj.maxPValue ??= possibleMaxPValue;
621
- if (genObj.maxPValue < possibleMaxPValue)
622
- genObj.maxPValue = possibleMaxPValue;
596
+ if (!isNaN(stats?.pValue ?? NaN)) {
597
+ const possibleMaxPValue = stats?.pValue ?? summaryStats!.maxPValue;
598
+ genObj.maxPValue ??= possibleMaxPValue;
599
+ if (genObj.maxPValue < possibleMaxPValue)
600
+ genObj.maxPValue = possibleMaxPValue;
623
601
 
624
- const possibleMinPValue = stats?.pValue ?? summaryStats!.minPValue;
625
- genObj.minPValue ??= possibleMinPValue;
626
- if (genObj.minPValue > possibleMinPValue)
627
- genObj.minPValue = possibleMinPValue;
602
+ const possibleMinPValue = stats?.pValue ?? summaryStats!.minPValue;
603
+ genObj.minPValue ??= possibleMinPValue;
604
+ if (genObj.minPValue > possibleMinPValue)
605
+ genObj.minPValue = possibleMinPValue;
606
+ }
628
607
 
629
608
  const possibleMaxRatio = stats?.ratio ?? summaryStats!.maxRatio;
630
609
  genObj.maxRatio ??= possibleMaxRatio;
@@ -658,13 +637,14 @@ export class PeptidesModel {
658
637
  const origClustStats: ClusterStats = {};
659
638
  const customClustStats: ClusterStats = {};
660
639
 
661
- for (let clustType = 0; clustType < 2; ++clustType) {
662
- const masks = clustType === 0 ? origClustMasks : customClustMasks;
663
- const clustNames = clustType === 0 ? origClustColCat : customClustColNamesList;
664
- const resultStats = clustType === 0 ? origClustStats : customClustStats;
640
+ for (const clustType of Object.values(CLUSTER_TYPE)) {
641
+ const masks = clustType === CLUSTER_TYPE.ORIGINAL ? origClustMasks : customClustMasks;
642
+ const clustNames = clustType === CLUSTER_TYPE.ORIGINAL ? origClustColCat : customClustColNamesList;
643
+ const resultStats = clustType === CLUSTER_TYPE.ORIGINAL ? origClustStats : customClustStats;
665
644
  for (let maskIdx = 0; maskIdx < masks.length; ++maskIdx) {
666
645
  const mask = masks[maskIdx];
667
- const stats = getStats(activityColData, mask);
646
+ const stats = mask.allTrue || mask.allFalse ? {count: mask.length, meanDifference: 0, ratio: 1.0, pValue: null, mask: mask} :
647
+ getStats(activityColData, mask);
668
648
  resultStats[clustNames[maskIdx]] = stats;
669
649
  }
670
650
  }
@@ -675,66 +655,16 @@ export class PeptidesModel {
675
655
  return resultStats;
676
656
  }
677
657
 
678
- createMostPotentResiduesDf(): DG.DataFrame {
679
- const monomerPositionStatsEntries = Object.entries(this.monomerPositionStats) as [string, PositionStats][];
680
- const mprDf = DG.DataFrame.create(monomerPositionStatsEntries.length - 1); // Subtract 'general' entry from mp-stats
681
- const mprDfCols = mprDf.columns;
682
- const posCol = mprDfCols.addNewInt(C.COLUMNS_NAMES.POSITION);
683
- const monomerCol = mprDfCols.addNewString(C.COLUMNS_NAMES.MONOMER);
684
- const mdCol = mprDfCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
685
- const pValCol = mprDfCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
686
- const countCol = mprDfCols.addNewInt(C.COLUMNS_NAMES.COUNT);
687
- const ratioCol = mprDfCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
688
-
689
- let i = 0;
690
- for (const [position, positionStats] of monomerPositionStatsEntries) {
691
- const generalPositionStats = positionStats.general;
692
- if (!generalPositionStats)
693
- continue;
694
-
695
- const filteredMonomerStats = Object.entries(positionStats).filter((v) => {
696
- const key = v[0];
697
- if (key === 'general')
698
- return false;
699
-
700
- return (v[1] as Stats).pValue === generalPositionStats.minPValue;
701
- }) as [string, Stats][];
702
-
703
- let maxEntry: [string, Stats];
704
- for (const [monomer, monomerStats] of filteredMonomerStats) {
705
- if (typeof maxEntry! === 'undefined' || maxEntry[1].meanDifference < monomerStats.meanDifference)
706
- maxEntry = [monomer, monomerStats];
707
- }
708
-
709
- posCol.set(i, parseInt(position));
710
- monomerCol.set(i, maxEntry![0]);
711
- mdCol.set(i, maxEntry![1].meanDifference);
712
- pValCol.set(i, maxEntry![1].pValue);
713
- countCol.set(i, maxEntry![1].count);
714
- ratioCol.set(i, maxEntry![1].ratio);
715
- ++i;
716
- }
717
- return mprDf;
718
- }
719
-
720
- modifyClusterSelection(cluster: string): void {
721
- const tempSelection = this.clusterSelection;
722
- const idx = tempSelection.indexOf(cluster);
723
- if (idx !== -1)
724
- tempSelection.splice(idx, 1);
725
- else
726
- tempSelection.push(cluster);
727
-
728
- this.clusterSelection = tempSelection;
729
- }
730
-
731
658
  initClusterSelection(options: {notify?: boolean} = {}): void {
732
659
  options.notify ??= true;
733
660
 
661
+ const newClusterSelection = {} as type.Selection;
662
+ newClusterSelection[CLUSTER_TYPE.ORIGINAL] = [];
663
+ newClusterSelection[CLUSTER_TYPE.CUSTOM] = [];
734
664
  if (options.notify)
735
- this.clusterSelection = [];
665
+ this.clusterSelection = newClusterSelection;
736
666
  else
737
- this._clusterSelection = [];
667
+ this._clusterSelection = newClusterSelection;
738
668
  }
739
669
 
740
670
  setWebLogoInteraction(): void {
@@ -742,8 +672,13 @@ export class PeptidesModel {
742
672
  const eventAction = (ev: MouseEvent): void => {
743
673
  const cell = sourceView.hitTest(ev.offsetX, ev.offsetY);
744
674
  if (cell?.isColHeader && cell.tableColumn?.semType === C.SEM_TYPES.MONOMER) {
745
- const newBarPart = this.findAARandPosition(cell, ev);
746
- this.requestBarchartAction(ev, newBarPart);
675
+ const monomerPosition = this.findWebLogoMonomerPosition(cell, ev);
676
+ if (monomerPosition === null) {
677
+ this.unhighlight();
678
+ return;
679
+ }
680
+ this.requestBarchartAction(ev, monomerPosition);
681
+ this.highlightMonomerPosition(monomerPosition);
747
682
  }
748
683
  };
749
684
 
@@ -754,37 +689,61 @@ export class PeptidesModel {
754
689
  .subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
755
690
  }
756
691
 
757
- findAARandPosition(cell: DG.GridCell, ev: MouseEvent): { monomer: string, position: string } | null {
692
+ highlightMonomerPosition(monomerPosition: type.SelectionItem): void {
693
+ const bitArray = new BitArray(this.df.rowCount);
694
+ if (monomerPosition.positionOrClusterType === C.COLUMNS_NAMES.MONOMER) {
695
+ const positionStats = Object.values(this.monomerPositionStats);
696
+ for (const posStat of positionStats) {
697
+ const monomerPositionStats = (posStat as PositionStats)[monomerPosition.monomerOrCluster];
698
+ if (monomerPositionStats ?? false)
699
+ bitArray.or(monomerPositionStats!.mask);
700
+ }
701
+ } else {
702
+ const monomerPositionStats = this.monomerPositionStats[monomerPosition.positionOrClusterType]![monomerPosition.monomerOrCluster];
703
+ if (monomerPositionStats ?? false)
704
+ bitArray.or(monomerPositionStats!.mask);
705
+ }
706
+
707
+ this.df.rows.highlight((i) => bitArray.getBit(i));
708
+ this.isHighlighting = true;
709
+ }
710
+
711
+ highlightCluster(cluster: type.SelectionItem): void {
712
+ const bitArray = this.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster].mask;
713
+ this.df.rows.highlight((i) => bitArray.getBit(i));
714
+ this.isHighlighting = true;
715
+ }
716
+
717
+ unhighlight(): void {
718
+ if (!this.isHighlighting)
719
+ return;
720
+ this.df.rows.highlight(null);
721
+ this.isHighlighting = false;
722
+ }
723
+
724
+ findWebLogoMonomerPosition(cell: DG.GridCell, ev: MouseEvent): type.SelectionItem | null {
758
725
  const barCoords = this.webLogoBounds[cell.tableColumn!.name];
759
726
  for (const [monomer, coords] of Object.entries(barCoords)) {
760
727
  const isIntersectingX = ev.offsetX >= coords.x && ev.offsetX <= coords.x + coords.width;
761
728
  const isIntersectingY = ev.offsetY >= coords.y && ev.offsetY <= coords.y + coords.height;
762
729
  if (isIntersectingX && isIntersectingY)
763
- return {monomer: monomer, position: cell.tableColumn!.name};
730
+ return {monomerOrCluster: monomer, positionOrClusterType: cell.tableColumn!.name};
764
731
  }
765
732
 
766
733
  return null;
767
734
  }
768
735
 
769
- requestBarchartAction(ev: MouseEvent, barPart: {position: string, monomer: string} | null): void {
770
- if (!barPart)
771
- return;
772
- const monomer = barPart.monomer;
773
- const position = barPart.position;
774
- if (ev.type === 'click') {
775
- if (!ev.shiftKey)
776
- this.initInvariantMapSelection({cleanInit: true, notify: false});
777
-
778
- this.modifyMonomerPositionSelection(monomer, position, true);
779
- } else {
780
- const bar = `${position} = ${monomer}`;
736
+ requestBarchartAction(ev: MouseEvent, monomerPosition: type.SelectionItem): void {
737
+ if (ev.type === 'click')
738
+ this.modifyInvariantMapSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
739
+ else {
740
+ const bar = `${monomerPosition.positionOrClusterType} = ${monomerPosition.monomerOrCluster}`;
781
741
  if (this.cachedWebLogoTooltip.bar === bar)
782
742
  ui.tooltip.show(this.cachedWebLogoTooltip.tooltip!, ev.clientX, ev.clientY);
783
- else
784
- this.cachedWebLogoTooltip = {bar: bar, tooltip: this.showTooltipAt(monomer, position, ev.clientX, ev.clientY)};
785
-
786
- //TODO: how to unghighlight?
787
- // this.df.rows.match(bar).highlight();
743
+ else {
744
+ this.cachedWebLogoTooltip = {bar: bar,
745
+ tooltip: this.showTooltipAt(monomerPosition, ev.clientX, ev.clientY)};
746
+ }
788
747
  }
789
748
  }
790
749
 
@@ -805,6 +764,8 @@ export class PeptidesModel {
805
764
  //TODO: optimize
806
765
  if (gcArgs.cell.isColHeader && col?.semType === C.SEM_TYPES.MONOMER) {
807
766
  const stats = this.monomerPositionStats[col.name];
767
+ if (!stats)
768
+ return;
808
769
  //TODO: precalc on stats creation
809
770
  const sortedStatsOrder = Object.keys(stats).sort((a, b) => {
810
771
  if (a === '' || a === '-')
@@ -830,10 +791,8 @@ export class PeptidesModel {
830
791
 
831
792
  if (!this._layoutEventInitialized) {
832
793
  grok.events.onViewLayoutApplied.subscribe((layout) => {
833
- if (layout.view.id === this.analysisView.id) {
834
- // this.analysisView.grid.onCellRender.subscribe((gcArgs) => headerRenderer(gcArgs));
794
+ if (layout.view.id === this.analysisView.id)
835
795
  this.updateGrid();
836
- }
837
796
  });
838
797
  this._layoutEventInitialized = true;
839
798
  }
@@ -851,19 +810,19 @@ export class PeptidesModel {
851
810
  });
852
811
  }
853
812
 
854
- showMonomerTooltip(aar: string, x: number, y: number): boolean {
813
+ showMonomerTooltip(monomer: string, x: number, y: number): boolean {
855
814
  const tooltipElements: HTMLDivElement[] = [];
856
- const monomerName = aar.toLowerCase();
815
+ const monomerName = monomer.toLowerCase();
857
816
 
858
817
  const mw = getMonomerWorksInstance();
859
- const mol = mw?.getCappedRotatedMonomer('PEPTIDE', aar);
818
+ const mol = mw?.getCappedRotatedMonomer('PEPTIDE', monomer);
860
819
 
861
820
  if (mol) {
862
821
  tooltipElements.push(ui.div(monomerName));
863
822
  const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
864
823
  tooltipElements.push(grok.chem.svgMol(mol, undefined, undefined, options));
865
- } else if (aar !== '')
866
- tooltipElements.push(ui.div(aar));
824
+ } else if (monomer !== '')
825
+ tooltipElements.push(ui.div(monomer));
867
826
  else
868
827
  return true;
869
828
 
@@ -873,27 +832,35 @@ export class PeptidesModel {
873
832
  return true;
874
833
  }
875
834
 
835
+ showTooltip(monomerPosition: type.SelectionItem, x: number, y: number, fromViewer: boolean = false): boolean {
836
+ if (monomerPosition.positionOrClusterType === C.COLUMNS_NAMES.MONOMER)
837
+ this.showMonomerTooltip(monomerPosition.monomerOrCluster, x, y);
838
+ else
839
+ this.showTooltipAt(monomerPosition, x, y, fromViewer);
840
+ return true;
841
+ }
876
842
  //TODO: move out to viewer code
877
- showTooltipAt(aar: string, position: string, x: number, y: number): HTMLDivElement | null {
878
- const stats = this.monomerPositionStats[position][aar];
843
+ showTooltipAt(monomerPosition: type.SelectionItem, x: number, y: number, fromViewer: boolean = false): HTMLDivElement | null {
844
+ const stats = this.monomerPositionStats[monomerPosition.positionOrClusterType]![monomerPosition.monomerOrCluster];
879
845
  if (!stats?.count)
880
846
  return null;
881
847
 
882
848
  const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
883
- const posCol = this.df.getCol(position);
884
- const posColCategories = posCol.categories;
885
- const aarCategoryIndex = posColCategories.indexOf(aar);
886
- const posColData = posCol.getRawData();
887
- const mask = DG.BitSet.create(activityCol.length, (i) => posColData[i] === aarCategoryIndex);
888
-
849
+ const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer, activityCol.length);
889
850
  const distributionTable = DG.DataFrame.fromColumns(
890
851
  [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
891
- const labels = getDistributionLegend(`${position} : ${aar}`, 'Other');
892
852
  const hist = getActivityDistribution(prepareTableForHistogram(distributionTable), true);
853
+
893
854
  const tableMap = getStatsTableMap(stats);
855
+ if (fromViewer) {
856
+ tableMap['Mean difference'] = `${tableMap['Mean difference']} (size)`;
857
+ if (tableMap['p-value'])
858
+ tableMap['p-value'] = `${tableMap['p-value']} (color)`;
859
+ }
894
860
  const aggregatedColMap = this.getAggregatedColumnValues({mask: mask});
895
-
896
861
  const resultMap = {...tableMap, ...aggregatedColMap};
862
+
863
+ const labels = getDistributionLegend(`${monomerPosition.positionOrClusterType} : ${monomerPosition.monomerOrCluster}`, 'Other');
897
864
  const distroStatsElem = getStatsSummary(labels, hist, resultMap);
898
865
 
899
866
  ui.tooltip.show(distroStatsElem, x, y);
@@ -917,99 +884,84 @@ export class PeptidesModel {
917
884
  return colResults;
918
885
  }
919
886
 
920
- modifyMonomerPositionSelection(aar: string, position: string, isInvariantMap: boolean): void {
921
- if (this.df.getCol(position).categories.indexOf(aar) === -1)
922
- return;
923
-
924
- const tempSelection = isInvariantMap ? this.invariantMapSelection : this.mutationCliffsSelection;
925
- const tempSelectionAt = tempSelection[position];
926
- const aarIndex = tempSelectionAt.indexOf(aar);
927
- if (aarIndex === -1)
928
- tempSelectionAt.push(aar);
929
- else
930
- tempSelectionAt.splice(aarIndex, 1);
931
-
932
- if (isInvariantMap)
933
- this.invariantMapSelection = tempSelection;
934
- else
935
- this.mutationCliffsSelection = tempSelection;
936
- }
937
-
938
887
  setBitsetCallback(): void {
939
888
  if (this.isBitsetChangedInitialized)
940
889
  return;
941
890
  const selection = this.df.selection;
942
891
  const filter = this.df.filter;
943
- const clusterCol = this.df.col(this.settings.clustersColumnName!);
944
892
 
945
- const changeSelectionBitset = (currentBitset: DG.BitSet, clustColCat: string[],
946
- clustColData: type.RawData, customClust: {[key: string]: BitArray}): void => {
947
- const indexes = new Set<number>();
893
+ const getCombinedSelection = (): DG.BitSet => {
894
+ const combinedSelection = new BitArray(this.df.rowCount, false);
895
+ // Invariant map selection
896
+ for (const [position, monomerList] of Object.entries(this.invariantMapSelection)) {
897
+ for (const monomer of monomerList) {
898
+ const monomerPositionStats = this.monomerPositionStats[position]![monomer]!;
899
+ combinedSelection.or(monomerPositionStats.mask);
900
+ }
901
+ }
902
+
903
+ // Mutation cliffs selection
948
904
  for (const [position, monomerList] of Object.entries(this.mutationCliffsSelection)) {
949
905
  for (const monomer of monomerList) {
950
906
  const substitutions = this.mutationCliffs?.get(monomer)?.get(position) ?? null;
951
907
  if (substitutions === null)
952
908
  continue;
953
909
  for (const [key, value] of substitutions.entries()) {
954
- indexes.add(key);
910
+ combinedSelection.setTrue(key);
955
911
  for (const v of value)
956
- indexes.add(v);
912
+ combinedSelection.setTrue(v);
957
913
  }
958
914
  }
959
915
  }
960
916
 
961
- const positionList = Object.keys(this.invariantMapSelection);
962
- const rowCount = this.df.rowCount;
963
- for (const position of positionList) {
964
- const positionCol: DG.Column<string> = this.df.getCol(position);
965
- const positionColData = positionCol.getRawData();
966
- const positionColCat = positionCol.categories;
967
- const aarList = this.invariantMapSelection[position];
968
- for (const aar of aarList) {
969
- const aarIndex = positionColCat.indexOf(aar);
970
- if (aarIndex === -1)
971
- continue;
972
- for (let i = 0; i < rowCount; ++i) {
973
- if (positionColData[i] === aarIndex)
974
- indexes.add(i);
975
- }
917
+ // Cluster selection
918
+ for (const clustType of Object.keys(this.clusterSelection)) {
919
+ for (const clust of this.clusterSelection[clustType]) {
920
+ const clusterStats = this.clusterStats[clustType as CLUSTER_TYPE][clust]!;
921
+ combinedSelection.or(clusterStats.mask);
976
922
  }
977
923
  }
978
924
 
979
- const getBitAt = (i: number): boolean => {
980
- if (indexes.has(i))
981
- return true;
982
-
983
- //TODO: preprocess
984
- const currentOrigClust = clustColCat[clustColData[i]];
985
- if (typeof currentOrigClust === undefined)
986
- return false;
987
-
988
- for (const clust of this.clusterSelection) {
989
- if (clust === currentOrigClust)
990
- return true;
925
+ return DG.BitSet.fromBytes(combinedSelection.buffer.buffer, combinedSelection.length);
926
+ };
991
927
 
992
- if (Object.hasOwn(customClust, clust) && customClust[clust].getBit(i))
993
- return true;
928
+ const getLatestSelection = (): DG.BitSet => {
929
+ if (this.latestSelectionItem === null)
930
+ return getCombinedSelection();
931
+ if (this.latestSelectionItem.kind === SELECTION_MODE.INVARIANT_MAP) {
932
+ const monomerPositionStats = this.monomerPositionStats[this.latestSelectionItem.positionOrClusterType]![this.latestSelectionItem.monomerOrCluster]!;
933
+ return DG.BitSet.fromBytes(monomerPositionStats.mask.buffer.buffer, monomerPositionStats.mask.length);
934
+ } else if (this.latestSelectionItem.kind === SELECTION_MODE.MUTATION_CLIFFS) {
935
+ const substitutions = this.mutationCliffs?.get(this.latestSelectionItem.monomerOrCluster)?.get(this.latestSelectionItem.positionOrClusterType) ?? null;
936
+ if (substitutions === null)
937
+ throw new Error(`Couldn't find substitutions for ${this.latestSelectionItem.monomerOrCluster} at ${this.latestSelectionItem.positionOrClusterType}`);
938
+ const latestSelection = new BitArray(this.df.rowCount, false);
939
+ for (const [key, value] of substitutions.entries()) {
940
+ latestSelection.setTrue(key);
941
+ for (const v of value)
942
+ latestSelection.setTrue(v);
994
943
  }
944
+ return DG.BitSet.fromBytes(latestSelection.buffer.buffer, latestSelection.length);
945
+ } else if (this.latestSelectionItem.kind === 'Cluster') {
946
+ const clusterStats = this.clusterStats[this.latestSelectionItem.positionOrClusterType as CLUSTER_TYPE][this.latestSelectionItem.monomerOrCluster]!;
947
+ return DG.BitSet.fromBytes(clusterStats.mask.buffer.buffer, clusterStats.mask.length);
948
+ }
949
+ throw new Error(`Unknown selection kind: ${this.latestSelectionItem.kind}`);
950
+ };
995
951
 
996
- return false;
997
- };
998
- currentBitset.init((i) => getBitAt(i), false);
952
+ const showAccordion = (): void => {
953
+ const acc = this.createAccordion();
954
+ if (acc !== null) {
955
+ grok.shell.o = acc.root;
956
+ for (const pane of acc.panes)
957
+ pane.expanded = true;
958
+ }
999
959
  };
1000
960
 
1001
961
  selection.onChanged.subscribe(() => {
1002
- if (this.isUserChangedSelection)
1003
- return;
1004
-
1005
- const clustColCat = clusterCol?.categories ?? [];
1006
- const clustColData = clusterCol?.getRawData() ?? new Int32Array(0);
1007
- const customClust: {[key: string]: BitArray} = {};
1008
- const rowCount = this.df.rowCount;
1009
- for (const clust of this.customClusters)
1010
- customClust[clust.name] = BitArray.fromUint32Array(rowCount, clust.getRawData() as Uint32Array);
1011
-
1012
- changeSelectionBitset(selection, clustColCat, clustColData, customClust);
962
+ if (!this.isUserChangedSelection)
963
+ selection.copyFrom(getLatestSelection(), false);
964
+ showAccordion();
1013
965
  });
1014
966
 
1015
967
  filter.onChanged.subscribe(() => {
@@ -1018,6 +970,7 @@ export class PeptidesModel {
1018
970
  lstViewer.createLogoSummaryTableGrid();
1019
971
  lstViewer.render();
1020
972
  }
973
+ showAccordion();
1021
974
  });
1022
975
  this.isBitsetChangedInitialized = true;
1023
976
  }
@@ -1027,15 +980,7 @@ export class PeptidesModel {
1027
980
  this.df.selection.fireChanged();
1028
981
  if (fireFilterChanged)
1029
982
  this.df.filter.fireChanged();
1030
- this.modifyOrCreateSplitCol();
1031
983
  this.headerSelectedMonomers = calculateSelected(this.df);
1032
-
1033
- const acc = this.createAccordion();
1034
- if (acc !== null) {
1035
- grok.shell.o = acc.root;
1036
- for (const pane of acc.panes)
1037
- pane.expanded = true;
1038
- }
1039
984
  this.isUserChangedSelection = true;
1040
985
  }
1041
986
 
@@ -1084,19 +1029,6 @@ export class PeptidesModel {
1084
1029
  }
1085
1030
  }
1086
1031
 
1087
- getSplitColValueAt(index: number, aar: string, position: string, aarLabel: string): string {
1088
- const currentAAR = this.df.get(position, index) as string;
1089
- return currentAAR === aar ? aarLabel : C.CATEGORIES.OTHER;
1090
- }
1091
-
1092
- modifyOrCreateSplitCol(): void {
1093
- const bs = this.df.selection;
1094
- this.splitCol = this.df.col(C.COLUMNS_NAMES.SPLIT_COL) ??
1095
- this.df.columns.addNewBool(C.COLUMNS_NAMES.SPLIT_COL);
1096
- this.splitCol.init((i) => bs.get(i));
1097
- this.splitCol.compact();
1098
- }
1099
-
1100
1032
  /** Class initializer */
1101
1033
  init(): void {
1102
1034
  if (this.isInitialized)
@@ -1224,4 +1156,51 @@ export class PeptidesModel {
1224
1156
 
1225
1157
  return newDfId;
1226
1158
  }
1159
+
1160
+ modifyInvariantMapSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {shiftPressed: false, ctrlPressed: false}, notify: boolean = true): void {
1161
+ if (notify)
1162
+ this.invariantMapSelection = this.modifySelection(this.invariantMapSelection, monomerPosition, options);
1163
+ else
1164
+ this._invariantMapSelection = this.modifySelection(this._invariantMapSelection, monomerPosition, options);
1165
+ }
1166
+
1167
+ modifyMutationCliffsSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {shiftPressed: false, ctrlPressed: false}, notify: boolean = true): void {
1168
+ if (notify)
1169
+ this.mutationCliffsSelection = this.modifySelection(this.mutationCliffsSelection, monomerPosition, options);
1170
+ else
1171
+ this._mutationCliffsSelection = this.modifySelection(this._mutationCliffsSelection, monomerPosition, options);
1172
+ }
1173
+
1174
+ modifyClusterSelection(cluster: type.SelectionItem, options: type.SelectionOptions = {shiftPressed: false, ctrlPressed: false}, notify: boolean = true): void {
1175
+ if (notify)
1176
+ this.clusterSelection = this.modifySelection(this.clusterSelection, cluster, options);
1177
+ else
1178
+ this._clusterSelection = this.modifySelection(this._clusterSelection, cluster, options);
1179
+ }
1180
+
1181
+ modifySelection(selection: type.Selection, clusterOrMonomerPosition: type.SelectionItem, options: type.SelectionOptions): type.Selection {
1182
+ const monomerList = selection[clusterOrMonomerPosition.positionOrClusterType];
1183
+ const monomerIndex = monomerList.indexOf(clusterOrMonomerPosition.monomerOrCluster);
1184
+ if (options.shiftPressed && options.ctrlPressed) {
1185
+ if (monomerIndex !== -1)
1186
+ monomerList.splice(monomerIndex, 1);
1187
+ } else if (options.ctrlPressed) {
1188
+ if (monomerIndex === -1)
1189
+ monomerList.push(clusterOrMonomerPosition.monomerOrCluster);
1190
+ else
1191
+ monomerList.splice(monomerIndex, 1);
1192
+ } else if (options.shiftPressed) {
1193
+ if (monomerIndex === -1)
1194
+ monomerList.push(clusterOrMonomerPosition.monomerOrCluster);
1195
+ } else {
1196
+ const selectionKeys = Object.keys(selection);
1197
+ selection = {};
1198
+ for (const posOrClustType of selectionKeys) {
1199
+ selection[posOrClustType] = [];
1200
+ if (posOrClustType === clusterOrMonomerPosition.positionOrClusterType)
1201
+ selection[posOrClustType].push(clusterOrMonomerPosition.monomerOrCluster);
1202
+ }
1203
+ }
1204
+ return selection;
1205
+ }
1227
1206
  }