@datagrok/peptides 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/model.ts CHANGED
@@ -9,8 +9,8 @@ import * as rxjs from 'rxjs';
9
9
 
10
10
  import * as C from './utils/constants';
11
11
  import * as type from './utils/types';
12
- import {calculateSelected, isGridCellInvalid, scaleActivity} from './utils/misc';
13
- import {MutationCliffsViewer, MostPotentResiduesViewer} from './viewers/sar-viewer';
12
+ import {calculateSelected, extractMonomerInfo, scaleActivity} from './utils/misc';
13
+ import {MonomerPosition, MostPotentResiduesViewer} from './viewers/sar-viewer';
14
14
  import * as CR from './utils/cell-renderer';
15
15
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
16
16
  import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
@@ -24,30 +24,24 @@ import {findMutations} from './utils/algorithms';
24
24
  export class PeptidesModel {
25
25
  static modelName = 'peptidesModel';
26
26
 
27
- mutationCliffsGridSubject = new rxjs.Subject<DG.Grid>();
28
- mostPotentResiduesGridSubject = new rxjs.Subject<DG.Grid>();
29
- logoSummaryGridSubject = new rxjs.Subject<DG.Grid>();
30
- settingsSubject = new rxjs.Subject();
27
+ settingsSubject: rxjs.Subject<type.PeptidesSettings> = new rxjs.Subject();
28
+ _mutatinCliffsSelectionSubject: rxjs.Subject<undefined> = new rxjs.Subject();
31
29
 
32
30
  _isUpdating: boolean = false;
33
31
  isBitsetChangedInitialized = false;
34
32
  isCellChanging = false;
35
33
 
36
- mutationCliffsGrid!: DG.Grid;
37
- mostPotentResiduesGrid!: DG.Grid;
38
- // logoSummaryGrid!: DG.Grid;
39
- sourceGrid!: DG.Grid;
40
34
  df: DG.DataFrame;
41
35
  splitCol!: DG.Column<boolean>;
42
36
  edf: DG.DataFrame | null = null;
43
- monomerPositionStatsDf!: DG.DataFrame;
44
- clusterStatsDf!: DG.DataFrame;
37
+ _monomerPositionStatsDf?: DG.DataFrame;
38
+ _clusterStatsDf?: DG.DataFrame;
45
39
  _mutationCliffsSelection!: type.PositionToAARList;
46
40
  _invariantMapSelection!: type.PositionToAARList;
47
41
  _logoSummarySelection!: number[];
48
- substitutionsInfo: type.SubstitutionsInfo = new Map();
42
+ _substitutionsInfo?: type.SubstitutionsInfo;
49
43
  isInitialized = false;
50
- currentView!: DG.TableView;
44
+ _analysisView?: DG.TableView;
51
45
 
52
46
  isPeptideSpaceChangingBitset = false;
53
47
  isChangingEdfBitset = false;
@@ -59,38 +53,122 @@ export class PeptidesModel {
59
53
  _settings!: type.PeptidesSettings;
60
54
  isRibbonSet = false;
61
55
 
62
- cp: bio.SeqPalette;
56
+ _cp?: bio.SeqPalette;
63
57
  initBitset: DG.BitSet;
64
58
  isInvariantMapTrigger: boolean = false;
65
59
  headerSelectedMonomers: type.MonomerSelectionStats = {};
66
60
  webLogoBounds: {[positon: string]: {[monomer: string]: DG.Rect}} = {};
67
61
  cachedWebLogoTooltip: {bar: string; tooltip: HTMLDivElement | null;} = {bar: '', tooltip: null};
62
+ _monomerPositionDf?: DG.DataFrame;
63
+ _alphabet?: string;
64
+ _mostPotentResiduesDf?: DG.DataFrame;
65
+ _matrixDf?: DG.DataFrame;
66
+ _splitSeqDf?: DG.DataFrame;
68
67
 
69
68
  private constructor(dataFrame: DG.DataFrame) {
70
69
  this.df = dataFrame;
71
70
  this.initBitset = this.df.filter.clone();
72
- this.cp = bio.pickUpPalette(this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE));
73
71
  }
74
72
 
75
- static async getInstance(dataFrame: DG.DataFrame): Promise<PeptidesModel> {
73
+ static getInstance(dataFrame: DG.DataFrame): PeptidesModel {
76
74
  dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
77
- await (dataFrame.temp[PeptidesModel.modelName] as PeptidesModel).init();
75
+ (dataFrame.temp[PeptidesModel.modelName] as PeptidesModel).init();
78
76
  return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
79
77
  }
80
78
 
81
- get onMutationCliffsGridChanged(): rxjs.Observable<DG.Grid> {
82
- return this.mutationCliffsGridSubject.asObservable();
79
+ get monomerPositionDf(): DG.DataFrame {
80
+ this._monomerPositionDf ??= this.createMonomerPositionDf();
81
+ return this._monomerPositionDf;
82
+ }
83
+ set monomerPositionDf(df: DG.DataFrame) {
84
+ this._monomerPositionDf = df;
85
+ }
86
+
87
+ get monomerPositionStatsDf(): DG.DataFrame {
88
+ this._monomerPositionStatsDf ??= this.calculateMonomerPositionStatistics();
89
+ return this._monomerPositionStatsDf;
90
+ }
91
+ set monomerPositionStatsDf(df: DG.DataFrame) {
92
+ this._monomerPositionStatsDf = df;
93
+ }
94
+
95
+ get matrixDf(): DG.DataFrame {
96
+ this._matrixDf ??= this.buildMatrixDf();
97
+ return this._matrixDf;
98
+ }
99
+ set matrixDf(df: DG.DataFrame) {
100
+ this._matrixDf = df;
101
+ }
102
+
103
+ get splitSeqDf(): DG.DataFrame {
104
+ this._splitSeqDf ??= this.buildSplitSeqDf();
105
+ return this._splitSeqDf;
106
+ }
107
+ set splitSeqDf(df: DG.DataFrame) {
108
+ this._splitSeqDf = df;
109
+ }
110
+
111
+ get mostPotentResiduesDf(): DG.DataFrame {
112
+ this._mostPotentResiduesDf ??= this.createMostPotentResiduesDf();
113
+ return this._mostPotentResiduesDf;
114
+ }
115
+ set mostPotentResiduesDf(df: DG.DataFrame) {
116
+ this._mostPotentResiduesDf = df;
83
117
  }
84
118
 
85
- get onMostPotentResiduesGridChanged(): rxjs.Observable<DG.Grid> {
86
- return this.mostPotentResiduesGridSubject.asObservable();
119
+ get alphabet(): string {
120
+ const col = this.settings.sequenceColumnName ? this.df.getCol(this.settings.sequenceColumnName) :
121
+ this.df.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!;
122
+ return col.getTag(bio.TAGS.alphabet);
87
123
  }
88
124
 
89
- get onLogoSummaryGridChanged(): rxjs.Observable<DG.Grid> {
90
- return this.logoSummaryGridSubject.asObservable();
125
+ get substitutionsInfo(): type.SubstitutionsInfo {
126
+ if (this._substitutionsInfo)
127
+ return this._substitutionsInfo;
128
+
129
+ const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
130
+ //TODO: set categories ordering the same to share compare indexes instead of strings
131
+ const monomerColumns: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
132
+ this._substitutionsInfo = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
133
+ return this._substitutionsInfo;
134
+ }
135
+ set substitutionsInfo(si: type.SubstitutionsInfo) {
136
+ this._substitutionsInfo = si;
91
137
  }
92
138
 
93
- get onSettingsChanged(): rxjs.Observable<unknown> {
139
+ get clusterStatsDf(): DG.DataFrame {
140
+ this._clusterStatsDf ??= this.calculateClusterStatistics();
141
+ return this._clusterStatsDf;
142
+ }
143
+ set clusterStatsDf(df: DG.DataFrame) {
144
+ this._clusterStatsDf = df;
145
+ }
146
+
147
+ get cp(): bio.SeqPalette {
148
+ this._cp ??= bio.pickUpPalette(this.df.getCol(this.settings.sequenceColumnName!));
149
+ return this._cp;
150
+ }
151
+ set cp(_cp: bio.SeqPalette) {
152
+ this._cp = _cp;
153
+ }
154
+
155
+ get analysisView(): DG.TableView {
156
+ const shell = grok.shell;
157
+ if (this.df.getTag('newAnalysis') !== '1') {
158
+ this._analysisView = wu(shell.tableViews).find(({dataFrame}) => dataFrame.tags[C.PEPTIDES_ANALYSIS] === '1')!;
159
+ grok.shell.v = this._analysisView;
160
+ }
161
+
162
+ this._analysisView ??= shell.addTableView(this.df);
163
+ this.df.setTag('newAnalysis', '');
164
+ return this._analysisView;
165
+ }
166
+
167
+ get onMutationCliffsSelectionChanged(): rxjs.Observable<undefined> {
168
+ return this._mutatinCliffsSelectionSubject.asObservable();
169
+ }
170
+
171
+ get onSettingsChanged(): rxjs.Observable<type.PeptidesSettings> {
94
172
  return this.settingsSubject.asObservable();
95
173
  }
96
174
 
@@ -103,7 +181,7 @@ export class PeptidesModel {
103
181
  this._mutationCliffsSelection = selection;
104
182
  this.df.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
105
183
  this.fireBitsetChanged();
106
- this.invalidateGrids();
184
+ this._mutatinCliffsSelectionSubject.next();
107
185
  }
108
186
 
109
187
  get invariantMapSelection(): type.PositionToAARList {
@@ -117,7 +195,7 @@ export class PeptidesModel {
117
195
  this.isInvariantMapTrigger = true;
118
196
  this.df.filter.fireChanged();
119
197
  this.isInvariantMapTrigger = false;
120
- this.invalidateGrids();
198
+ this.analysisView.grid.invalidate();
121
199
  }
122
200
 
123
201
  get logoSummarySelection(): number[] {
@@ -129,7 +207,7 @@ export class PeptidesModel {
129
207
  this._logoSummarySelection = selection;
130
208
  this.df.tags[C.TAGS.CLUSTER_SELECTION] = JSON.stringify(selection);
131
209
  this.fireBitsetChanged();
132
- this.invalidateGrids();
210
+ this.analysisView.grid.invalidate();
133
211
  }
134
212
 
135
213
  get splitByPos(): boolean {
@@ -177,122 +255,132 @@ export class PeptidesModel {
177
255
  return this._settings;
178
256
  }
179
257
  set settings(s: type.PeptidesSettings) {
180
- for (const [key, value] of Object.entries(s))
258
+ const newSettingsEntries = Object.entries(s);
259
+ const updateVars: Set<string> = new Set();
260
+ for (const [key, value] of newSettingsEntries) {
181
261
  this._settings[key as keyof type.PeptidesSettings] = value as any;
262
+ switch (key) {
263
+ case 'scaling':
264
+ updateVars.add('activity');
265
+ updateVars.add('mutationCliffs');
266
+ updateVars.add('stats');
267
+ break;
268
+ // case 'columns':
269
+ // updateVars.add('grid');
270
+ // break;
271
+ case 'maxMutations':
272
+ case 'minActivityDelta':
273
+ updateVars.add('mutationCliffs');
274
+ break;
275
+ }
276
+ }
182
277
  this.df.setTag('settings', JSON.stringify(this._settings));
183
- //TODO: update only needed components
184
- this.updateDefault();
185
- this.settingsSubject.next();
278
+ // this.updateDefault();
279
+ for (const variable of updateVars) {
280
+ switch (variable) {
281
+ case 'activity':
282
+ this.createScaledCol();
283
+ break;
284
+ case 'mutationCliffs':
285
+ const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
286
+ //TODO: set categories ordering the same to share compare indexes instead of strings
287
+ const monomerColumns: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
288
+ this.substitutionsInfo = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
289
+ break;
290
+ case 'stats':
291
+ this.monomerPositionStatsDf = this.calculateMonomerPositionStatistics();
292
+ this.monomerPositionDf = this.createMonomerPositionDf();
293
+ this.mostPotentResiduesDf = this.createMostPotentResiduesDf();
294
+ this.clusterStatsDf = this.calculateClusterStatistics();
295
+ break;
296
+ case 'grid':
297
+ this.updateGrid();
298
+ break;
299
+ }
300
+ }
301
+
302
+ //TODO: handle settings change
303
+ this.settingsSubject.next(this.settings);
304
+ }
305
+
306
+ createMonomerPositionDf(): DG.DataFrame {
307
+ const matrixDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
308
+ .pivot(C.COLUMNS_NAMES.POSITION)
309
+ .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
310
+ .aggregate();
311
+ const monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
312
+ for (let i = 0; i < monomerCol.length; ++i) {
313
+ if (monomerCol.get(i) == '') {
314
+ matrixDf.rows.removeAt(i);
315
+ break;
316
+ }
317
+ }
318
+ matrixDf.name = 'SAR';
319
+
320
+ return matrixDf;
321
+ }
322
+
323
+ buildMatrixDf(): DG.DataFrame {
324
+ const splitSeqDfColumns = this.splitSeqDf.columns;
325
+ const positionColumns = splitSeqDfColumns.names();
326
+ return this.splitSeqDf
327
+ .groupBy(positionColumns)
328
+ .aggregate()
329
+ .unpivot([], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER);
330
+ }
331
+
332
+ buildSplitSeqDf(): DG.DataFrame {
333
+ const sequenceCol = this.df.getCol(this.settings.sequenceColumnName!);
334
+ const splitSeqDf = splitAlignedSequences(sequenceCol);
335
+
336
+ return splitSeqDf;
186
337
  }
187
338
 
188
339
  createAccordion(): DG.Accordion {
189
340
  const acc = ui.accordion();
190
341
  acc.root.style.width = '100%';
191
342
  acc.addTitle(ui.h1(`${this.df.selection.trueCount} selected rows`));
192
- acc.addPane('Mutation Cliff pairs', () => mutationCliffsWidget(this.df, this).root, true);
193
- acc.addPane('Distribution', () => getDistributionWidget(this.df, this).root, true);
343
+ acc.addPane('Mutation Cliff pairs', () => mutationCliffsWidget(this.df, this).root);
344
+ acc.addPane('Distribution', () => getDistributionWidget(this.df, this).root);
194
345
 
195
346
  return acc;
196
347
  }
197
348
 
198
349
  updateDefault(): void {
199
- if ((this.sourceGrid && !this._isUpdating) || !this.isInitialized) {
200
- // this.isInitialized = true;
350
+ if (!this._isUpdating || !this.isInitialized) {
201
351
  this._isUpdating = true;
202
- this.initializeViewersComponents();
203
- //FIXME: modify during the initializeViewersComponents stages
204
- this.mutationCliffsGridSubject.next(this.mutationCliffsGrid);
205
- this.mostPotentResiduesGridSubject.next(this.mostPotentResiduesGrid);
206
- // if (this.df.getTag(C.TAGS.CLUSTERS))
207
- // this.logoSummaryGridSubject.next(this.logoSummaryGrid);
208
-
209
- this.fireBitsetChanged();
210
- this.invalidateGrids();
352
+ this.updateGrid();
353
+
354
+ this.analysisView.grid.invalidate();
211
355
  this._isUpdating = false;
212
356
  }
213
357
  }
214
358
 
215
- initializeViewersComponents(): void {
216
- if (this.sourceGrid === null)
217
- throw new Error(`Source grid is not initialized`);
218
-
219
- //Split the aligned sequence into separate AARs
220
- const col = this.df.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
221
- const alphabet = col.tags['alphabet'];
222
- const splitSeqDf = splitAlignedSequences(col);
223
- for (const col of splitSeqDf.columns)
224
- col.name = `p${col.name}`;
225
-
226
- const positionColumns = splitSeqDf.columns.names();
227
-
228
- const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
229
- splitSeqDf.columns.add(activityCol);
230
-
231
- this.joinDataFrames(positionColumns, splitSeqDf, alphabet);
359
+ updateGrid(): void {
360
+ this.joinDataFrames();
232
361
 
233
362
  this.sortSourceGrid();
234
363
 
235
- this.createScaledCol(splitSeqDf);
236
-
237
- //unpivot a table and handle duplicates
238
- let matrixDf = splitSeqDf.groupBy(positionColumns).aggregate();
239
-
240
- matrixDf = matrixDf.unpivot([], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER);
241
-
242
- //statistics for specific AAR at a specific position
243
- this.monomerPositionStatsDf = this.calculateMonomerPositionStatistics(matrixDf);
244
-
245
- // SAR matrix table
246
- //pivot a table to make it matrix-like
247
- const monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
248
- matrixDf = matrixDf.clone(DG.BitSet.create(matrixDf.rowCount, (i) => monomerCol.get(i) ? true : false));
249
- matrixDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
250
- .pivot(C.COLUMNS_NAMES.POSITION)
251
- .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
252
- .aggregate();
253
- matrixDf.name = 'SAR';
254
-
255
- // Setting category order
256
- // this.setCategoryOrder(matrixDf);
364
+ this.createScaledCol();
257
365
 
258
- // SAR vertical table (naive, choose best Mean difference from pVals <= 0.01)
259
- const sequenceDf = this.createVerticalTable();
260
-
261
- const scaledActivityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
262
- const monomerColumns = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER);
263
- this.substitutionsInfo = findMutations(scaledActivityCol, monomerColumns, this.settings);
264
-
265
- [this.mutationCliffsGrid, this.mostPotentResiduesGrid] =
266
- this.createGrids(matrixDf, sequenceDf, positionColumns, alphabet);
267
-
268
- if (this.df.getTag(C.TAGS.CLUSTERS)) {
269
- this.clusterStatsDf = this.calculateClusterStatistics();
270
- // this.logoSummaryGrid = this.createLogoSummaryGrid();
271
- }
272
-
273
- // init invariant map & mutation cliffs selections
274
- this.initSelections(positionColumns);
275
-
276
- // positionColumns.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
366
+ this.initSelections();
277
367
 
278
368
  this.setWebLogoInteraction();
279
369
  this.webLogoBounds = {};
280
370
 
281
- this.setCellRenderers([...positionColumns, C.COLUMNS_NAMES.MEAN_DIFFERENCE]);
371
+ this.setCellRenderers();
282
372
 
283
- // show all the statistics in a tooltip over cell
284
- this.setTooltips([...positionColumns, C.COLUMNS_NAMES.MEAN_DIFFERENCE]);
285
-
286
- this.setInteractionCallback();
373
+ this.setTooltips();
287
374
 
288
375
  this.setBitsetCallback();
289
376
 
290
- this.postProcessGrids(positionColumns);
377
+ this.postProcessGrids();
291
378
  }
292
379
 
293
- initSelections(positionColumns: string[]): void {
380
+ initSelections(): void {
294
381
  const tempInvariantMapSelection: type.PositionToAARList = this.invariantMapSelection;
295
382
  const mutationCliffsSelection: type.PositionToAARList = this.mutationCliffsSelection;
383
+ const positionColumns = this.splitSeqDf.columns.names();
296
384
  for (const pos of positionColumns) {
297
385
  tempInvariantMapSelection[pos] ??= [];
298
386
  mutationCliffsSelection[pos] ??= [];
@@ -301,29 +389,31 @@ export class PeptidesModel {
301
389
  this.mutationCliffsSelection = mutationCliffsSelection;
302
390
  }
303
391
 
304
- joinDataFrames(positionColumns: string[], splitSeqDf: DG.DataFrame, alphabet: string): void {
392
+ joinDataFrames(): void {
305
393
  // append splitSeqDf columns to source table and make sure columns are not added more than once
306
394
  const name = this.df.name;
307
395
  const cols = this.df.columns;
396
+ const positionColumns = this.splitSeqDf.columns.names();
308
397
  for (const colName of positionColumns) {
309
398
  const col = this.df.col(colName);
310
- const newCol = splitSeqDf.getCol(colName);
399
+ const newCol = this.splitSeqDf.getCol(colName);
311
400
  if (col === null)
312
401
  cols.add(newCol);
313
402
  else {
314
403
  cols.remove(colName);
315
404
  cols.add(newCol);
316
405
  }
317
- CR.setAARRenderer(newCol, alphabet, this.sourceGrid);
406
+ CR.setAARRenderer(newCol, this.alphabet);
318
407
  }
319
408
  this.df.name = name;
320
- this.currentView.name = name;
321
409
  }
322
410
 
323
411
  sortSourceGrid(): void {
324
412
  const colNames: DG.GridColumn[] = [];
325
- for (let i = 1; i < this.sourceGrid.columns.length; i++)
326
- colNames.push(this.sourceGrid.columns.byIndex(i)!);
413
+ const sourceGridCols = this.analysisView.grid.columns;
414
+ const sourceGridColsCount = sourceGridCols.length;
415
+ for (let i = 1; i < sourceGridColsCount; i++)
416
+ colNames.push(sourceGridCols.byIndex(i)!);
327
417
 
328
418
  colNames.sort((a, b) => {
329
419
  if (a.column!.semType == C.SEM_TYPES.MONOMER) {
@@ -335,99 +425,133 @@ export class PeptidesModel {
335
425
  return 1;
336
426
  return 0;
337
427
  });
338
- this.sourceGrid.columns.setOrder(colNames.map((v) => v.name));
428
+ sourceGridCols.setOrder(colNames.map((v) => v.name));
339
429
  }
340
430
 
341
- createScaledCol(splitSeqDf: DG.DataFrame): void {
342
- const scaledCol = scaleActivity(this.df.getCol(C.COLUMNS_NAMES.ACTIVITY), this.settings.scaling);
431
+ createScaledCol(): void {
432
+ const sourceGrid = this.analysisView.grid;
433
+ const scaledCol = scaleActivity(this.df.getCol(this.settings.activityColumnName!), this.settings.scaling);
343
434
  //TODO: make another func
344
- splitSeqDf.columns.add(scaledCol);
345
435
  this.df.columns.replace(C.COLUMNS_NAMES.ACTIVITY_SCALED, scaledCol);
346
- const gridCol = this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED);
347
- if (gridCol)
348
- gridCol.name = scaledCol.getTag('gridName');
436
+ // const gridCol = sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED);
437
+ // if (gridCol)
438
+ // gridCol.name = scaledCol.getTag('gridName');
349
439
 
350
- this.sourceGrid.columns.setOrder([scaledCol.getTag('gridName')]);
440
+ sourceGrid.columns.setOrder([scaledCol.name]);
351
441
  }
352
442
 
353
- calculateMonomerPositionStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
354
- matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER]).aggregate();
443
+ calculateMonomerPositionStatistics(): DG.DataFrame {
444
+ const matrixDf = this.matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER]).aggregate();
445
+ const matrixLen = matrixDf.rowCount;
446
+
447
+ const posRawColumns: type.RawColumn[] = [];
355
448
 
356
- let monomerCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
357
- // monomerCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
449
+ const posCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
450
+ const posColData = posCol.getRawData();
451
+ const posColCategories = posCol.categories;
452
+ for (const position of posColCategories) {
453
+ const currentCol = this.df.getCol(position);
454
+ posRawColumns.push({
455
+ name: position,
456
+ rawData: currentCol.getRawData(),
457
+ cat: currentCol.categories,
458
+ });
459
+ }
460
+
461
+ const monomerCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
462
+ const monomerColData = monomerCol.getRawData();
463
+ const monomerColCategories = monomerCol.categories;
358
464
 
359
465
  //calculate p-values based on t-test
360
466
  const matrixCols = matrixDf.columns;
361
- const mdCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
362
- const pValCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
363
- const countCol = matrixCols.addNewInt(C.COLUMNS_NAMES.COUNT);
364
- const ratioCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
365
- const posCol: DG.Column<string> = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
366
- const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
367
- const sourceDfLen = activityCol.length;
368
-
369
- for (let i = 0; i < matrixDf.rowCount; i++) {
370
- const position = posCol.get(i)!;
371
- const monomer = monomerCol.get(i)!;
372
- const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) === monomer);
373
- const stats = getStats(activityCol, mask);
374
-
375
- mdCol.set(i, stats.meanDifference);
376
- pValCol.set(i, stats.pValue);
377
- countCol.set(i, stats.count);
378
- ratioCol.set(i, stats.ratio);
467
+ const mdColData = matrixCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE).init(0).getRawData();
468
+ const pValColData = matrixCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE).init(0).getRawData();
469
+ const countColData = matrixCols.addNewInt(C.COLUMNS_NAMES.COUNT).init(0).getRawData();
470
+ const ratioColData = matrixCols.addNewFloat(C.COLUMNS_NAMES.RATIO).init(0).getRawData();
471
+ const activityColData = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
472
+ const sourceDfLen = activityColData.length;
473
+
474
+ for (let i = 0; i < matrixLen; i++) {
475
+ const positionRawIdx = posColData[i];
476
+ const currentPosRawCol = posRawColumns[positionRawIdx];
477
+ const monomerRawIdx = monomerColData[i];
478
+ const mask: boolean[] = new Array(sourceDfLen);
479
+ const monomer = monomerColCategories[monomerRawIdx];
480
+ if (monomer == '')
481
+ continue;
482
+
483
+ let trueCount = 0;
484
+ for (let j = 0; j < sourceDfLen; ++j) {
485
+ mask[j] = currentPosRawCol.cat![currentPosRawCol.rawData[j]] == monomer;
486
+
487
+ if (mask[j])
488
+ ++trueCount;
489
+ }
490
+
491
+ const maskInfo = {
492
+ trueCount: trueCount,
493
+ falseCount: sourceDfLen - trueCount,
494
+ mask: mask,
495
+ };
496
+
497
+ const stats = getStats(activityColData, maskInfo);
498
+
499
+ mdColData[i] = stats.meanDifference;
500
+ pValColData[i] = stats.pValue;
501
+ countColData[i] = stats.count;
502
+ ratioColData[i] = stats.ratio;
379
503
  }
504
+ matrixDf.fireValuesChanged();
380
505
 
381
506
  return matrixDf as DG.DataFrame;
382
507
  }
383
508
 
384
509
  calculateClusterStatistics(): DG.DataFrame {
385
- const originalClustersCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
386
- const statsDf = this.df.groupBy([C.COLUMNS_NAMES.CLUSTERS]).aggregate();
387
- const clustersCol = statsDf.getCol(C.COLUMNS_NAMES.CLUSTERS);
510
+ const originalClustersCol = this.df.getCol(this.settings.clustersColumnName!);
511
+ const originalClustersColData = originalClustersCol.getRawData();
512
+ const originalClustersColCategories = originalClustersCol.categories;
513
+
514
+ const statsDf = this.df.groupBy([this.settings.clustersColumnName!]).aggregate();
515
+ const clustersCol = statsDf.getCol(this.settings.clustersColumnName!);
516
+ clustersCol.setCategoryOrder(originalClustersColCategories);
517
+ const clustersColData = clustersCol.getRawData();
518
+
388
519
  const statsDfCols = statsDf.columns;
389
- const mdCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
390
- const pValCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
391
- const countCol = statsDfCols.addNewInt(C.COLUMNS_NAMES.COUNT);
392
- const ratioCol = statsDfCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
393
- const activityList: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
394
-
395
- for (let rowIdx = 0; rowIdx < clustersCol.length; ++rowIdx) {
396
- const cluster = clustersCol.get(rowIdx);
397
- const mask = DG.BitSet.create(activityList.length, (bitIdx) => originalClustersCol.get(bitIdx) === cluster);
398
- const stats = getStats(activityList, mask);
399
-
400
- mdCol.set(rowIdx, stats.meanDifference);
401
- pValCol.set(rowIdx, stats.pValue);
402
- countCol.set(rowIdx, stats.count);
403
- ratioCol.set(rowIdx, stats.ratio);
520
+ const mdColData = statsDfCols.addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE).getRawData();
521
+ const pValColData = statsDfCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE).getRawData();
522
+ const countColData = statsDfCols.addNewInt(C.COLUMNS_NAMES.COUNT).getRawData();
523
+ const ratioColData = statsDfCols.addNewFloat(C.COLUMNS_NAMES.RATIO).getRawData();
524
+ const activityColData: type.RawData = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
525
+ const activityColLen = activityColData.length;
526
+
527
+ for (let rowIdx = 0; rowIdx < clustersColData.length; ++rowIdx) {
528
+ const clusterIdx = clustersColData[rowIdx];
529
+ const mask = new Array(activityColLen);
530
+ let trueCount = 0;
531
+ for (let maskIdx = 0; maskIdx < activityColLen; ++maskIdx) {
532
+ mask[maskIdx] = clusterIdx == originalClustersColData[maskIdx];
533
+
534
+ if (mask[maskIdx])
535
+ ++trueCount;
536
+ }
537
+
538
+ const maskInfo = {
539
+ trueCount: trueCount,
540
+ falseCount: activityColLen - trueCount,
541
+ mask: mask,
542
+ };
543
+
544
+ const stats = getStats(activityColData, maskInfo);
545
+
546
+ mdColData[rowIdx] = stats.meanDifference;
547
+ pValColData[rowIdx] = stats.pValue;
548
+ countColData[rowIdx] = stats.count;
549
+ ratioColData[rowIdx] = stats.ratio;
404
550
  }
405
551
  return statsDf;
406
552
  }
407
553
 
408
- // setCategoryOrder(matrixDf: DG.DataFrame): void {
409
- // let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
410
- // if (this.settings.isBidirectional) {
411
- // const mdCol = this.monomerPositionStatsDf.getCol(sortArgument);
412
- // sortArgument = 'Absolute Mean difference';
413
- // const absMDCol = this.monomerPositionStatsDf.columns.addNewFloat(sortArgument);
414
- // absMDCol.init((i) => Math.abs(mdCol.get(i)));
415
- // }
416
-
417
- // const aarWeightsDf = this.monomerPositionStatsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
418
- // .aggregate();
419
- // const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
420
- // const getWeight = (aar: string): number => aarWeightsDf
421
- // .groupBy(['weight'])
422
- // .where(`${C.COLUMNS_NAMES.MONOMER} = ${aar}`)
423
- // .aggregate()
424
- // .get('weight', 0) as number;
425
- // aarList.sort((first, second) => getWeight(second) - getWeight(first));
426
-
427
- // matrixDf.getCol(C.COLUMNS_NAMES.MONOMER).setCategoryOrder(aarList);
428
- // }
429
-
430
- createVerticalTable(): DG.DataFrame {
554
+ createMostPotentResiduesDf(): DG.DataFrame {
431
555
  // TODO: aquire ALL of the positions
432
556
  const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.POSITION,
433
557
  'Count', 'Ratio', C.COLUMNS_NAMES.P_VALUE];
@@ -440,6 +564,7 @@ export class PeptidesModel {
440
564
  const posColCategories = sequenceDf.getCol(C.COLUMNS_NAMES.POSITION).categories;
441
565
  const mdCol = sequenceDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
442
566
  const posCol = sequenceDf.getCol(C.COLUMNS_NAMES.POSITION);
567
+ const monomerCol = sequenceDf.getCol(C.COLUMNS_NAMES.MONOMER);
443
568
  const rowCount = sequenceDf.rowCount;
444
569
  for (const pos of posColCategories) {
445
570
  tempStats = DG.Stats.fromColumn(mdCol, DG.BitSet.create(rowCount, (i) => posCol.get(i) === pos));
@@ -447,32 +572,12 @@ export class PeptidesModel {
447
572
  (tempStats.max > Math.abs(tempStats.min) ? tempStats.max : tempStats.min) :
448
573
  tempStats.max;
449
574
  }
450
- sequenceDf = sequenceDf.clone(DG.BitSet.create(rowCount, (i) => mdCol.get(i) === maxAtPos[posCol.get(i)]));
575
+ sequenceDf = sequenceDf.clone(DG.BitSet.create(rowCount,
576
+ (i) => monomerCol.get(i) !== '' && maxAtPos[posCol.get(i)] != 0 && mdCol.get(i) === maxAtPos[posCol.get(i)]));
451
577
 
452
578
  return sequenceDf;
453
579
  }
454
580
 
455
- createGrids(mutationCliffsDf: DG.DataFrame, mostPotentResiduesDf: DG.DataFrame, positionColumns: string[],
456
- alphabet: string): [DG.Grid, DG.Grid] {
457
- // Creating Mutation Cliffs grid and sorting columns
458
- const mutationCliffsGrid = mutationCliffsDf.plot.grid();
459
- mutationCliffsGrid.sort([C.COLUMNS_NAMES.MONOMER]);
460
- mutationCliffsGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER].concat(positionColumns as C.COLUMNS_NAMES[]));
461
-
462
- // Creating Monomer-Position grid, sorting and setting column format
463
- const mostPotentResiduesGrid = mostPotentResiduesDf.plot.grid();
464
- mostPotentResiduesGrid.sort([C.COLUMNS_NAMES.POSITION]);
465
- const pValGridCol = mostPotentResiduesGrid.col(C.COLUMNS_NAMES.P_VALUE)!;
466
- pValGridCol.format = '#.000';
467
- pValGridCol.name = 'P-value';
468
-
469
- // Setting Monomer column renderer
470
- CR.setAARRenderer(mutationCliffsDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mutationCliffsGrid);
471
- CR.setAARRenderer(mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER), alphabet, mostPotentResiduesGrid);
472
-
473
- return [mutationCliffsGrid, mostPotentResiduesGrid];
474
- }
475
-
476
581
  modifyClusterSelection(cluster: number): void {
477
582
  const tempSelection = this.logoSummarySelection;
478
583
  const idx = tempSelection.indexOf(cluster);
@@ -489,8 +594,9 @@ export class PeptidesModel {
489
594
  }
490
595
 
491
596
  setWebLogoInteraction(): void {
597
+ const sourceView = this.analysisView.grid;
492
598
  const eventAction = (ev: MouseEvent): void => {
493
- const cell = this.sourceGrid.hitTest(ev.offsetX, ev.offsetY);
599
+ const cell = sourceView.hitTest(ev.offsetX, ev.offsetY);
494
600
  if (cell?.isColHeader && cell.tableColumn?.semType == C.SEM_TYPES.MONOMER) {
495
601
  const newBarPart = this.findAARandPosition(cell, ev);
496
602
  this.requestBarchartAction(ev, newBarPart);
@@ -498,9 +604,9 @@ export class PeptidesModel {
498
604
  };
499
605
 
500
606
  // The following events makes the barchart interactive
501
- rxjs.fromEvent<MouseEvent>(this.sourceGrid.overlay, 'mousemove')
607
+ rxjs.fromEvent<MouseEvent>(sourceView.overlay, 'mousemove')
502
608
  .subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
503
- rxjs.fromEvent<MouseEvent>(this.sourceGrid.overlay, 'click')
609
+ rxjs.fromEvent<MouseEvent>(sourceView.overlay, 'click')
504
610
  .subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
505
611
  }
506
612
 
@@ -531,126 +637,58 @@ export class PeptidesModel {
531
637
  else
532
638
  this.cachedWebLogoTooltip = {bar: bar, tooltip: this.showTooltipAt(monomer, position, ev.clientX, ev.clientY)};
533
639
 
534
- //TODO: how to unghighlight?
640
+ //TODO: how to unghighlight?
535
641
  // this.df.rows.match(bar).highlight();
536
642
  }
537
643
  }
538
644
 
539
- setCellRenderers(renderColNames: string[]): void {
540
- const mdCol = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
541
- //decompose into two different renering funcs
542
- const renderCell = (args: DG.GridCellRenderArgs): void => {
543
- const canvasContext = args.g;
544
- const bound = args.bounds;
545
-
546
- canvasContext.save();
547
- canvasContext.beginPath();
548
- canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
549
- canvasContext.clip();
550
-
551
- // Hide row column
552
- const cell = args.cell;
553
- if (cell.isRowHeader && cell.gridColumn.visible) {
554
- cell.gridColumn.visible = false;
555
- args.preventDefault();
556
- return;
557
- }
558
-
559
- const tableColName = cell.tableColumn?.name;
560
- const tableRowIndex = cell.tableRowIndex!;
561
- if (cell.isTableCell && tableColName && tableRowIndex !== null && renderColNames.indexOf(tableColName) !== -1) {
562
- const cellValue: number | null = cell.cell.value;
563
-
564
- if (cellValue && cellValue !== DG.INT_NULL && cellValue !== DG.FLOAT_NULL) {
565
- const gridTable = cell.grid.table;
566
- const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
567
- tableColName : gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
568
- const currentAAR: string = gridTable.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
569
-
570
- if (this.isInvariantMap) {
571
- const value: number = this.monomerPositionStatsDf
572
- .groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.COUNT])
573
- .where(`${C.COLUMNS_NAMES.POSITION} = ${currentPosition} and ${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`)
574
- .aggregate().get(C.COLUMNS_NAMES.COUNT, 0);
575
- CR.renderInvaraintMapCell(
576
- canvasContext, currentAAR, currentPosition, this.invariantMapSelection, value, bound);
577
- } else {
578
- CR.renderMutationCliffCell(canvasContext, currentAAR, currentPosition, this.monomerPositionStatsDf,
579
- mdCol, bound, cellValue, this.mutationCliffsSelection, this.substitutionsInfo,
580
- this.settings.isBidirectional);
581
- }
582
- }
583
- args.preventDefault();
584
- }
585
- canvasContext.restore();
586
- };
587
- this.mutationCliffsGrid.onCellRender.subscribe(renderCell);
588
- this.mostPotentResiduesGrid.onCellRender.subscribe(renderCell);
589
-
590
- this.sourceGrid.setOptions({'colHeaderHeight': 130});
591
- this.sourceGrid.onCellRender.subscribe((gcArgs) => {
645
+ setCellRenderers(): void {
646
+ const sourceGrid = this.analysisView.grid;
647
+ sourceGrid.setOptions({'colHeaderHeight': 130});
648
+ sourceGrid.onCellRender.subscribe((gcArgs) => {
592
649
  const ctx = gcArgs.g;
593
650
  const bounds = gcArgs.bounds;
594
651
  const col = gcArgs.cell.tableColumn;
595
652
 
596
653
  ctx.save();
597
- ctx.beginPath();
598
- ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height);
599
- ctx.clip();
600
-
601
- if (gcArgs.cell.isColHeader && col?.semType == C.SEM_TYPES.MONOMER) {
602
- const monomerStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MONOMER);
603
- const positionStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.POSITION);
604
- const rowMask = DG.BitSet.create(this.monomerPositionStatsDf.rowCount, (i) => positionStatsCol.get(i) === col.name);
605
- //TODO: precalc on stats creation
606
- const sortedStatsOrder = this.monomerPositionStatsDf.getSortedOrder([C.COLUMNS_NAMES.COUNT], [false], rowMask)
607
- .sort((a, b) => {
608
- if (monomerStatsCol.get(a) === '-' || monomerStatsCol.get(a) === '')
609
- return -1;
610
- else if (monomerStatsCol.get(b) === '-' || monomerStatsCol.get(b) === '')
611
- return +1;
612
- return 0;
613
- });
614
- const statsInfo: type.StatsInfo = {
615
- countCol: this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.COUNT),
616
- monomerCol: monomerStatsCol,
617
- orderedIndexes: sortedStatsOrder,
618
- };
619
-
620
- this.webLogoBounds[col.name] =
621
- CR.drawLogoInBounds(ctx, bounds, statsInfo, this.df.rowCount, this.cp, this.headerSelectedMonomers[col.name]);
622
- gcArgs.preventDefault();
654
+ try {
655
+ // ctx.beginPath();
656
+ // ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height);
657
+ // ctx.clip();
658
+
659
+ //TODO: optimize
660
+ if (gcArgs.cell.isColHeader && col?.semType == C.SEM_TYPES.MONOMER) {
661
+ const monomerStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MONOMER);
662
+ const positionStatsCol: DG.Column<string> = this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.POSITION);
663
+ const rowMask = DG.BitSet.create(this.monomerPositionStatsDf.rowCount,
664
+ (i) => positionStatsCol.get(i) === col.name);
665
+ //TODO: precalc on stats creation
666
+ const sortedStatsOrder = this.monomerPositionStatsDf.getSortedOrder([C.COLUMNS_NAMES.COUNT], [false], rowMask)
667
+ .sort((a, b) => {
668
+ if (monomerStatsCol.get(a) === '-' || monomerStatsCol.get(a) === '')
669
+ return -1;
670
+ else if (monomerStatsCol.get(b) === '-' || monomerStatsCol.get(b) === '')
671
+ return +1;
672
+ return 0;
673
+ });
674
+ const statsInfo: type.StatsInfo = {
675
+ countCol: this.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.COUNT),
676
+ monomerCol: monomerStatsCol,
677
+ orderedIndexes: sortedStatsOrder,
678
+ };
679
+
680
+ this.webLogoBounds[col.name] =
681
+ CR.drawLogoInBounds(ctx, bounds, statsInfo, this.df.rowCount, this.cp, this.headerSelectedMonomers[col.name]);
682
+ gcArgs.preventDefault();
683
+ }
684
+ } finally {
685
+ ctx.restore();
623
686
  }
624
-
625
- ctx.restore();
626
687
  });
627
688
  }
628
689
 
629
- setTooltips(renderColNames: string[]): void {
630
- const showTooltip = (cell: DG.GridCell, x: number, y: number): boolean => {
631
- const tableCol = cell.tableColumn;
632
- const tableColName = tableCol?.name;
633
- const tableRowIndex = cell.tableRowIndex;
634
-
635
- if (!cell.isRowHeader && !cell.isColHeader && tableCol && tableRowIndex != null) {
636
- const table = cell.grid.table;
637
- const currentAAR = table.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
638
-
639
- if (tableCol.semType == C.SEM_TYPES.MONOMER)
640
- this.showMonomerTooltip(currentAAR, x, y);
641
- else if (cell.cell.value && renderColNames.includes(tableColName!)) {
642
- const currentPosition = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
643
- table.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
644
-
645
- this.showTooltipAt(currentAAR, currentPosition, x, y);
646
- }
647
- }
648
- return true;
649
- };
650
-
651
- this.mutationCliffsGrid.onCellTooltip(showTooltip);
652
- this.mostPotentResiduesGrid.onCellTooltip(showTooltip);
653
- this.sourceGrid.onCellTooltip((cell, x, y) => {
690
+ setTooltips(): void {
691
+ this.analysisView.grid.onCellTooltip((cell, x, y) => {
654
692
  const col = cell.tableColumn;
655
693
  const cellValue = cell.cell.value;
656
694
  if (cellValue && col && col.semType === C.SEM_TYPES.MONOMER)
@@ -663,8 +701,8 @@ export class PeptidesModel {
663
701
  const tooltipElements: HTMLDivElement[] = [];
664
702
  const monomerName = aar.toLowerCase();
665
703
 
666
- let mw = getMonomerWorks();
667
- let mol = mw?.getCappedRotatedMonomer('PEPTIDE', aar);
704
+ const mw = getMonomerWorks();
705
+ const mol = mw?.getCappedRotatedMonomer('PEPTIDE', aar);
668
706
 
669
707
  if (mol) {
670
708
  tooltipElements.push(ui.div(monomerName));
@@ -676,13 +714,30 @@ export class PeptidesModel {
676
714
  ui.tooltip.show(ui.divV(tooltipElements), x, y);
677
715
  }
678
716
 
717
+ //TODO: move out to viewer code
679
718
  showTooltipAt(aar: string, position: string, x: number, y: number): HTMLDivElement | null {
680
719
  const currentStatsDf = this.monomerPositionStatsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
681
- const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
720
+ const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
682
721
  //TODO: use bitset instead of splitCol
683
722
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
684
723
  const currentPosCol = this.df.getCol(position);
685
- splitCol.init((i) => currentPosCol.get(i) == aar);
724
+ const indexes: number[] = [];
725
+ splitCol.init((i) => {
726
+ const sameMonomer = currentPosCol.get(i) == aar;
727
+ if (sameMonomer)
728
+ indexes.push(i);
729
+
730
+ return sameMonomer;
731
+ });
732
+ const colResults: {[colName: string]: number} = {};
733
+ for (const [col, agg] of Object.entries(this.settings.columns ?? {})) {
734
+ const currentCol = this.df.getCol(col);
735
+ const currentColData = currentCol.getRawData();
736
+ const tempCol = DG.Column.float('', indexes.length);
737
+ tempCol.init((i) => currentColData[indexes[i]]);
738
+ colResults[`${agg}(${col})`] = tempCol.stats[agg as keyof DG.Stats] as number;
739
+ }
740
+
686
741
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
687
742
  const stats: Stats = {
688
743
  count: currentStatsDf.get(C.COLUMNS_NAMES.COUNT, 0),
@@ -693,19 +748,21 @@ export class PeptidesModel {
693
748
  if (!stats.count)
694
749
  return null;
695
750
 
696
- const tooltip = getDistributionAndStats(distributionTable, stats, `${position} : ${aar}`, 'Other', true);
751
+ const distroStatsElem = getDistributionAndStats(distributionTable, stats, `${position} : ${aar}`, 'Other', true);
697
752
 
698
- ui.tooltip.show(tooltip, x, y);
753
+ ui.tooltip.show(ui.divV([distroStatsElem, ui.tableFromMap(colResults)]), x, y);
699
754
 
700
- return tooltip;
755
+ return distroStatsElem;
701
756
  }
702
757
 
703
758
  showTooltipCluster(cluster: number, x: number, y: number): HTMLDivElement | null {
704
- const currentStatsDf = this.clusterStatsDf.rows.match({clusters: cluster}).toDataFrame();
705
- const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
759
+ const matcher: {[key: string]: number} = {};
760
+ matcher[this.settings.clustersColumnName!] = cluster;
761
+ const currentStatsDf = this.clusterStatsDf.rows.match(matcher).toDataFrame();
762
+ const activityCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
706
763
  //TODO: use bitset instead of splitCol
707
764
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
708
- const currentClusterCol = this.df.getCol(C.COLUMNS_NAMES.CLUSTERS);
765
+ const currentClusterCol = this.df.getCol(this.settings.clustersColumnName!);
709
766
  splitCol.init((i) => currentClusterCol.get(i) == cluster);
710
767
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
711
768
  const stats: Stats = {
@@ -724,48 +781,6 @@ export class PeptidesModel {
724
781
  return tooltip;
725
782
  }
726
783
 
727
- setInteractionCallback(): void {
728
- const mutationCliffsDf = this.mutationCliffsGrid.dataFrame;
729
- const mostPotentResiduesDf = this.mostPotentResiduesGrid.dataFrame;
730
-
731
- const chooseAction =
732
- (aar: string, position: string, isShiftPressed: boolean, isInvariantMapSelection: boolean = true): void => {
733
- isShiftPressed ? this.modifyMonomerPositionSelection(aar, position, isInvariantMapSelection) :
734
- this.initMonomerPositionSelection(aar, position, isInvariantMapSelection);
735
- };
736
-
737
- this.mutationCliffsGrid.root.addEventListener('click', (ev) => {
738
- const gridCell = this.mutationCliffsGrid.hitTest(ev.offsetX, ev.offsetY);
739
- if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name == C.COLUMNS_NAMES.MONOMER)
740
- return;
741
-
742
- const position = gridCell!.tableColumn!.name;
743
- const aar = mutationCliffsDf.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!);
744
- chooseAction(aar, position, ev.shiftKey, this.isInvariantMap);
745
- });
746
-
747
- this.mostPotentResiduesGrid.root.addEventListener('click', (ev) => {
748
- const gridCell = this.mostPotentResiduesGrid.hitTest(ev.offsetX, ev.offsetY);
749
- if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name != C.COLUMNS_NAMES.MEAN_DIFFERENCE)
750
- return;
751
-
752
- const tableRowIdx = gridCell!.tableRowIndex!;
753
- const position = mostPotentResiduesDf.get(C.COLUMNS_NAMES.POSITION, tableRowIdx);
754
- const aar = mostPotentResiduesDf.get(C.COLUMNS_NAMES.MONOMER, tableRowIdx);
755
- chooseAction(aar, position, ev.shiftKey, false);
756
- });
757
-
758
- const cellChanged = (table: DG.DataFrame): void => {
759
- if (this.isCellChanging)
760
- return;
761
- this.isCellChanging = true;
762
- table.currentRowIdx = -1;
763
- this.isCellChanging = false;
764
- };
765
- this.mutationCliffsGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(mutationCliffsDf));
766
- this.mostPotentResiduesGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(mostPotentResiduesDf));
767
- }
768
-
769
784
  modifyMonomerPositionSelection(aar: string, position: string, isInvariantMapSelection: boolean): void {
770
785
  const tempSelection = isInvariantMapSelection ? this.invariantMapSelection : this.mutationCliffsSelection;
771
786
  const tempSelectionAt = tempSelection[position];
@@ -793,19 +808,13 @@ export class PeptidesModel {
793
808
  this.mutationCliffsSelection = tempSelection;
794
809
  }
795
810
 
796
- invalidateGrids(): void {
797
- this.mutationCliffsGrid.invalidate();
798
- this.mostPotentResiduesGrid.invalidate();
799
- // this.logoSummaryGrid?.invalidate();
800
- this.sourceGrid?.invalidate();
801
- }
802
-
803
811
  setBitsetCallback(): void {
804
812
  if (this.isBitsetChangedInitialized)
805
813
  return;
806
814
  const selection = this.df.selection;
807
815
  const filter = this.df.filter;
808
- const clusterCol = this.df.col(C.COLUMNS_NAMES.CLUSTERS);
816
+ const clusterCol = this.df.col(this.settings.clustersColumnName!);
817
+ const clusterColData = clusterCol?.getRawData();
809
818
 
810
819
  const changeSelectionBitset = (currentBitset: DG.BitSet): void => {
811
820
  const edfSelection = this.edf?.selection;
@@ -832,7 +841,7 @@ export class PeptidesModel {
832
841
  if (this.mutationCliffsSelection[position].includes(positionCol.get(i)!))
833
842
  return true;
834
843
  }
835
- if (this.logoSummarySelection.includes(clusterCol?.get(i)!))
844
+ if (clusterColData && this.logoSummarySelection.includes(clusterColData[i]))
836
845
  return true;
837
846
  return false;
838
847
  };
@@ -842,7 +851,7 @@ export class PeptidesModel {
842
851
  };
843
852
 
844
853
  selection.onChanged.subscribe(() => changeSelectionBitset(selection));
845
-
854
+
846
855
  filter.onChanged.subscribe(() => {
847
856
  const positionList = Object.keys(this.invariantMapSelection);
848
857
  const invariantMapBitset = DG.BitSet.create(filter.length, (index) => {
@@ -859,7 +868,6 @@ export class PeptidesModel {
859
868
  if (!this.isInvariantMapTrigger)
860
869
  this.initBitset = filter.clone();
861
870
 
862
- // filter.copyFrom(invariantMapBitset.and(this.initBitset), false);
863
871
  const temp = invariantMapBitset.and(this.initBitset);
864
872
  filter.init((i) => temp.get(i), false);
865
873
  });
@@ -871,54 +879,35 @@ export class PeptidesModel {
871
879
  this.df.selection.fireChanged();
872
880
  this.modifyOrCreateSplitCol();
873
881
  this.headerSelectedMonomers = calculateSelected(this.df);
874
- grok.shell.o = this.createAccordion().root;
882
+ const acc = this.createAccordion();
883
+ grok.shell.o = acc.root;
884
+ for (const pane of acc.panes)
885
+ pane.expanded = true;
875
886
  this.isPeptideSpaceChangingBitset = false;
876
887
  }
877
888
 
878
- postProcessGrids(posCols: string[]): void {
879
- const mdCol: DG.GridColumn = this.mostPotentResiduesGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
880
- mdCol.name = 'Diff';
881
-
882
- for (const grid of [this.mutationCliffsGrid, this.mostPotentResiduesGrid]) {
883
- const gridProps = grid.props;
884
- gridProps.rowHeight = 20;
885
- const girdCols = grid.columns;
886
- const colNum = girdCols.length;
887
- for (let i = 0; i < colNum; ++i) {
888
- const col = girdCols.byIndex(i)!;
889
- const colName = col.name;
890
- col.width =
891
- grid == this.mostPotentResiduesGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER ? 50 :
892
- gridProps.rowHeight + 10;
893
- }
894
- }
895
-
896
- const setViewerGridProps = (grid: DG.Grid): void => {
897
- const gridProps = grid.props;
898
- gridProps.allowEdit = false;
899
- gridProps.allowRowSelection = false;
900
- gridProps.allowBlockSelection = false;
901
- gridProps.allowColSelection = false;
902
- };
903
-
904
- setViewerGridProps(this.mutationCliffsGrid);
905
- setViewerGridProps(this.mostPotentResiduesGrid);
906
- // if (this.df.getTag(C.TAGS.CLUSTERS))
907
- // setViewerGridProps(this.logoSummaryGrid);
908
-
909
- for (let gcIndex = 0; gcIndex < this.sourceGrid.columns.length; ++gcIndex) {
910
- const col = this.sourceGrid.columns.byIndex(gcIndex)!;
911
- if (!col.column)
912
- continue;
913
-
914
- if (posCols.includes(col.name))
915
- col.name = col.name.substring(1);
916
-
917
- col.visible =
918
- col.column?.semType === C.SEM_TYPES.MONOMER ||
919
- col.column.name === C.COLUMNS_NAMES.ACTIVITY_SCALED ||
920
- Object.keys(this.settings.columns ?? {}).includes(col.column.name ?? '');
889
+ postProcessGrids(): void {
890
+ const posCols = this.splitSeqDf.columns.names();
891
+ const sourceGrid = this.analysisView.grid;
892
+ const sourceGridCols = sourceGrid.columns;
893
+ const sourceGridColsLen = sourceGridCols.length;
894
+ const visibleColumns = Object.keys(this.settings.columns ?? {});
895
+ const sourceGridProps = sourceGrid.props;
896
+ sourceGridProps.allowColSelection = false;
897
+ sourceGridProps.allowEdit = false;
898
+ sourceGridProps.allowRowResizing = false;
899
+ sourceGridProps.showCurrentRowIndicator = false;
900
+ this.df.temp[C.EMBEDDING_STATUS] = false;
901
+ for (let colIdx = 1; colIdx < sourceGridColsLen; ++colIdx) {
902
+ const gridCol = sourceGridCols.byIndex(colIdx)!;
903
+ const tableColName = gridCol.column!.name;
904
+ gridCol.visible = posCols.includes(tableColName) || (tableColName === C.COLUMNS_NAMES.ACTIVITY_SCALED) ||
905
+ visibleColumns.includes(tableColName);
906
+ gridCol.width = 60;
921
907
  }
908
+ setTimeout(() => {
909
+ sourceGridProps.rowHeight = 20;
910
+ }, 500);
922
911
  }
923
912
 
924
913
  getSplitColValueAt(index: number, aar: string, position: string, aarLabel: string): string {
@@ -935,77 +924,41 @@ export class PeptidesModel {
935
924
  }
936
925
 
937
926
  /** Class initializer */
938
- async init(): Promise<void> {
927
+ init(): void {
939
928
  if (this.isInitialized)
940
929
  return;
941
930
  this.isInitialized = true;
942
931
 
943
- // Don't find the dataset if the analysis started from button
944
- if (this.df.getTag('newAnalysis') !== '1')
945
- this.currentView = wu(grok.shell.tableViews).find(({dataFrame}) => dataFrame.tags[C.PEPTIDES_ANALYSIS] === '1')!;
946
-
947
- this.currentView ??= grok.shell.addTableView(this.df);
948
-
949
- this.df.setTag('newAnalysis', '');
950
932
  if (!this.isRibbonSet) {
951
933
  //TODO: don't pass model, pass parameters instead
952
934
  const settingsButton = ui.bigButton('Settings', () => getSettingsDialog(this), 'Peptides analysis settings');
953
- this.currentView.setRibbonPanels([[settingsButton]], false);
935
+ this.analysisView.setRibbonPanels([[settingsButton]], false);
954
936
  this.isRibbonSet = true;
955
937
  }
956
- grok.shell.v = this.currentView;
957
- this.sourceGrid = this.currentView.grid;
958
- if (this.df.tags[C.PEPTIDES_ANALYSIS] === '1')
959
- return;
960
938
 
961
939
  this.df.tags[C.PEPTIDES_ANALYSIS] = '1';
962
- const scaledGridCol = this.sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!;
963
- scaledGridCol.name = scaledGridCol.column!.getTag('gridName');
964
- scaledGridCol.format = '#.000';
965
- this.sourceGrid.columns.setOrder([scaledGridCol.name]);
966
- this.sourceGrid.props.allowColSelection = false;
967
940
 
968
- this.df.temp[C.EMBEDDING_STATUS] = false;
969
- const adjustCellSize = (grid: DG.Grid): void => {
970
- const colNum = grid.columns.length;
971
- for (let i = 0; i < colNum; ++i) {
972
- const iCol = grid.columns.byIndex(i)!;
973
- iCol.width = isNaN(parseInt(iCol.name)) ? 50 : 40;
974
- }
975
- grid.props.rowHeight = 20;
976
- };
977
-
978
- for (let i = 0; i < this.sourceGrid.columns.length; i++) {
979
- const currentCol = this.sourceGrid.columns.byIndex(i);
980
- if (currentCol?.column?.getTag(C.TAGS.VISIBLE) === '0')
981
- currentCol.visible = false;
982
- }
983
-
984
- const options = {scaling: this.df.tags['scaling']};
985
-
986
- const dockManager = this.currentView.dockManager;
987
-
988
- const mutationCliffsViewer = await this.df.plot.fromType('peptide-sar-viewer', options) as MutationCliffsViewer;
941
+ this.updateDefault();
989
942
 
990
- const mostPotentResiduesViewer =
991
- await this.df.plot.fromType('peptide-sar-viewer-vertical', options) as MostPotentResiduesViewer;
943
+ this.analysisView.grid.invalidate();
944
+ }
992
945
 
993
- if (this.df.getTag(C.TAGS.CLUSTERS)) {
994
- const logoSummary = await this.df.plot.fromType('logo-summary-viewer') as LogoSummary;
995
- dockManager.dock(logoSummary, DG.DOCK_TYPE.RIGHT, null, 'Logo Summary Table');
996
- }
946
+ async addViewers(): Promise<void> {
947
+ const dockManager = this.analysisView.dockManager;
948
+ const dfPlt = this.df.plot;
997
949
 
998
- this.updateDefault();
950
+ const mutationCliffsViewer = await dfPlt.fromType('peptide-sar-viewer') as MonomerPosition;
951
+ const mostPotentResiduesViewer = await dfPlt.fromType('peptide-sar-viewer-vertical') as MostPotentResiduesViewer;
952
+ if (this.settings.clustersColumnName)
953
+ await this.addLogoSummaryTableViewer();
999
954
 
1000
- const mcNode =
1001
- dockManager.dock(mutationCliffsViewer, DG.DOCK_TYPE.DOWN, null, mutationCliffsViewer.name);
955
+ const mcNode = dockManager.dock(mutationCliffsViewer, DG.DOCK_TYPE.DOWN, null, mutationCliffsViewer.name);
1002
956
 
1003
957
  dockManager.dock(mostPotentResiduesViewer, DG.DOCK_TYPE.RIGHT, mcNode, mostPotentResiduesViewer.name, 0.3);
958
+ }
1004
959
 
1005
-
1006
- this.sourceGrid.props.allowEdit = false;
1007
- adjustCellSize(this.sourceGrid);
1008
-
1009
- this.invalidateGrids();
960
+ async addLogoSummaryTableViewer(): Promise<void> {
961
+ const logoSummary = await this.df.plot.fromType('logo-summary-viewer') as LogoSummary;
962
+ this.analysisView.dockManager.dock(logoSummary, DG.DOCK_TYPE.RIGHT, null, 'Logo Summary Table');
1010
963
  }
1011
964
  }