@datagrok/peptides 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.eslintrc.json +2 -2
  2. package/README.md +3 -3
  3. package/dist/563.js +2 -0
  4. package/dist/611.js +2 -0
  5. package/dist/802.js +2 -0
  6. package/dist/96.js +2 -0
  7. package/dist/package-test.js +2 -29778
  8. package/dist/package.js +2 -28285
  9. package/files/icons/logo-summary-viewer.svg +13 -0
  10. package/files/icons/peptide-sar-vertical-viewer.svg +13 -0
  11. package/files/icons/peptide-sar-viewer.svg +19 -0
  12. package/files/icons/peptide-space-viewer.svg +40 -0
  13. package/files/tests/HELM_small.csv +12 -0
  14. package/package.json +7 -8
  15. package/src/demo/fasta.ts +24 -0
  16. package/src/model.ts +381 -325
  17. package/src/package-test.ts +3 -0
  18. package/src/package.ts +54 -30
  19. package/src/tests/algorithms.ts +1 -1
  20. package/src/tests/core.ts +13 -8
  21. package/src/tests/model.ts +152 -0
  22. package/src/tests/peptide-space-test.ts +1 -2
  23. package/src/tests/table-view.ts +158 -0
  24. package/src/tests/viewers.ts +142 -4
  25. package/src/tests/widgets.ts +135 -0
  26. package/src/utils/algorithms.ts +2 -2
  27. package/src/utils/cell-renderer.ts +2 -2
  28. package/src/utils/constants.ts +8 -0
  29. package/src/utils/distance-matrix.worker.ts +16 -0
  30. package/src/utils/misc.ts +4 -4
  31. package/src/utils/peptide-similarity-space.ts +0 -1
  32. package/src/utils/statistics.ts +14 -10
  33. package/src/utils/types.ts +8 -4
  34. package/src/utils/worker-creator.ts +11 -0
  35. package/src/viewers/logo-summary.ts +246 -168
  36. package/src/viewers/peptide-space-viewer.ts +6 -6
  37. package/src/viewers/sar-viewer.ts +108 -110
  38. package/src/widgets/distribution.ts +95 -128
  39. package/src/widgets/mutation-cliffs.ts +2 -2
  40. package/src/widgets/peptides.ts +11 -3
  41. package/src/widgets/settings.ts +94 -24
  42. package/tsconfig.json +1 -1
  43. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +0 -9077
package/src/model.ts CHANGED
@@ -2,27 +2,36 @@ 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
4
 
