@datagrok/peptides 1.17.0 → 1.17.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 (58) hide show
  1. package/.eslintrc.json +17 -6
  2. package/dist/196.js +2 -0
  3. package/dist/23.js +2 -0
  4. package/dist/282.js +2 -0
  5. package/dist/361.js +2 -2
  6. package/dist/40.js +2 -0
  7. package/dist/436.js +2 -2
  8. package/dist/65.js +2 -0
  9. package/dist/704.js +2 -0
  10. package/dist/package-test.js +2 -3
  11. package/dist/package.js +2 -3
  12. package/package.json +14 -14
  13. package/src/demo/fasta.ts +8 -2
  14. package/src/model.ts +780 -531
  15. package/src/package-test.ts +1 -3
  16. package/src/package.ts +15 -28
  17. package/src/tests/benchmarks.ts +31 -11
  18. package/src/tests/core.ts +11 -6
  19. package/src/tests/misc.ts +6 -6
  20. package/src/tests/model.ts +79 -44
  21. package/src/tests/table-view.ts +48 -38
  22. package/src/tests/utils.ts +0 -76
  23. package/src/tests/viewers.ts +30 -12
  24. package/src/tests/widgets.ts +30 -11
  25. package/src/utils/algorithms.ts +115 -38
  26. package/src/utils/cell-renderer.ts +181 -72
  27. package/src/utils/constants.ts +33 -7
  28. package/src/utils/misc.ts +244 -10
  29. package/src/utils/parallel-mutation-cliffs.ts +18 -15
  30. package/src/utils/statistics.ts +70 -15
  31. package/src/utils/tooltips.ts +42 -17
  32. package/src/utils/types.ts +29 -26
  33. package/src/utils/worker-creator.ts +5 -0
  34. package/src/viewers/logo-summary.ts +591 -130
  35. package/src/viewers/sar-viewer.ts +893 -239
  36. package/src/widgets/distribution.ts +305 -64
  37. package/src/widgets/manual-alignment.ts +18 -11
  38. package/src/widgets/mutation-cliffs.ts +44 -18
  39. package/src/widgets/peptides.ts +86 -91
  40. package/src/widgets/selection.ts +56 -22
  41. package/src/widgets/settings.ts +94 -44
  42. package/src/workers/dimensionality-reducer.ts +5 -6
  43. package/src/workers/mutation-cliffs-worker.ts +3 -16
  44. package/dist/209.js +0 -2
  45. package/dist/381.js +0 -2
  46. package/dist/770.js +0 -2
  47. package/dist/831.js +0 -2
  48. package/dist/868.js +0 -2
  49. package/dist/931.js +0 -3
  50. package/dist/931.js.LICENSE.txt +0 -51
  51. package/dist/932.js +0 -2
  52. package/dist/package-test.js.LICENSE.txt +0 -51
  53. package/dist/package.js.LICENSE.txt +0 -51
  54. package/src/tests/peptide-space-test.ts +0 -48
  55. package/src/tests/test-data.ts +0 -649
  56. package/src/utils/molecular-measure.ts +0 -174
  57. package/src/utils/peptide-similarity-space.ts +0 -216
  58. package/src/viewers/peptide-space-viewer.ts +0 -150
@@ -3,60 +3,319 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import $ from 'cash-dom';
6
- import {CLUSTER_TYPE, ClusterType, PeptidesModel, VIEWER_TYPE} from '../model';
6
+ import {PeptidesModel, VIEWER_TYPE} from '../model';
7
7
  import * as C from '../utils/constants';
8
+ import {COLUMN_NAME, SCALING_METHODS} from '../utils/constants';
8
9
  import * as CR from '../utils/cell-renderer';
