@datagrok/peptides 1.9.2 → 1.11.3

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.
@@ -6,14 +6,11 @@ import $ from 'cash-dom';
6
6
  import {ClusterType, CLUSTER_TYPE, PeptidesModel, VIEWER_TYPE} from '../model';
7
7
  import * as C from '../utils/constants';
8
8
  import * as CR from '../utils/cell-renderer';
9
- import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
- import {getSplitterForColumn} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
11
- import {HorizontalAlignments, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
9
+ import {HorizontalAlignments, IWebLogoViewer, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
12
10
  import {getAggregatedValue, getStats, Stats} from '../utils/statistics';
13
11
  import wu from 'wu';
14
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
15
12
  import {getActivityDistribution, getDistributionLegend, getStatsTableMap} from '../widgets/distribution';
16
- import {getStatsSummary} from '../utils/misc';
13
+ import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
17
14
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
18
15
 
19
16
  const getAggregatedColName = (aggF: string, colName: string): string => `${aggF}(${colName})`;
@@ -30,8 +27,7 @@ export class LogoSummaryTable extends DG.JsViewer {
30
27
  initialized: boolean = false;
31
28
  webLogoMode: string;
32
29
  membersRatioThreshold: number;
33
- webLogoDfPlot: DG.DataFrame[] = [];
34
- distributionDfPlot: DG.DataFrame[] = [];
30
+ bitsets: DG.BitSet[] = [];
35
31
 
36
32
  constructor() {
37
33
  super();
@@ -43,20 +39,8 @@ export class LogoSummaryTable extends DG.JsViewer {
43
39
 
44
40
  onTableAttached(): void {
45
41
  super.onTableAttached();
46
-
47
42
  this.model = PeptidesModel.getInstance(this.dataFrame);
48
- this.subs.push(this.model.onSettingsChanged.subscribe(() => {
49
- this.createLogoSummaryGrid();
50
- this.render();
51
- }));
52
- this.subs.push(this.model.onNewCluster.subscribe(() => this.clusterFromSelection()));
53
- this.subs.push(this.model.onRemoveCluster.subscribe(() => this.removeCluster()));
54
- this.subs.push(this.model.onFilterChanged.subscribe(() => {
55
- this.createLogoSummaryGrid();
56
- this.render();
57
- }));
58
-
59
- this.createLogoSummaryGrid();
43
+ this.createLogoSummaryTableGrid();
60
44
  this.initialized = true;
61
45
  this.render();
62
46
  }
@@ -64,19 +48,30 @@ export class LogoSummaryTable extends DG.JsViewer {
64
48
  detach(): void {this.subs.forEach((sub) => sub.unsubscribe());}
65
49
 
66
50
  render(): void {
67
- if (this.initialized) {
68
- $(this.root).empty();
69
- const df = this.viewerGrid.dataFrame;
70
- if (!df.filter.anyTrue) {
71
- const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
72
- 'Please, lower the threshold in viewer proeperties to include clusters');
73
- this.root.appendChild(ui.divV([this._titleHost, emptyDf]));
74
- return;
75
- }
76
- this.viewerGrid.root.style.width = 'auto';
77
- this.root.appendChild(ui.divV([this._titleHost, this.viewerGrid.root]));
78
- this.viewerGrid.invalidate();
51
+ if (!this.initialized)
52
+ return;
53
+ $(this.root).empty();
54
+ const df = this.viewerGrid.dataFrame;
55
+ if (!df.filter.anyTrue) {
56
+ const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
57
+ 'Please, lower the threshold in viewer proeperties to include clusters');
58
+ this.root.appendChild(ui.divV([this._titleHost, emptyDf]));
59
+ return;
79
60
  }
61
+ const expand = ui.iconFA('expand-alt', () => {
62
+ const dialog = ui.dialog('Logo Summary Table');
63
+ dialog.add(this.viewerGrid.root);
64
+ dialog.onCancel(() => this.render());
65
+ dialog.showModal(true);
66
+ this.viewerGrid.invalidate();
67
+ }, 'Show Logo Summary Table in full screen');
68
+ $(expand).addClass('pep-help-icon');
69
+ this.viewerGrid.root.style.width = 'auto';
70
+ this.root.appendChild(ui.divV([
71
+ ui.divH([this._titleHost, expand], {style: {alignSelf: 'center', lineHeight: 'normal'}}),
72
+ this.viewerGrid.root,
73
+ ]));
74
+ this.viewerGrid.invalidate();
80
75
  }
81
76
 
82
77
  onPropertyChanged(property: DG.Property): void {
@@ -86,7 +81,7 @@ export class LogoSummaryTable extends DG.JsViewer {
86
81
  this.render();
87
82
  }
88
83
 
89
- createLogoSummaryGrid(): DG.Grid {
84
+ createLogoSummaryTableGrid(): DG.Grid {
90
85
  const clustersColName = this.model.settings.clustersColumnName!;
91
86
  const isDfFiltered = this.dataFrame.filter.anyFalse;
92
87
  const filteredDf = isDfFiltered ? this.dataFrame.clone(this.dataFrame.filter) : this.dataFrame;
@@ -132,21 +127,19 @@ export class LogoSummaryTable extends DG.JsViewer {
132
127
  }
133
128
 
134
129
  // BEGIN: fill LST part with custom clusters
135
- const customWebLogoTables: DG.DataFrame[] = new Array(customClustColList.length);
136
- const customDistTables: DG.DataFrame[] = new Array(customClustColList.length);
130
+ const customBitsets: DG.BitSet[] = new Array(customClustColList.length);
137
131
 
138
132
  for (let rowIdx = 0; rowIdx < customClustColList.length; ++rowIdx) {
139
133
  const customClustCol = customClustColList[rowIdx];
140
134
  customLSTClustCol.set(rowIdx, customClustCol.name);
141
135
  const bitArray = BitArray.fromUint32Array(filteredDfRowCount, customClustCol.getRawData() as Uint32Array);
142
- const bsMask = DG.BitSet.create(filteredDfRowCount, (i) => bitArray.getBit(i));
136
+ const bsMask = DG.BitSet.fromBytes(bitArray.buffer.buffer, filteredDfRowCount);
143
137
 
144
138
  const stats: Stats = isDfFiltered ? getStats(activityColData, bitArray) :
145
139
  this.model.clusterStats[CLUSTER_TYPE.CUSTOM][customClustCol.name];
146
140
 
147
141
  customMembersColData[rowIdx] = stats.count;
148
- customWebLogoTables[rowIdx] = this.createWebLogoPlot(pepCol, bsMask);
149
- customDistTables[rowIdx] = this.createDistributionPlot(activityCol, customClustColList[rowIdx]);
142
+ customBitsets[rowIdx] = bsMask;
150
143
  customMDColData[rowIdx] = stats.meanDifference;
151
144
  customPValColData[rowIdx] = stats.pValue;
152
145
  customRatioColData[rowIdx] = stats.ratio;
@@ -176,12 +169,10 @@ export class LogoSummaryTable extends DG.JsViewer {
176
169
  const origMDColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.MEAN_DIFFERENCE).getRawData();
177
170
  const origPValColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.P_VALUE).getRawData();
178
171
  const origRatioColData = origLSTCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
179
-
180
- const origWebLogoTables: DG.DataFrame[] = new Array(origLSTLen);
181
- const origDistTables: DG.DataFrame[] = new Array(origLSTLen);
172
+ const origBitsets: DG.BitSet[] = new Array(origLSTLen);
182
173
 
183
174
  const origClustMasks = Array.from({length: origLSTLen},
184
- () => BitArray.fromSeq(filteredDfRowCount, () => false));
175
+ () => new BitArray(filteredDfRowCount, false));
185
176
 
186
177
  for (let rowIdx = 0; rowIdx < filteredDfRowCount; ++rowIdx) {
187
178
  const filteredClustName = filteredDfClustColCat[filteredDfClustColData[rowIdx]];
@@ -191,15 +182,13 @@ export class LogoSummaryTable extends DG.JsViewer {
191
182
 
192
183
  for (let rowIdx = 0; rowIdx < origLSTLen; ++rowIdx) {
193
184
  const mask = origClustMasks[rowIdx];
194
- const bsMask = DG.BitSet.create(filteredDfRowCount, (i) => mask.getBit(i));
185
+ const bsMask = DG.BitSet.fromBytes(mask.buffer.buffer, filteredDfRowCount);
195
186
 
196
187
  const stats = isDfFiltered ? getStats(activityColData, mask) :
197
188
  this.model.clusterStats[CLUSTER_TYPE.ORIGINAL][origLSTClustColCat[rowIdx]];
198
189
 
199
190
  origMembersColData[rowIdx] = stats.count;
200
- origWebLogoTables[rowIdx] = this.createWebLogoPlot(pepCol, bsMask);
201
- origDistTables[rowIdx] = this.createDistributionPlot(activityCol,
202
- DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, bsMask));
191
+ origBitsets[rowIdx] = bsMask;
203
192
  origMDColData[rowIdx] = stats.meanDifference;
204
193
  origPValColData[rowIdx] = stats.pValue;
205
194
  origRatioColData[rowIdx] = stats.ratio;
@@ -211,8 +200,7 @@ export class LogoSummaryTable extends DG.JsViewer {
211
200
 
212
201
  // combine LSTs and create a grid
213
202
  const summaryTable = origLST.append(customLST);
214
- this.webLogoDfPlot = origWebLogoTables.concat(customWebLogoTables);
215
- this.distributionDfPlot = origDistTables.concat(customDistTables);
203
+ this.bitsets = origBitsets.concat(customBitsets);
216
204
 
217
205
  this.viewerGrid = summaryTable.plot.grid();
218
206
  this.viewerGrid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
@@ -225,46 +213,85 @@ export class LogoSummaryTable extends DG.JsViewer {
225
213
  C.LST_COLUMN_NAMES.P_VALUE, C.LST_COLUMN_NAMES.RATIO, ...aggColNames]);
226
214
  this.viewerGrid.columns.rowHeader!.visible = false;
227
215
  this.viewerGrid.props.rowHeight = 55;
228
- this.viewerGrid.onCellPrepare((cell) => {
229
- const currentRowIdx = cell.tableRowIndex;
230
- if (!cell.isTableCell || currentRowIdx === null || currentRowIdx === -1)
216
+
217
+ const webLogoCache = new DG.LruCache<number, DG.Viewer & IWebLogoViewer>();
218
+ const distCache = new DG.LruCache<number, DG.Viewer<DG.IHistogramLookSettings>>();
219
+ const maxSequenceLen = this.model.splitSeqDf.columns.length;
220
+ const webLogoGridCol = this.viewerGrid.columns.byName(C.LST_COLUMN_NAMES.WEB_LOGO)!;
221
+ webLogoGridCol.cellType = 'html';
222
+ webLogoGridCol.width = 350;
223
+
224
+ this.viewerGrid.onCellRender.subscribe(async (gridCellArgs) => {
225
+ const gridCell = gridCellArgs.cell;
226
+ const currentRowIdx = gridCell.tableRowIndex;
227
+ if (!gridCell.isTableCell || currentRowIdx === null || currentRowIdx === -1)
231
228
  return;
232
229
 
233
- const height = cell.bounds.height;
234
- if (cell.tableColumn?.name === C.LST_COLUMN_NAMES.WEB_LOGO) {
235
- const webLogoTable = this.webLogoDfPlot[currentRowIdx];
236
- const webLogoTableRowCount = webLogoTable.rowCount;
237
- const webLogoTablePepCol = webLogoTable.getCol(pepCol.name);
238
- const webLogoTablePepColData = webLogoTablePepCol.getRawData();
239
- const webLogoTablePepColCat = webLogoTablePepCol.categories;
240
- const splitter = getSplitterForColumn(webLogoTablePepCol);
241
- let maxSequenceLength = 0;
242
- for (let i = 0; i < webLogoTableRowCount; ++i) {
243
- maxSequenceLength = Math.max(maxSequenceLength,
244
- splitter(webLogoTablePepColCat[webLogoTablePepColData[i]]).length);
230
+ const canvasContext = gridCellArgs.g;
231
+ const bound = gridCellArgs.bounds;
232
+ canvasContext.save();
233
+ canvasContext.beginPath();
234
+ canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
235
+ canvasContext.clip();
236
+
237
+ try {
238
+ const height = Math.max(gridCell.bounds.height - 2, 0);
239
+ const clusterBitSet = this.bitsets[currentRowIdx];
240
+
241
+ if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.CLUSTER) {
242
+ CR.renderLogoSummaryCell(canvasContext, gridCell.cell.value, this.model.clusterSelection, bound);
243
+ gridCellArgs.preventDefault();
244
+ } else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.WEB_LOGO) {
245
+ const positionWidth = Math.floor((gridCell.bounds.width - 2 - (4 * (maxSequenceLen - 1))) / maxSequenceLen);
246
+
247
+ let viewer = webLogoCache.get(currentRowIdx);
248
+ if (viewer !== undefined) {
249
+ const viewerProps = viewer.getProperties();
250
+
251
+ for (const prop of viewerProps) {
252
+ if (prop.name === 'positionHeight' && prop.get(viewer) !== this.webLogoMode)
253
+ prop.set(viewer, this.webLogoMode);
254
+ else if (prop.name === 'positionWidth' && prop.get(viewer) !== positionWidth)
255
+ prop.set(viewer, positionWidth);
256
+ else if (prop.name === 'minHeight' && prop.get(viewer) !== height)
257
+ prop.set(viewer, height);
258
+ }
259
+ const viewerRoot = $(viewer.root).css('height', `${height}px`);//;
260
+ viewerRoot.children().first().css('overflow-y', 'hidden !important');
261
+ } else {
262
+ const webLogoTable = this.createWebLogoDf(pepCol, clusterBitSet);
263
+ viewer = await webLogoTable.plot
264
+ .fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
265
+ maxHeight: 1000, minHeight: height, positionWidth: positionWidth});
266
+ webLogoCache.set(currentRowIdx, viewer);
267
+ }
268
+ gridCell.element = viewer.root;
269
+ gridCellArgs.preventDefault();
270
+ } else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
271
+ let viewer = distCache.get(currentRowIdx);
272
+ if (viewer === undefined) {
273
+ const distributionDf = this.createDistributionDf(activityCol, clusterBitSet);
274
+ viewer = distributionDf.plot.histogram({
275
+ filteringEnabled: false,
276
+ valueColumnName: C.COLUMNS_NAMES.ACTIVITY_SCALED,
277
+ splitColumnName: C.COLUMNS_NAMES.SPLIT_COL,
278
+ legendVisibility: 'Never',
279
+ showXAxis: false,
280
+ showColumnSelector: false,
281
+ showRangeSlider: false,
282
+ showBinSelector: false,
283
+ backColor: DG.Color.toHtml(DG.Color.white),
284
+ xAxisHeight: 1,
285
+ });
286
+ viewer.root.style.width = 'auto';
287
+ distCache.set(currentRowIdx, viewer);
288
+ }
289
+ viewer.root.style.height = `${height}px`;
290
+ gridCell.element = viewer.root;
291
+ gridCellArgs.preventDefault();
245
292
  }
246
- const positionWidth = Math.floor((cell.bounds.width - 2 - (4 * (maxSequenceLength - 1))) / maxSequenceLength);
247
- webLogoTable.plot
248
- .fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
249
- maxHeight: 1000, minHeight: height - 2, positionWidth: positionWidth})
250
- .then((viewer) => cell.element = viewer.root);
251
- } else if (cell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
252
- const viewerRoot = this.distributionDfPlot[currentRowIdx].plot.histogram({
253
- filteringEnabled: false,
254
- valueColumnName: C.COLUMNS_NAMES.ACTIVITY_SCALED,
255
- splitColumnName: C.COLUMNS_NAMES.SPLIT_COL,
256
- legendVisibility: 'Never',
257
- showXAxis: false,
258
- showColumnSelector: false,
259
- showRangeSlider: false,
260
- showBinSelector: false,
261
- backColor: DG.Color.toHtml(DG.Color.white),
262
- xAxisHeight: 1,
263
- }).root;
264
-
265
- viewerRoot.style.width = 'auto';
266
- viewerRoot.style.height = `${height-2}px`;
267
- cell.element = viewerRoot;
293
+ } finally {
294
+ canvasContext.restore();
268
295
  }
269
296
  });
270
297
  this.viewerGrid.root.addEventListener('click', (ev) => {
@@ -279,20 +306,6 @@ export class LogoSummaryTable extends DG.JsViewer {
279
306
  this.model.modifyClusterSelection(cell.cell.value);
280
307
  this.viewerGrid.invalidate();
281
308
  });
282
- this.viewerGrid.onCellRender.subscribe((gridCellArgs) => {
283
- const gc = gridCellArgs.cell;
284
- if (gc.tableColumn?.name !== clustersColName || gc.isColHeader)
285
- return;
286
- const canvasContext = gridCellArgs.g;
287
- const bound = gridCellArgs.bounds;
288
- canvasContext.save();
289
- canvasContext.beginPath();
290
- canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
291
- canvasContext.clip();
292
- CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.model.clusterSelection, bound);
293
- gridCellArgs.preventDefault();
294
- canvasContext.restore();
295
- });
296
309
  this.viewerGrid.onCellTooltip((cell, x, y) => {
297
310
  if (!cell.isColHeader && cell.tableColumn?.name === clustersColName) {
298
311
  const clustName = cell.cell.value;
@@ -302,9 +315,6 @@ export class LogoSummaryTable extends DG.JsViewer {
302
315
  }
303
316
  return true;
304
317
  });
305
- const webLogoGridCol = this.viewerGrid.columns.byName('WebLogo')!;
306
- webLogoGridCol.cellType = 'html';
307
- webLogoGridCol.width = 350;
308
318
 
309
319
  const gridProps = this.viewerGrid.props;
310
320
  gridProps.allowEdit = false;
@@ -336,27 +346,8 @@ export class LogoSummaryTable extends DG.JsViewer {
336
346
  const activityScaledCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
337
347
  const bitArray = BitArray.fromString(selection.toBinaryString());
338
348
  const stats = getStats(activityScaledCol.getRawData(), bitArray);
339
- const distributionTable =
340
- DG.DataFrame.fromColumns([activityScaledCol, filteredDf.getCol(this.model.splitCol.name)]);
341
-
342
- const peptideCol: DG.Column<string> = filteredDf.getCol(this.model.settings.sequenceColumnName!);
343
- const peptideColData = peptideCol.getRawData();
344
- const peptideColCategories = peptideCol.categories;
345
- const peptideColTags = peptideCol.tags;
346
- const selectedIndexes = selection.getSelectedIndexes();
347
- const tCol = DG.Column.string('peptides', selectedIndexes.length);
348
-
349
- for (let i = 0; i < selectedIndexes.length; ++i)
350
- tCol.set(i, peptideColCategories[peptideColData[selectedIndexes[i]]]);
351
- for (const tag of peptideColTags)
352
- tCol.setTag(tag[0], tag[1]);
353
-
354
- const uh = new UnitsHandler(tCol);
355
- tCol.setTag(bioTAGS.alphabetSize, uh.getAlphabetSize().toString());
356
349
 
357
- const webLogoTable = DG.DataFrame.fromColumns([tCol]);
358
- this.webLogoDfPlot.push(webLogoTable);
359
- this.distributionDfPlot.push(distributionTable);
350
+ this.bitsets.push(selection.clone());
360
351
 
361
352
  const newClusterName = viewerDfCols.getUnusedName('New Cluster');
362
353
 
@@ -407,8 +398,7 @@ export class LogoSummaryTable extends DG.JsViewer {
407
398
  delete this.model.clusterStats[CLUSTER_TYPE.CUSTOM][cluster];
408
399
  const clustIdx = clustColCat.indexOf(cluster);
409
400
  viewerDfRows.removeAt(clustIdx);
410
- this.webLogoDfPlot.splice(clustIdx, 1);
411
- this.distributionDfPlot.splice(clustIdx, 1);
401
+ this.bitsets.splice(clustIdx, 1);
412
402
  }
413
403
 
414
404
  clustCol.compact();
@@ -421,20 +411,21 @@ export class LogoSummaryTable extends DG.JsViewer {
421
411
  const bs = this.dataFrame.filter;
422
412
  const filteredDf = bs.anyFalse ? this.dataFrame.clone(bs) : this.dataFrame;
423
413
  const rowCount = filteredDf.rowCount;
424
-
425
- const bitArray = new BitArray(rowCount);
414
+ const bitArray = new BitArray(rowCount, false);
426
415
  const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
427
416
  const activityColData = activityCol.getRawData();
428
417
 
429
-
430
418
  if (clustType === CLUSTER_TYPE.ORIGINAL) {
431
419
  const origClustCol = filteredDf.getCol(C.LST_COLUMN_NAMES.CLUSTER);
432
420
  const origClustColData = origClustCol.getRawData();
433
421
  const origClustColCategories = origClustCol.categories;
434
422
  const seekValue = origClustColCategories.indexOf(clustName);
435
423
 
436
- for (let i = 0; i < rowCount; ++i)
437
- bitArray.setBit(i, origClustColData[i] === seekValue);
424
+ for (let i = 0; i < rowCount; ++i) {
425
+ if (origClustColData[i] === seekValue)
426
+ bitArray.setTrue(i);
427
+ }
428
+ bitArray.incrementVersion();
438
429
  } else {
439
430
  const clustCol: DG.Column<boolean> = filteredDf.getCol(clustName);
440
431
  bitArray.buffer = clustCol.getRawData() as Uint32Array;
@@ -445,27 +436,28 @@ export class LogoSummaryTable extends DG.JsViewer {
445
436
  if (!stats.count)
446
437
  return null;
447
438
 
448
- const mask = DG.BitSet.create(rowCount, (i) => bitArray.getBit(i));
449
- const distributionTable = DG.DataFrame.fromColumns(
450
- [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
439
+ const mask = DG.BitSet.fromBytes(bitArray.buffer.buffer, rowCount);
440
+ const distributionTable = this.createDistributionDf(activityCol, mask);
451
441
  const labels = getDistributionLegend(`Cluster: ${clustName}`, 'Other');
452
442
  const hist = getActivityDistribution(distributionTable, true);
453
- const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
454
- const aggregatedColMap = this.model.getAggregatedColumnValues({filterDf: true, mask: mask, fractionDigits: 2});
455
-
443
+ const tableMap = getStatsTableMap(stats);
444
+ const aggregatedColMap = this.model.getAggregatedColumnValues({filterDf: true, mask: mask});
456
445
  const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
457
- const tooltip = getStatsSummary(labels, hist, resultMap, true);
446
+ const tooltip = getStatsSummary(labels, hist, resultMap);
458
447
 
459
448
  ui.tooltip.show(tooltip, x, y);
460
449
 
461
450
  return tooltip;
462
451
  }
463
452
 
464
- createWebLogoPlot(pepCol: DG.Column<string>, mask: DG.BitSet): DG.DataFrame {
465
- return DG.DataFrame.fromColumns([pepCol]).clone(mask);
453
+ createWebLogoDf(pepCol: DG.Column<string>, mask: DG.BitSet): DG.DataFrame {
454
+ const newDf = DG.DataFrame.fromColumns([pepCol]);
455
+ newDf.filter.copyFrom(mask);
456
+ return newDf;
466
457
  }
467
458
 
468
- createDistributionPlot(activityCol: DG.Column<number>, splitCol: DG.Column<boolean>): DG.DataFrame {
469
- return DG.DataFrame.fromColumns([activityCol, splitCol]);
459
+ createDistributionDf(activityCol: DG.Column<number>, splitMask: DG.BitSet): DG.DataFrame {
460
+ const table = DG.DataFrame.fromColumns([activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, splitMask)]);
461
+ return prepareTableForHistogram(table);
470
462
  }
471
463
  }
@@ -6,11 +6,12 @@ import $ from 'cash-dom';
6
6
 
7
7
  import {getSequenceMolecularWeight} from '../utils/molecular-measure';
8
8
  import {AlignedSequenceEncoder} from '@datagrok-libraries/bio/src/sequence-encoder';
9
- import {createDimensinalityReducingWorker, IReduceDimensionalityResult,
9
+ import {createDimensinalityReducingWorker,
10
10
  } from '@datagrok-libraries/ml/src/workers/dimensionality-reducing-worker-creator';
11
11
  import {StringMetrics} from '@datagrok-libraries/ml/src/typed-metrics';
12
12
  import * as C from '../utils/constants';
13
13
  import {PeptidesModel} from '../model';
14
+ import {IReduceDimensionalityResult} from '@datagrok-libraries/ml/src/reduce-dimensionality';
14
15
 
15
16
  export class PeptideSpaceViewer extends DG.JsViewer {
16
17
  method: string;
@@ -1,3 +1,4 @@
1
+ import * as grok from 'datagrok-api/grok';
1
2
  import * as ui from 'datagrok-api/ui';
2
3
  import * as DG from 'datagrok-api/dg';
3
4
 
@@ -5,6 +6,7 @@ import $ from 'cash-dom';
5
6
  import * as C from '../utils/constants';
6
7
  import * as CR from '../utils/cell-renderer';
7
8
  import {PeptidesModel, VIEWER_TYPE} from '../model';
9
+ import wu from 'wu';
8
10
 
9
11
  export enum MONOMER_POSITION_MODE {
10
12
  MUTATION_CLIFFS = 'Mutation Cliffs',
@@ -12,7 +14,7 @@ export enum MONOMER_POSITION_MODE {
12
14
  }
13
15
 
14
16
  export enum MONOMER_POSITION_PROPERTIES {
15
- COLOR_COLUMN_NAME = 'colorColumnName',
17
+ COLOR_COLUMN_NAME = 'color',
16
18
  AGGREGATION = 'aggregation',
17
19
  TARGET = 'target',
18
20
  };
@@ -22,7 +24,7 @@ export class MonomerPosition extends DG.JsViewer {
22
24
  _titleHost = ui.divText(MONOMER_POSITION_MODE.MUTATION_CLIFFS, {id: 'pep-viewer-title'});
23
25
  _viewerGrid!: DG.Grid;
24
26
  _model!: PeptidesModel;
25
- colorColumnName: string;
27
+ colorCol: string;
26
28
  aggregation: string;
27
29
  target: string;
28
30
 
@@ -30,10 +32,13 @@ export class MonomerPosition extends DG.JsViewer {
30
32
  super();
31
33
  this.target = this.string(MONOMER_POSITION_PROPERTIES.TARGET, null,
32
34
  {category: MONOMER_POSITION_MODE.MUTATION_CLIFFS, choices: []});
33
- this.colorColumnName = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
34
- {category: MONOMER_POSITION_MODE.INVARIANT_MAP});
35
+ this.colorCol = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
36
+ {category: MONOMER_POSITION_MODE.INVARIANT_MAP,
37
+ choices: wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name)});
35
38
  this.aggregation = this.string(MONOMER_POSITION_PROPERTIES.AGGREGATION, DG.AGG.AVG,
36
- {category: MONOMER_POSITION_MODE.INVARIANT_MAP, choices: Object.values(DG.AGG)});
39
+ {category: MONOMER_POSITION_MODE.INVARIANT_MAP,
40
+ choices: Object.values(DG.AGG)
41
+ .filter((agg) => ![DG.AGG.KEY, DG.AGG.PIVOT, DG.AGG.SELECTED_ROWS_COUNT].includes(agg))});
37
42
  }
38
43
 
39
44
  get name(): string {return VIEWER_TYPE.MONOMER_POSITION;}
@@ -65,12 +70,7 @@ export class MonomerPosition extends DG.JsViewer {
65
70
 
66
71
  onTableAttached(): void {
67
72
  super.onTableAttached();
68
- this.subs.push(this.model.onMonomerPositionSelectionChanged.subscribe(() => this.viewerGrid.invalidate()));
69
73
  this.helpUrl = '/help/domains/bio/peptides.md';
70
- this.subs.push(this.model.onSettingsChanged.subscribe(() => {
71
- this.createMonomerPositionGrid();
72
- this.render();
73
- }));
74
74
  this.render();
75
75
  }
76
76
 
@@ -89,7 +89,7 @@ export class MonomerPosition extends DG.JsViewer {
89
89
  const monomerCol = this.model.monomerPositionDf.getCol(C.COLUMNS_NAMES.MONOMER);
90
90
  CR.setAARRenderer(monomerCol, this.model.alphabet);
91
91
  this.viewerGrid.onCellRender.subscribe((args: DG.GridCellRenderArgs) => renderCell(args, this.model,
92
- this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.colorColumnName),
92
+ this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.colorCol),
93
93
  this.aggregation as DG.AggregationType));
94
94
  this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
95
95
  this.viewerGrid.root.addEventListener('click', (ev) => {
@@ -101,9 +101,11 @@ export class MonomerPosition extends DG.JsViewer {
101
101
  const aar = monomerCol.get(gridCell!.tableRowIndex!);
102
102
  chooseAction(aar, position, ev.shiftKey, this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP, this.model);
103
103
  this.viewerGrid.invalidate();
104
- // this.model.fireBitsetChanged();
105
104
  });
106
- this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(this.model.monomerPositionDf, this.model));
105
+ this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => {
106
+ cellChanged(this.model.monomerPositionDf, this.model);
107
+ this.viewerGrid.invalidate();
108
+ });
107
109
 
108
110
  setViewerGridProps(this.viewerGrid, false);
109
111
  }
@@ -129,18 +131,20 @@ export class MonomerPosition extends DG.JsViewer {
129
131
  invariantMapMode.addPostfix(MONOMER_POSITION_MODE.INVARIANT_MAP);
130
132
  const setDefaultProperties = (input: DG.InputBase): void => {
131
133
  $(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
132
- $(input.root).find('.ui-input-description').css('padding', '0px').css('padding-left', '5px');
134
+ $(input.root).find('.ui-input-description').css('padding', '0px').css('padding-right', '16px');
133
135
  };
134
136
  setDefaultProperties(mutationCliffsMode);
135
137
  setDefaultProperties(invariantMapMode);
136
- $(mutationCliffsMode.root).css('padding-right', '10px').css('padding-left', '5px');
137
138
 
138
139
  switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root], {id: 'pep-viewer-title'});
139
140
  $(switchHost).css('width', 'auto').css('align-self', 'center');
140
141
  }
141
142
  const tips: HTMLElement = ui.iconFA('question');
142
- ui.tooltip.bind(tips,
143
- () => ui.divV([ui.divText('Color intensity - p-value'), ui.divText('Circle size - Mean difference')]));
143
+ ui.tooltip.bind(tips, () => ui.divV([
144
+ ui.divText('Color intensity - p-value'),
145
+ ui.divText('Circle size - Mean difference'),
146
+ ui.divText('Number - # of unique sequences that form mutation cliffs pairs'),
147
+ ]));
144
148
 
145
149
  $(tips).addClass('pep-help-icon');
146
150
 
@@ -154,7 +158,7 @@ export class MonomerPosition extends DG.JsViewer {
154
158
  }
155
159
 
156
160
  /** Vertical structure activity relationship viewer */
157
- export class MostPotentResiduesViewer extends DG.JsViewer {
161
+ export class MostPotentResidues extends DG.JsViewer {
158
162
  _titleHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
159
163
  _viewerGrid!: DG.Grid;
160
164
  _model!: PeptidesModel;
@@ -183,12 +187,7 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
183
187
 
184
188
  onTableAttached(): void {
185
189
  super.onTableAttached();
186
- this.subs.push(this.model.onMonomerPositionSelectionChanged.subscribe(() => this.viewerGrid.invalidate()));
187
190
  this.helpUrl = '/help/domains/bio/peptides.md';
188
- this.subs.push(this.model.onSettingsChanged.subscribe(() => {
189
- this.createMostPotentResiduesGrid();
190
- this.render();
191
- }));
192
191
  this.render();
193
192
  }
194
193
 
@@ -208,7 +207,8 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
208
207
 
209
208
  // Setting Monomer column renderer
210
209
  CR.setAARRenderer(monomerCol, this.model.alphabet);
211
- this.viewerGrid.onCellRender.subscribe((args: DG.GridCellRenderArgs) => renderCell(args, this.model));
210
+ this.viewerGrid.onCellRender.subscribe(
211
+ (args: DG.GridCellRenderArgs) => renderCell(args, this.model, false, undefined, undefined, false));
212
212
  this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
213
213
  this.viewerGrid.root.addEventListener('click', (ev) => {
214
214
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -222,7 +222,10 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
222
222
  this.viewerGrid.invalidate();
223
223
  // this.model.fireBitsetChanged();
224
224
  });
225
- this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(this.model.mostPotentResiduesDf, this.model));
225
+ this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => {
226
+ cellChanged(this.model.mostPotentResiduesDf, this.model);
227
+ this.viewerGrid.invalidate();
228
+ });
226
229
  const mdCol: DG.GridColumn = this.viewerGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
227
230
  mdCol.name = 'Diff';
228
231
  setViewerGridProps(this.viewerGrid, true);
@@ -233,8 +236,11 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
233
236
  $(this.root).empty();
234
237
  const switchHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
235
238
  const tips: HTMLElement = ui.iconFA('question');
236
- ui.tooltip.bind(tips,
237
- () => ui.divV([ui.divText('Color intensity - p-value'), ui.divText('Circle size - Mean difference')]));
239
+ ui.tooltip.bind(tips, () => ui.divV([
240
+ ui.divText('Color intensity - p-value'),
241
+ ui.divText('Circle size - Mean difference'),
242
+ ui.divText('Number - # of unique sequences that form mutation cliffs pairs'),
243
+ ]));
238
244
 
239
245
  $(tips).addClass('pep-help-icon');
240
246
 
@@ -248,7 +254,7 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
248
254
  }
249
255
 
250
256
  function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvariantMap?: boolean,
251
- colorCol?: DG.Column<number>, colorAgg?: DG.AggregationType): void {
257
+ colorCol?: DG.Column<number>, colorAgg?: DG.AggregationType, renderNums?: boolean): void {
252
258
  const renderColNames = [...model.splitSeqDf.columns.names(), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
253
259
  const canvasContext = args.g;
254
260
  const bound = args.bounds;
@@ -293,20 +299,21 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
293
299
  const positionColCategories = positionCol.categories;
294
300
 
295
301
  const colorColData = colorCol!.getRawData();
296
- const colorDataList: number[] = [];
297
- for (let i = 0; i < positionColData.length; ++i) {
302
+ const colorValuesIndexes: number[] = [];
303
+ for (let i = 0; i < positionCol.length; ++i) {
298
304
  if (positionColCategories[positionColData[i]] === currentMonomer)
299
- colorDataList.push(colorColData[i]);
305
+ colorValuesIndexes.push(i);
300
306
  }
301
- const cellColorDataCol = DG.Column.fromList('double', '', colorDataList);
307
+ const cellColorDataCol = DG.Column.float('color', colorValuesIndexes.length)
308
+ .init((i) => colorColData[colorValuesIndexes[i]]);
302
309
  const colorColStats = colorCol!.stats;
303
310
 
304
311
  const color = DG.Color.scaleColor(cellColorDataCol.aggregate(colorAgg!), colorColStats.min, colorColStats.max);
305
312
  CR.renderInvaraintMapCell(
306
- canvasContext, currentMonomer, currentPosition, model.monomerPositionFilter, value, bound, color);
313
+ canvasContext, currentMonomer, currentPosition, model.invariantMapSelection, value, bound, color);
307
314
  } else {
308
315
  CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition, model.monomerPositionStats, bound,
309
- model.monomerPositionSelection, model.mutationCliffs, model.settings.isBidirectional);
316
+ model.mutationCliffsSelection, model.mutationCliffs, model.settings.isBidirectional, renderNums);
310
317
  }
311
318
  args.preventDefault();
312
319
  canvasContext.restore();
@@ -334,16 +341,16 @@ export function showTooltip(cell: DG.GridCell, x: number, y: number, model: Pept
334
341
  return true;
335
342
  }
336
343
 
337
- function chooseAction(aar: string, position: string, isShiftPressed: boolean, isFilter: boolean,
344
+ function chooseAction(aar: string, position: string, isShiftPressed: boolean, isInvariantMap: boolean,
338
345
  model: PeptidesModel): void {
339
346
  if (!isShiftPressed) {
340
- if (isFilter)
341
- model.initMonomerPositionFilter({cleanInit: true, notify: false});
347
+ if (isInvariantMap)
348
+ model.initInvariantMapSelection({cleanInit: true, notify: false});
342
349
  else
343
- model.initMonomerPositionSelection({cleanInit: true, notify: false});
350
+ model.initMutationCliffsSelection({cleanInit: true, notify: false});
344
351
  }
345
352
 
346
- model.modifyMonomerPositionSelection(aar, position, isFilter);
353
+ model.modifyMonomerPositionSelection(aar, position, isInvariantMap);
347
354
  }
348
355
 
349
356
  function cellChanged(table: DG.DataFrame, model: PeptidesModel): void {