@datagrok/peptides 1.5.1 → 1.7.2

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.
@@ -1,12 +1,16 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
+ import * as grok from 'datagrok-api/grok';
2
3
  import * as DG from 'datagrok-api/dg';
3
4
 
4
5
  import $ from 'cash-dom';
5
6
  import {PeptidesModel} from '../model';
6
7
  import * as C from '../utils/constants';
7
8
  import * as CR from '../utils/cell-renderer';
8
- import * as bio from '@datagrok-libraries/bio';
9
- import { Stats } from '../utils/statistics';
9
+ import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
+ import {PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
11
+ import {getStats, MaskInfo, Stats} from '../utils/statistics';
12
+ import wu from 'wu';
13
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
14
 
11
15
  export class LogoSummary extends DG.JsViewer {
12
16
  _titleHost = ui.divText('Logo Summary Table', {id: 'pep-viewer-title'});
@@ -15,13 +19,17 @@ export class LogoSummary extends DG.JsViewer {
15
19
  initialized: boolean = false;
16
20
  webLogoMode: string;
17
21
  membersRatioThreshold: number;
22
+ newClusterName: string;
23
+ webLogoDfPlot: DG.DataFramePlotHelper[] = [];
24
+ distributionDfPlot: DG.DataFramePlotHelper[] = [];
18
25
 
19
26
  constructor() {
20
27
  super();
21
28
 
22
- this.webLogoMode = this.string('webLogoMode', bio.PositionHeight.full,
23
- {choices: [bio.PositionHeight.full, bio.PositionHeight.Entropy]});
24
- this.membersRatioThreshold = this.float('membersRatioThreshold', 0.7, {min: 0.01, max: 1.0});
29
+ this.webLogoMode = this.string('webLogoMode', PositionHeight.full,
30
+ {choices: [PositionHeight.full, PositionHeight.Entropy]});
31
+ this.membersRatioThreshold = this.float('membersRatioThreshold', 0.7, {min: 0, max: 1.0});
32
+ this.newClusterName = this.string('newClusterName', 'New cluster');
25
33
  }
26
34
 
27
35
  onTableAttached(): void {
@@ -32,6 +40,12 @@ export class LogoSummary extends DG.JsViewer {
32
40
  this.createLogoSummaryGrid();
33
41
  this.render();
34
42
  }));
43
+ this.subs.push(this.model.onNewCluster.subscribe(() => this.clusterFromSelection()));
44
+ this.subs.push(this.model.onRemoveCluster.subscribe(() => this.removeCluster()));
45
+ this.subs.push(this.model.onFilterChanged.subscribe(() => {
46
+ this.createLogoSummaryGrid();
47
+ this.render();
48
+ }));
35
49
 
36
50
  this.createLogoSummaryGrid();
37
51
  this.initialized = true;
@@ -43,6 +57,13 @@ export class LogoSummary extends DG.JsViewer {
43
57
  render(): void {
44
58
  if (this.initialized) {
45
59
  $(this.root).empty();
60
+ const df = this.viewerGrid.dataFrame;
61
+ if (!df.filter.anyTrue) {
62
+ const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
63
+ 'Please, lower the threshold in viewer proeperties to include clusters');
64
+ this.root.appendChild(ui.divV([this._titleHost, emptyDf]));
65
+ return;
66
+ }
46
67
  this.viewerGrid.root.style.width = 'auto';
47
68
  this.root.appendChild(ui.divV([this._titleHost, this.viewerGrid.root]));
48
69
  this.viewerGrid.invalidate();
@@ -58,76 +79,136 @@ export class LogoSummary extends DG.JsViewer {
58
79
 
59
80
  createLogoSummaryGrid(): DG.Grid {
60
81
  const clustersColName = this.model.settings.clustersColumnName!;
61
- let summaryTableBuilder = this.dataFrame.groupBy([clustersColName]);
62
- for (const [colName, aggregationFunc] of Object.entries(this.model.settings.columns ?? {}))
63
- summaryTableBuilder = summaryTableBuilder.add(aggregationFunc as any, colName, `${aggregationFunc}(${colName})`);
82
+ const isDfFiltered = this.dataFrame.filter.anyFalse;
83
+ const filteredDf = isDfFiltered ? this.dataFrame.clone(this.dataFrame.filter) : this.dataFrame;
84
+ const filteredDfCols = filteredDf.columns;
85
+ const activityCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
86
+ const activityColData = activityCol.getRawData();
87
+
88
+ const filteredDfClustersCol = filteredDf.getCol(clustersColName);
89
+ const filteredDfClustersColData = filteredDfClustersCol.getRawData();
90
+ const filteredDfClustersColCategories = filteredDfClustersCol.categories;
91
+ const filteredDfClustersColLength = filteredDfClustersColData.length;
92
+
93
+ // const customClustersColumnsList = wu(this.model.customClusters).toArray();
94
+ const query: { [key: string]: string } = {};
95
+ query[C.TAGS.CUSTOM_CLUSTER] = '1';
96
+ const customClustersColumnsList = wu(filteredDfCols.byTags(query)).filter(c => c.max > 0).toArray();
97
+ const getAggregatedColName = (aggF: string, colName: string) => `${aggF}(${colName})`;
98
+ const isCustomCluster = (cluster: string) => filteredDfCols.contains(cluster);
64
99
 
65
- const summaryTable = summaryTableBuilder.aggregate();
100
+ let summaryTableBuilder = filteredDf.groupBy([clustersColName]);
101
+ const aggregateColumnsEntries = Object.entries(this.model.settings.columns ?? {});
102
+ for (const [colName, aggregationFunc] of aggregateColumnsEntries) {
103
+ summaryTableBuilder = summaryTableBuilder.add(
104
+ aggregationFunc as any, colName, getAggregatedColName(aggregationFunc, colName));
105
+ }
106
+
107
+ const tempSummaryTable = summaryTableBuilder.aggregate();
108
+ const tempSummaryTableLength = tempSummaryTable.rowCount;
109
+ const tempClustersCol: DG.Column<string> = tempSummaryTable.getCol(clustersColName);
110
+ const summaryTableLength = tempSummaryTableLength + customClustersColumnsList.length;
111
+ const summaryTable = DG.DataFrame.create(summaryTableLength);
66
112
  const summaryTableCols = summaryTable.columns;
67
- const webLogoCol: DG.Column<string> = summaryTableCols.addNewString('WebLogo');
68
- // const webLogoColData = webLogoCol.getRawData();
69
- const clustersCol: DG.Column<string> = summaryTable.getCol(clustersColName);
113
+
114
+ const clustersCol = summaryTableCols.addNewString(clustersColName);
115
+ for (let i = 0; i < summaryTableLength; ++i) {
116
+ clustersCol.set(i, i < tempSummaryTableLength ? tempClustersCol.get(i) :
117
+ customClustersColumnsList[i - tempSummaryTableLength].name);
118
+ }
70
119
  const clustersColData = clustersCol.getRawData();
71
120
  const clustersColCategories = clustersCol.categories;
72
- const summaryTableLength = clustersColData.length;
73
- const membersColData = summaryTableCols.addNewInt(C.COLUMNS_NAMES.MEMBERS).getRawData();
74
- const tempWebLogoDfPlotList: DG.DataFramePlotHelper[] = new Array(summaryTableLength);
75
- const tempDistributionDfPlotList: DG.DataFramePlotHelper[] = new Array(summaryTableLength);
76
- const originalClustersCol = this.dataFrame.getCol(clustersColName);
77
- const originalClustersColData = originalClustersCol.getRawData();
78
- const originalClustersColCategories = originalClustersCol.categories;
79
- const originalClustersColLength = originalClustersColData.length;
80
- const peptideCol: DG.Column<string> = this.dataFrame.getCol(this.model.settings.sequenceColumnName!);
121
+
122
+ const peptideCol: DG.Column<string> = filteredDf.getCol(this.model.settings.sequenceColumnName!);
81
123
  const peptideColData = peptideCol.getRawData();
82
124
  const peptideColCategories = peptideCol.categories;
83
- const meanDifferenceColData = summaryTableCols.addNewFloat('Mean difference').getRawData();
84
- const pValColData = summaryTableCols.addNewFloat('P-Value').getRawData();
85
- const ratioColData = summaryTableCols.addNewFloat('Ratio').getRawData();
86
- const distributionCol = summaryTableCols.addNewString('Distribution');
87
- // const distributionColData = distributionCol.getRawData();
88
-
89
- for (let index = 0; index < summaryTableLength; ++index) {
90
- const indexes: number[] = [];
91
- const currentCluster = clustersColCategories[clustersColData[index]];
92
- for (let j = 0; j < originalClustersColLength; ++j) {
93
- if (originalClustersColCategories[originalClustersColData[j]] == currentCluster)
94
- indexes.push(j);
95
- }
96
- const tCol = DG.Column.string('peptides', indexes.length);
97
- tCol.init((i) => peptideColCategories[peptideColData[indexes[i]]]);
125
+ const peptideColTags = peptideCol.tags;
98
126
 
99
- for (const tag of peptideCol.tags)
100
- tCol.setTag(tag[0], tag[1]);
127
+ const membersColData = summaryTableCols.addNewInt(C.LST_COLUMN_NAMES.MEMBERS).getRawData();
128
+ const webLogoCol = summaryTableCols.addNewString(C.LST_COLUMN_NAMES.WEB_LOGO);
129
+ const distributionCol = summaryTableCols.addNewString(C.LST_COLUMN_NAMES.DISTRIBUTION);
130
+ const meanDifferenceColData = summaryTableCols.addNewFloat(C.LST_COLUMN_NAMES.MEAN_DIFFERENCE).getRawData();
131
+ const pValColData = summaryTableCols.addNewFloat(C.LST_COLUMN_NAMES.P_VALUE).getRawData();
132
+ const ratioColData = summaryTableCols.addNewFloat(C.LST_COLUMN_NAMES.RATIO).getRawData();
101
133
 
102
- const uh = new bio.UnitsHandler(tCol);
103
- tCol.setTag(bio.TAGS.alphabetSize, uh.getAlphabetSize().toString());
134
+ for (const [colName, aggregationFunc] of aggregateColumnsEntries) {
135
+ const tempSummaryTableCol = tempSummaryTable.getCol(getAggregatedColName(aggregationFunc, colName));
136
+ const summaryTableCol = summaryTableCols.addNew(tempSummaryTableCol.name, tempSummaryTableCol.type);
137
+ summaryTableCol.init((i) => i < tempSummaryTableLength ? tempSummaryTableCol.get(i) : null);
138
+ }
139
+
140
+ this.webLogoDfPlot = new Array(summaryTableLength);
141
+ this.distributionDfPlot = new Array(summaryTableLength);
142
+
143
+ for (let summaryTableRowIndex = 0; summaryTableRowIndex < summaryTableLength; ++summaryTableRowIndex) {
144
+ const isOriginalCluster = summaryTableRowIndex < tempSummaryTableLength;
145
+ const currentClusterCategoryIndex = clustersColData[summaryTableRowIndex];
146
+ const currentCluster = clustersColCategories[currentClusterCategoryIndex]; // Cluster name
147
+ const customClusterColData = customClustersColumnsList.find((col) => col.name == currentCluster)?.toList();
148
+
149
+ const isValidIndex = isOriginalCluster ?
150
+ (j: number) => filteredDfClustersColCategories[filteredDfClustersColData[j]] == currentCluster :
151
+ (j: number) => customClusterColData![j];
104
152
 
105
- // Get Stats
106
- const matcher: {[key: string]: number | string} = {};
107
- matcher[this.model.settings.clustersColumnName!] = currentCluster;
108
- const currentStatsDf = this.model.clusterStatsDf.rows.match(matcher).toDataFrame();
109
- const activityCol = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
153
+
110
154
  //TODO: use bitset instead of splitCol
111
155
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
112
- const currentClusterCol = this.dataFrame.getCol(this.model.settings.clustersColumnName!);
113
- splitCol.init((i) => currentClusterCol.get(i) == currentCluster);
114
- const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
115
- const stats: Stats = {
116
- count: currentStatsDf.get(C.COLUMNS_NAMES.COUNT, 0),
117
- ratio: currentStatsDf.get(C.COLUMNS_NAMES.RATIO, 0),
118
- pValue: currentStatsDf.get(C.COLUMNS_NAMES.P_VALUE, 0),
119
- meanDifference: currentStatsDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, 0),
120
- };
156
+ const getSplitColValueAt = isOriginalCluster ?
157
+ (splitColIndex: number) => filteredDfClustersColData[splitColIndex] == currentClusterCategoryIndex :
158
+ (splitColIndex: number) => customClusterColData![splitColIndex];
159
+ splitCol.init((i) => getSplitColValueAt(i));
160
+
161
+ let stats: Stats;
162
+ if (isDfFiltered) {
163
+ const trueCount = splitCol.stats.sum;
164
+ const maskInfo = {
165
+ trueCount: trueCount,
166
+ falseCount: activityColData.length - trueCount,
167
+ mask: splitCol.toList() as boolean[],
168
+ };
169
+ stats = getStats(activityColData, maskInfo);
170
+ } else
171
+ stats = this.model.clusterStats[currentCluster];
121
172
 
173
+ const tCol = DG.Column.string('peptides', stats.count);
174
+ let tColIdx = 0;
175
+ for (let j = 0; j < filteredDfClustersColLength; ++j) {
176
+ if (isValidIndex(j))
177
+ tCol.set(tColIdx++, peptideColCategories[peptideColData[j]]);
178
+ }
179
+
180
+ for (const tag of peptideColTags)
181
+ tCol.setTag(tag[0], tag[1]);
182
+
183
+ const uh = new UnitsHandler(tCol);
184
+ tCol.setTag(bioTAGS.alphabetSize, uh.getAlphabetSize().toString());
185
+
186
+
187
+ const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
122
188
  const dfSlice = DG.DataFrame.fromColumns([tCol]);
123
- tempWebLogoDfPlotList[index] = dfSlice.plot;
124
- tempDistributionDfPlotList[index] = distributionTable.plot;
125
- // webLogoColData[index] = index;
126
- // distributionColData[index] = index;
127
- membersColData[index] = indexes.length;
128
- meanDifferenceColData[index] = stats.meanDifference;
129
- pValColData[index] = stats.pValue;
130
- ratioColData[index] = stats.ratio;
189
+
190
+ this.webLogoDfPlot[summaryTableRowIndex] = dfSlice.plot;
191
+ this.distributionDfPlot[summaryTableRowIndex] = distributionTable.plot;
192
+
193
+ membersColData[summaryTableRowIndex] = stats.count;
194
+ meanDifferenceColData[summaryTableRowIndex] = stats.meanDifference;
195
+ pValColData[summaryTableRowIndex] = stats.pValue;
196
+ ratioColData[summaryTableRowIndex] = stats.ratio;
197
+
198
+ //Setting aggregated col values
199
+ if (!isOriginalCluster) {
200
+ for (const [colName, aggregationFunc] of aggregateColumnsEntries) {
201
+ const arrayBuffer = filteredDf.getCol(colName).getRawData();
202
+ const clusterMask = DG.BitSet.fromBytes(arrayBuffer.buffer, arrayBuffer.byteLength / 4);
203
+ const subDf = filteredDf.clone(clusterMask, [colName]);
204
+ const newColName = getAggregatedColName(aggregationFunc, colName);
205
+ const aggregatedDf = subDf.groupBy()
206
+ .add(aggregationFunc as any, colName, newColName)
207
+ .aggregate();
208
+ const value = aggregatedDf.get(newColName, 0);
209
+ summaryTable.set(newColName, summaryTableRowIndex, value);
210
+ }
211
+ }
131
212
  }
132
213
  webLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
133
214
  distributionCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
@@ -145,11 +226,12 @@ export class LogoSummary extends DG.JsViewer {
145
226
  return;
146
227
 
147
228
  if (cell.tableColumn?.name == 'WebLogo') {
148
- tempWebLogoDfPlotList[currentRowIdx]
149
- .fromType('WebLogo', {maxHeight: cell.grid.props.rowHeight - 5, positionHeight: this.webLogoMode})
229
+ this.webLogoDfPlot[currentRowIdx]
230
+ .fromType('WebLogo', {maxHeight: cell.grid.props.rowHeight - 5, positionHeight: this.webLogoMode,
231
+ horizontalAlignment: 'left'})
150
232
  .then((viewer) => cell.element = viewer.root);
151
233
  } else if (cell.tableColumn?.name == 'Distribution') {
152
- const viewerRoot = tempDistributionDfPlotList[currentRowIdx].histogram({
234
+ const viewerRoot = this.distributionDfPlot[currentRowIdx].histogram({
153
235
  filteringEnabled: false,
154
236
  valueColumnName: C.COLUMNS_NAMES.ACTIVITY_SCALED,
155
237
  splitColumnName: C.COLUMNS_NAMES.SPLIT_COL,
@@ -169,15 +251,14 @@ export class LogoSummary extends DG.JsViewer {
169
251
  });
170
252
  this.viewerGrid.root.addEventListener('click', (ev) => {
171
253
  const cell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
172
- if (!cell || !cell.isTableCell)
254
+ if (!cell || !cell.isTableCell || cell.tableColumn?.name != clustersColName)
173
255
  return;
174
256
 
175
- const clusterIdx = clustersColData[cell.tableRowIndex!];
176
257
  summaryTable.currentRowIdx = -1;
177
258
  if (ev.shiftKey)
178
- this.model.modifyClusterSelection(clusterIdx);
259
+ this.model.modifyClusterSelection(cell.cell.value);
179
260
  else
180
- this.model.initClusterSelection(clusterIdx);
261
+ this.model.initClusterSelection(cell.cell.value);
181
262
  this.viewerGrid.invalidate();
182
263
  });
183
264
  this.viewerGrid.onCellRender.subscribe((gridCellArgs) => {
@@ -190,14 +271,13 @@ export class LogoSummary extends DG.JsViewer {
190
271
  canvasContext.beginPath();
191
272
  canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
192
273
  canvasContext.clip();
193
- const cellRawData = clustersColData[gc.cell.rowIndex];
194
- CR.renderLogoSummaryCell(canvasContext, gc.cell.value, cellRawData, this.model.logoSummarySelection, bound);
274
+ CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.model.logoSummarySelection, bound);
195
275
  gridCellArgs.preventDefault();
196
276
  canvasContext.restore();
197
277
  });
198
278
  this.viewerGrid.onCellTooltip((cell, x, y) => {
199
279
  if (!cell.isColHeader && cell.tableColumn?.name === clustersColName)
200
- this.model.showTooltipCluster(cell.cell.value, x, y);
280
+ this.model.showTooltipCluster(cell.cell.rowIndex, x, y, cell.cell.value);
201
281
  return true;
202
282
  });
203
283
  const webLogoGridCol = this.viewerGrid.columns.byName('WebLogo')!;
@@ -215,9 +295,94 @@ export class LogoSummary extends DG.JsViewer {
215
295
 
216
296
  updateFilter(): void {
217
297
  const table = this.viewerGrid.table;
218
- const memberstCol = table.getCol(C.COLUMNS_NAMES.MEMBERS);
298
+ const memberstCol = table.getCol(C.LST_COLUMN_NAMES.MEMBERS);
219
299
  const membersColData = memberstCol.getRawData();
220
300
  const maxCount = memberstCol.stats.max;
221
- table.filter.init((i) => membersColData[i] > Math.ceil(maxCount * this.membersRatioThreshold));
301
+ const minMembers = Math.ceil(maxCount * this.membersRatioThreshold);
302
+ table.filter.init((i) => membersColData[i] > minMembers);
303
+ }
304
+
305
+ clusterFromSelection(): void {
306
+ const filteredDf = this.dataFrame.filter.anyFalse ? this.dataFrame.clone(this.dataFrame.filter, null, true) :
307
+ this.dataFrame;
308
+ const selection = filteredDf.selection;
309
+ const viewerDf = this.viewerGrid.dataFrame;
310
+ const viewerDfCols = viewerDf.columns;
311
+ const viewerDfColsLength = viewerDfCols.length;
312
+ const newClusterVals = new Array(viewerDfCols.length);
313
+
314
+ const activityScaledCol = filteredDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
315
+ const maskInfo: MaskInfo = {
316
+ mask: selection.getBuffer(),
317
+ trueCount: selection.trueCount,
318
+ falseCount: selection.falseCount,
319
+ };
320
+ const stats = getStats(activityScaledCol.getRawData(), maskInfo);
321
+ const distributionTable = DG.DataFrame.fromColumns([activityScaledCol, filteredDf.getCol(this.model.splitCol.name)]);
322
+
323
+ const peptideCol: DG.Column<string> = filteredDf.getCol(this.model.settings.sequenceColumnName!);
324
+ const peptideColData = peptideCol.getRawData();
325
+ const peptideColCategories = peptideCol.categories;
326
+ const peptideColTags = peptideCol.tags;
327
+ const selectedIndexes = selection.getSelectedIndexes();
328
+ const tCol = DG.Column.string('peptides', selectedIndexes.length);
329
+
330
+ for (let i = 0; i < selectedIndexes.length; ++i)
331
+ tCol.set(i, peptideColCategories[peptideColData[selectedIndexes[i]]]);
332
+ for (const tag of peptideColTags)
333
+ tCol.setTag(tag[0], tag[1]);
334
+
335
+ const uh = new UnitsHandler(tCol);
336
+ tCol.setTag(bioTAGS.alphabetSize, uh.getAlphabetSize().toString());
337
+
338
+ const webLogoTable = DG.DataFrame.fromColumns([tCol]);
339
+ this.webLogoDfPlot.push(webLogoTable.plot);
340
+ this.distributionDfPlot.push(distributionTable.plot);
341
+
342
+ const colCategories = viewerDfCols.byName(this.model.settings.clustersColumnName!).categories;
343
+ let newClusterName = this.newClusterName;
344
+ let clusterNum = 1;
345
+ const getString = !isNaN(parseInt(newClusterName)) ? () => `${parseInt(newClusterName) + 1}` :
346
+ newClusterName == '' ? () => `${clusterNum++}` :
347
+ () => `${this.newClusterName} ${clusterNum++}`;
348
+ while (colCategories.includes(newClusterName))
349
+ newClusterName = getString();
350
+
351
+ this.getProperty('newClusterName')?.set(this, getString());
352
+
353
+ for (let i = 0; i < viewerDfColsLength; ++i) {
354
+ const col = viewerDfCols.byIndex(i);
355
+ newClusterVals[i] = col.name == this.model.settings.clustersColumnName! ? newClusterName :
356
+ col.name == C.LST_COLUMN_NAMES.MEMBERS ? maskInfo.trueCount :
357
+ col.name == C.LST_COLUMN_NAMES.WEB_LOGO ? null :
358
+ col.name == C.LST_COLUMN_NAMES.DISTRIBUTION ? null :
359
+ col.name == C.LST_COLUMN_NAMES.MEAN_DIFFERENCE ? stats.meanDifference:
360
+ col.name == C.LST_COLUMN_NAMES.P_VALUE ? stats.pValue:
361
+ col.name == C.LST_COLUMN_NAMES.RATIO ? stats.ratio:
362
+ console.warn(`PeptidesLSTWarn: value for column ${col.name} is undefined`)! || null;
363
+ }
364
+ viewerDf.rows.addNew(newClusterVals);
365
+
366
+ this.model.clusterStats[newClusterName] = stats;
367
+ this.model.addNewCluster(newClusterName);
368
+ }
369
+
370
+ removeCluster(): void {
371
+ const lss = this.model.logoSummarySelection;
372
+ const dfCols = this.dataFrame.columns;
373
+
374
+ const removeClusterIndexesList = lss.filter((cluster) => dfCols.contains(cluster));
375
+ if (removeClusterIndexesList.length == 0)
376
+ return grok.shell.info('Nothing removed. Please select a created cluster to remove');
377
+
378
+ for (const cluster of removeClusterIndexesList) {
379
+ lss.splice(lss.indexOf(cluster), 1);
380
+ dfCols.remove(cluster);
381
+ }
382
+
383
+ this.model.logoSummarySelection = lss;
384
+ this.model.clusterStats = this.model.calculateClusterStatistics();
385
+ this.createLogoSummaryGrid();
386
+ this.render();
222
387
  }
223
388
  }
@@ -6,13 +6,12 @@ import $ from 'cash-dom';
6
6
  import * as C from '../utils/constants';
7
7
  import * as CR from '../utils/cell-renderer';
8
8
  import {PeptidesModel} from '../model';
9
- import {isGridCellInvalid} from '../utils/misc';
10
9
 
11
10
  export class SARViewerBase extends DG.JsViewer {
12
11
  tempName!: string;
13
12
  _viewerGrid!: DG.Grid;
14
13
  sourceGrid!: DG.Grid;
15
- model!: PeptidesModel;
14
+ _model!: PeptidesModel;
16
15
  isPropertyChanging: boolean = false;
17
16
  _isVertical = false;
18
17
 
@@ -29,10 +28,15 @@ export class SARViewerBase extends DG.JsViewer {
29
28
  this._viewerGrid = grid;
30
29
  }
31
30
 
31
+ get model(): PeptidesModel {
32
+ this._model ??= PeptidesModel.getInstance(this.dataFrame);
33
+ return this._model;
34
+ }
35
+
32
36
  onTableAttached(): void {
33
37
  super.onTableAttached();
34
38
  this.sourceGrid = this.view?.grid ?? (grok.shell.v as DG.TableView).grid;
35
- this.model = PeptidesModel.getInstance(this.dataFrame);
39
+ // this.model = PeptidesModel.getInstance(this.dataFrame);
36
40
  this.subs.push(this.model.onMutationCliffsSelectionChanged.subscribe(() => this.viewerGrid.invalidate()));
37
41
  this.helpUrl = '/help/domains/bio/peptides.md';
38
42
  }
@@ -80,9 +84,16 @@ export class SARViewerBase extends DG.JsViewer {
80
84
  switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root], {id: 'pep-viewer-title'});
81
85
  $(switchHost).css('width', 'auto').css('align-self', 'center');
82
86
  }
87
+ const tips: HTMLElement = ui.iconFA('question');
88
+ ui.tooltip.bind(tips,
89
+ () => ui.divV([ui.divText('Color intensity - p-value'), ui.divText('Circle size - Mean difference')]));
90
+
91
+ $(tips).addClass('pep-help-icon');
92
+
83
93
  const viewerRoot = this.viewerGrid.root;
84
94
  viewerRoot.style.width = 'auto';
85
- this.root.appendChild(ui.divV([switchHost, viewerRoot]));
95
+ const header = ui.divH([switchHost, tips], {style: {alignSelf: 'center', lineHeight: 'normal'}});
96
+ this.root.appendChild(ui.divV([header, viewerRoot]));
86
97
  }
87
98
  this.viewerGrid?.invalidate();
88
99
  }
@@ -143,18 +154,18 @@ export class MonomerPosition extends SARViewerBase {
143
154
  this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
144
155
  this.viewerGrid.root.addEventListener('click', (ev) => {
145
156
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
146
- if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name == C.COLUMNS_NAMES.MONOMER)
157
+ if (!gridCell?.isTableCell || gridCell?.tableColumn?.name == C.COLUMNS_NAMES.MONOMER)
147
158
  return;
148
159
 
149
160
  const position = gridCell!.tableColumn!.name;
150
161
  const aar = monomerCol.get(gridCell!.tableRowIndex!);
151
162
  chooseAction(aar, position, ev.shiftKey, this.model.isInvariantMap, this.model);
152
163
  this.viewerGrid.invalidate();
164
+ this.model.fireBitsetChanged();
153
165
  });
154
166
  this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(this.model.monomerPositionDf, this.model));
155
167
 
156
168
  setViewerGridProps(this.viewerGrid, false);
157
- setTimeout(() => this.viewerGrid.col(C.COLUMNS_NAMES.MONOMER)!.width = 60, 10);
158
169
  }
159
170
  }
160
171
 
@@ -199,7 +210,7 @@ export class MostPotentResiduesViewer extends SARViewerBase {
199
210
  pValGridCol.format = '#.000';
200
211
  pValGridCol.name = 'P-value';
201
212
  const monomerCol = this.model.mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.MONOMER);
202
- const positionCol = this.model.mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.POSITION);
213
+ const positionCol: DG.Column<number> = this.model.mostPotentResiduesDf.getCol(C.COLUMNS_NAMES.POSITION);
203
214
 
204
215
  // Setting Monomer column renderer
205
216
  CR.setAARRenderer(monomerCol, this.model.alphabet);
@@ -207,27 +218,27 @@ export class MostPotentResiduesViewer extends SARViewerBase {
207
218
  this.viewerGrid.onCellTooltip((cell: DG.GridCell, x: number, y: number) => showTooltip(cell, x, y, this.model));
208
219
  this.viewerGrid.root.addEventListener('click', (ev) => {
209
220
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
210
- if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name != C.COLUMNS_NAMES.MEAN_DIFFERENCE)
221
+ if (!gridCell?.isTableCell || gridCell!.tableColumn!.name != C.COLUMNS_NAMES.MEAN_DIFFERENCE)
211
222
  return;
212
223
 
213
224
  const tableRowIdx = gridCell!.tableRowIndex!;
214
225
  const position = positionCol.get(tableRowIdx);
215
226
  const aar = monomerCol.get(tableRowIdx);
216
- chooseAction(aar, position, ev.shiftKey, false, this.model);
227
+ chooseAction(aar, position!.toFixed(), ev.shiftKey, false, this.model);
217
228
  this.viewerGrid.invalidate();
229
+ this.model.fireBitsetChanged();
218
230
  });
219
231
  this.viewerGrid.onCurrentCellChanged.subscribe((_gc) => cellChanged(this.model.mostPotentResiduesDf, this.model));
220
232
  const mdCol: DG.GridColumn = this.viewerGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
221
233
  mdCol.name = 'Diff';
222
234
  setViewerGridProps(this.viewerGrid, true);
223
- setTimeout(() => this.viewerGrid.col(C.COLUMNS_NAMES.MONOMER)!.width = 60, 10);
224
235
  }
225
236
  }
226
237
 
227
238
  function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvariantMap?: boolean,
228
239
  colorCol?: DG.Column<number>, colorAgg?: DG.AggregationType): void {
229
240
  const renderColNames = [...model.splitSeqDf.columns.names(), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
230
- const mdCol = model.monomerPositionStatsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
241
+ // const mdCol = model.monomerPositionStats.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
231
242
  const canvasContext = args.g;
232
243
  const bound = args.bounds;
233
244
 
@@ -241,49 +252,52 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
241
252
  if (cell.isRowHeader && cell.gridColumn.visible) {
242
253
  cell.gridColumn.visible = false;
243
254
  args.preventDefault();
255
+ canvasContext.restore();
244
256
  return;
245
257
  }
246
258
 
247
259
  const tableColName = cell.tableColumn?.name;
248
260
  const tableRowIndex = cell.tableRowIndex!;
249
- if (cell.isTableCell && tableColName && tableRowIndex !== null && renderColNames.indexOf(tableColName) !== -1) {
250
- const cellValue: number | null = cell.cell.value;
251
-
252
- if (cellValue && cellValue !== DG.INT_NULL && cellValue !== DG.FLOAT_NULL) {
253
- const gridTable = cell.grid.table;
254
- const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
255
- tableColName : gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
256
- const currentAAR: string = gridTable.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
257
-
258
- if (isInvariantMap) {
259
- const value: number = model.monomerPositionStatsDf
260
- .groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.COUNT])
261
- .where(`${C.COLUMNS_NAMES.POSITION} = ${currentPosition} and ${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`)
262
- .aggregate().get(C.COLUMNS_NAMES.COUNT, 0);
263
- const positionCol = model.df.getCol(currentPosition);
264
- const positionColData = positionCol.getRawData();
265
- const positionColCategories = positionCol.categories;
266
-
267
- const colorColData = colorCol!.getRawData();
268
- const colorDataList: number[] = [];
269
- for (let i = 0; i < positionColData.length; ++i) {
270
- if (positionColCategories[positionColData[i]] === currentAAR)
271
- colorDataList.push(colorColData[i]);
272
- }
273
- const cellColorDataCol = DG.Column.fromList('double', '', colorDataList);
274
- const colorColStats = colorCol!.stats;
275
-
276
- const color = DG.Color.scaleColor(cellColorDataCol.aggregate(colorAgg!), colorColStats.min, colorColStats.max);
277
- CR.renderInvaraintMapCell(
278
- canvasContext, currentAAR, currentPosition, model.invariantMapSelection, value, bound, color);
279
- } else {
280
- CR.renderMutationCliffCell(canvasContext, currentAAR, currentPosition, model.monomerPositionStatsDf,
281
- mdCol, bound, cellValue, model.mutationCliffsSelection, model.substitutionsInfo,
282
- model.settings.isBidirectional);
283
- }
284
- }
261
+ if (!cell.isTableCell || renderColNames.indexOf(tableColName!) == -1) {
262
+ canvasContext.restore();
263
+ return;
264
+ }
265
+
266
+ const gridTable = cell.grid.table;
267
+ const currentMonomer: string = gridTable.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
268
+ const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
269
+ gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex).toFixed();
270
+ const currentPosStats = model.monomerPositionStats[currentPosition];
271
+
272
+ if (!currentPosStats[currentMonomer]) {
285
273
  args.preventDefault();
274
+ canvasContext.restore();
275
+ return;
276
+ }
277
+
278
+ if (isInvariantMap) {
279
+ const value = currentPosStats[currentMonomer].count;
280
+ const positionCol = model.df.getCol(currentPosition);
281
+ const positionColData = positionCol.getRawData();
282
+ const positionColCategories = positionCol.categories;
283
+
284
+ const colorColData = colorCol!.getRawData();
285
+ const colorDataList: number[] = [];
286
+ for (let i = 0; i < positionColData.length; ++i) {
287
+ if (positionColCategories[positionColData[i]] === currentMonomer)
288
+ colorDataList.push(colorColData[i]);
289
+ }
290
+ const cellColorDataCol = DG.Column.fromList('double', '', colorDataList);
291
+ const colorColStats = colorCol!.stats;
292
+
293
+ const color = DG.Color.scaleColor(cellColorDataCol.aggregate(colorAgg!), colorColStats.min, colorColStats.max);
294
+ CR.renderInvaraintMapCell(
295
+ canvasContext, currentMonomer, currentPosition, model.invariantMapSelection, value, bound, color);
296
+ } else {
297
+ CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition, model.monomerPositionStats, bound,
298
+ model.mutationCliffsSelection, model.substitutionsInfo, model.settings.isBidirectional);
286
299
  }
300
+ args.preventDefault();
287
301
  canvasContext.restore();
288
302
  }
289
303
 
@@ -299,9 +313,9 @@ function showTooltip(cell: DG.GridCell, x: number, y: number, model: PeptidesMod
299
313
 
300
314
  if (tableCol.semType == C.SEM_TYPES.MONOMER)
301
315
  model.showMonomerTooltip(currentAAR, x, y);
302
- else if (cell.cell.value && renderColNames.includes(tableColName!)) {
316
+ else if (renderColNames.includes(tableColName!)) {
303
317
  const currentPosition = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
304
- table.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
318
+ table.get(C.COLUMNS_NAMES.POSITION, tableRowIndex).toFixed();
305
319
 
306
320
  model.showTooltipAt(currentAAR, currentPosition, x, y);
307
321
  }