@datagrok/peptides 1.15.3 → 1.16.0

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,10 +1,15 @@
1
+ import * as ui from 'datagrok-api/ui';
1
2
  import * as DG from 'datagrok-api/dg';
2
3
 
3
4
  import * as C from './constants';
4
- import * as types from './types';
5
- import {PositionStats, MonomerPositionStats, CLUSTER_TYPE} from '../model';
5
+ import * as type from './types';
6
+ import {CLUSTER_TYPE, PeptidesModel} from '../model';
6
7
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
7
8
  import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
+ import {calculateMonomerPositionStatistics} from './algorithms';
10
+ import * as rxjs from 'rxjs';
11
+ import {TooltipOptions, showTooltipAt} from './tooltips';
12
+ import {MonomerPositionStats, PositionStats} from './statistics';
8
13
 
9
14
  export function renderCellSelection(canvasContext: CanvasRenderingContext2D, bound: DG.Rect): void {
10
15
  canvasContext.strokeStyle = '#000';
@@ -21,7 +26,7 @@ export function setMonomerRenderer(col: DG.Column, alphabet: string): void {
21
26
 
22
27
  export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
23
28
  currentPosition: string, monomerPositionStats: MonomerPositionStats, bound: DG.Rect,
24
- mutationCliffsSelection: types.Selection, substitutionsInfo: types.MutationCliffs | null = null,
29
+ mutationCliffsSelection: type.Selection, substitutionsInfo: type.MutationCliffs | null = null,
25
30
  renderNums: boolean = true): void {
26
31
  const positionStats = monomerPositionStats[currentPosition];
27
32
  const pVal = positionStats![currentMonomer]!.pValue;
@@ -79,7 +84,7 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
79
84
  }
80
85
 
81
86
  export function renderInvaraintMapCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
82
- currentPosition: string, invariantMapSelection: types.Selection, cellValue: number, bound: DG.Rect,
87
+ currentPosition: string, invariantMapSelection: type.Selection, cellValue: number, bound: DG.Rect,
83
88
  color: number): void {
84
89
  //FIXME: This is a hack, because `color` value sometimes comes incomplete. E.g. we found that here `color` value is
85
90
  // 255 and its contrast color would be black, which is not visible on blue (color code) background. The full number
@@ -99,7 +104,7 @@ export function renderInvaraintMapCell(canvasContext: CanvasRenderingContext2D,
99
104
  }
100
105
 
101
106
  export function renderLogoSummaryCell(canvasContext: CanvasRenderingContext2D, cellValue: string,
102
- clusterSelection: types.Selection, bound: DG.Rect): void {
107
+ clusterSelection: type.Selection, bound: DG.Rect): void {
103
108
  canvasContext.font = '13px Roboto, Roboto Local, sans-serif';
104
109
  canvasContext.textAlign = 'center';
105
110
  canvasContext.textBaseline = 'middle';
@@ -113,7 +118,7 @@ export function renderLogoSummaryCell(canvasContext: CanvasRenderingContext2D, c
113
118
 
114
119
  export function drawLogoInBounds(ctx: CanvasRenderingContext2D, bounds: DG.Rect, stats: PositionStats, position: string,
115
120
  sortedOrder: string[], rowCount: number, cp: SeqPalette, monomerSelectionStats: { [monomer: string]: number } = {},
116
- drawOptions: types.DrawOptions = {}): { [monomer: string]: DG.Rect } {
121
+ drawOptions: type.DrawOptions = {}): { [monomer: string]: DG.Rect } {
117
122
  const pr = window.devicePixelRatio;
118
123
  drawOptions.symbolStyle ??= '16px Roboto, Roboto Local, sans-serif';
119
124
  drawOptions.upperLetterHeight ??= 12.2;
@@ -172,3 +177,114 @@ export function drawLogoInBounds(ctx: CanvasRenderingContext2D, bounds: DG.Rect,
172
177
 
173
178
  return monomerBounds;
174
179
  }
180
+
181
+ export type CellRendererOptions = {isSelectionTable?: boolean, headerSelectedMonomers?: type.SelectionStats,
182
+ webLogoBounds?: WebLogoBounds, cachedWebLogoTooltip?: type.CachedWebLogoTooltip};
183
+ export type WebLogoBounds = {[positon: string]: {[monomer: string]: DG.Rect}};
184
+
185
+ export function setWebLogoRenderer(grid: DG.Grid, model: PeptidesModel, options: CellRendererOptions = {},
186
+ tooltipOptions: TooltipOptions = {x: 0, y: 0, mpStats: {} as MonomerPositionStats, monomerPosition: {} as type.SelectionItem}): void {
187
+ options.isSelectionTable ??= false;
188
+ if (Object.keys(tooltipOptions.mpStats).length == 0)
189
+ tooltipOptions.mpStats = model.monomerPositionStats;
190
+ if (options.isSelectionTable && (!options.webLogoBounds || !options.cachedWebLogoTooltip)) {
191
+ throw new Error('Peptides: Cannot set WebLogo renderer for selection table without `headerSelectedMonomers`, ' +
192
+ '`webLogoBounds` and `cachedWebLogoTooltip` options.');
193
+ }
194
+
195
+ options.webLogoBounds ??= model.webLogoBounds;
196
+ options.cachedWebLogoTooltip ??= model.cachedWebLogoTooltip;
197
+ const df = grid.dataFrame;
198
+ grid.setOptions({'colHeaderHeight': 130});
199
+ const headerRenderer = (gcArgs: DG.GridCellRenderArgs): void => {
200
+ const ctx = gcArgs.g;
201
+ const bounds = gcArgs.bounds;
202
+ const col = gcArgs.cell.tableColumn;
203
+
204
+ ctx.save();
205
+ try {
206
+ ctx.beginPath();
207
+ ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height);
208
+ ctx.clip();
209
+
210
+ //TODO: optimize
211
+ if (gcArgs.cell.isColHeader && col?.semType === C.SEM_TYPES.MONOMER) {
212
+ const isDfFiltered = df.filter.anyFalse;
213
+ const stats = (isDfFiltered || options.isSelectionTable ?
214
+ calculateMonomerPositionStatistics(df, model.positionColumns.toArray(), {isFiltered: true, columns: [col.name]}) :
215
+ model.monomerPositionStats)[col.name];
216
+ if (!stats)
217
+ return;
218
+ //TODO: precalc on stats creation
219
+ const sortedStatsOrder = Object.keys(stats).sort((a, b) => {
220
+ if (a === '' || a === '-')
221
+ return +1;
222
+ else if (b === '' || b === '-')
223
+ return -1;
224
+ return 0;
225
+ }).filter((v) => v !== 'general');
226
+
227
+ options.webLogoBounds![col.name] = drawLogoInBounds(ctx, bounds, stats, col.name,
228
+ sortedStatsOrder, df.filter.trueCount, model.cp, options.headerSelectedMonomers ? options.headerSelectedMonomers[col.name] : {});
229
+ gcArgs.preventDefault();
230
+ }
231
+ } catch (e) {
232
+ console.warn(`PeptidesHeaderLogoError: couldn't render WebLogo for column \`${col!.name}\`. ` +
233
+ `See original error below.`);
234
+ console.warn(e);
235
+ } finally {
236
+ ctx.restore();
237
+ }
238
+ };
239
+ grid.onCellRender.subscribe((gcArgs) => headerRenderer(gcArgs));
240
+
241
+ const eventAction = (ev: MouseEvent): void => {
242
+ const cell = grid.hitTest(ev.offsetX, ev.offsetY);
243
+ if (cell?.isColHeader && cell.tableColumn?.semType === C.SEM_TYPES.MONOMER) {
244
+ const monomerPosition = findWebLogoMonomerPosition(cell, ev, (options.webLogoBounds ?? model.webLogoBounds));
245
+ if (monomerPosition === null) {
246
+ if (!options.isSelectionTable)
247
+ model.unhighlight();
248
+ return;
249
+ }
250
+ tooltipOptions.monomerPosition = monomerPosition;
251
+ requestBarchartAction(ev, monomerPosition, model, df, options, tooltipOptions);
252
+ if (!options.isSelectionTable)
253
+ model.highlightMonomerPosition(monomerPosition);
254
+ }
255
+ };
256
+
257
+ // The following events makes the barchart interactive
258
+ rxjs.fromEvent<MouseEvent>(grid.overlay, 'mousemove').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
259
+ rxjs.fromEvent<MouseEvent>(grid.overlay, 'click').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
260
+ }
261
+
262
+ function requestBarchartAction(ev: MouseEvent, monomerPosition: type.SelectionItem, model: PeptidesModel, df: DG.DataFrame,
263
+ options: CellRendererOptions, tooltipOptions: TooltipOptions): void {
264
+ if (ev.type === 'click' && !options.isSelectionTable)
265
+ model.modifyInvariantMapSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
266
+ else {
267
+ const bar = `${monomerPosition.positionOrClusterType} = ${monomerPosition.monomerOrCluster}`;
268
+ if (options.cachedWebLogoTooltip!.bar === bar)
269
+ ui.tooltip.show(options.cachedWebLogoTooltip!.tooltip!, ev.clientX, ev.clientY);
270
+ else {
271
+ options.cachedWebLogoTooltip!.bar = bar;
272
+ tooltipOptions.x = ev.clientX;
273
+ tooltipOptions.y = ev.clientY;
274
+ tooltipOptions.monomerPosition = monomerPosition;
275
+ options.cachedWebLogoTooltip!.tooltip = showTooltipAt(df, model.settings.columns!, tooltipOptions);
276
+ }
277
+ }
278
+ }
279
+
280
+ function findWebLogoMonomerPosition(cell: DG.GridCell, ev: MouseEvent, webLogoBounds: WebLogoBounds): type.SelectionItem | null {
281
+ const barCoords = webLogoBounds[cell.tableColumn!.name];
282
+ for (const [monomer, coords] of Object.entries(barCoords)) {
283
+ const isIntersectingX = ev.offsetX >= coords.x && ev.offsetX <= coords.x + coords.width;
284
+ const isIntersectingY = ev.offsetY >= coords.y && ev.offsetY <= coords.y + coords.height;
285
+ if (isIntersectingX && isIntersectingY)
286
+ return {monomerOrCluster: monomer, positionOrClusterType: cell.tableColumn!.name};
287
+ }
288
+
289
+ return null;
290
+ }
@@ -7,6 +7,7 @@ export enum COLUMNS_NAMES {
7
7
  MEAN_DIFFERENCE = 'Mean difference',
8
8
  COUNT = 'Count',
9
9
  RATIO = 'Ratio',
10
+ MEAN = 'Mean',
10
11
  }
11
12
 
12
13
  export enum LST_COLUMN_NAMES {
@@ -2,6 +2,8 @@ import * as DG from 'datagrok-api/dg';
2
2
  import {tTest} from '@datagrok-libraries/statistics/src/tests';
3
3
  import {RawData} from './types';
4
4
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
5
+ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
+ import {ClusterType} from '../model';
5
7
 
6
8
  export type Stats = {
7
9
  count: number,
@@ -9,8 +11,25 @@ export type Stats = {
9
11
  meanDifference: number,
10
12
  ratio: number,
11
13
  mask: BitArray,
14
+ mean: number,
12
15
  };
13
16
 
17
+ export type PositionStats = {[monomer: string]: Stats | undefined} & {general: SummaryStats};
18
+ export type MonomerPositionStats = {[position: string]: PositionStats | undefined} & {general: SummaryStats};
19
+ export type ClusterStats = {[cluster: string]: Stats};
20
+ export type ClusterTypeStats = {[clusterType in ClusterType]: ClusterStats};
21
+
22
+ export type SummaryStats = {
23
+ minCount: number, maxCount: number,
24
+ minMeanDifference: number, maxMeanDifference: number,
25
+ minPValue: number, maxPValue: number,
26
+ minRatio: number, maxRatio: number,
27
+ minMean: number, maxMean: number,
28
+ };
29
+
30
+ export const getAggregatedColName = (aggF: string, colName: string): string => `${aggF}(${colName})`;
31
+ export type AggregationColumns = {[col: string]: DG.AggregationType};
32
+
14
33
  export function getStats(data: RawData | number[], bitArray: BitArray): Stats {
15
34
  if (data.length !== bitArray.length && data.some((v, i) => i >= bitArray.length ? v !== 0 : false))
16
35
  throw new Error('PeptidesError: Data and bit array have different lengths');
@@ -29,12 +48,13 @@ export function getStats(data: RawData | number[], bitArray: BitArray): Stats {
29
48
  rest[restIndex++] = data[i];
30
49
  }
31
50
 
51
+ const selectedMean = selected.reduce((a, b) => a + b, 0) / selected.length;
32
52
  if (selected.length === 1 || rest.length === 1) {
33
- const selectedMean = selected.reduce((a, b) => a + b, 0) / selected.length;
34
53
  const restMean = rest.reduce((a, b) => a + b, 0) / rest.length;
35
54
  return {
36
55
  count: selected.length,
37
56
  pValue: null,
57
+ mean: selectedMean,
38
58
  meanDifference: selectedMean - restMean,
39
59
  ratio: selected.length / (bitArray.length),
40
60
  mask: bitArray,
@@ -46,6 +66,7 @@ export function getStats(data: RawData | number[], bitArray: BitArray): Stats {
46
66
  return {
47
67
  count: selected.length,
48
68
  pValue: testResult[currentMeanDiff >= 0 ? 'p-value more' : 'p-value less'],
69
+ mean: selectedMean,
49
70
  meanDifference: currentMeanDiff,
50
71
  ratio: selected.length / (bitArray.length),
51
72
  mask: bitArray,
@@ -59,3 +80,19 @@ export function getAggregatedValue(col: DG.Column<number>, agg: DG.AggregationTy
59
80
  //@ts-ignore: this is a hack to avoid using switch to access the getters
60
81
  return stat[agg] as number;
61
82
  }
83
+
84
+ export function getAggregatedColumnValues(df: DG.DataFrame, columns: AggregationColumns,
85
+ options: {filterDf?: boolean, mask?: DG.BitSet, fractionDigits?: number} = {}): StringDictionary {
86
+ options.filterDf ??= false;
87
+ options.fractionDigits ??= 3;
88
+
89
+ const filteredDf = options.filterDf && df.filter.anyFalse ? df.clone(df.filter) : df;
90
+
91
+ const colResults: StringDictionary = {};
92
+ for (const [colName, aggFn] of Object.entries(columns)) {
93
+ const newColName = getAggregatedColName(aggFn, colName);
94
+ const value = getAggregatedValue(filteredDf.getCol(colName), aggFn, options.mask);
95
+ colResults[newColName] = value.toFixed(options.fractionDigits);
96
+ }
97
+ return colResults;
98
+ }
@@ -0,0 +1,78 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as DG from 'datagrok-api/dg';
4
+
5
+ import * as type from './types';
6
+ import * as C from '../utils/constants';
7
+
8
+ import {getActivityDistribution, getDistributionLegend, getStatsTableMap} from '../widgets/distribution';
9
+ import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
10
+ import {getMonomerWorksInstance} from '../package';
11
+ import {AggregationColumns, MonomerPositionStats, getAggregatedColumnValues} from './statistics';
12
+
13
+ export type TooltipOptions = {fromViewer?: boolean, isMutationCliffs?: boolean, x: number, y: number,
14
+ monomerPosition: type.SelectionItem, mpStats: MonomerPositionStats};
15
+
16
+ export function showMonomerTooltip(monomer: string, x: number, y: number): boolean {
17
+ const tooltipElements: HTMLDivElement[] = [];
18
+ const monomerName = monomer.toLowerCase();
19
+
20
+ const mw = getMonomerWorksInstance();
21
+ const mol = mw?.getCappedRotatedMonomer('PEPTIDE', monomer);
22
+
23
+ if (mol) {
24
+ tooltipElements.push(ui.div(monomerName));
25
+ const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
26
+ tooltipElements.push(grok.chem.svgMol(mol, undefined, undefined, options));
27
+ } else if (monomer !== '')
28
+ tooltipElements.push(ui.div(monomer));
29
+ else
30
+ return true;
31
+
32
+
33
+ ui.tooltip.show(ui.divV(tooltipElements), x, y);
34
+
35
+ return true;
36
+ }
37
+
38
+ export function showTooltip(df: DG.DataFrame, columns: AggregationColumns, options: TooltipOptions): boolean {
39
+ options.fromViewer ??= false;
40
+ options.isMutationCliffs ??= false;
41
+ if (options.monomerPosition.positionOrClusterType === C.COLUMNS_NAMES.MONOMER)
42
+ showMonomerTooltip(options.monomerPosition.monomerOrCluster, options.x, options.y);
43
+ else
44
+ showTooltipAt(df, columns, options);
45
+ return true;
46
+ }
47
+
48
+ //TODO: move out to viewer code
49
+ export function showTooltipAt(df: DG.DataFrame, columns: AggregationColumns, options: TooltipOptions): HTMLDivElement | null {
50
+ options.fromViewer ??= false;
51
+ options.isMutationCliffs ??= false;
52
+ const stats = options.mpStats[options.monomerPosition.positionOrClusterType]![options.monomerPosition.monomerOrCluster];
53
+ if (!stats?.count)
54
+ return null;
55
+
56
+ const activityCol = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
57
+ const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer, activityCol.length);
58
+ const distributionTable = DG.DataFrame.fromColumns(
59
+ [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
60
+ const hist = getActivityDistribution(prepareTableForHistogram(distributionTable), true);
61
+
62
+ const tableMap = getStatsTableMap(stats);
63
+ if (options.fromViewer) {
64
+ tableMap['Mean difference'] = `${tableMap['Mean difference']}${options.isMutationCliffs ? ' (size)' : ''}`;
65
+ if (tableMap['p-value'])
66
+ tableMap['p-value'] = `${tableMap['p-value']}${options.isMutationCliffs ? ' (color)' : ''}`;
67
+ }
68
+ const aggregatedColMap = getAggregatedColumnValues(df, columns, {mask: mask});
69
+ const resultMap = {...tableMap, ...aggregatedColMap};
70
+
71
+ const labels = getDistributionLegend(
72
+ `${options.monomerPosition.positionOrClusterType} : ${options.monomerPosition.monomerOrCluster}`, 'Other');
73
+ const distroStatsElem = getStatsSummary(labels, hist, resultMap);
74
+
75
+ ui.tooltip.show(distroStatsElem, options.x, options.y);
76
+
77
+ return distroStatsElem;
78
+ }
@@ -50,3 +50,5 @@ export type StatsInfo = {
50
50
  export type RawColumn = {name: string, rawData: RawData, cat?: string[]};
51
51
 
52
52
  export type SelectionOptions = {shiftPressed: boolean, ctrlPressed: boolean};
53
+
54
+ export type CachedWebLogoTooltip = {bar: string, tooltip: HTMLDivElement | null};
@@ -7,7 +7,7 @@ import {CLUSTER_TYPE, ClusterType, PeptidesModel, VIEWER_TYPE} from '../model';
7
7
  import * as C from '../utils/constants';
8
8
  import * as CR from '../utils/cell-renderer';
9
9
  import {HorizontalAlignments, IWebLogoViewer, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
10
- import {getAggregatedValue, getStats, Stats} from '../utils/statistics';
10
+ import {getAggregatedColumnValues, getAggregatedValue, getStats, Stats} from '../utils/statistics';
11
11
  import wu from 'wu';
12
12
  import {getActivityDistribution, getDistributionLegend, getStatsTableMap} from '../widgets/distribution';
13
13
  import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
@@ -319,7 +319,21 @@ export class LogoSummaryTable extends DG.JsViewer {
319
319
  this.currentRowIndex = gridCell.gridRow;
320
320
  }
321
321
  });
322
- this.viewerGrid.root.addEventListener('keydown', (ev) => this.keyPress = ev.key.startsWith('Arrow'));
322
+ this.viewerGrid.root.addEventListener('keydown', (ev) => {
323
+ this.keyPress = ev.key.startsWith('Arrow');
324
+ if (this.keyPress)
325
+ 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);
332
+ }
333
+ }
334
+ this.model.fireBitsetChanged();
335
+ this.viewerGrid.invalidate();
336
+ });
323
337
  this.viewerGrid.root.addEventListener('click', (ev) => {
324
338
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
325
339
  if (!gridCell || !gridCell.isTableCell || gridCell.tableColumn?.name !== C.LST_COLUMN_NAMES.CLUSTER)
@@ -449,7 +463,7 @@ export class LogoSummaryTable extends DG.JsViewer {
449
463
  const activityColData = activityCol.getRawData();
450
464
 
451
465
  if (cluster.positionOrClusterType === CLUSTER_TYPE.ORIGINAL) {
452
- const origClustCol = filteredDf.getCol(C.LST_COLUMN_NAMES.CLUSTER);
466
+ const origClustCol = filteredDf.getCol(this.model.settings.clustersColumnName!);
453
467
  const origClustColData = origClustCol.getRawData();
454
468
  const origClustColCategories = origClustCol.categories;
455
469
  const seekValue = origClustColCategories.indexOf(cluster.monomerOrCluster);
@@ -475,7 +489,7 @@ export class LogoSummaryTable extends DG.JsViewer {
475
489
  const labels = getDistributionLegend(`Cluster: ${cluster.monomerOrCluster}`, 'Other');
476
490
  const hist = getActivityDistribution(distributionTable, true);
477
491
  const tableMap = getStatsTableMap(stats);
478
- const aggregatedColMap = this.model.getAggregatedColumnValues({filterDf: true, mask: mask});
492
+ const aggregatedColMap = getAggregatedColumnValues(this.model.df, this.model.settings.columns!, {filterDf: true, mask: mask});
479
493
  const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
480
494
  const tooltip = getStatsSummary(labels, hist, resultMap);
481
495
 
@@ -5,11 +5,12 @@ import * as DG from 'datagrok-api/dg';
5
5
  import $ from 'cash-dom';
6
6
  import * as C from '../utils/constants';
7
7
  import * as CR from '../utils/cell-renderer';
8
- import {PeptidesModel, PositionStats, VIEWER_TYPE} from '../model';
8
+ import {PeptidesModel, VIEWER_TYPE} from '../model';
9
9
  import wu from 'wu';
10
10
  import {SelectionItem} from '../utils/types';
11
- import {Stats} from '../utils/statistics';
11
+ import {PositionStats, Stats} from '../utils/statistics';
12
12
  import {_package} from '../package';
13
+ import {showTooltip} from '../utils/tooltips';
13
14
 
14
15
  export enum SELECTION_MODE {
15
16
  MUTATION_CLIFFS = 'Mutation Cliffs',
@@ -75,7 +76,7 @@ export class MonomerPosition extends DG.JsViewer {
75
76
 
76
77
  onTableAttached(): void {
77
78
  super.onTableAttached();
78
- this.helpUrl = '/help/domains/bio/peptides.md';
79
+ this.helpUrl = 'https://datagrok.ai/help/datagrok/solutions/domains/bio/peptides-sar';
79
80
  this.render();
80
81
  }
81
82
 
@@ -125,7 +126,9 @@ export class MonomerPosition extends DG.JsViewer {
125
126
  }
126
127
  const monomerPosition = this.getMonomerPosition(gridCell);
127
128
  this.model.highlightMonomerPosition(monomerPosition);
128
- return this.model.showTooltip(monomerPosition, x, y, true);
129
+ return showTooltip(this.model.df, this.model.settings.columns!, {fromViewer: true,
130
+ isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
131
+ mpStats: this.model.monomerPositionStats});
129
132
  });
130
133
  this.viewerGrid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
131
134
  DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
@@ -151,7 +154,31 @@ export class MonomerPosition extends DG.JsViewer {
151
154
  this.currentGridCell = gridCell;
152
155
  }
153
156
  });
154
- this.viewerGrid.root.addEventListener('keydown', (ev) => this.keyPressed = ev.key.startsWith('Arrow'));
157
+ this.viewerGrid.root.addEventListener('keydown', (ev) => {
158
+ this.keyPressed = ev.key.startsWith('Arrow');
159
+ if (this.keyPressed)
160
+ return;
161
+ if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.ctrlKey && ev.shiftKey)) {
162
+ if (this.mode === SELECTION_MODE.INVARIANT_MAP)
163
+ this.model.initInvariantMapSelection({notify: false});
164
+ else
165
+ this.model.initMutationCliffsSelection({notify: false});
166
+ } else if (ev.code === 'KeyA' && ev.ctrlKey) {
167
+ const positions = Object.keys(this.model.monomerPositionStats).filter((pos) => pos !== 'general');
168
+ for (const position of positions) {
169
+ const monomers = Object.keys(this.model.monomerPositionStats[position]!).filter((monomer) => monomer !== 'general');
170
+ for (const monomer of monomers) {
171
+ const monomerPosition = {monomerOrCluster: monomer, positionOrClusterType: position};
172
+ if (this.mode === SELECTION_MODE.INVARIANT_MAP)
173
+ this.model.modifyInvariantMapSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, false);
174
+ else
175
+ this.model.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, false);
176
+ }
177
+ }
178
+ }
179
+ this.model.fireBitsetChanged();
180
+ this.viewerGrid.invalidate();
181
+ });
155
182
  this.viewerGrid.root.addEventListener('click', (ev) => {
156
183
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
157
184
  if (!gridCell?.isTableCell || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.MONOMER)
@@ -257,7 +284,7 @@ export class MostPotentResidues extends DG.JsViewer {
257
284
 
258
285
  onTableAttached(): void {
259
286
  super.onTableAttached();
260
- this.helpUrl = '/help/domains/bio/peptides.md';
287
+ this.helpUrl = 'https://datagrok.ai/help/datagrok/solutions/domains/bio/peptides-sar';
261
288
  this.render();
262
289
  }
263
290
 
@@ -274,6 +301,7 @@ export class MostPotentResidues extends DG.JsViewer {
274
301
  const pValData: (number | null)[] = new Array(posData.length);
275
302
  const countData: number[] = new Array(posData.length);
276
303
  const ratioData: number[] = new Array(posData.length);
304
+ const meanData: number[] = new Array(posData.length);
277
305
 
278
306
  let i = 0;
279
307
  for (const [position, positionStats] of monomerPositionStatsEntries) {
@@ -312,6 +340,7 @@ export class MostPotentResidues extends DG.JsViewer {
312
340
  pValData[i] = maxEntry![1].pValue;
313
341
  countData[i] = maxEntry![1].count;
314
342
  ratioData[i] = maxEntry![1].ratio;
343
+ meanData[i] = maxEntry![1].mean;
315
344
  ++i;
316
345
  }
317
346
 
@@ -327,6 +356,7 @@ export class MostPotentResidues extends DG.JsViewer {
327
356
  mprDfCols.add(DG.Column.fromList(DG.TYPE.INT, C.COLUMNS_NAMES.POSITION, posData));
328
357
  mprDfCols.add(DG.Column.fromList(DG.TYPE.STRING, C.COLUMNS_NAMES.MONOMER, monomerData));
329
358
  mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.MEAN_DIFFERENCE, mdData));
359
+ mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.MEAN, meanData));
330
360
  mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.P_VALUE, pValData));
331
361
  mprDfCols.add(DG.Column.fromList(DG.TYPE.INT, C.COLUMNS_NAMES.COUNT, countData));
332
362
  mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.RATIO, ratioData));
@@ -359,7 +389,8 @@ export class MostPotentResidues extends DG.JsViewer {
359
389
  monomerPosition.positionOrClusterType = C.COLUMNS_NAMES.MONOMER;
360
390
  else if (gridCell.tableColumn?.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE)
361
391
  return false;
362
- return this.model.showTooltip(monomerPosition, x, y, true);
392
+ return showTooltip(this.model.df, this.model.settings.columns!,
393
+ {fromViewer: true, isMutationCliffs: true, monomerPosition, x, y, mpStats: this.model.monomerPositionStats});
363
394
  });
364
395
  DG.debounce(this.viewerGrid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
365
396
  try {
@@ -378,7 +409,21 @@ export class MostPotentResidues extends DG.JsViewer {
378
409
  this.currentGridRowIdx = gridCell.gridRow;
379
410
  }
380
411
  });
381
- this.viewerGrid.root.addEventListener('keydown', (ev) => this.keyPressed = ev.key.startsWith('Arrow'));
412
+ this.viewerGrid.root.addEventListener('keydown', (ev) => {
413
+ this.keyPressed = ev.key.startsWith('Arrow');
414
+ if (this.keyPressed)
415
+ return;
416
+ if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.ctrlKey && ev.shiftKey))
417
+ this.model.initMutationCliffsSelection({notify: false});
418
+ else if (ev.code === 'KeyA' && ev.ctrlKey) {
419
+ for (let rowIdx = 0; rowIdx < mostPotentResiduesDf.rowCount; ++rowIdx) {
420
+ const monomerPosition = this.getMonomerPosition(this.viewerGrid.cell('Diff', rowIdx));
421
+ this.model.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, false);
422
+ }
423
+ }
424
+ this.model.fireBitsetChanged();
425
+ this.viewerGrid.invalidate();
426
+ });
382
427
  this.viewerGrid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
383
428
  this.viewerGrid.root.addEventListener('click', (ev) => {
384
429
  const gridCell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
@@ -5,7 +5,7 @@ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations'
5
5
  import $ from 'cash-dom';
6
6
 
7
7
  import * as C from '../utils/constants';
8
- import {getStats, Stats} from '../utils/statistics';
8
+ import {getAggregatedColumnValues, getStats, Stats} from '../utils/statistics';
9
9
  import {PeptidesModel} from '../model';
10
10
  import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
11
11
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
@@ -57,7 +57,7 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
57
57
  const stats = model.monomerPositionStats[position]![monomer]!;
58
58
  const tableMap = getStatsTableMap(stats);
59
59
 
60
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
60
+ const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
61
61
 
62
62
  const resultMap = {...tableMap, ...aggregatedColMap};
63
63
  const distributionRoot = getStatsSummary(labels, hist, resultMap);
@@ -83,7 +83,7 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
83
83
  const mask = DG.BitSet.create(rowCount, (i) => monomerIndexesList.includes(posColData[i]));
84
84
  const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
85
85
 
86
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
86
+ const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
87
87
 
88
88
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
89
89
  const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
@@ -126,7 +126,7 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
126
126
 
127
127
  const mask = DG.BitSet.create(rowCount,
128
128
  (i) => posColDataList.some((posColData, j) => posColData[i] === monomerCategoryIndexList[j]));
129
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
129
+ const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
130
130
 
131
131
  const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
132
132
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
@@ -166,8 +166,10 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
166
166
  const bitArray = BitArray.fromString(table.selection.toBinaryString());
167
167
  const mask = DG.BitSet.create(rowCount,
168
168
  bitArray.allFalse || bitArray.allTrue ? (_): boolean => true : (i): boolean => bitArray.getBit(i));
169
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
170
- const stats = bitArray.allFalse || bitArray.allTrue ? {count: rowCount, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray} :
169
+ const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
170
+ const stats = bitArray.allFalse || bitArray.allTrue ?
171
+ {count: rowCount, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray,
172
+ mean: activityCol.stats.avg} :
171
173
  getStats(activityColData, bitArray);
172
174
  const tableMap = getStatsTableMap(stats);
173
175
  const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
@@ -231,6 +233,7 @@ export function getStatsTableMap(stats: Stats, options: {fractionDigits?: number
231
233
  const tableMap: StringDictionary = {
232
234
  'Count': `${stats.count} (${stats.ratio.toFixed(options.fractionDigits)}%)`,
233
235
  'Mean difference': stats.meanDifference.toFixed(options.fractionDigits),
236
+ 'Mean activity': stats.mean.toFixed(options.fractionDigits),
234
237
  };
235
238
  if (stats.pValue !== null)
236
239
  tableMap['p-value'] = stats.pValue < 0.01 ? '<0.01' : stats.pValue.toFixed(options.fractionDigits);
@@ -6,6 +6,10 @@ import {PeptidesModel} from '../model';
6
6
  import wu from 'wu';
7
7
  import {COLUMNS_NAMES} from '../utils/constants';
8
8
  import {addExpandIcon} from '../utils/misc';
9
+ import {CellRendererOptions, WebLogoBounds, setWebLogoRenderer} from '../utils/cell-renderer';
10
+ import {CachedWebLogoTooltip, SelectionItem} from '../utils/types';
11
+ import {TooltipOptions} from '../utils/tooltips';
12
+ import {calculateMonomerPositionStatistics} from '../utils/algorithms';
9
13
 
10
14
  export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): HTMLElement {
11
15
  const compBitset = model.getCompoundBitset();
@@ -58,5 +62,15 @@ export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): H
58
62
  grid.col(gridCol.name)!.width = gridCol.width;
59
63
  }
60
64
  }, 500);
65
+
66
+ const mpStats = calculateMonomerPositionStatistics(grid.dataFrame, model.positionColumns.toArray());
67
+
68
+ const cachedWebLogoTooltip: CachedWebLogoTooltip = {bar: '', tooltip: null};
69
+ const webLogoBounds: WebLogoBounds = {};
70
+ const cellRendererOptions: CellRendererOptions = {isSelectionTable: true, cachedWebLogoTooltip, webLogoBounds};
71
+ const tooltipOptions: TooltipOptions = {x: 0, y: 0, monomerPosition: {} as SelectionItem, mpStats};
72
+
73
+ setWebLogoRenderer(grid, model, cellRendererOptions, tooltipOptions);
74
+
61
75
  return gridHost;
62
76
  }