5
+ import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
6
+ import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
7
+ import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
8
+ import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
9
+ import {pickUpPalette, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
+ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
11
+ import {DistanceMatrix} from '@datagrok-libraries/bio/src/trees/distance-matrix';
12
+ import {StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
13
+ import {ITreeHelper} from '@datagrok-libraries/bio/src/trees/tree-helper';
14
+ import {TAGS as treeTAGS} from '@datagrok-libraries/bio/src/trees';
15
+
5
16
  import wu from 'wu';
6
17
  import * as rxjs from 'rxjs';
7
18
  import * as uuid from 'uuid';
8
19
 
9
20
  import * as C from './utils/constants';
10
21
  import * as type from './utils/types';
11
- import {calculateSelected, extractMonomerInfo, scaleActivity, wrapDistroAndStatsDefault} from './utils/misc';
22
+ import {calculateSelected, extractMonomerInfo, scaleActivity, getStatsSummary} from './utils/misc';
12
23
  import {MonomerPosition, MostPotentResiduesViewer} from './viewers/sar-viewer';
13
24
  import * as CR from './utils/cell-renderer';
14
25
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
15
- import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
16
- import {getStats, Stats} from './utils/statistics';
17
- import {LogoSummary} from './viewers/logo-summary';
26
+ import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap,
27
+ } from './widgets/distribution';
28
+ import {getAggregatedValue, getStats, Stats} from './utils/statistics';
29
+ import {LogoSummaryTable} from './viewers/logo-summary';
18
30
  import {getSettingsDialog} from './widgets/settings';
19
- import {getMonomerWorks} from './package';
31
+ import {_package, getMonomerWorksInstance, getTreeHelperInstance} from './package';
20
32
  import {findMutations} from './utils/algorithms';
21
- import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
22
- import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
23
- import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
24
- import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
25
- import {pickUpPalette, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
33
+ import {createDistanceMatrixWorker} from './utils/worker-creator';
34
+ import BitArray from '@datagrok-libraries/utils/src/bit-array';
26
35
 
27
36
  export type SummaryStats = {
28
37
  minCount: number, maxCount: number,
@@ -32,35 +41,47 @@ export type SummaryStats = {
32
41
  };
33
42
  export type PositionStats = { [monomer: string]: Stats } & { general: SummaryStats };
34
43
  export type MonomerPositionStats = { [position: string]: PositionStats } & { general: SummaryStats };
44
+ export type ClusterStats = {[cluster: string]: Stats};
45
+ export enum CLUSTER_TYPE {
46
+ ORIGINAL = 'original',
47
+ CUSTOM = 'custom',
48
+ };
49
+ export type ClusterType = `${CLUSTER_TYPE}`;
50
+ export type ClusterTypeStats = {[clusterType in ClusterType]: ClusterStats};
51
+ export enum VIEWER_TYPE {
52
+ MONOMER_POSITION = 'Monomer-Position',
53
+ MOST_POTENT_RESIDUES = 'Most Potent Residues',
54
+ LOGO_SUMMARY_TABLE = 'Logo Summary Table',
55
+ DENDROGRAM = 'Dendrogram',
56
+ };
57
+
58
+ export const getAggregatedColName = (aggF: string, colName: string): string => `${aggF}(${colName})`;
35
59
 
36
60
  export class PeptidesModel {
37
61
  static modelName = 'peptidesModel';
38
62
 
39
- settingsSubject: rxjs.Subject<type.PeptidesSettings> = new rxjs.Subject();
40
- _mutatinCliffsSelectionSubject: rxjs.Subject<undefined> = new rxjs.Subject();
41
- _newClusterSubject: rxjs.Subject<undefined> = new rxjs.Subject();
42
- _removeClusterSubject: rxjs.Subject<undefined> = new rxjs.Subject();
43
- _filterChangedSubject: rxjs.Subject<undefined> = new rxjs.Subject();
63
+ _settingsSubject: rxjs.Subject<type.PeptidesSettings> = new rxjs.Subject();
64
+ _monomerPositionSelectionSubject: rxjs.Subject<undefined> = new rxjs.Subject();
65
+ _newClusterSubject: rxjs.Subject<undefined> = new rxjs.Subject();
66
+ _removeClusterSubject: rxjs.Subject<undefined> = new rxjs.Subject();
67
+ _filterChangedSubject: rxjs.Subject<undefined> = new rxjs.Subject();
44
68
 
45
69
  _isUpdating: boolean = false;
46
70
  isBitsetChangedInitialized = false;
47
71
  isCellChanging = false;
72
+ isUserChangedSelection = true;
48
73
 
49
74
  df: DG.DataFrame;
50
75
  splitCol!: DG.Column<boolean>;
51
- edf: DG.DataFrame | null = null;
52
76
  _monomerPositionStats?: MonomerPositionStats;
53
- _clusterStats?: {[cluster: string]: Stats};
54
- _mutationCliffsSelection!: type.PositionToAARList;
55
- _invariantMapSelection!: type.PositionToAARList;
56
- _logoSummarySelection!: string[];
57
- _substitutionsInfo?: type.SubstitutionsInfo;
77
+ _clusterStats?: ClusterTypeStats;
78
+ _monomerPositionSelection!: type.PositionToAARList;
79
+ _monomerPositionFilter!: type.PositionToAARList;
80
+ _clusterSelection!: string[];
81
+ _mutationCliffs?: type.MutationCliffs;
58
82
  isInitialized = false;
59
83
  _analysisView?: DG.TableView;
60
84
 
61
- isPeptideSpaceChangingBitset = false;
62
- isChangingEdfBitset = false;
63
-
64
85
  monomerMap: { [key: string]: { molfile: string, fullName: string } } = {};
65
86
  monomerLib: IMonomerLib | null = null; // To get monomers from lib(s)
66
87
  monomerWorks: MonomerWorks | null = null; // To get processed monomers
@@ -79,6 +100,9 @@ export class PeptidesModel {
79
100
  _mostPotentResiduesDf?: DG.DataFrame;
80
101
  _matrixDf?: DG.DataFrame;
81
102
  _splitSeqDf?: DG.DataFrame;
103
+ _distanceMatrix!: DistanceMatrix;
104
+ _treeHelper!: ITreeHelper;
105
+ _dm!: DistanceMatrix;
82
106
 
83
107
  private constructor(dataFrame: DG.DataFrame) {
84
108
  this.df = dataFrame;
@@ -91,6 +115,11 @@ export class PeptidesModel {
91
115
  return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
92
116
  }
93
117
 
118
+ get treeHelper(): ITreeHelper {
119
+ this._treeHelper ??= getTreeHelperInstance();
120
+ return this._treeHelper;
121
+ }
122
+
94
123
  get monomerPositionDf(): DG.DataFrame {
95
124
  this._monomerPositionDf ??= this.createMonomerPositionDf();
96
125
  return this._monomerPositionDf;
@@ -141,27 +170,27 @@ export class PeptidesModel {
141
170
  return col.getTag(bioTAGS.alphabet);
142
171
  }
143
172
 
144
- get substitutionsInfo(): type.SubstitutionsInfo {
145
- if (this._substitutionsInfo)
146
- return this._substitutionsInfo;
173
+ get mutationCliffs(): type.MutationCliffs {
174
+ if (this._mutationCliffs)
175
+ return this._mutationCliffs;
147
176
 
148
177
  const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
149
178
  //TODO: set categories ordering the same to share compare indexes instead of strings
150
179
  const monomerColumns: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
151
- this._substitutionsInfo = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
152
- return this._substitutionsInfo;
180
+ this._mutationCliffs = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
181
+ return this._mutationCliffs;
153
182
  }
154
183
 
155
- set substitutionsInfo(si: type.SubstitutionsInfo) {
156
- this._substitutionsInfo = si;
184
+ set mutationCliffs(si: type.MutationCliffs) {
185
+ this._mutationCliffs = si;
157
186
  }
158
187
 
159
- get clusterStats(): {[cluster: string]: Stats} {
188
+ get clusterStats(): ClusterTypeStats {
160
189
  this._clusterStats ??= this.calculateClusterStatistics();
161
190
  return this._clusterStats;
162
191
  }
163
192
 
164
- set clusterStats(clusterStats: {[cluster: string]: Stats}) {
193
+ set clusterStats(clusterStats: ClusterTypeStats) {
165
194
  this._clusterStats = clusterStats;
166
195
  }
167
196
 
@@ -184,12 +213,12 @@ export class PeptidesModel {
184
213
  return this._analysisView;
185
214
  }
186
215
 
187
- get onMutationCliffsSelectionChanged(): rxjs.Observable<undefined> {
188
- return this._mutatinCliffsSelectionSubject.asObservable();
216
+ get onMonomerPositionSelectionChanged(): rxjs.Observable<undefined> {
217
+ return this._monomerPositionSelectionSubject.asObservable();
189
218
  }
190
219
 
191
220
  get onSettingsChanged(): rxjs.Observable<type.PeptidesSettings> {
192
- return this.settingsSubject.asObservable();
221
+ return this._settingsSubject.asObservable();
193
222
  }
194
223
 
195
224
  get onNewCluster(): rxjs.Observable<undefined> {
@@ -204,40 +233,40 @@ export class PeptidesModel {
204
233
  return this._filterChangedSubject.asObservable();
205
234
  }
206
235
 
207
- get mutationCliffsSelection(): type.PositionToAARList {
208
- this._mutationCliffsSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
209
- return this._mutationCliffsSelection;
236
+ get monomerPositionSelection(): type.PositionToAARList {
237
+ this._monomerPositionSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
238
+ return this._monomerPositionSelection;
210
239
  }
211
240
 
212
- set mutationCliffsSelection(selection: type.PositionToAARList) {
213
- this._mutationCliffsSelection = selection;
241
+ set monomerPositionSelection(selection: type.PositionToAARList) {
242
+ this._monomerPositionSelection = selection;
214
243
  this.df.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
215
244
  this.fireBitsetChanged();
216
- this._mutatinCliffsSelectionSubject.next();
245
+ this._monomerPositionSelectionSubject.next();
217
246
  this.analysisView.grid.invalidate();
218
247
  }
219
248
 
220
- get invariantMapSelection(): type.PositionToAARList {
221
- this._invariantMapSelection ??= JSON.parse(this.df.tags[C.TAGS.FILTER] || '{}');
222
- return this._invariantMapSelection;
249
+ get monomerPositionFilter(): type.PositionToAARList {
250
+ this._monomerPositionFilter ??= JSON.parse(this.df.tags[C.TAGS.FILTER] || '{}');
251
+ return this._monomerPositionFilter;
223
252
  }
224
253
 
225
- set invariantMapSelection(selection: type.PositionToAARList) {
226
- this._invariantMapSelection = selection;
254
+ set monomerPositionFilter(selection: type.PositionToAARList) {
255
+ this._monomerPositionFilter = selection;
227
256
  this.df.tags[C.TAGS.FILTER] = JSON.stringify(selection);
228
257
  this.isInvariantMapTrigger = true;
229
- this.fireBitsetChanged(false, true);
258
+ this.fireBitsetChanged(true);
230
259
  this.isInvariantMapTrigger = false;
231
260
  this.analysisView.grid.invalidate();
232
261
  }
233
262
 
234
- get logoSummarySelection(): string[] {
235
- this._logoSummarySelection ??= JSON.parse(this.df.tags[C.TAGS.CLUSTER_SELECTION] || '[]');
236
- return this._logoSummarySelection;
263
+ get clusterSelection(): string[] {
264
+ this._clusterSelection ??= JSON.parse(this.df.tags[C.TAGS.CLUSTER_SELECTION] || '[]');
265
+ return this._clusterSelection;
237
266
  }
238
267
 
239
- set logoSummarySelection(selection: string[]) {
240
- this._logoSummarySelection = selection;
268
+ set clusterSelection(selection: string[]) {
269
+ this._clusterSelection = selection;
241
270
  this.df.tags[C.TAGS.CLUSTER_SELECTION] = JSON.stringify(selection);
242
271
  this.fireBitsetChanged();
243
272
  this.analysisView.grid.invalidate();
@@ -263,24 +292,16 @@ export class PeptidesModel {
263
292
  this.df.tags['distributionSplit'] = `${splitByAARFlag}${flag ? 1 : 0}`;
264
293
  }
265
294
 
266
- get isInvariantMap(): boolean {
267
- return this.df.getTag('isInvariantMap') === '1';
268
- }
269
-
270
- set isInvariantMap(x: boolean) {
271
- this.df.setTag('isInvariantMap', x ? '1' : '0');
272
- }
273
-
274
- get isMutationCliffSelectionEmpty(): boolean {
275
- for (const aarList of Object.values(this.mutationCliffsSelection)) {
295
+ get isMonomerPositionSelectionEmpty(): boolean {
296
+ for (const aarList of Object.values(this.monomerPositionSelection)) {
276
297
  if (aarList.length !== 0)
277
298
  return false;
278
299
  }
279
300
  return true;
280
301
  }
281
302
 
282
- get isLogoSummarySelectionEmpty(): boolean {
283
- return this.logoSummarySelection.length === 0;
303
+ get isClusterSelectionEmpty(): boolean {
304
+ return this.clusterSelection.length === 0;
284
305
  }
285
306
 
286
307
  get customClusters(): Iterable<DG.Column<boolean>> {
@@ -290,7 +311,7 @@ export class PeptidesModel {
290
311
  }
291
312
 
292
313
  get settings(): type.PeptidesSettings {
293
- this._settings ??= JSON.parse(this.df.getTag('settings') || '{}');
314
+ this._settings ??= JSON.parse(this.df.getTag('settings')!);
294
315
  return this._settings;
295
316
  }
296
317
 
@@ -305,17 +326,28 @@ export class PeptidesModel {
305
326
  updateVars.add('mutationCliffs');
306
327
  updateVars.add('stats');
307
328
  break;
308
- // case 'columns':
309
- // updateVars.add('grid');
310
- // break;
329
+ case 'columns':
330
+ updateVars.add('grid');
331
+ break;
311
332
  case 'maxMutations':
312
333
  case 'minActivityDelta':
313
334
  updateVars.add('mutationCliffs');
314
335
  break;
336
+ case 'showDendrogram':
337
+ updateVars.add('dendrogram');
338
+ break;
339
+ case 'showLogoSummaryTable':
340
+ updateVars.add('logoSummaryTable');
341
+ break;
342
+ case 'showMonomerPosition':
343
+ updateVars.add('monomerPosition');
344
+ break;
345
+ case 'showMostPotentResidues':
346
+ updateVars.add('mostPotentResidues');
347
+ break;
315
348
  }
316
349
  }
317
350
  this.df.setTag('settings', JSON.stringify(this._settings));
318
- // this.updateDefault();
319
351
  for (const variable of updateVars) {
320
352
  switch (variable) {
321
353
  case 'activity':
@@ -324,8 +356,8 @@ export class PeptidesModel {
324
356
  case 'mutationCliffs':
325
357
  const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
326
358
  //TODO: set categories ordering the same to share compare indexes instead of strings
327
- const monomerColumns: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
328
- this.substitutionsInfo = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
359
+ const monomerCols: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
360
+ this.mutationCliffs = findMutations(scaledActivityCol.getRawData(), monomerCols, this.settings);
329
361
  break;
330
362
  case 'stats':
331
363
  this.monomerPositionStats = this.calculateMonomerPositionStatistics();
@@ -334,22 +366,34 @@ export class PeptidesModel {
334
366
  this.clusterStats = this.calculateClusterStatistics();
335
367
  break;
336
368
  case 'grid':
337
- this.updateGrid();
369
+ this.postProcessGrids();
370
+ break;
371
+ case 'dendrogram':
372
+ this.settings.showDendrogram ? this.addDendrogram() : this.closeViewer(VIEWER_TYPE.DENDROGRAM);
373
+ break;
374
+ case 'logoSummaryTable':
375
+ this.settings.showLogoSummaryTable ? this.addLogoSummaryTable() :
376
+ this.closeViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE);
377
+ break;
378
+ case 'monomerPosition':
379
+ this.settings.showMonomerPosition ? this.addMonomerPosition() :
380
+ this.closeViewer(VIEWER_TYPE.MONOMER_POSITION);
381
+ break;
382
+ case 'mostPotentResidues':
383
+ this.settings.showMostPotentResidues ? this.addMostPotentResidues() :
384
+ this.closeViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES);
338
385
  break;
339
386
  }
340
387
  }
341
388
 
342
389
  //TODO: handle settings change
343
- this.settingsSubject.next(this.settings);
390
+ this._settingsSubject.next(this.settings);
344
391
  }
345
392
 
346
393
  createMonomerPositionDf(): DG.DataFrame {
347
394
  const positions = this.splitSeqDf.columns.names();
348
395
  const matrixDf = this.matrixDf
349
396
  .groupBy([C.COLUMNS_NAMES.MONOMER])
350
- // .pivot(C.COLUMNS_NAMES.POSITION)
351
- // .add('values')
352
- // .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
353
397
  .aggregate();
354
398
  for (const pos of positions)
355
399
  matrixDf.columns.addNewString(pos);
@@ -384,7 +428,7 @@ export class PeptidesModel {
384
428
  return splitSeqDf;
385
429
  }
386
430
 
387
- getCompoundBitest(): DG.BitSet {
431
+ getCompoundBitset(): DG.BitSet {
388
432
  return this.df.selection.clone().and(this.df.filter);
389
433
  }
390
434
 
@@ -395,17 +439,19 @@ export class PeptidesModel {
395
439
 
396
440
  const acc = ui.accordion();
397
441
  acc.root.style.width = '100%';
398
- const filterAndSelectionBs = trueModel.getCompoundBitest();
442
+ const filterAndSelectionBs = trueModel.getCompoundBitset();
399
443
  const filteredTitlePart = trueModel.df.filter.anyFalse ? ` among ${trueModel.df.filter.trueCount} filtered` : '';
400
444
  acc.addTitle(ui.h1(`${filterAndSelectionBs.trueCount} selected rows${filteredTitlePart}`));
401
445
  if (filterAndSelectionBs.anyTrue) {
402
446
  acc.addPane('Actions', () => {
403
- const newViewButton = ui.button('New view', async () => await trueModel.createNewView(),
447
+ const newViewButton = ui.button('New view', () => trueModel.createNewView(),
404
448
  'Creates a new view from current selection');
405
449
  const newCluster = ui.button('New cluster', () => trueModel._newClusterSubject.next(),
406
450
  'Creates a new cluster from selection');
407
451
  const removeCluster = ui.button('Remove cluster', () => trueModel._removeClusterSubject.next(),
408
452
  'Removes currently selected custom cluster');
453
+ removeCluster.disabled = trueModel.clusterSelection.length === 0 ||
454
+ !wu(this.customClusters).some((c) => trueModel.clusterSelection.includes(c.name));
409
455
  return ui.divV([newViewButton, newCluster, removeCluster]);
410
456
  });
411
457
  }
@@ -423,7 +469,8 @@ export class PeptidesModel {
423
469
 
424
470
  this.createScaledCol();
425
471
 
426
- this.initSelections();
472
+ this.initMonomerPositionFilter({notify: false});
473
+ this.initMonomerPositionSelection({notify: false});
427
474
 
428
475
  this.setWebLogoInteraction();
429
476
  this.webLogoBounds = {};
@@ -437,16 +484,38 @@ export class PeptidesModel {
437
484
  this.postProcessGrids();
438
485
  }
439
486
 
440
- initSelections(): void {
441
- const tempInvariantMapSelection: type.PositionToAARList = this.invariantMapSelection;
442
- const mutationCliffsSelection: type.PositionToAARList = this.mutationCliffsSelection;
487
+ initMonomerPositionFilter(options: {cleanInit?: boolean, notify?: boolean} = {}): void {
488
+ options.cleanInit ??= false;
489
+ options.notify ??= true;
490
+
491
+ const tempFilter: type.PositionToAARList = this.monomerPositionFilter;
443
492
  const positionColumns = this.splitSeqDf.columns.names();
444
493
  for (const pos of positionColumns) {
445
- tempInvariantMapSelection[pos] ??= [];
446
- mutationCliffsSelection[pos] ??= [];
494
+ if (options.cleanInit || !tempFilter.hasOwnProperty(pos))
495
+ tempFilter[pos] = [];
447
496
  }
448
- this.invariantMapSelection = tempInvariantMapSelection;
449
- this.mutationCliffsSelection = mutationCliffsSelection;
497
+
498
+ if (options.notify)
499
+ this.monomerPositionFilter = tempFilter;
500
+ else
501
+ this._monomerPositionFilter = tempFilter;
502
+ }
503
+
504
+ initMonomerPositionSelection(options: {cleanInit?: boolean, notify?: boolean} = {}): void {
505
+ options.cleanInit ??= false;
506
+ options.notify ??= true;
507
+
508
+ const tempSelection: type.PositionToAARList = this.monomerPositionSelection;
509
+ const positionColumns = this.splitSeqDf.columns.names();
510
+ for (const pos of positionColumns) {
511
+ if (options.cleanInit || !tempSelection.hasOwnProperty(pos))
512
+ tempSelection[pos] = [];
513
+ }
514
+
515
+ if (options.notify)
516
+ this.monomerPositionSelection = tempSelection;
517
+ else
518
+ this._monomerPositionSelection = tempSelection;
450
519
  }
451
520
 
452
521
  joinDataFrames(): void {
@@ -505,31 +574,17 @@ export class PeptidesModel {
505
574
 
506
575
  for (const posCol of positionColumns) {
507
576
  const posColData = posCol.getRawData();
508
- const currentMonomerSet = posCol.categories;
577
+ const posColCateogries = posCol.categories;
509
578
  const currentPositionObject = {general: {}} as PositionStats & { general: SummaryStats };
510
579
 
511
- for (const [categoryIndex, monomer] of currentMonomerSet.entries()) {
580
+ for (let categoryIndex = 0; categoryIndex < posColCateogries.length; ++categoryIndex) {
581
+ const monomer = posColCateogries[categoryIndex];
512
582
  if (monomer == '')
513
583
  continue;
514
584
 
515
- const mask: boolean[] = new Array(sourceDfLen);
516
- let trueCount = 0;
517
- for (let j = 0; j < sourceDfLen; ++j) {
518
- mask[j] = posColData[j] == categoryIndex;
519
-
520
- if (mask[j])
521
- ++trueCount;
522
- }
523
-
524
- const maskInfo = {
525
- trueCount: trueCount,
526
- falseCount: sourceDfLen - trueCount,
527
- mask: mask,
528
- };
529
-
530
- const stats = getStats(activityColData, maskInfo);
585
+ const bitArray = BitArray.fromSeq(sourceDfLen, (i: number) => posColData[i] === categoryIndex);
586
+ const stats = getStats(activityColData, bitArray);
531
587
  currentPositionObject[monomer] = stats;
532
-
533
588
  this.getSummaryStats(currentPositionObject.general, stats);
534
589
  }
535
590
  monomerPositionObject[posCol.name] = currentPositionObject;
@@ -538,93 +593,87 @@ export class PeptidesModel {
538
593
  return monomerPositionObject;
539
594
  }
540
595
 
541
- getSummaryStats(generalObj: SummaryStats, stats: Stats | null = null, summaryStats: SummaryStats | null = null): void {
596
+ getSummaryStats(genObj: SummaryStats, stats: Stats | null = null, summaryStats: SummaryStats | null = null): void {
542
597
  if (stats == null && summaryStats == null)
543
598
  throw new Error(`MonomerPositionStatsError: either stats or summaryStats must be present`);
544
599
 
545
600
  const possibleMaxCount = stats?.count ?? summaryStats!.maxCount;
546
- generalObj.maxCount ??= possibleMaxCount;
547
- if (generalObj.maxCount < possibleMaxCount)
548
- generalObj.maxCount = possibleMaxCount;
601
+ genObj.maxCount ??= possibleMaxCount;
602
+ if (genObj.maxCount < possibleMaxCount)
603
+ genObj.maxCount = possibleMaxCount;
549
604
 
550
605
  const possibleMinCount = stats?.count ?? summaryStats!.minCount;
551
- generalObj.minCount ??= possibleMinCount;
552
- if (generalObj.minCount > possibleMinCount)
553
- generalObj.minCount = possibleMinCount;
606
+ genObj.minCount ??= possibleMinCount;
607
+ if (genObj.minCount > possibleMinCount)
608
+ genObj.minCount = possibleMinCount;
554
609
 
555
610
  const possibleMaxMeanDifference = stats?.meanDifference ?? summaryStats!.maxMeanDifference;
556
- generalObj.maxMeanDifference ??= possibleMaxMeanDifference;
557
- if (generalObj.maxMeanDifference < possibleMaxMeanDifference)
558
- generalObj.maxMeanDifference = possibleMaxMeanDifference;
611
+ genObj.maxMeanDifference ??= possibleMaxMeanDifference;
612
+ if (genObj.maxMeanDifference < possibleMaxMeanDifference)
613
+ genObj.maxMeanDifference = possibleMaxMeanDifference;
559
614
 
560
615
  const possibleMinMeanDifference = stats?.meanDifference ?? summaryStats!.minMeanDifference;
561
- generalObj.minMeanDifference ??= possibleMinMeanDifference;
562
- if (generalObj.minMeanDifference > possibleMinMeanDifference)
563
- generalObj.minMeanDifference = possibleMinMeanDifference;
616
+ genObj.minMeanDifference ??= possibleMinMeanDifference;
617
+ if (genObj.minMeanDifference > possibleMinMeanDifference)
618
+ genObj.minMeanDifference = possibleMinMeanDifference;
564
619
 
565
620
  const possibleMaxPValue = stats?.pValue ?? summaryStats!.maxPValue;
566
- generalObj.maxPValue ??= possibleMaxPValue;
567
- if (generalObj.maxPValue < possibleMaxPValue)
568
- generalObj.maxPValue = possibleMaxPValue;
621
+ genObj.maxPValue ??= possibleMaxPValue;
622
+ if (genObj.maxPValue < possibleMaxPValue)
623
+ genObj.maxPValue = possibleMaxPValue;
569
624
 
570
625
  const possibleMinPValue = stats?.pValue ?? summaryStats!.minPValue;
571
- generalObj.minPValue ??= possibleMinPValue;
572
- if (generalObj.minPValue > possibleMinPValue)
573
- generalObj.minPValue = possibleMinPValue;
626
+ genObj.minPValue ??= possibleMinPValue;
627
+ if (genObj.minPValue > possibleMinPValue)
628
+ genObj.minPValue = possibleMinPValue;
574
629
 
575
630
  const possibleMaxRatio = stats?.ratio ?? summaryStats!.maxRatio;
576
- generalObj.maxRatio ??= possibleMaxRatio;
577
- if (generalObj.maxRatio < possibleMaxRatio)
578
- generalObj.maxRatio = possibleMaxRatio;
631
+ genObj.maxRatio ??= possibleMaxRatio;
632
+ if (genObj.maxRatio < possibleMaxRatio)
633
+ genObj.maxRatio = possibleMaxRatio;
579
634
 
580
635
  const possibleMinRatio = stats?.ratio ?? summaryStats!.minRatio;
581
- generalObj.minRatio ??= possibleMinRatio;
582
- if (generalObj.minRatio > possibleMinRatio)
583
- generalObj.minRatio = possibleMinRatio;
636
+ genObj.minRatio ??= possibleMinRatio;
637
+ if (genObj.minRatio > possibleMinRatio)
638
+ genObj.minRatio = possibleMinRatio;
584
639
  }
585
640
 
586
- calculateClusterStatistics(): {[cluster: string]: Stats} {
587
- const originalClustersCol = this.df.getCol(this.settings.clustersColumnName!);
588
- const originalClustersColData = originalClustersCol.getRawData();
589
- const originalClustersColCategories = originalClustersCol.categories;
641
+ calculateClusterStatistics(): ClusterTypeStats {
642
+ const rowCount = this.df.rowCount;
590
643
 
591
- const customClustersColumnsList = wu(this.customClusters).toArray();
644
+ const origClustCol = this.df.getCol(this.settings.clustersColumnName!);
645
+ const origClustColData = origClustCol.getRawData();
646
+ const origClustColCat = origClustCol.categories;
647
+ const origClustMasks: BitArray[] = Array.from({length: origClustColCat.length},
648
+ () => BitArray.fromSeq(rowCount, (_: number) => false));
649
+ for (let rowIdx = 0; rowIdx < rowCount; ++rowIdx)
650
+ origClustMasks[origClustColData[rowIdx]].setTrue(rowIdx);
592
651
 
593
- const activityColData: type.RawData = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
594
- const activityColLen = activityColData.length;
595
-
596
- // const resultStats: Stats[] = new Array(originalClustersColCategories.length + customClustersColumnsList.length);
597
- const resultStats: {[cluster: string]: Stats} = {};
598
-
599
- const clusterCount = originalClustersColCategories.length + customClustersColumnsList.length;
600
- for (let clusterIdx = 0; clusterIdx < clusterCount; ++clusterIdx) {
601
- const customClusterIdx = clusterIdx - originalClustersColCategories.length;
602
- const customClusterColData = customClustersColumnsList[customClusterIdx]?.toList();
603
- const isAcitvityIdxValid = customClusterIdx < 0 ?
604
- (i: number) => clusterIdx == originalClustersColData[i] :
605
- (i: number) => customClusterColData[i];
606
-
607
- const mask: boolean[] = new Array(activityColLen);
608
- let trueCount = 0;
609
- for (let maskIdx = 0; maskIdx < activityColLen; ++maskIdx) {
610
- mask[maskIdx] = isAcitvityIdxValid(maskIdx);
611
-
612
- if (mask[maskIdx])
613
- ++trueCount;
614
- }
615
652
 
616
- const maskInfo = {
617
- trueCount: trueCount,
618
- falseCount: activityColLen - trueCount,
619
- mask: mask,
620
- };
653
+ const customClustColList = wu(this.customClusters).toArray();
654
+ const customClustMasks = customClustColList.map(
655
+ (v) => BitArray.fromUint32Array(rowCount, v.getRawData() as Uint32Array));
656
+ const customClustColNamesList = customClustColList.map((v) => v.name);
657
+
658
+ const activityColData: type.RawData = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
621
659
 
622
- const stats = getStats(activityColData, maskInfo);
623
- const clusterName = customClusterIdx < 0 ? originalClustersColCategories[clusterIdx] :
624
- customClustersColumnsList[customClusterIdx].name;
625
- resultStats[clusterName] = stats;
660
+ const origClustStats: ClusterStats = {};
661
+ const customClustStats: ClusterStats = {};
662
+
663
+ for (let clustType = 0; clustType < 2; ++clustType) {
664
+ const masks = clustType == 0 ? origClustMasks : customClustMasks;
665
+ const clustNames = clustType == 0 ? origClustColCat : customClustColNamesList;
666
+ const resultStats = clustType == 0 ? origClustStats : customClustStats;
667
+ for (let maskIdx = 0; maskIdx < masks.length; ++maskIdx) {
668
+ const mask = masks[maskIdx];
669
+ const stats = getStats(activityColData, mask);
670
+ resultStats[clustNames[maskIdx]] = stats;
671
+ }
626
672
  }
627
673
 
674
+ const resultStats = {} as ClusterTypeStats;
675
+ resultStats[CLUSTER_TYPE.ORIGINAL] = origClustStats;
676
+ resultStats[CLUSTER_TYPE.CUSTOM] = customClustStats;
628
677
  return resultStats;
629
678
  }
630
679
 
@@ -671,18 +720,23 @@ export class PeptidesModel {
671
720
  }
672
721
 
673
722
  modifyClusterSelection(cluster: string): void {
674
- const tempSelection = this.logoSummarySelection;
723
+ const tempSelection = this.clusterSelection;
675
724
  const idx = tempSelection.indexOf(cluster);
676
725
  if (idx !== -1)
677
726
  tempSelection.splice(idx, 1);
678
727
  else
679
728
  tempSelection.push(cluster);
680
729
 
681
- this.logoSummarySelection = tempSelection;
730
+ this.clusterSelection = tempSelection;
682
731
  }
683
732
 
684
- initClusterSelection(cluster: string): void {
685
- this.logoSummarySelection = [cluster];
733
+ initClusterSelection(options: {notify?: boolean} = {}): void {
734
+ options.notify ??= true;
735
+
736
+ if (options.notify)
737
+ this.clusterSelection = [];
738
+ else
739
+ this._clusterSelection = [];
686
740
  }
687
741
 
688
742
  setWebLogoInteraction(): void {
@@ -714,14 +768,16 @@ export class PeptidesModel {
714
768
  return null;
715
769
  }
716
770
 
717
- requestBarchartAction(ev: MouseEvent, barPart: { position: string, monomer: string } | null): void {
771
+ requestBarchartAction(ev: MouseEvent, barPart: {position: string, monomer: string} | null): void {
718
772
  if (!barPart)
719
773
  return;
720
774
  const monomer = barPart.monomer;
721
775
  const position = barPart.position;
722
776
  if (ev.type === 'click') {
723
- ev.shiftKey ? this.modifyMonomerPositionSelection(monomer, position, false) :
724
- this.initMonomerPositionSelection(monomer, position, false);
777
+ if (!ev.shiftKey)
778
+ this.initMonomerPositionSelection({cleanInit: true, notify: false});
779
+
780
+ this.modifyMonomerPositionSelection(monomer, position, false);
725
781
  } else {
726
782
  const bar = `${position} = ${monomer}`;
727
783
  if (this.cachedWebLogoTooltip.bar == bar)
@@ -760,12 +816,13 @@ export class PeptidesModel {
760
816
  return 0;
761
817
  }).filter((v) => v != 'general');
762
818
 
763
- this.webLogoBounds[col.name] =
764
- CR.drawLogoInBounds(ctx, bounds, stats, sortedStatsOrder, this.df.rowCount, this.cp, this.headerSelectedMonomers[col.name]);
819
+ this.webLogoBounds[col.name] = CR.drawLogoInBounds(ctx, bounds, stats, sortedStatsOrder, this.df.rowCount,
820
+ this.cp, this.headerSelectedMonomers[col.name]);
765
821
  gcArgs.preventDefault();
766
822
  }
767
823
  } catch (e) {
768
- console.warn(`PeptidesHeaderLogoError: couldn't render WebLogo for column \`${col!.name}\`. See original error below.`);
824
+ console.warn(`PeptidesHeaderLogoError: couldn't render WebLogo for column \`${col!.name}\`. ` +
825
+ `See original error below.`);
769
826
  console.warn(e);
770
827
  } finally {
771
828
  ctx.restore();
@@ -783,12 +840,12 @@ export class PeptidesModel {
783
840
  });
784
841
  }
785
842
 
786
- showMonomerTooltip(aar: string, x: number, y: number): void {
843
+ showMonomerTooltip(aar: string, x: number, y: number): boolean {
787
844
  const tooltipElements: HTMLDivElement[] = [];
788
845
  const monomerName = aar.toLowerCase();
789
846
 
790
- const mw = getMonomerWorks();
791
- const mol = mw?.getCappedRotatedMonomer('PEPTIDE', aar);
847
+ const mw = getMonomerWorksInstance();
848
+ const mol = mw.getCappedRotatedMonomer('PEPTIDE', aar);
792
849
 
793
850
  if (mol) {
794
851
  tooltipElements.push(ui.div(monomerName));
@@ -798,6 +855,8 @@ export class PeptidesModel {
798
855
  tooltipElements.push(ui.div(aar));
799
856
 
800
857
  ui.tooltip.show(ui.divV(tooltipElements), x, y);
858
+
859
+ return mol !== null;
801
860
  }
802
861
 
803
862
  //TODO: move out to viewer code
@@ -807,92 +866,44 @@ export class PeptidesModel {
807
866
  return null;
808
867
 
809
868
  const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
810
- //TODO: use bitset instead of splitCol
811
- const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
812
- const currentPosCol = this.df.getCol(position);
813
- const indexes: number[] = [];
814
- splitCol.init((i) => {
815
- const sameMonomer = currentPosCol.get(i) == aar;
816
- if (sameMonomer)
817
- indexes.push(i);
818
-
819
- return sameMonomer;
820
- });
821
- const colResults: { [colName: string]: number } = {};
822
- for (const [col, agg] of Object.entries(this.settings.columns || {})) {
823
- const currentCol = this.df.getCol(col);
824
- const currentColData = currentCol.getRawData();
825
- const tempCol = DG.Column.float('', indexes.length);
826
- tempCol.init((i) => currentColData[indexes[i]]);
827
- colResults[`${agg}(${col})`] = tempCol.stats[agg as keyof DG.Stats] as number;
828
- }
829
-
830
- const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
831
- const das = getDistributionAndStats(distributionTable, stats, `${position} : ${aar}`, 'Other', true);
832
- const resultMap: { [key: string]: any } = {...das.tableMap, ...colResults};
833
- const distroStatsElem = wrapDistroAndStatsDefault(das.labels, das.histRoot, resultMap);
869
+ const posCol = this.df.getCol(position);
870
+ const posColCategories = posCol.categories;
871
+ const aarCategoryIndex = posColCategories.indexOf(aar);
872
+ const posColData = posCol.getRawData();
873
+ const mask = DG.BitSet.create(activityCol.length, (i) => posColData[i] === aarCategoryIndex);
874
+
875
+ const distributionTable = DG.DataFrame.fromColumns(
876
+ [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
877
+ const labels = getDistributionLegend(`${position} : ${aar}`, 'Other');
878
+ const hist = getActivityDistribution(distributionTable, true);
879
+ const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
880
+ const aggregatedColMap = this.getAggregatedColumnValues({mask: mask, fractionDigits: 2});
881
+
882
+ const resultMap = {...tableMap, ...aggregatedColMap};
883
+ const distroStatsElem = getStatsSummary(labels, hist, resultMap, true);
834
884
 
835
885
  ui.tooltip.show(distroStatsElem, x, y);
836
886
 
837
887
  return distroStatsElem;
838
888
  }
839
889
 
840
- showTooltipCluster(cluster: number, x: number, y: number, clusterName: string): HTMLDivElement | null {
841
- const bs = this.df.filter;
842
- const filteredDf = bs.anyFalse ? this.df.clone(bs) : this.df;
843
-
844
- const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
845
- const activityColData = activityCol.getRawData();
846
- //TODO: use bitset instead of splitCol
847
- const clusterCol = filteredDf.getCol(this.settings.clustersColumnName!);
848
- const clusterColData = clusterCol.getRawData();
849
- let splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
850
- const indexes: number[] = [];
851
- splitCol.init((i) => {
852
- const result = clusterColData[i] == cluster;
853
- if (result)
854
- indexes.push(i);
855
- return result;
856
- });
857
- if (splitCol.max == 0)
858
- splitCol = filteredDf.getCol(clusterName);
859
- const distDf = DG.DataFrame.fromColumns([activityCol, splitCol]);
860
-
861
- let stats: Stats;
862
- if (bs.anyFalse) {
863
- const trueCount = splitCol.stats.sum;
864
- const maskInfo = {
865
- trueCount: trueCount,
866
- falseCount: activityColData.length - trueCount,
867
- mask: splitCol.toList() as boolean[],
868
- };
869
- stats = getStats(activityColData, maskInfo);
870
- } else
871
- stats = this.clusterStats[clusterName];
890
+ getAggregatedColumnValues(options: {filterDf?: boolean, mask?: DG.BitSet, fractionDigits?: number} = {},
891
+ ): StringDictionary {
892
+ options.filterDf ??= false;
872
893
 
873
- if (!stats.count)
874
- return null;
894
+ const filteredDf = options.filterDf && this.df.filter.anyFalse ? this.df.clone(this.df.filter) : this.df;
875
895
 
876
- const colResults: {[colName: string]: number} = {};
877
- for (const [col, agg] of Object.entries(this.settings.columns || {})) {
878
- const currentCol = filteredDf.getCol(col);
879
- const currentColData = currentCol.getRawData();
880
- const tempCol = DG.Column.float('', indexes.length);
881
- tempCol.init((i) => currentColData[indexes[i]]);
882
- colResults[`${agg}(${col})`] = tempCol.stats[agg as keyof DG.Stats] as number;
896
+ const colResults: StringDictionary = {};
897
+ for (const [colName, aggFn] of Object.entries(this.settings.columns!)) {
898
+ const newColName = getAggregatedColName(aggFn, colName);
899
+ const value = getAggregatedValue(filteredDf.getCol(colName), aggFn, options.mask);
900
+ colResults[newColName] = value.toFixed(options.fractionDigits);
883
901
  }
884
-
885
- const das = getDistributionAndStats(distDf, stats, `Cluster: ${clusterName}`, 'Other', true, splitCol.name);
886
- const resultMap: {[key: string]: any} = {...das.tableMap, ...colResults};
887
- const tooltip = wrapDistroAndStatsDefault(das.labels, das.histRoot, resultMap, true);
888
-
889
- ui.tooltip.show(tooltip, x, y);
890
-
891
- return tooltip;
902
+ return colResults;
892
903
  }
893
904
 
894
- modifyMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
895
- const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
905
+ modifyMonomerPositionSelection(aar: string, position: string, isFilter: boolean): void {
906
+ const tempSelection = isFilter ? this.monomerPositionFilter : this.monomerPositionSelection;
896
907
  const tempSelectionAt = tempSelection[position];
897
908
  const aarIndex = tempSelectionAt.indexOf(aar);
898
909
  if (aarIndex === -1)
@@ -900,22 +911,10 @@ export class PeptidesModel {
900
911
  else
901
912
  tempSelectionAt.splice(aarIndex, 1);
902
913
 
903
- if (isInvariantMapSelection)
904
- this.invariantMapSelection = tempSelection;
914
+ if (isFilter)
915
+ this.monomerPositionFilter = tempSelection;
905
916
  else
906
- this.mutationCliffsSelection = tempSelection;
907
- }
908
-
909
- initMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
910
- const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
911
- for (const key of Object.keys(tempSelection))
912
- tempSelection[key] = [];
913
- tempSelection[position] = [aar];
914
-
915
- if (isInvariantMapSelection)
916
- this.invariantMapSelection = tempSelection;
917
- else
918
- this.mutationCliffsSelection = tempSelection;
917
+ this.monomerPositionSelection = tempSelection;
919
918
  }
920
919
 
921
920
  setBitsetCallback(): void {
@@ -925,51 +924,56 @@ export class PeptidesModel {
925
924
  const filter = this.df.filter;
926
925
  const clusterCol = this.df.col(this.settings.clustersColumnName!);
927
926
 
928
- const changeSelectionBitset = (currentBitset: DG.BitSet): void => {
929
- const clusterColCategories = clusterCol?.categories;
930
- const clusterColData = clusterCol?.getRawData();
931
-
932
- const edfSelection = this.edf?.selection;
933
- if (this.isPeptideSpaceChangingBitset) {
934
- if (edfSelection == null)
935
- return;
936
-
937
- currentBitset.init((i) => edfSelection.get(i) || false, false);
938
- return;
939
- }
927
+ const changeSelectionBitset = (currentBitset: DG.BitSet, posList: type.RawColumn[], clustColCat: string[],
928
+ clustColData: type.RawData, customClust: {[key: string]: BitArray}): void => {
929
+ const getBitAt = (i: number): boolean => {
930
+ for (const posRawCol of posList) {
931
+ if (this.monomerPositionSelection[posRawCol.name].includes(posRawCol.cat![posRawCol.rawData[i]]))
932
+ return true;
933
+ }
940
934
 
941
- const updateEdfSelection = (): void => {
942
- this.isChangingEdfBitset = true;
943
- edfSelection?.copyFrom(currentBitset);
944
- this.isChangingEdfBitset = false;
945
- };
935
+ const currentOrigClust = clustColCat[clustColData[i]];
936
+ if (typeof currentOrigClust === undefined)
937
+ return false;
946
938
 
947
- const positionList = Object.keys(this.mutationCliffsSelection);
939
+ for (const clust of this.clusterSelection) {
940
+ if (clust === currentOrigClust)
941
+ return true;
948
942
 
949
- //TODO: move out
950
- const getBitAt = (i: number): boolean => {
951
- for (const position of positionList) {
952
- const positionCol: DG.Column<string> = this.df.getCol(position);
953
- if (this.mutationCliffsSelection[position].includes(positionCol.get(i)!))
943
+ if (Object.hasOwn(customClust, clust) && customClust[clust].getBit(i))
954
944
  return true;
955
945
  }
956
- return (typeof clusterColData != 'undefined' && typeof clusterColCategories != 'undefined' &&
957
- this.logoSummarySelection.includes(clusterColCategories![clusterColData![i]])) ||
958
- this.logoSummarySelection.some((cluster) => this.df.columns.contains(cluster) && this.df.get(cluster, i));
946
+
947
+ return false;
959
948
  };
960
949
  currentBitset.init((i) => getBitAt(i), false);
961
-
962
- updateEdfSelection();
963
950
  };
964
951
 
965
- selection.onChanged.subscribe(() => changeSelectionBitset(selection));
952
+ selection.onChanged.subscribe(() => {
953
+ if (this.isUserChangedSelection)
954
+ return;
955
+
956
+ const positionList: type.RawColumn[] = Object.keys(this.monomerPositionSelection).map((pos) => {
957
+ const posCol = this.df.getCol(pos);
958
+ return {name: pos, cat: posCol.categories, rawData: posCol.getRawData()};
959
+ });
960
+
961
+ const clustColCat = clusterCol?.categories ?? [];
962
+ const clustColData = clusterCol?.getRawData() ?? new Int32Array(0);
963
+ const customClust: {[key: string]: BitArray} = {};
964
+ const rowCount = this.df.rowCount;
965
+ for (const clust of this.customClusters)
966
+ customClust[clust.name] = BitArray.fromUint32Array(rowCount, clust.getRawData() as Uint32Array);
967
+
968
+ changeSelectionBitset(selection, positionList, clustColCat, clustColData, customClust);
969
+ });
966
970
 
967
971
  filter.onChanged.subscribe(() => {
968
- const positionList = Object.keys(this.invariantMapSelection);
972
+ const positionList = Object.keys(this.monomerPositionFilter);
969
973
  const invariantMapBitset = DG.BitSet.create(filter.length, (index) => {
970
974
  let result = true;
971
975
  for (const position of positionList) {
972
- const aarList = this.invariantMapSelection[position];
976
+ const aarList = this.monomerPositionFilter[position];
973
977
  result &&= aarList.length === 0 || aarList.includes(this.df.get(position, index));
974
978
  if (!result)
975
979
  return result;
@@ -988,8 +992,8 @@ export class PeptidesModel {
988
992
  this.isBitsetChangedInitialized = true;
989
993
  }
990
994
 
991
- fireBitsetChanged(isPeptideSpaceSource: boolean = false, fireFilterChanged: boolean = false): void {
992
- this.isPeptideSpaceChangingBitset = isPeptideSpaceSource;
995
+ fireBitsetChanged(fireFilterChanged: boolean = false): void {
996
+ this.isUserChangedSelection = false;
993
997
  this.df.selection.fireChanged();
994
998
  if (fireFilterChanged)
995
999
  this.df.filter.fireChanged();
@@ -1002,8 +1006,7 @@ export class PeptidesModel {
1002
1006
  for (const pane of acc.panes)
1003
1007
  pane.expanded = true;
1004
1008
  }
1005
-
1006
- this.isPeptideSpaceChangingBitset = false;
1009
+ this.isUserChangedSelection = true;
1007
1010
  }
1008
1011
 
1009
1012
  postProcessGrids(): void {
@@ -1015,7 +1018,6 @@ export class PeptidesModel {
1015
1018
  const sourceGridProps = sourceGrid.props;
1016
1019
  sourceGridProps.allowColSelection = false;
1017
1020
  sourceGridProps.allowEdit = false;
1018
- sourceGridProps.allowRowResizing = false;
1019
1021
  sourceGridProps.showCurrentRowIndicator = false;
1020
1022
  this.df.temp[C.EMBEDDING_STATUS] = false;
1021
1023
  for (let colIdx = 1; colIdx < sourceGridColsLen; ++colIdx) {
@@ -1023,7 +1025,41 @@ export class PeptidesModel {
1023
1025
  const tableColName = gridCol.column!.name;
1024
1026
  gridCol.visible = posCols.includes(tableColName) || (tableColName === C.COLUMNS_NAMES.ACTIVITY_SCALED) ||
1025
1027
  visibleColumns.includes(tableColName);
1026
- gridCol.width = 60;
1028
+ }
1029
+ }
1030
+
1031
+ closeViewer(viewerType: VIEWER_TYPE): void {
1032
+ const viewer = this.findViewer(viewerType);
1033
+ viewer?.detach();
1034
+ viewer?.close();
1035
+ }
1036
+
1037
+ findViewerNode(viewerType: VIEWER_TYPE): DG.DockNode | null {
1038
+ for (const node of this.analysisView.dockManager.rootNode.children) {
1039
+ if (node.container.containerElement.innerHTML.includes(viewerType))
1040
+ return node;
1041
+ }
1042
+ return null;
1043
+ }
1044
+
1045
+ async addDendrogram(): Promise<void> {
1046
+ const pi = DG.TaskBarProgressIndicator.create('Calculating distance matrix...');
1047
+ try {
1048
+ const pepColValues: string[] = this.df.getCol(this.settings.sequenceColumnName!).toList();
1049
+ this._dm ??= new DistanceMatrix(await createDistanceMatrixWorker(pepColValues, StringMetricsNames.Levenshtein));
1050
+ const leafCol = this.df.col('~leaf-id') ?? this.df.columns.addNewString('~leaf-id').init((i) => i.toString());
1051
+ const treeNode = await this.treeHelper.hierarchicalClusteringByDistance(this._dm, 'ward');
1052
+
1053
+ this.df.setTag(treeTAGS.NEWICK, this.treeHelper.toNewick(treeNode));
1054
+ const leafOrdering = this.treeHelper.getLeafList(treeNode).map((leaf) => parseInt(leaf.name));
1055
+ this.analysisView.grid.setRowOrder(leafOrdering);
1056
+ const dendrogramViewer = await this.df.plot.fromType('Dendrogram', {nodeColumnName: leafCol.name}) as DG.JsViewer;
1057
+
1058
+ this.analysisView.dockManager.dock(dendrogramViewer, DG.DOCK_TYPE.LEFT, null, 'Dendrogram', 0.25);
1059
+ } catch (e) {
1060
+ _package.logger.error(e as string);
1061
+ } finally {
1062
+ pi.close();
1027
1063
  }
1028
1064
  }
1029
1065
 
@@ -1051,41 +1087,61 @@ export class PeptidesModel {
1051
1087
  const settingsButton = ui.iconFA('wrench', () => getSettingsDialog(this), 'Peptides analysis settings');
1052
1088
  this.analysisView.setRibbonPanels([[settingsButton]], false);
1053
1089
  this.isRibbonSet = true;
1090
+ grok.events.onResetFilterRequest.subscribe(() => {
1091
+ this.isInvariantMapTrigger = true;
1092
+ this.initMonomerPositionFilter({cleanInit: true});
1093
+ this.isInvariantMapTrigger = false;
1094
+ });
1054
1095
  }
1055
1096
 
1056
1097
  this.updateGrid();
1057
- this.fireBitsetChanged(false, true);
1098
+ this.fireBitsetChanged(true);
1058
1099
  this.analysisView.grid.invalidate();
1059
1100
  }
1060
1101
 
1061
- async addViewers(): Promise<void> {
1062
- const dockManager = this.analysisView.dockManager;
1063
- const dfPlt = this.df.plot;
1064
-
1065
- const mutationCliffsViewer = await dfPlt.fromType('peptide-sar-viewer') as MonomerPosition;
1066
- const mostPotentResiduesViewer = await dfPlt.fromType('peptide-sar-viewer-vertical') as MostPotentResiduesViewer;
1067
- if (this.settings.clustersColumnName)
1068
- await this.addLogoSummaryTableViewer();
1102
+ findViewer(viewerType: VIEWER_TYPE): DG.Viewer | null {
1103
+ return wu(this.analysisView.viewers).find((v) => v.type === viewerType) || null;
1104
+ }
1069
1105
 
1070
- const mcNode = dockManager.dock(mutationCliffsViewer, DG.DOCK_TYPE.DOWN, null, mutationCliffsViewer.name);
1106
+ async addLogoSummaryTable(): Promise<void> {
1107
+ this.closeViewer(VIEWER_TYPE.MONOMER_POSITION);
1108
+ this.closeViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES);
1109
+ const logoSummaryTable = await this.df.plot.fromType(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable;
1110
+ this.analysisView.dockManager.dock(logoSummaryTable, DG.DOCK_TYPE.RIGHT, null, VIEWER_TYPE.LOGO_SUMMARY_TABLE);
1111
+ if (this.settings.showMonomerPosition)
1112
+ await this.addMonomerPosition();
1113
+ if (this.settings.showMostPotentResidues)
1114
+ await this.addMostPotentResidues();
1115
+ }
1071
1116
 
1072
- dockManager.dock(mostPotentResiduesViewer, DG.DOCK_TYPE.RIGHT, mcNode, mostPotentResiduesViewer.name, 0.3);
1117
+ async addMonomerPosition(): Promise<void> {
1118
+ const monomerPosition = await this.df.plot.fromType(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition;
1119
+ const mostPotentResidues = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResiduesViewer | null;
1120
+ const dm = this.analysisView.dockManager;
1121
+ const [dockType, refNode, ratio] = mostPotentResidues === null ? [DG.DOCK_TYPE.DOWN, null, undefined] :
1122
+ [DG.DOCK_TYPE.LEFT, this.findViewerNode(VIEWER_TYPE.MOST_POTENT_RESIDUES), 0.7];
1123
+ dm.dock(monomerPosition, dockType, refNode, VIEWER_TYPE.MONOMER_POSITION, ratio);
1073
1124
  }
1074
1125
 
1075
- async addLogoSummaryTableViewer(): Promise<void> {
1076
- const logoSummary = await this.df.plot.fromType('logo-summary-viewer') as LogoSummary;
1077
- this.analysisView.dockManager.dock(logoSummary, DG.DOCK_TYPE.RIGHT, null, 'Logo Summary Table');
1126
+ async addMostPotentResidues(): Promise<void> {
1127
+ const mostPotentResidues =
1128
+ await this.df.plot.fromType(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResiduesViewer;
1129
+ const monomerPosition = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
1130
+ const dm = this.analysisView.dockManager;
1131
+ const [dockType, refNode, ratio] = monomerPosition === null ? [DG.DOCK_TYPE.DOWN, null, undefined] :
1132
+ [DG.DOCK_TYPE.RIGHT, this.findViewerNode(VIEWER_TYPE.MONOMER_POSITION), 0.3];
1133
+ dm.dock(mostPotentResidues, dockType, refNode, VIEWER_TYPE.MOST_POTENT_RESIDUES, ratio);
1078
1134
  }
1079
1135
 
1080
1136
  addNewCluster(clusterName: string): void {
1081
- const newClusterCol = DG.Column.fromBitSet(clusterName, this.getCompoundBitest());
1137
+ const newClusterCol = DG.Column.fromBitSet(clusterName, this.getCompoundBitset());
1082
1138
  newClusterCol.setTag(C.TAGS.CUSTOM_CLUSTER, '1');
1083
1139
  this.df.columns.add(newClusterCol);
1084
1140
  this.analysisView.grid.col(newClusterCol.name)!.visible = false;
1085
1141
  }
1086
1142
 
1087
- async createNewView(): Promise<void> {
1088
- const rowMask = this.getCompoundBitest();
1143
+ createNewView(): void {
1144
+ const rowMask = this.getCompoundBitset();
1089
1145
  if (!rowMask.anyTrue)
1090
1146
  return grok.shell.warning('Cannot create a new view, there are no visible selected rows in your dataset');
1091
1147