@datagrok/peptides 1.16.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 (60) hide show
  1. package/.eslintrc.json +17 -6
  2. package/CHANGELOG.md +33 -8
  3. package/README.md +12 -7
  4. package/dist/196.js +2 -3
  5. package/dist/23.js +2 -0
  6. package/dist/282.js +2 -0
  7. package/dist/361.js +2 -2
  8. package/dist/40.js +2 -0
  9. package/dist/436.js +2 -2
  10. package/dist/65.js +2 -0
  11. package/dist/704.js +2 -0
  12. package/dist/package-test.js +2 -3
  13. package/dist/package.js +2 -3
  14. package/package.json +13 -13
  15. package/setup-unlink-clean.cmd +6 -0
  16. package/setup.cmd +2 -2
  17. package/src/demo/fasta.ts +8 -2
  18. package/src/model.ts +857 -560
  19. package/src/package-test.ts +1 -3
  20. package/src/package.ts +28 -50
  21. package/src/tests/benchmarks.ts +31 -11
  22. package/src/tests/core.ts +11 -6
  23. package/src/tests/misc.ts +6 -6
  24. package/src/tests/model.ts +80 -45
  25. package/src/tests/table-view.ts +49 -39
  26. package/src/tests/utils.ts +0 -76
  27. package/src/tests/viewers.ts +30 -12
  28. package/src/tests/widgets.ts +30 -11
  29. package/src/utils/algorithms.ts +115 -38
  30. package/src/utils/cell-renderer.ts +217 -96
  31. package/src/utils/constants.ts +37 -7
  32. package/src/utils/misc.ts +285 -30
  33. package/src/utils/parallel-mutation-cliffs.ts +18 -15
  34. package/src/utils/statistics.ts +70 -14
  35. package/src/utils/tooltips.ts +46 -25
  36. package/src/utils/types.ts +29 -26
  37. package/src/utils/worker-creator.ts +5 -0
  38. package/src/viewers/logo-summary.ts +597 -135
  39. package/src/viewers/sar-viewer.ts +946 -249
  40. package/src/widgets/distribution.ts +291 -196
  41. package/src/widgets/manual-alignment.ts +18 -11
  42. package/src/widgets/mutation-cliffs.ts +45 -21
  43. package/src/widgets/peptides.ts +86 -91
  44. package/src/widgets/selection.ts +56 -22
  45. package/src/widgets/settings.ts +94 -44
  46. package/src/workers/dimensionality-reducer.ts +5 -6
  47. package/src/workers/mutation-cliffs-worker.ts +3 -16
  48. package/dist/196.js.LICENSE.txt +0 -51
  49. package/dist/209.js +0 -2
  50. package/dist/381.js +0 -2
  51. package/dist/694.js +0 -2
  52. package/dist/831.js +0 -2
  53. package/dist/868.js +0 -2
  54. package/dist/package-test.js.LICENSE.txt +0 -51
  55. package/dist/package.js.LICENSE.txt +0 -51
  56. package/src/tests/peptide-space-test.ts +0 -48
  57. package/src/tests/test-data.ts +0 -649
  58. package/src/utils/molecular-measure.ts +0 -174
  59. package/src/utils/peptide-similarity-space.ts +0 -216
  60. 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
- import {getActivityDistribution, getDistributionLegend, getStatsTableMap} from '../widgets/distribution';
13
- import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
20
+ import {getActivityDistribution, getStatsTableMap} from '../widgets/distribution';
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',
23
- };
49
+ SEQUENCE = 'sequence',
50
+ CLUSTERS = 'clusters',
51
+ COLUMNS = 'columns',
52
+ AGGREGATION = 'aggregation',
53
+ ACTIVITY_SCALING = 'activityScaling',
54
+ ACTIVITY = 'activity',
55
+ }
24
56
 
25
- export class LogoSummaryTable extends DG.JsViewer {
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;
68
+ }
69
+
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;
42
156
  }
43
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]));
284
+ }
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_SCALED);
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_SCALED,
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
- const currentSelection = this.model.getCompoundBitset();
391
- const viewerDf = this.viewerGrid.dataFrame;
392
- const viewerDfCols = viewerDf.columns;
800
+ const currentSelection = this.model.getVisibleSelection();
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_SCALED);
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,35 +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
- console.warn(`PeptidesLSTWarn: value for column ${col.name} is undefined`)! || null;
831
+ undefined;
832
+ if (typeof newClusterVals[i] === 'undefined') {
833
+ _package.logger.warning(`PeptidesLSTWarn: value for column ${col.name} is undefined`);
834
+ }
421
835
  }
422
- viewerDf.rows.addNew(newClusterVals);
836
+ this.logoSummaryTable.rows.addNew(newClusterVals);
423
837
 
424
- this.model.clusterStats[CLUSTER_TYPE.CUSTOM][newClusterName] = stats;
425
- this.model.addNewCluster(newClusterName);
838
+ this.clusterStats[CLUSTER_TYPE.CUSTOM][newClusterName] = stats;
839
+ this.addNewCluster(newClusterName);
426
840
  }