9
10
  import {HorizontalAlignments, IWebLogoViewer, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
10
- import {getAggregatedColumnValues, getAggregatedValue, getStats, Stats} from '../utils/statistics';
11
+ import {
12
+ AggregationColumns,
13
+ ClusterTypeStats,
14
+ getAggregatedColumnValues,
15
+ getAggregatedValue,
16
+ getStats,
17
+ StatsItem,
18
+ } from '../utils/statistics';
11
19
  import wu from 'wu';
12
20
  import {getActivityDistribution, getStatsTableMap} from '../widgets/distribution';
13
- import {getDistributionPanel, getDistributionTable} from '../utils/misc';
21
+ import {
22
+ getDistributionPanel,
23
+ getDistributionTable,
24
+ getTotalAggColumns,
25
+ isApplicableDataframe,
26
+ modifySelection,
27
+ scaleActivity,
28
+ } from '../utils/misc';
14
29
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
30
+ import * as type from '../utils/types';
15
31
  import {SelectionItem} from '../utils/types';
16
32
  import {_package} from '../package';
33
+ import {calculateClusterStatistics} from '../utils/algorithms';
34
+ import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
35
+ import {SARViewer} from './sar-viewer';
17
36
 
18
37
  const getAggregatedColName = (aggF: string, colName: string): string => `${aggF}(${colName})`;
19
38
 
20
- export enum LST_PROPERTIES {
39
+ export enum CLUSTER_TYPE {
40
+ ORIGINAL = 'original',
41
+ CUSTOM = 'custom',
42
+ }
43
+
44
+ export type ClusterType = `${CLUSTER_TYPE}`;
45
+
46
+ export const enum LST_PROPERTIES {
21
47
  WEB_LOGO_MODE = 'webLogoMode',
22
48
  MEMBERS_RATIO_THRESHOLD = 'membersRatioThreshold',
49
+ SEQUENCE = 'sequence',
50
+ CLUSTERS = 'clusters',
51
+ COLUMNS = 'columns',
52
+ AGGREGATION = 'aggregation',
53
+ ACTIVITY_SCALING = 'activityScaling',
54
+ ACTIVITY = 'activity',
55
+ }
56
+
57
+ enum LST_CATEGORIES {
58
+ GENERAL = 'General',
59
+ STYLE = 'WebLogo',
60
+ AGGREGATION = 'Aggregation',
61
+ }
62
+
63
+ export interface ILogoSummaryTable {
64
+ sequenceColumnName: string;
65
+ clustersColumnName: string;
66
+ activityColumnName: string;
67
+ activityScaling: string;
23
68
  }
24
69
 
25
- export class LogoSummaryTable extends DG.JsViewer {
70
+ /** LogoSummaryTable viewer shows per cluster information. */
71
+ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
26
72
  _titleHost = ui.divText(VIEWER_TYPE.LOGO_SUMMARY_TABLE, {id: 'pep-viewer-title'});
27
- model!: PeptidesModel;
28
- viewerGrid!: DG.Grid;
29
- initialized: boolean = false;
73
+ sequenceColumnName: string;
74
+ clustersColumnName: string;
30
75
  webLogoMode: string;
31
76
  membersRatioThreshold: number;
32
77
  bitsets: DG.BitSet[] = [];
33
78
  keyPress: boolean = false;
34
79
  currentRowIndex: number | null = null;
80
+ columns: string[];
81
+ aggregation: string;
82
+ activityColumnName: string;
83
+ activityScaling: SCALING_METHODS;
84
+ _scaledActivityColumn: DG.Column | null = null;
35
85
 
86
+ /** Creates LogoSummaryTable properties. */
36
87
  constructor() {
37
88
  super();
38
89
 
90
+ this.sequenceColumnName = this.column(LST_PROPERTIES.SEQUENCE,
91
+ {
92
+ category: LST_CATEGORIES.GENERAL,
93
+ nullable: false,
94
+ });
95
+ this.clustersColumnName = this.column(LST_PROPERTIES.CLUSTERS,
96
+ {
97
+ category: LST_CATEGORIES.GENERAL,
98
+ nullable: false,
99
+ });
100
+ this.activityColumnName = this.column(LST_PROPERTIES.ACTIVITY, {category: LST_CATEGORIES.GENERAL, nullable: false});
101
+ this.activityScaling = this.string(LST_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
102
+ {
103
+ category: LST_CATEGORIES.GENERAL,
104
+ choices: Object.values(C.SCALING_METHODS),
105
+ }) as SCALING_METHODS;
106
+
39
107
  this.webLogoMode = this.string(LST_PROPERTIES.WEB_LOGO_MODE, PositionHeight.Entropy,
40
- {choices: [PositionHeight.full, PositionHeight.Entropy]});
41
- this.membersRatioThreshold = this.float(LST_PROPERTIES.MEMBERS_RATIO_THRESHOLD, 0.3, {min: 0, max: 1.0});
108
+ {
109
+ choices: [PositionHeight.full, PositionHeight.Entropy],
110
+ category: LST_CATEGORIES.STYLE,
111
+ });
112
+ this.membersRatioThreshold = this.float(LST_PROPERTIES.MEMBERS_RATIO_THRESHOLD, 0.3,
113
+ {
114
+ min: 0,
115
+ max: 1.0,
116
+ category: LST_CATEGORIES.STYLE,
117
+ });
118
+
119
+ this.columns = this.columnList(LST_PROPERTIES.COLUMNS, [], {category: LST_CATEGORIES.AGGREGATION});
120
+ this.aggregation = this.string(LST_PROPERTIES.AGGREGATION, DG.AGG.AVG,
121
+ {
122
+ category: LST_CATEGORIES.AGGREGATION,
123
+ choices: C.AGGREGATION_TYPES,
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Returns PeptidesModel instance that belongs to the attached dataframe.
129
+ * @return - PeptidesModel instance.
130
+ */
131
+ get model(): PeptidesModel {
132
+ return PeptidesModel.getInstance(this.dataFrame);
133
+ }
134
+
135
+ _viewerGrid: DG.Grid | null = null;
136
+
137
+ /**
138
+ * Returns LogoSummaryTable grid. Creates a new one if it is null.
139
+ * @return - LogoSummaryTable grid.
140
+ */
141
+ get viewerGrid(): DG.Grid {
142
+ this._viewerGrid ??= this.createLogoSummaryTableGrid();
143
+ return this._viewerGrid;
144
+ }
145
+
146
+ _clusterStats: ClusterTypeStats | null = null;
147
+
148
+ /**
149
+ * Returns cluster statistics. Calculates it if it is null.
150
+ * @return - cluster statistics.
151
+ */
152
+ get clusterStats(): ClusterTypeStats {
153
+ this._clusterStats ??= calculateClusterStatistics(this.dataFrame, this.clustersColumnName,
154
+ this.customClusters.toArray(), this.getScaledActivityColumn());
155
+ return this._clusterStats;
156
+ }
157
+
158
+ _clusterSelection: type.Selection | null = null;
159
+
160
+ /**
161
+ * Returns cluster selection. Initializes it if it is null.
162
+ * @return - cluster selection.
163
+ */
164
+ get clusterSelection(): type.Selection {
165
+ const tagSelection = this.dataFrame.getTag(C.TAGS.CLUSTER_SELECTION);
166
+ this._clusterSelection ??= tagSelection === null ? this.initClusterSelection({notify: false}) :
167
+ JSON.parse(tagSelection);
168
+ return this._clusterSelection!;
169
+ }
170
+
171
+ /**
172
+ * Sets cluster selection.
173
+ * @param selection - cluster selection.
174
+ */
175
+ set clusterSelection(selection: type.Selection) {
176
+ this._clusterSelection = selection;
177
+ this.dataFrame.setTag(C.TAGS.CLUSTER_SELECTION, JSON.stringify(selection));
178
+ this.model.fireBitsetChanged(VIEWER_TYPE.LOGO_SUMMARY_TABLE);
179
+ this.model.analysisView.grid.invalidate();
180
+ }
181
+
182
+ _logoSummaryTable: DG.DataFrame | null = null;
183
+
184
+ /**
185
+ * Returns LogoSummaryTable dataframe. Creates a new one if it is null.
186
+ * @return - LogoSummaryTable dataframe.
187
+ */
188
+ get logoSummaryTable(): DG.DataFrame {
189
+ this._logoSummaryTable ??= this.createLogoSummaryTable();
190
+ return this._logoSummaryTable;
191
+ }
192
+
193
+ /**
194
+ * Sets LogoSummaryTable dataframe.
195
+ * @param df - LogoSummaryTable dataframe.
196
+ */
197
+ set logoSummaryTable(df: DG.DataFrame) {
198
+ this._logoSummaryTable = df;
199
+ }
200
+
201
+ _positionColumns: DG.Column<string>[] | null = null;
202
+
203
+ /**
204
+ * Returns position columns. If position columns are not attached to LogoSummaryTable, it tries to get them from
205
+ * other viewers or analysis (given that relevant parameters are the same), or creates own position columns.
206
+ * @return - position columns.
207
+ */
208
+ get positionColumns(): DG.Column<string>[] {
209
+ if (this._positionColumns != null) {
210
+ return this._positionColumns;
211
+ }
212
+
213
+
214
+ const getSharedPositionColumns = (viewerType: VIEWER_TYPE): DG.Column<string>[] | null => {
215
+ const viewer = this.model.findViewer(viewerType) as SARViewer | null;
216
+ if (this.sequenceColumnName === viewer?.sequenceColumnName) {
217
+ return viewer._positionColumns;
218
+ }
219
+
220
+
221
+ return null;
222
+ };
223
+
224
+ if (this.model.positionColumns != null && this.sequenceColumnName === this.model.settings?.sequenceColumnName) {
225
+ this._positionColumns = this.model.positionColumns;
226
+ }
227
+
228
+
229
+ this._positionColumns ??= getSharedPositionColumns(VIEWER_TYPE.MONOMER_POSITION) ??
230
+ getSharedPositionColumns(VIEWER_TYPE.MOST_POTENT_RESIDUES) ??
231
+ splitAlignedSequences(this.dataFrame.getCol(this.sequenceColumnName)).columns.toList();
232
+ return this._positionColumns!;
233
+ }
234
+
235
+ /**
236
+ * Checks if cluster selection is empty.
237
+ * @return - flag indicating if cluster selection is empty.
238
+ */
239
+ get isClusterSelectionEmpty(): boolean {
240
+ const clusterSelectionCount =
241
+ this.clusterSelection[CLUSTER_TYPE.ORIGINAL].length + this.clusterSelection[CLUSTER_TYPE.CUSTOM].length;
242
+ return clusterSelectionCount === 0;
243
+ }
244
+
245
+ /**
246
+ * Gets iterable over custom clusters columns.
247
+ * @return - iterable over custom clusters columns.
248
+ */
249
+ get customClusters(): wu.WuIterable<DG.Column<boolean>> {
250
+ const query: {
251
+ [key: string]: string
252
+ } = {};
253
+ query[C.TAGS.CUSTOM_CLUSTER] = '1';
254
+ return wu(this.dataFrame.columns.byTags(query));
255
+ }
256
+
257
+ /**
258
+ * Gets scaled activity column.
259
+ * @param isFiltered - flag indicating if only filtered rows should be taken into account.
260
+ * @return - scaled activity column.
261
+ */
262
+ getScaledActivityColumn(isFiltered: boolean = false): DG.Column<number> {
263
+ if (this.model.settings?.activityColumnName === this.activityColumnName &&
264
+ this.model.settings?.activityScaling === this.activityScaling) {
265
+ this._scaledActivityColumn = this.model.getScaledActivityColumn(isFiltered);
266
+ }
267
+
268
+
269
+ this._scaledActivityColumn ??= scaleActivity(this.dataFrame.getCol(this.activityColumnName),
270
+ this.activityScaling);
271
+ if (isFiltered) {
272
+ return DG.DataFrame.fromColumns([this._scaledActivityColumn]).clone(this.dataFrame.filter)
273
+ .getCol(this._scaledActivityColumn.name) as DG.Column<number>;
274
+ }
275
+ return this._scaledActivityColumn as DG.Column<number>;
276
+ }
277
+
278
+ /**
279
+ * Gets a map of columns and aggregations.
280
+ * @return - map of columns and aggregations.
281
+ */
282
+ getAggregationColumns(): AggregationColumns {
283
+ return Object.fromEntries(this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG]));
42
284
  }
43
285
 
286
+ /** Processes attached table and sets viewer properties. */
44
287
  onTableAttached(): void {
45
288
  super.onTableAttached();
46
- this.model = PeptidesModel.getInstance(this.dataFrame);
47
- this.createLogoSummaryTableGrid();
48
- this.initialized = true;
289
+ if (isApplicableDataframe(this.dataFrame)) {
290
+ this.getProperty(`${LST_PROPERTIES.SEQUENCE}${COLUMN_NAME}`)
291
+ ?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
292
+ this.getProperty(`${LST_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
293
+ ?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
294
+ this.getProperty(`${LST_PROPERTIES.CLUSTERS}${COLUMN_NAME}`)
295
+ ?.set(this, wu(this.dataFrame.columns.categorical).next().value.name);
296
+ } else {
297
+ const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
298
+ grok.log.error(msg);
299
+ grok.shell.warning(msg);
300
+ }
49
301
  this.render();
50
302
  }
51
303
 
52
- detach(): void {this.subs.forEach((sub) => sub.unsubscribe());}
304
+ /** Removes all the active subscriptions. */
305
+ detach(): void {
306
+ this.subs.forEach((sub) => sub.unsubscribe());
307
+ }
53
308
 
309
+ /** Renders Logo Summary Table body. */
54
310
  render(): void {
55
- if (!this.initialized)
56
- return;
57
311
  $(this.root).empty();
58
- const df = this.viewerGrid.dataFrame;
59
- if (!df.filter.anyTrue) {
312
+ if (this.clustersColumnName == null || this.sequenceColumnName == null || this.activityColumnName == null) {
313
+ this.root.appendChild(
314
+ ui.divText('Please, select a sequence, cluster and activity columns in the viewer properties'));
315
+ return;
316
+ }
317
+
318
+ if (!this.logoSummaryTable.filter.anyTrue) {
60
319
  const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
61
320
  'Please, lower the threshold in viewer proeperties to include clusters');
62
321
  this.root.appendChild(ui.divV([this._titleHost, emptyDf]));
@@ -72,37 +331,115 @@ export class LogoSummaryTable extends DG.JsViewer {
72
331
  $(expand).addClass('pep-help-icon');
73
332
  this.viewerGrid.root.style.width = 'auto';
74
333
  this.root.appendChild(ui.divV([
75
- ui.divH([this._titleHost, expand], {style: {alignSelf: 'center', lineHeight: 'normal'}}),
334
+ ui.divH([this._titleHost, expand], {
335
+ style: {
336
+ alignSelf: 'center',
337
+ lineHeight: 'normal',
338
+ },
339
+ }),
76
340
  this.viewerGrid.root,
77
341
  ]));
78
342
  this.viewerGrid.invalidate();
79
343
  }
80
344
 
345
+ /**
346
+ * Processes property changes.
347
+ * @param property - changed property.
348
+ */
81
349
  onPropertyChanged(property: DG.Property): void {
82
350
  super.onPropertyChanged(property);
83
- if (property.name === 'membersRatioThreshold')
351
+ let doRender = false;
352
+ switch (property.name) {
353
+ case LST_PROPERTIES.MEMBERS_RATIO_THRESHOLD:
84
354
  this.updateFilter();
85
- this.render();
355
+ break;
356
+ case `${LST_PROPERTIES.SEQUENCE}${COLUMN_NAME}`:
357
+ this._viewerGrid = null;
358
+ this._logoSummaryTable = null;
359
+ doRender = true;
360
+ break;
361
+ case `${LST_PROPERTIES.CLUSTERS}${COLUMN_NAME}`:
362
+ this._clusterStats = null;
363
+ this._clusterSelection = null;
364
+ this._viewerGrid = null;
365
+ this._logoSummaryTable = null;
366
+ doRender = true;
367
+ break;
368
+ case `${LST_PROPERTIES.ACTIVITY}${COLUMN_NAME}`:
369
+ case LST_PROPERTIES.ACTIVITY_SCALING:
370
+ this._scaledActivityColumn = null;
371
+ this._viewerGrid = null;
372
+ this._clusterStats = null;
373
+ this._logoSummaryTable = null;
374
+ doRender = true;
375
+ break;
376
+ case LST_PROPERTIES.COLUMNS:
377
+ case LST_PROPERTIES.AGGREGATION:
378
+ this._viewerGrid = null;
379
+ this._logoSummaryTable = null;
380
+ doRender = true;
381
+ break;
382
+ }
383
+ if (doRender) {
384
+ this.render();
385
+ }
86
386
  }
87
387
 
88
- createLogoSummaryTableGrid(): DG.Grid {
89
- const clustersColName = this.model.settings.clustersColumnName!;
388
+ /**
389
+ * Initializes cluster selection.
390
+ * @param options - initializatiion options.
391
+ * @param options.notify - flag that indicates if bitset changed event should fire.
392
+ * @return - cluster selection.
393
+ */
394
+ initClusterSelection(options: {
395
+ notify?: boolean
396
+ } = {}): type.Selection {
397
+ options.notify ??= true;
398
+
399
+ const newClusterSelection = {} as type.Selection;
400
+ newClusterSelection[CLUSTER_TYPE.ORIGINAL] = [];
401
+ newClusterSelection[CLUSTER_TYPE.CUSTOM] = [];
402
+ if (options.notify) {
403
+ this.clusterSelection = newClusterSelection;
404
+ } else {
405
+ this._clusterSelection = newClusterSelection;
406
+ }
407
+
408
+
409
+ return this.clusterSelection;
410
+ }
411
+
412
+ /**
413
+ * Gets total viewer aggregated columns from viewer properties and analysis settings if applicable.
414
+ * @return - total viewer aggregated columns.
415
+ */
416
+ getTotalViewerAggColumns(): [string, DG.AggregationType][] {
417
+ const aggrCols = this.getAggregationColumns();
418
+ return getTotalAggColumns(this.columns, aggrCols, this.model?.settings?.columns);
419
+ }
420
+
421
+ /**
422
+ * Creates LogoSummaryTable dataframe to be used in LogoSummaryTable grid.
423
+ * @return - LogoSummaryTable dataframe.
424
+ */
425
+ createLogoSummaryTable(): DG.DataFrame {
426
+ const clustersColName = this.clustersColumnName;
90
427
  const isDfFiltered = this.dataFrame.filter.anyFalse;
91
428
  const filteredDf = isDfFiltered ? this.dataFrame.clone(this.dataFrame.filter) : this.dataFrame;
92
429
  const filteredDfCols = filteredDf.columns;
93
430
  const filteredDfRowCount = filteredDf.rowCount;
94
- const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY);
95
- const activityColData = activityCol.getRawData();
431
+ const activityColData = this.getScaledActivityColumn(isDfFiltered).getRawData();
96
432
 
97
433
  const filteredDfClustCol = filteredDf.getCol(clustersColName);
98
434
  const filteredDfClustColData = filteredDfClustCol.getRawData();
99
435
  const filteredDfClustColCat = filteredDfClustCol.categories;
100
436
 
101
- const pepCol: DG.Column<string> = filteredDf.getCol(this.model.settings.sequenceColumnName!);
102
-
103
- const query: { [key: string]: string } = {};
437
+ const query: {
438
+ [key: string]: string
439
+ } = {};
104
440
  query[C.TAGS.CUSTOM_CLUSTER] = '1';
105
- const customClustColList: DG.Column<boolean>[] = wu(filteredDfCols.byTags(query)).filter((c) => c.max > 0).toArray();
441
+ const customClustColList: DG.Column<boolean>[] = wu(filteredDfCols.byTags(query))
442
+ .filter((c) => c.max > 0).toArray();
106
443
 
107
444
  const customLST = DG.DataFrame.create(customClustColList.length);
108
445
  const customLSTCols = customLST.columns;
@@ -116,7 +453,7 @@ export class LogoSummaryTable extends DG.JsViewer {
116
453
  const customRatioColData = customLSTCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
117
454
 
118
455
  let origLSTBuilder = filteredDf.groupBy([clustersColName]);
119
- const aggColsEntries = Object.entries(this.model.settings.columns ?? {});
456
+ const aggColsEntries = this.getTotalViewerAggColumns();
120
457
  const aggColNames = aggColsEntries.map(([colName, aggFn]) => getAggregatedColName(aggFn, colName));
121
458
  const customAggRawCols = new Array(aggColNames.length);
122
459
  const colAggEntries = aggColsEntries.map(
@@ -136,12 +473,15 @@ export class LogoSummaryTable extends DG.JsViewer {
136
473
  const customClustCol = customClustColList[rowIdx];
137
474
  customLSTClustCol.set(rowIdx, customClustCol.name);
138
475
  const bitArray = BitArray.fromUint32Array(filteredDfRowCount, customClustCol.getRawData() as Uint32Array);
139
- if (bitArray.allFalse || bitArray.allTrue)
476
+ if (bitArray.allFalse || bitArray.allTrue) {
140
477
  continue;
478
+ }
479
+
480
+
141
481
  const bsMask = DG.BitSet.fromBytes(bitArray.buffer.buffer, filteredDfRowCount);
142
482
 
143
- const stats: Stats = isDfFiltered ? getStats(activityColData, bitArray) :
144
- this.model.clusterStats[CLUSTER_TYPE.CUSTOM][customClustCol.name];
483
+ const stats: StatsItem = isDfFiltered ? getStats(activityColData, bitArray) :
484
+ this.clusterStats[CLUSTER_TYPE.CUSTOM][customClustCol.name];
145
485
 
146
486
  customMembersColData[rowIdx] = stats.count;
147
487
  customBitsets[rowIdx] = bsMask;
@@ -154,10 +494,8 @@ export class LogoSummaryTable extends DG.JsViewer {
154
494
  customAggRawCols[aggColIdx][rowIdx] = getAggregatedValue(col, aggFn, bsMask);
155
495
  }
156
496
  }
157
-
158
497
  customWebLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
159
498
  customDistCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
160
-
161
499
  // END
162
500
 
163
501
  // BEGIN: fill LST part with original clusters
@@ -166,9 +504,12 @@ export class LogoSummaryTable extends DG.JsViewer {
166
504
  const origLSTCols = origLST.columns;
167
505
  const origLSTClustCol: DG.Column<string> = origLST.getCol(clustersColName);
168
506
  origLSTClustCol.name = C.LST_COLUMN_NAMES.CLUSTER;
507
+ if (origLSTClustCol.type !== DG.COLUMN_TYPE.STRING) {
508
+ origLST.columns.replace(origLSTClustCol, origLSTClustCol.convertTo(DG.COLUMN_TYPE.STRING));
509
+ }
169
510
 
170
- const origLSTClustColCat = origLSTClustCol.categories;
171
511
 
512
+ const origLSTClustColCat = origLSTClustCol.categories;
172
513
  const origMembersColData = origLSTCols.addNewInt(C.LST_COLUMN_NAMES.MEMBERS).getRawData();
173
514
  const origWebLogoCol = origLSTCols.addNewString(C.LST_COLUMN_NAMES.WEB_LOGO);
174
515
  const origDistCol = origLSTCols.addNewString(C.LST_COLUMN_NAMES.DISTRIBUTION);
@@ -176,7 +517,6 @@ export class LogoSummaryTable extends DG.JsViewer {
176
517
  const origPValColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.P_VALUE).getRawData();
177
518
  const origRatioColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
178
519
  const origBitsets: DG.BitSet[] = new Array(origLSTLen);
179
-
180
520
  const origClustMasks = Array.from({length: origLSTLen},
181
521
  () => new BitArray(filteredDfRowCount, false));
182
522
 
@@ -188,12 +528,14 @@ export class LogoSummaryTable extends DG.JsViewer {
188
528
 
189
529
  for (let rowIdx = 0; rowIdx < origLSTLen; ++rowIdx) {
190
530
  const mask = origClustMasks[rowIdx];
191
- if (mask.allFalse || mask.allTrue)
531
+ if (mask.allFalse || mask.allTrue) {
192
532
  continue;
193
- const bsMask = DG.BitSet.fromBytes(mask.buffer.buffer, filteredDfRowCount);
533
+ }
194
534
 
535
+
536
+ const bsMask = DG.BitSet.fromBytes(mask.buffer.buffer, filteredDfRowCount);
195
537
  const stats = isDfFiltered ? getStats(activityColData, mask) :
196
- this.model.clusterStats[CLUSTER_TYPE.ORIGINAL][origLSTClustColCat[rowIdx]];
538
+ this.clusterStats[CLUSTER_TYPE.ORIGINAL][origLSTClustColCat[rowIdx]];
197
539
 
198
540
  origMembersColData[rowIdx] = stats.count;
199
541
  origBitsets[rowIdx] = bsMask;
@@ -201,7 +543,6 @@ export class LogoSummaryTable extends DG.JsViewer {
201
543
  origPValColData[rowIdx] = stats.pValue ?? DG.FLOAT_NULL;
202
544
  origRatioColData[rowIdx] = stats.ratio;
203
545
  }
204
-
205
546
  origWebLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
206
547
  origDistCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
207
548
  // END
@@ -209,30 +550,46 @@ export class LogoSummaryTable extends DG.JsViewer {
209
550
  // combine LSTs and create a grid
210
551
  const summaryTable = origLST.append(customLST);
211
552
  this.bitsets = origBitsets.concat(customBitsets);
553
+ return summaryTable;
554
+ }
212
555
 
213
- this.viewerGrid = summaryTable.plot.grid();
214
- this.viewerGrid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
556
+ /**
557
+ * Builds LogoSummaryTable interactive grid.
558
+ * @return - LogoSummaryTable grid.
559
+ */
560
+ createLogoSummaryTableGrid(): DG.Grid {
561
+ const isDfFiltered = this.dataFrame.filter.anyFalse;
562
+ const filteredDf = isDfFiltered ? this.dataFrame.clone(this.dataFrame.filter) : this.dataFrame;
563
+ const aggColsEntries = this.getTotalViewerAggColumns();
564
+ const aggColNames = aggColsEntries.map(([colName, aggFn]) => getAggregatedColName(aggFn, colName));
565
+
566
+ const grid = this.logoSummaryTable.plot.grid();
567
+ grid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
215
568
  this.updateFilter();
216
- const gridClustersCol = this.viewerGrid.col(C.LST_COLUMN_NAMES.CLUSTER)!;
569
+ const gridClustersCol = grid.col(C.LST_COLUMN_NAMES.CLUSTER)!;
217
570
  gridClustersCol.visible = true;
218
- this.viewerGrid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
571
+ grid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
219
572
  C.LST_COLUMN_NAMES.WEB_LOGO, C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
220
573
  C.LST_COLUMN_NAMES.P_VALUE, C.LST_COLUMN_NAMES.RATIO, ...aggColNames]);
221
- this.viewerGrid.columns.rowHeader!.visible = false;
222
- this.viewerGrid.props.rowHeight = 55;
574
+ grid.columns.rowHeader!.visible = false;
575
+ grid.props.rowHeight = 55;
223
576
 
224
577
  const webLogoCache = new DG.LruCache<number, DG.Viewer & IWebLogoViewer>();
225
578
  const distCache = new DG.LruCache<number, DG.Viewer<DG.IHistogramLookSettings>>();
226
- const maxSequenceLen = this.model.positionColumns.toArray().length;
227
- const webLogoGridCol = this.viewerGrid.columns.byName(C.LST_COLUMN_NAMES.WEB_LOGO)!;
579
+ const maxSequenceLen = this.positionColumns.length;
580
+ const webLogoGridCol = grid.columns.byName(C.LST_COLUMN_NAMES.WEB_LOGO)!;
228
581
  webLogoGridCol.cellType = 'html';
229
582
  webLogoGridCol.width = 350;
583
+ const activityCol = this.getScaledActivityColumn(isDfFiltered);
584
+ const pepCol: DG.Column<string> = filteredDf.getCol(this.sequenceColumnName);
230
585
 
231
- this.viewerGrid.onCellRender.subscribe(async (gridCellArgs) => {
586
+ grid.onCellRender.subscribe(async (gridCellArgs) => {
232
587
  const gridCell = gridCellArgs.cell;
233
588
  const currentRowIdx = gridCell.tableRowIndex;
234
- if (!gridCell.isTableCell || currentRowIdx === null || currentRowIdx === -1)
589
+ if (!gridCell.isTableCell || currentRowIdx === null || currentRowIdx === -1) {
235
590
  return;
591
+ }
592
+
236
593
 
237
594
  const canvasContext = gridCellArgs.g;
238
595
  const bound = gridCellArgs.bounds;
@@ -246,7 +603,7 @@ export class LogoSummaryTable extends DG.JsViewer {
246
603
  const clusterBitSet = this.bitsets[currentRowIdx];
247
604
 
248
605
  if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.CLUSTER) {
249
- CR.renderLogoSummaryCell(canvasContext, gridCell.cell.value, this.model.clusterSelection, bound);
606
+ CR.renderLogoSummaryCell(canvasContext, gridCell.cell.value, this.clusterSelection, bound);
250
607
  gridCellArgs.preventDefault();
251
608
  } else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.WEB_LOGO) {
252
609
  const positionWidth = Math.floor((gridCell.bounds.width - 2 - (4 * (maxSequenceLen - 1))) / maxSequenceLen);
@@ -256,20 +613,27 @@ export class LogoSummaryTable extends DG.JsViewer {
256
613
  const viewerProps = viewer.getProperties();
257
614
 
258
615
  for (const prop of viewerProps) {
259
- if (prop.name === 'positionHeight' && prop.get(viewer) !== this.webLogoMode)
616
+ if (prop.name === 'positionHeight' && prop.get(viewer) !== this.webLogoMode) {
260
617
  prop.set(viewer, this.webLogoMode);
261
- else if (prop.name === 'positionWidth' && prop.get(viewer) !== positionWidth)
618
+ } else if (prop.name === 'positionWidth' && prop.get(viewer) !== positionWidth) {
262
619
  prop.set(viewer, positionWidth);
263
- else if (prop.name === 'minHeight' && prop.get(viewer) !== height)
620
+ } else if (prop.name === 'minHeight' && prop.get(viewer) !== height) {
264
621
  prop.set(viewer, height);
622
+ }
265
623
  }
266
624
  const viewerRoot = $(viewer.root).css('height', `${height}px`);//;
267
625
  viewerRoot.children().first().css('overflow-y', 'hidden !important');
268
626
  } else {
269
627
  const webLogoTable = this.createWebLogoDf(pepCol, clusterBitSet);
270
628
  viewer = await webLogoTable.plot
271
- .fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
272
- maxHeight: 1000, minHeight: height, positionWidth: positionWidth, showPositionLabels: false});
629
+ .fromType('WebLogo', {
630
+ positionHeight: this.webLogoMode,
631
+ horizontalAlignment: HorizontalAlignments.LEFT,
632
+ maxHeight: 1000,
633
+ minHeight: height,
634
+ positionWidth: positionWidth,
635
+ showPositionLabels: false,
636
+ });
273
637
  webLogoCache.set(currentRowIdx, viewer);
274
638
  }
275
639
  gridCell.element = viewer.root;
@@ -277,10 +641,10 @@ export class LogoSummaryTable extends DG.JsViewer {
277
641
  } else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
278
642
  let viewer = distCache.get(currentRowIdx);
279
643
  if (viewer === undefined) {
280
- const distributionDf = this.createDistributionDf(activityCol, clusterBitSet);
644
+ const distributionDf = getDistributionTable(activityCol, clusterBitSet);
281
645
  viewer = distributionDf.plot.histogram({
282
646
  filteringEnabled: false,
283
- valueColumnName: C.COLUMNS_NAMES.ACTIVITY,
647
+ valueColumnName: activityCol.name,
284
648
  splitColumnName: C.COLUMNS_NAMES.SPLIT_COL,
285
649
  legendVisibility: 'Never',
286
650
  showXAxis: false,
@@ -301,106 +665,153 @@ export class LogoSummaryTable extends DG.JsViewer {
301
665
  canvasContext.restore();
302
666
  }
303
667
  });
304
- this.viewerGrid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
305
- DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell) => {
306
- if (!gridCell.isTableCell)
668
+ grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
669
+ DG.debounce(grid.onCurrentCellChanged, 500).subscribe((gridCell) => {
670
+ if (!gridCell.isTableCell) {
307
671
  return;
672
+ }
673
+
308
674
 
309
675
  try {
310
- if (!this.keyPress || gridCell.tableColumn?.name !== C.LST_COLUMN_NAMES.CLUSTER)
676
+ if (!this.keyPress || gridCell.tableColumn?.name !== C.LST_COLUMN_NAMES.CLUSTER) {
311
677
  return;
312
- if (this.currentRowIndex !== null && this.currentRowIndex !== -1)
313
- this.model.modifyClusterSelection(this.getCluster(this.viewerGrid.cell(C.LST_COLUMN_NAMES.CLUSTER, this.currentRowIndex)), {shiftPressed: true, ctrlPressed: true}, false);
678
+ }
679
+
314
680
 
315
- this.model.modifyClusterSelection(this.getCluster(gridCell), {shiftPressed: true, ctrlPressed: false});
316
- this.viewerGrid.invalidate();
681
+ if (this.currentRowIndex !== null && this.currentRowIndex !== -1) {
682
+ this.modifyClusterSelection(this.getCluster(grid.cell(C.LST_COLUMN_NAMES.CLUSTER, this.currentRowIndex)),
683
+ {
684
+ shiftPressed: true,
685
+ ctrlPressed: true,
686
+ }, false);
687
+ }
688
+
689
+ this.modifyClusterSelection(this.getCluster(gridCell), {
690
+ shiftPressed: true,
691
+ ctrlPressed: false,
692
+ });
693
+ grid.invalidate();
317
694
  } finally {
318
695
  this.keyPress = false;
319
696
  this.currentRowIndex = gridCell.gridRow;
320
697
  }
321
698
  });
322
- this.viewerGrid.root.addEventListener('keydown', (ev) => {
699
+ grid.root.addEventListener('keydown', (ev) => {
323
700
  this.keyPress = ev.key.startsWith('Arrow');
324
- if (this.keyPress)
701
+ if (this.keyPress) {
325
702
  return;
326
- if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.shiftKey && ev.ctrlKey))
327
- this.model.initClusterSelection({notify: false});
328
- else if (ev.code === 'KeyA' && ev.ctrlKey) {
329
- for (let rowIdx = 0; rowIdx < summaryTable.rowCount; ++rowIdx) {
330
- this.model.modifyClusterSelection(this.getCluster(this.viewerGrid.cell(C.LST_COLUMN_NAMES.CLUSTER, rowIdx)),
331
- {shiftPressed: true, ctrlPressed: false}, false);
703
+ }
704
+
705
+
706
+ if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.shiftKey && ev.ctrlKey)) {
707
+ this.initClusterSelection({notify: false});
708
+ } else if (ev.code === 'KeyA' && ev.ctrlKey) {
709
+ for (let rowIdx = 0; rowIdx < this.logoSummaryTable.rowCount; ++rowIdx) {
710
+ this.modifyClusterSelection(this.getCluster(grid.cell(C.LST_COLUMN_NAMES.CLUSTER, rowIdx)),
711
+ {
712
+ shiftPressed: true,
713
+ ctrlPressed: false,
714
+ }, false);
332
715
  }
333
716
  }
334
- this.model.fireBitsetChanged();
335
- this.viewerGrid.invalidate();
717
+ this.model.fireBitsetChanged(VIEWER_TYPE.LOGO_SUMMARY_TABLE);
718
+ grid.invalidate();
336
719
  });
337
- this.viewerGrid.root.addEventListener('click', (ev) => {
338
- const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
339
- if (!gridCell || !gridCell.isTableCell || gridCell.tableColumn?.name !== C.LST_COLUMN_NAMES.CLUSTER)
720
+ grid.root.addEventListener('click', (ev) => {
721
+ const gridCell = grid.hitTest(ev.offsetX, ev.offsetY);
722
+ if (!gridCell || !gridCell.isTableCell || gridCell.tableColumn?.name !== C.LST_COLUMN_NAMES.CLUSTER) {
340
723
  return;
724
+ }
725
+
341
726
 
342
727
  const selection = this.getCluster(gridCell);
343
- this.model.modifyClusterSelection(selection, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
344
- this.viewerGrid.invalidate();
728
+ this.modifyClusterSelection(selection, {
729
+ shiftPressed: ev.shiftKey,
730
+ ctrlPressed: ev.ctrlKey,
731
+ });
732
+ grid.invalidate();
345
733
 
346
734
  _package.files.readAsText('help/logo-summary-table.md').then((text) => {
347
735
  grok.shell.windows.help.showHelp(ui.markdown(text));
348
736
  }).catch((e) => grok.log.error(e));
349
737
  });
350
- this.viewerGrid.onCellTooltip((gridCell, x, y) => {
738
+ grid.onCellTooltip((gridCell, x, y) => {
351
739
  if (!gridCell.isTableCell) {
352
740
  this.model.unhighlight();
353
741
  return true;
354
742
  }
355
743
 
356
744
  const cluster = this.getCluster(gridCell);
357
- this.model.highlightCluster(cluster);
358
- if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.CLUSTER)
745
+ this.highlightCluster(cluster);
746
+ if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.CLUSTER) {
359
747
  this.showTooltip(cluster, x, y);
748
+ }
749
+
750
+
360
751
  return true;
361
752
  });
362
753
 
363
- const gridProps = this.viewerGrid.props;
754
+ const gridProps = grid.props;
364
755
  gridProps.allowEdit = false;
365
756
  gridProps.allowRowSelection = false;
366
757
  gridProps.allowBlockSelection = false;
367
758
  gridProps.allowColSelection = false;
368
759
  gridProps.showCurrentRowIndicator = false;
760
+ gridProps.showReadOnlyNotifications = false;
369
761
 
370
- return this.viewerGrid;
762
+ return grid;
371
763
  }
372
764
 
765
+ /**
766
+ * Highlights selected cluster.
767
+ * @param cluster - cluster to highlight.
768
+ */
769
+ highlightCluster(cluster: type.SelectionItem): void {
770
+ const bitArray = this.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster].mask;
771
+ this.dataFrame.rows.highlight((i) => bitArray.getBit(i));
772
+ this.model.isHighlighting = true;
773
+ }
774
+
775
+ /**
776
+ * Gets cluster from LogoSummaryTable grid cell.
777
+ * @param gridCell - LogoSummaryTable grid cell.
778
+ * @return - cluster.
779
+ */
373
780
  getCluster(gridCell: DG.GridCell): SelectionItem {
374
- const clustName = this.viewerGrid.dataFrame.get(C.LST_COLUMN_NAMES.CLUSTER, gridCell.tableRowIndex!);
375
- const clustColCat = this.dataFrame.getCol(this.model.settings.clustersColumnName!).categories;
376
- return {positionOrClusterType: clustColCat.includes(clustName) ? CLUSTER_TYPE.ORIGINAL : CLUSTER_TYPE.CUSTOM,
377
- monomerOrCluster: clustName};
781
+ const clustName = this.logoSummaryTable.get(C.LST_COLUMN_NAMES.CLUSTER, gridCell.tableRowIndex!);
782
+ const clustColCat = this.dataFrame.getCol(this.clustersColumnName).categories;
783
+ return {
784
+ positionOrClusterType: clustColCat.includes(clustName) ? CLUSTER_TYPE.ORIGINAL : CLUSTER_TYPE.CUSTOM,
785
+ monomerOrCluster: clustName,
786
+ };
378
787
  }
379
788
 
789
+ /** Updates LogoSummaryTable filter. */
380
790
  updateFilter(): void {
381
- const table = this.viewerGrid.table;
382
- const memberstCol = table.getCol(C.LST_COLUMN_NAMES.MEMBERS);
791
+ const memberstCol = this.logoSummaryTable.getCol(C.LST_COLUMN_NAMES.MEMBERS);
383
792
  const membersColData = memberstCol.getRawData();
384
793
  const maxCount = memberstCol.stats.max;
385
794
  const minMembers = Math.ceil(maxCount * this.membersRatioThreshold);
386
- table.filter.init((i) => membersColData[i] > minMembers);
795
+ this.logoSummaryTable.filter.init((i) => membersColData[i] > minMembers);
387
796
  }
388
797
 
798
+ /** Creates a new cluster from current selection and adds to Logo Summary Table. */
389
799
  clusterFromSelection(): void {
390
800
  const currentSelection = this.model.getVisibleSelection();
391
- const viewerDf = this.viewerGrid.dataFrame;
392
- const viewerDfCols = viewerDf.columns;
801
+ const viewerDfCols = this.logoSummaryTable.columns;
393
802
  const viewerDfColsLength = viewerDfCols.length;
394
803
  const newClusterVals = new Array(viewerDfCols.length);
395
- const activityScaledCol = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY);
804
+ const activityScaledCol = this.getScaledActivityColumn();
396
805
  const bitArray = BitArray.fromString(currentSelection.toBinaryString());
397
806
  const stats = getStats(activityScaledCol.getRawData(), bitArray);
398
807
 
399
808
  this.bitsets.push(currentSelection.clone());
400
809
 
401
- const newClusterName = this.model.df.columns.getUnusedName('New Cluster');
402
- const aggregatedValues: {[colName: string]: number} = {};
403
- const aggColsEntries = Object.entries(this.model.settings.columns ?? {});
810
+ const newClusterName = this.dataFrame.columns.getUnusedName('New Cluster');
811
+ const aggregatedValues: {
812
+ [colName: string]: number
813
+ } = {};
814
+ const aggColsEntries = this.getTotalViewerAggColumns();
404
815
  for (const [colName, aggFn] of aggColsEntries) {
405
816
  const newColName = getAggregatedColName(aggFn, colName);
406
817
  const col = this.dataFrame.getCol(colName);
@@ -413,37 +824,40 @@ export class LogoSummaryTable extends DG.JsViewer {
413
824
  col.name === C.LST_COLUMN_NAMES.MEMBERS ? currentSelection.trueCount :
414
825
  col.name === C.LST_COLUMN_NAMES.WEB_LOGO ? null :
415
826
  col.name === C.LST_COLUMN_NAMES.DISTRIBUTION ? null :
416
- col.name === C.LST_COLUMN_NAMES.MEAN_DIFFERENCE ? stats.meanDifference:
417
- col.name === C.LST_COLUMN_NAMES.P_VALUE ? stats.pValue:
418
- col.name === C.LST_COLUMN_NAMES.RATIO ? stats.ratio:
827
+ col.name === C.LST_COLUMN_NAMES.MEAN_DIFFERENCE ? stats.meanDifference :
828
+ col.name === C.LST_COLUMN_NAMES.P_VALUE ? stats.pValue :
829
+ col.name === C.LST_COLUMN_NAMES.RATIO ? stats.ratio :
419
830
  col.name in aggregatedValues ? aggregatedValues[col.name] :
420
831
  undefined;
421
- if (typeof newClusterVals[i] === 'undefined')
832
+ if (typeof newClusterVals[i] === 'undefined') {
422
833
  _package.logger.warning(`PeptidesLSTWarn: value for column ${col.name} is undefined`);
834
+ }
423
835
  }
424
- viewerDf.rows.addNew(newClusterVals);
836
+ this.logoSummaryTable.rows.addNew(newClusterVals);
425
837
 
426
- this.model.clusterStats[CLUSTER_TYPE.CUSTOM][newClusterName] = stats;
427
- this.model.addNewCluster(newClusterName);
838
+ this.clusterStats[CLUSTER_TYPE.CUSTOM][newClusterName] = stats;
839
+ this.addNewCluster(newClusterName);
428
840
  }
429
841
 
842
+ /** Removes selected custom clusters from Logo Summary Table. */
430
843
  removeCluster(): void {
431
- const lss = this.model.clusterSelection[CLUSTER_TYPE.CUSTOM];
844
+ const lss = this.clusterSelection[CLUSTER_TYPE.CUSTOM];
432
845
 
433
- // // Names of the clusters to remove
434
- if (lss.length === 0)
435
- return grok.shell.warning('No custom clusters selected to be removed');
846
+ // Names of the clusters to remove
847
+ if (lss.length === 0) {
848
+ grok.shell.warning('No custom clusters selected to be removed');
849
+ return;
850
+ }
436
851
 
437
- const viewerDf = this.viewerGrid.dataFrame;
438
- const viewerDfRows = viewerDf.rows;
439
- const clustCol = viewerDf.getCol(C.LST_COLUMN_NAMES.CLUSTER);
852
+ const viewerDfRows = this.logoSummaryTable.rows;
853
+ const clustCol = this.logoSummaryTable.getCol(C.LST_COLUMN_NAMES.CLUSTER);
440
854
  const clustColCat = clustCol.categories;
441
855
  const dfCols = this.dataFrame.columns;
442
856
 
443
857
  for (const cluster of lss) {
444
858
  lss.splice(lss.indexOf(cluster), 1);
445
859
  dfCols.remove(cluster);
446
- delete this.model.clusterStats[CLUSTER_TYPE.CUSTOM][cluster];
860
+ delete this.clusterStats[CLUSTER_TYPE.CUSTOM][cluster];
447
861
  const clustIdx = clustColCat.indexOf(cluster);
448
862
  viewerDfRows.removeAt(clustIdx);
449
863
  this.bitsets.splice(clustIdx, 1);
@@ -451,28 +865,66 @@ export class LogoSummaryTable extends DG.JsViewer {
451
865
 
452
866
  clustCol.compact();
453
867
 
454
- this.model.clusterSelection[CLUSTER_TYPE.CUSTOM] = lss;
455
- this.model.clusterSelection = this.model.clusterSelection;
868
+ this.clusterSelection[CLUSTER_TYPE.CUSTOM] = lss;
456
869
  this.render();
457
870
  }
458
871
 
872
+ /**
873
+ * Adds new cluster to the dataframe viewer attached to.
874
+ * @param clusterName - cluster name.
875
+ */
876
+ addNewCluster(clusterName: string): void {
877
+ const newClusterCol = DG.Column.fromBitSet(clusterName, this.model.getVisibleSelection());
878
+ newClusterCol.setTag(C.TAGS.CUSTOM_CLUSTER, '1');
879
+ newClusterCol.setTag(C.TAGS.ANALYSIS_COL, `${true}`);
880
+ this.dataFrame.columns.add(newClusterCol);
881
+ this.model.analysisView.grid.col(newClusterCol.name)!.visible = false;
882
+ }
883
+
884
+ /**
885
+ * Modifies cluster selection. If shift and ctrl keys are both pressed, it removes cluster from selection.
886
+ * If only shift key is pressed, it adds cluster to selection. If only ctrl key is pressed, it changes cluster
887
+ * presence in selection. If none of the keys is pressed, it sets cluster as the only selected one.
888
+ * @param cluster - cluster to modify selection with.
889
+ * @param options - selection options.
890
+ * @param notify - flag indicating if bitset changed event should fire.
891
+ */
892
+ modifyClusterSelection(cluster: type.SelectionItem, options: type.SelectionOptions = {
893
+ shiftPressed: false,
894
+ ctrlPressed: false,
895
+ }, notify: boolean = true): void {
896
+ if (notify) {
897
+ this.clusterSelection = modifySelection(this.clusterSelection, cluster, options);
898
+ } else {
899
+ this._clusterSelection = modifySelection(this.clusterSelection, cluster, options);
900
+ }
901
+ }
902
+
903
+ /**
904
+ * Shows tooltip for a cluster.
905
+ * @param cluster - cluster to show tooltip for.
906
+ * @param x - x coordinate of the tooltip.
907
+ * @param y - y coordinate of the tooltip.
908
+ * @return - tooltip body.
909
+ */
459
910
  showTooltip(cluster: SelectionItem, x: number, y: number): HTMLDivElement | null {
460
911
  const bs = this.dataFrame.filter;
461
912
  const filteredDf = bs.anyFalse ? this.dataFrame.clone(bs) : this.dataFrame;
462
913
  const rowCount = filteredDf.rowCount;
463
914
  const bitArray = new BitArray(rowCount, false);
464
- const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY);
915
+ const activityCol = this.getScaledActivityColumn(bs.anyFalse);
465
916
  const activityColData = activityCol.getRawData();
466
917
 
467
918
  if (cluster.positionOrClusterType === CLUSTER_TYPE.ORIGINAL) {
468
- const origClustCol = filteredDf.getCol(this.model.settings.clustersColumnName!);
919
+ const origClustCol = filteredDf.getCol(this.clustersColumnName);
469
920
  const origClustColData = origClustCol.getRawData();
470
921
  const origClustColCategories = origClustCol.categories;
471
922
  const seekValue = origClustColCategories.indexOf(cluster.monomerOrCluster);
472
923
 
473
924
  for (let i = 0; i < rowCount; ++i) {
474
- if (origClustColData[i] === seekValue)
925
+ if (origClustColData[i] === seekValue) {
475
926
  bitArray.setTrue(i);
927
+ }
476
928
  }
477
929
  bitArray.incrementVersion();
478
930
  } else {
@@ -481,17 +933,25 @@ export class LogoSummaryTable extends DG.JsViewer {
481
933
  }
482
934
 
483
935
  const stats = bs.anyFalse ? getStats(activityColData, bitArray) :
484
- this.model.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster];
936
+ this.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster];
485
937
 
486
- if (!stats.count)
938
+ if (!stats.count) {
487
939
  return null;
940
+ }
941
+
488
942
 
489
943
  const mask = DG.BitSet.fromBytes(bitArray.buffer.buffer, rowCount);
490
- const distributionTable = this.createDistributionDf(activityCol, mask);
944
+ const distributionTable = getDistributionTable(activityCol, mask);
491
945
  const hist = getActivityDistribution(distributionTable, true);
492
946
  const tableMap = getStatsTableMap(stats);
493
- const aggregatedColMap = getAggregatedColumnValues(this.model.df, this.model.settings.columns!, {filterDf: true, mask: mask});
494
- const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
947
+ const aggregatedColMap = getAggregatedColumnValues(this.dataFrame,
948
+ this.getTotalViewerAggColumns(), {
949
+ filterDf: true,
950
+ mask: mask,
951
+ });
952
+ const resultMap: {
953
+ [key: string]: any
954
+ } = {...tableMap, ...aggregatedColMap};
495
955
  const tooltip = getDistributionPanel(hist, resultMap);
496
956
 
497
957
  ui.tooltip.show(tooltip, x, y);
@@ -499,14 +959,15 @@ export class LogoSummaryTable extends DG.JsViewer {
499
959
  return tooltip;
500
960
  }
501
961
 
962
+ /**
963
+ * Creates a dataframe for WebLogo viewer.
964
+ * @param pepCol - column with peptides.
965
+ * @param mask - bitset to filter dataframe with.
966
+ * @return - dataframe for WebLogo viewer.
967
+ */
502
968
  createWebLogoDf(pepCol: DG.Column<string>, mask: DG.BitSet): DG.DataFrame {
503
969
  const newDf = DG.DataFrame.fromColumns([pepCol]);
504
970
  newDf.filter.copyFrom(mask);
505
971
  return newDf;
506
972
  }
507
-
508
- createDistributionDf(activityCol: DG.Column<number>, splitMask: DG.BitSet): DG.DataFrame {
509
- // const table = DG.DataFrame.fromColumns([activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, splitMask)]);
510
- return getDistributionTable(activityCol, splitMask);
511
- }
512
973
  }