427
841
 
842
+ /** Removes selected custom clusters from Logo Summary Table. */
428
843
  removeCluster(): void {
429
- const lss = this.model.clusterSelection[CLUSTER_TYPE.CUSTOM];
844
+ const lss = this.clusterSelection[CLUSTER_TYPE.CUSTOM];
430
845
 
431
- // // Names of the clusters to remove
432
- if (lss.length === 0)
433
- 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
+ }
434
851
 
435
- const viewerDf = this.viewerGrid.dataFrame;
436
- const viewerDfRows = viewerDf.rows;
437
- 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);
438
854
  const clustColCat = clustCol.categories;
439
855
  const dfCols = this.dataFrame.columns;
440
856
 
441
857
  for (const cluster of lss) {
442
858
  lss.splice(lss.indexOf(cluster), 1);
443
859
  dfCols.remove(cluster);
444
- delete this.model.clusterStats[CLUSTER_TYPE.CUSTOM][cluster];
860
+ delete this.clusterStats[CLUSTER_TYPE.CUSTOM][cluster];
445
861
  const clustIdx = clustColCat.indexOf(cluster);
446
862
  viewerDfRows.removeAt(clustIdx);
447
863
  this.bitsets.splice(clustIdx, 1);
@@ -449,28 +865,66 @@ export class LogoSummaryTable extends DG.JsViewer {
449
865
 
450
866
  clustCol.compact();
451
867
 
452
- this.model.clusterSelection[CLUSTER_TYPE.CUSTOM] = lss;
453
- this.model.clusterSelection = this.model.clusterSelection;
868
+ this.clusterSelection[CLUSTER_TYPE.CUSTOM] = lss;
454
869
  this.render();
455
870
  }
456
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
+ */
457
910
  showTooltip(cluster: SelectionItem, x: number, y: number): HTMLDivElement | null {
458
911
  const bs = this.dataFrame.filter;
459
912
  const filteredDf = bs.anyFalse ? this.dataFrame.clone(bs) : this.dataFrame;
460
913
  const rowCount = filteredDf.rowCount;
461
914
  const bitArray = new BitArray(rowCount, false);
462
- const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
915
+ const activityCol = this.getScaledActivityColumn(bs.anyFalse);
463
916
  const activityColData = activityCol.getRawData();
464
917
 
465
918
  if (cluster.positionOrClusterType === CLUSTER_TYPE.ORIGINAL) {
466
- const origClustCol = filteredDf.getCol(this.model.settings.clustersColumnName!);
919
+ const origClustCol = filteredDf.getCol(this.clustersColumnName);
467
920
  const origClustColData = origClustCol.getRawData();
468
921
  const origClustColCategories = origClustCol.categories;
469
922
  const seekValue = origClustColCategories.indexOf(cluster.monomerOrCluster);
470
923
 
471
924
  for (let i = 0; i < rowCount; ++i) {
472
- if (origClustColData[i] === seekValue)
925
+ if (origClustColData[i] === seekValue) {
473
926
  bitArray.setTrue(i);
927
+ }
474
928
  }
475
929
  bitArray.incrementVersion();
476
930
  } else {
@@ -479,33 +933,41 @@ export class LogoSummaryTable extends DG.JsViewer {
479
933
  }
480
934
 
481
935
  const stats = bs.anyFalse ? getStats(activityColData, bitArray) :
482
- this.model.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster];
936
+ this.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster];
483
937
 
484
- if (!stats.count)
938
+ if (!stats.count) {
485
939
  return null;
940
+ }
941
+
486
942
 
487
943
  const mask = DG.BitSet.fromBytes(bitArray.buffer.buffer, rowCount);
488
- const distributionTable = this.createDistributionDf(activityCol, mask);
489
- const labels = getDistributionLegend(`Cluster: ${cluster.monomerOrCluster}`, 'Other');
944
+ const distributionTable = getDistributionTable(activityCol, mask);
490
945
  const hist = getActivityDistribution(distributionTable, true);
491
946
  const tableMap = getStatsTableMap(stats);
492
- const aggregatedColMap = getAggregatedColumnValues(this.model.df, this.model.settings.columns!, {filterDf: true, mask: mask});
493
- const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
494
- const tooltip = getStatsSummary(labels, hist, resultMap);
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};
955
+ const tooltip = getDistributionPanel(hist, resultMap);
495
956
 
496
957
  ui.tooltip.show(tooltip, x, y);
497
958
 
498
959
  return tooltip;
499
960
  }
500
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
+ */
501
968
  createWebLogoDf(pepCol: DG.Column<string>, mask: DG.BitSet): DG.DataFrame {
502
969
  const newDf = DG.DataFrame.fromColumns([pepCol]);
503
970
  newDf.filter.copyFrom(mask);
504
971
  return newDf;
505
972
  }
506
-
507
- createDistributionDf(activityCol: DG.Column<number>, splitMask: DG.BitSet): DG.DataFrame {
508
- const table = DG.DataFrame.fromColumns([activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, splitMask)]);
509
- return prepareTableForHistogram(table);
510
- }
511
973
  }