@datagrok/peptides 0.8.7 → 0.8.10

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.
package/src/model.ts CHANGED
@@ -1,104 +1,582 @@
1
+ import * as ui from 'datagrok-api/ui';
1
2
  import * as DG from 'datagrok-api/dg';
2
3
 
3
- import {describe} from './describe';
4
- import {Subject} from 'rxjs';
4
+ import {Subject, Observable} from 'rxjs';
5
5
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
+ import {addViewerToHeader, StackedBarChart} from './viewers/stacked-barchart-viewer';
7
+ import {PeptidesController} from './peptides';
8
+ import {tTest} from '@datagrok-libraries/statistics/src/tests';
9
+ import {fdrcorrection} from '@datagrok-libraries/statistics/src/multiple-tests';
10
+ import {ChemPalette} from './utils/chem-palette';
11
+ import {MonomerLibrary} from './monomer-library';
6
12
 
7
- /**
8
- * Model class for SAR viewers that retrieves and stores data.
9
- *
10
- * @class SARViewerModel
11
- */
12
- class SARViewerModel {
13
- private viewerGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
14
- private viewerVGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
15
- private statsDf: Subject<DG.DataFrame> = new Subject<DG.DataFrame>();
16
- private groupMapping: Subject<StringDictionary> = new Subject<StringDictionary>();
17
- public viewerGrid$;
18
- public viewerVGrid$;
19
- public statsDf$;
20
- public groupMapping$;
21
- private dataFrame: DG.DataFrame | null;
22
- private activityColumn: string | null;
23
- private activityScaling: string | null;
24
- private sourceGrid: DG.Grid | null;
25
- private twoColorMode: boolean | null;
26
- private initialBitset: DG.BitSet | null;
27
- private isUpdating = false;
28
- grouping: boolean;
29
-
30
- /**
31
- * Creates an instance of SARViewerModel.
32
- *
33
- * @memberof SARViewerModel
34
- */
35
- constructor() {
36
- this.dataFrame = null;
37
- this.activityColumn = null;
38
- this.activityScaling = null;
39
- this.sourceGrid = null;
40
- this.twoColorMode = null;
41
- this.initialBitset = null;
42
- this.grouping = false;
43
- this.viewerGrid$ = this.viewerGrid.asObservable();
44
- this.viewerVGrid$ = this.viewerVGrid.asObservable();
45
- this.statsDf$ = this.statsDf.asObservable();
46
- this.groupMapping$ = this.groupMapping.asObservable();
47
- }
48
-
49
- /**
50
- * Updates data with using specified parameters.
51
- *
52
- * @param {DG.DataFrame} df Working table.
53
- * @param {string} activityCol Activity column name.
54
- * @param {string} activityScaling Activity scaling method.
55
- * @param {DG.Grid} sourceGrid Working table grid.
56
- * @param {boolean} twoColorMode Bidirectional analysis enabled.
57
- * @param {(DG.BitSet | null)} initialBitset Initial bitset.
58
- * @param {boolean} grouping Grouping enabled.
59
- * @memberof SARViewerModel
60
- */
61
- async updateData(
62
- df: DG.DataFrame,
63
- activityCol: string,
64
- activityScaling: string,
65
- sourceGrid: DG.Grid,
66
- twoColorMode: boolean,
67
- initialBitset: DG.BitSet | null,
68
- grouping: boolean,
69
- ) {
70
- this.dataFrame = df;
71
- this.activityColumn = activityCol;
72
- this.activityScaling = activityScaling;
73
- this.sourceGrid = sourceGrid;
74
- this.twoColorMode = twoColorMode;
75
- this.initialBitset = initialBitset;
76
- this.grouping = grouping;
13
+
14
+ export class PeptidesModel {
15
+ private _dataFrame: DG.DataFrame;
16
+ private _activityColumn: string | null;
17
+ private _activityScaling: string | null;
18
+ private _sourceGrid: DG.Grid | null;
19
+ private _twoColorMode: boolean | null;
20
+ private _initialBitset: DG.BitSet | null;
21
+ private _grouping: boolean = false;
22
+ private _isUpdating: boolean = false;
23
+ private _substFlag = false;
24
+ private _statsDataFrameSubject = new Subject<DG.DataFrame>();
25
+ private _sarGridSubject = new Subject<DG.Grid>();
26
+ private _sarVGridSubject = new Subject<DG.Grid>();
27
+ private _groupMappingSubject = new Subject<StringDictionary>();
28
+ private _substFlagSubject = new Subject<boolean>();
29
+ private static _modelName = 'peptidesModel';
30
+
31
+ private constructor(dataFrame: DG.DataFrame) {
32
+ this._dataFrame = dataFrame;
33
+ this._activityColumn = null;
34
+ this._activityScaling = null;
35
+ this._sourceGrid = null;
36
+ this._twoColorMode = null;
37
+ this._initialBitset = null;
38
+ }
39
+
40
+ static getInstance(dataFrame: DG.DataFrame): PeptidesModel {
41
+ dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
42
+ return dataFrame.temp[PeptidesModel.modelName];
43
+ }
44
+
45
+ get dataFrame(): DG.DataFrame {
46
+ return this._dataFrame;
47
+ }
48
+
49
+ get onStatsDataFrameChanged(): Observable<DG.DataFrame> {
50
+ return this._statsDataFrameSubject.asObservable();
51
+ }
52
+
53
+ get onSARGridChanged(): Observable<DG.Grid> {
54
+ return this._sarGridSubject.asObservable();
55
+ }
56
+
57
+ get onSARVGridChanged(): Observable<DG.Grid> {
58
+ return this._sarVGridSubject.asObservable();
59
+ }
60
+
61
+ get onGroupMappingChanged(): Observable<StringDictionary> {
62
+ return this._groupMappingSubject.asObservable();
63
+ }
64
+
65
+ get onSubstFlagChanged(): Observable<boolean> {
66
+ return this._substFlagSubject.asObservable();
67
+ }
68
+
69
+ async updateData(activityCol: string | null, activityScaling: string | null, sourceGrid: DG.Grid | null,
70
+ twoColorMode: boolean | null, initialBitset: DG.BitSet | null, grouping: boolean | null) {
71
+ this._activityColumn = activityCol ?? this._activityColumn;
72
+ this._activityScaling = activityScaling ?? this._activityScaling;
73
+ this._sourceGrid = sourceGrid ?? this._sourceGrid;
74
+ this._twoColorMode = twoColorMode ?? this._twoColorMode;
75
+ this._initialBitset = initialBitset ?? this._initialBitset;
76
+ this._grouping = grouping ?? this._grouping;
77
77
  await this.updateDefault();
78
78
  }
79
79
 
80
- /**
81
- * Update data using current parameters.
82
- *
83
- * @memberof SARViewerModel
84
- */
85
80
  async updateDefault() {
86
81
  if (
87
- this.dataFrame && this.activityColumn && this.activityScaling &&
88
- this.sourceGrid && this.twoColorMode !== null && !this.isUpdating
82
+ this._activityColumn && this._activityScaling && this._sourceGrid && this._twoColorMode !== null &&
83
+ !this._isUpdating
89
84
  ) {
90
- this.isUpdating = true;
91
- const [viewerGrid, viewerVGrid, statsDf, groupMapping] = await describe(
92
- this.dataFrame, this.activityColumn, this.activityScaling,
93
- this.sourceGrid, this.twoColorMode, this.initialBitset, this.grouping,
94
- );
95
- this.viewerGrid.next(viewerGrid);
96
- this.viewerVGrid.next(viewerVGrid);
97
- this.statsDf.next(statsDf);
98
- this.groupMapping.next(groupMapping);
99
- this.isUpdating = false;
85
+ this._isUpdating = true;
86
+ const [viewerGrid, viewerVGrid, statsDf, groupMapping] = await this.describe(
87
+ this._activityColumn, this._activityScaling, this._twoColorMode, this._grouping);
88
+ this._statsDataFrameSubject.next(statsDf);
89
+ this._groupMappingSubject.next(groupMapping);
90
+ this._sarGridSubject.next(viewerGrid);
91
+ this._sarVGridSubject.next(viewerVGrid);
92
+ this._substFlag = !this._substFlag;
93
+ this._substFlagSubject.next(this._substFlag);
94
+
95
+ this._sourceGrid.invalidate();
96
+
97
+ this._isUpdating = false;
98
+ }
99
+
100
+ await this.updateBarchart();
101
+ }
102
+
103
+ async updateBarchart() {
104
+ const stackedBarchart = await this._dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
105
+ if (stackedBarchart && this._sourceGrid)
106
+ addViewerToHeader(this._sourceGrid, stackedBarchart);
107
+ }
108
+
109
+ static get modelName() {
110
+ return PeptidesModel._modelName;
111
+ }
112
+
113
+ async describe(
114
+ activityColumn: string, activityScaling: string, twoColorMode: boolean, grouping: boolean,
115
+ ): Promise<[DG.Grid, DG.Grid, DG.DataFrame, StringDictionary]> {
116
+ if (this._sourceGrid === null)
117
+ throw new Error(`Source grid is not initialized`);
118
+
119
+ //Split the aligned sequence into separate AARs
120
+ let splitSeqDf: DG.DataFrame | undefined;
121
+ let invalidIndexes: number[];
122
+ const col: DG.Column = (this._dataFrame.columns as DG.ColumnList).bySemType('alignedSequence')!;
123
+ [splitSeqDf, invalidIndexes] = PeptidesController.splitAlignedPeptides(col);
124
+ splitSeqDf.name = 'Split sequence';
125
+
126
+ const positionColumns = (splitSeqDf.columns as DG.ColumnList).names();
127
+ const activityColumnScaled = `${activityColumn}Scaled`;
128
+ const renderColNames: string[] = (splitSeqDf.columns as DG.ColumnList).names();
129
+ const positionColName = 'Pos';
130
+ const aminoAcidResidue = 'AAR';
131
+
132
+ (splitSeqDf.columns as DG.ColumnList).add(this._dataFrame.getCol(activityColumn));
133
+
134
+ joinDataFrames(this._dataFrame, positionColumns, splitSeqDf, activityColumn);
135
+
136
+ for (const dfCol of (this._dataFrame.columns as DG.ColumnList)) {
137
+ if (splitSeqDf.col(dfCol.name) && dfCol.name != activityColumn)
138
+ PeptidesController.setAARRenderer(dfCol, this._sourceGrid);
100
139
  }
140
+
141
+ sortSourceGrid(this._sourceGrid);
142
+
143
+ const [scaledDf, newColName] = await PeptidesController.scaleActivity(
144
+ activityScaling, activityColumn, activityColumnScaled, this._dataFrame);
145
+ //TODO: make another func
146
+ const scaledCol = scaledDf.getCol(activityColumnScaled);
147
+ const oldScaledCol = this._dataFrame.getCol(activityColumnScaled);
148
+ const oldScaledColGridName = oldScaledCol.temp['gridName'];
149
+ const oldScaledGridCol = this._sourceGrid.col(oldScaledColGridName);
150
+
151
+ (splitSeqDf.columns as DG.ColumnList).add(scaledCol);
152
+ (this._dataFrame.columns as DG.ColumnList).replace(oldScaledCol, scaledCol);
153
+ if (newColName === activityColumn)
154
+ this._sourceGrid.col(activityColumn)!.name = `~${activityColumn}`;
155
+ if (oldScaledGridCol !== null) {
156
+ oldScaledGridCol.name = newColName;
157
+ oldScaledGridCol.visible = true;
158
+ }
159
+ this._sourceGrid.columns.setOrder([newColName]);
160
+
161
+ splitSeqDf = splitSeqDf.clone(this._initialBitset);
162
+
163
+ //unpivot a table and handle duplicates
164
+ splitSeqDf = splitSeqDf.groupBy(positionColumns)
165
+ .add('med', activityColumnScaled, activityColumnScaled)
166
+ .aggregate();
167
+
168
+ const peptidesCount = splitSeqDf.getCol(activityColumnScaled).length;
169
+
170
+ let matrixDf = splitSeqDf.unpivot([activityColumnScaled], positionColumns, positionColName, aminoAcidResidue);
171
+
172
+ //TODO: move to chem palette
173
+ let groupMapping: StringDictionary = {};
174
+ if (grouping) {
175
+ groupMapping = aarGroups;
176
+ const aarCol = matrixDf.getCol(aminoAcidResidue);
177
+ aarCol.init((index) => groupMapping[aarCol.get(index)[0]] ?? '-');
178
+ aarCol.compact();
179
+ } else
180
+ Object.keys(aarGroups).forEach((value) => groupMapping[value] = value);
181
+
182
+
183
+ //statistics for specific AAR at a specific position
184
+ const statsDf = await calculateStatistics(
185
+ matrixDf, positionColName, aminoAcidResidue, activityColumnScaled, peptidesCount, splitSeqDf, groupMapping,
186
+ );
187
+
188
+ // SAR matrix table
189
+ //pivot a table to make it matrix-like
190
+ matrixDf = statsDf.groupBy([aminoAcidResidue])
191
+ .pivot(positionColName)
192
+ .add('first', 'Mean difference', '')
193
+ .aggregate();
194
+ matrixDf.name = 'SAR';
195
+
196
+ // Setting category order
197
+ await setCategoryOrder(twoColorMode, statsDf, aminoAcidResidue, matrixDf);
198
+
199
+ // SAR vertical table (naive, choose best Mean difference from pVals <= 0.01)
200
+ const sequenceDf = createVerticalTable(statsDf, aminoAcidResidue, positionColName, twoColorMode);
201
+ renderColNames.push('Mean difference');
202
+
203
+ const [sarGrid, sarVGrid] = createGrids(
204
+ matrixDf, aminoAcidResidue, positionColumns, sequenceDf, positionColName, grouping,
205
+ );
206
+
207
+ setCellRendererFunc(
208
+ renderColNames, positionColName, aminoAcidResidue, statsDf, twoColorMode, sarGrid, sarVGrid,
209
+ );
210
+
211
+ // show all the statistics in a tooltip over cell
212
+ setTooltipFunc(
213
+ renderColNames, statsDf, aminoAcidResidue, positionColName, peptidesCount, grouping, sarGrid, sarVGrid,
214
+ this._dataFrame,
215
+ );
216
+
217
+ postProcessGrids(this._sourceGrid, invalidIndexes, grouping, aminoAcidResidue, sarGrid, sarVGrid);
218
+
219
+ //TODO: return class instead
220
+ return [sarGrid, sarVGrid, statsDf, groupMapping];
221
+ }
222
+ }
223
+
224
+ export const aarGroups = {
225
+ 'R': 'PC',
226
+ 'H': 'PC',
227
+ 'K': 'PC',
228
+ 'D': 'NC',
229
+ 'E': 'NC',
230
+ 'S': 'U',
231
+ 'T': 'U',
232
+ 'N': 'U',
233
+ 'Q': 'U',
234
+ 'C': 'SC',
235
+ 'U': 'SC',
236
+ 'G': 'SC',
237
+ 'P': 'SC',
238
+ 'A': 'H',
239
+ 'V': 'H',
240
+ 'I': 'H',
241
+ 'L': 'H',
242
+ 'M': 'H',
243
+ 'F': 'H',
244
+ 'Y': 'H',
245
+ 'W': 'H',
246
+ '-': '-',
247
+ };
248
+
249
+ const groupDescription: {[key: string]: {'description': string, 'aminoAcids': string[]}} = {
250
+ 'PC': {'description': 'Positive Amino Acids, with Electrically Charged Side Chains', 'aminoAcids': ['R', 'H', 'K']},
251
+ 'NC': {'description': 'Negative Amino Acids, with Electrically Charged Side Chains', 'aminoAcids': ['D', 'E']},
252
+ 'U': {'description': 'Amino Acids with Polar Uncharged Side Chains', 'aminoAcids': ['S', 'T', 'N', 'Q']},
253
+ 'SC': {'description': 'Special Cases', 'aminoAcids': ['C', 'U', 'G', 'P']},
254
+ 'H': {
255
+ 'description': 'Amino Acids with Hydrophobic Side Chain',
256
+ 'aminoAcids': ['A', 'V', 'I', 'L', 'M', 'F', 'Y', 'W'],
257
+ },
258
+ '-': {'description': 'Unknown Amino Acid', 'aminoAcids': ['-']},
259
+ };
260
+
261
+ function joinDataFrames(df: DG.DataFrame, positionColumns: string[], splitSeqDf: DG.DataFrame, activityColumn: string) {
262
+ // if (df.col(activityColumnScaled))
263
+ // (df.columns as DG.ColumnList).remove(activityColumnScaled);
264
+
265
+
266
+ //FIXME: this column usually duplicates, so remove it then
267
+ // if (df.col(`${activityColumnScaled} (2)`))
268
+ // (df.columns as DG.ColumnList).remove(`${activityColumnScaled} (2)`);
269
+
270
+
271
+ // append splitSeqDf columns to source table and make sure columns are not added more than once
272
+ const dfColsSet = new Set((df.columns as DG.ColumnList).names());
273
+ if (!positionColumns.every((col: string) => dfColsSet.has(col))) {
274
+ df.join(
275
+ splitSeqDf, [activityColumn], [activityColumn], (df.columns as DG.ColumnList).names(), positionColumns, 'inner',
276
+ true);
277
+ }
278
+ }
279
+
280
+ function sortSourceGrid(sourceGrid: DG.Grid) {
281
+ if (sourceGrid) {
282
+ const colNames: DG.GridColumn[] = [];
283
+ for (let i = 1; i < sourceGrid.columns.length; i++)
284
+ colNames.push(sourceGrid.columns.byIndex(i)!);
285
+
286
+ colNames.sort((a, b)=>{
287
+ if (a.column!.semType == 'aminoAcids') {
288
+ if (b.column!.semType == 'aminoAcids')
289
+ return 0;
290
+ return -1;
291
+ }
292
+ if (b.column!.semType == 'aminoAcids')
293
+ return 1;
294
+ return 0;
295
+ });
296
+ sourceGrid.columns.setOrder(colNames.map((v) => v.name));
101
297
  }
102
298
  }
103
299
 
104
- export const model = new SARViewerModel();
300
+ async function calculateStatistics(
301
+ matrixDf: DG.DataFrame, positionColName: string, aminoAcidResidue: string, activityColumnScaled: string,
302
+ peptidesCount: number, splitSeqDf: DG.DataFrame, groupMapping: StringDictionary,
303
+ ) {
304
+ matrixDf = matrixDf.groupBy([positionColName, aminoAcidResidue])
305
+ .add('count', activityColumnScaled, 'Count')
306
+ .aggregate();
307
+
308
+ const countThreshold = 4;
309
+ //@ts-ignore: never gets old
310
+ matrixDf.rows.filter((row) => row.Count >= countThreshold && row.Count <= peptidesCount - countThreshold);
311
+ matrixDf = matrixDf.clone(matrixDf.filter);
312
+
313
+ // calculate additional stats
314
+ await (matrixDf.columns as DG.ColumnList).addNewCalculated('Ratio', '${count}/'.concat(`${peptidesCount}`));
315
+
316
+ //calculate p-values based on t-test
317
+ let pvalues: Float32Array = new Float32Array(matrixDf.rowCount).fill(1);
318
+ const mdCol: DG.Column = (matrixDf.columns as DG.ColumnList).addNewFloat('Mean difference');
319
+ const pValCol: DG.Column = (matrixDf.columns as DG.ColumnList).addNewFloat('pValue');
320
+ for (let i = 0; i < matrixDf.rowCount; i++) {
321
+ const position = matrixDf.get(positionColName, i);
322
+ const aar = matrixDf.get(aminoAcidResidue, i);
323
+
324
+ //@ts-ignore
325
+ splitSeqDf.rows.select((row) => groupMapping[row[position]] === aar);
326
+ const currentActivity: number[] = splitSeqDf
327
+ .clone(splitSeqDf.selection, [activityColumnScaled])
328
+ .getCol(activityColumnScaled)
329
+ .toList();
330
+
331
+ //@ts-ignore
332
+ splitSeqDf.rows.select((row) => groupMapping[row[position]] !== aar);
333
+ const otherActivity: number[] = splitSeqDf
334
+ .clone(splitSeqDf.selection, [activityColumnScaled])
335
+ .getCol(activityColumnScaled)
336
+ .toList();
337
+
338
+ const testResult = tTest(currentActivity, otherActivity);
339
+ // testResult = uTest(currentActivity, otherActivity);
340
+ const currentMeanDiff = testResult['Mean difference']!;
341
+ const pvalue = testResult[currentMeanDiff >= 0 ? 'p-value more' : 'p-value less'];
342
+
343
+ mdCol.set(i, currentMeanDiff);
344
+ pvalues[i] = pvalue;
345
+ }
346
+
347
+ pvalues = fdrcorrection(pvalues)[1];
348
+
349
+ for (let i = 0; i < pvalues.length; ++i)
350
+ pValCol.set(i, pvalues[i]);
351
+
352
+ return matrixDf.clone();
353
+ }
354
+
355
+ async function setCategoryOrder(
356
+ twoColorMode: boolean, statsDf: DG.DataFrame, aminoAcidResidue: string, matrixDf: DG.DataFrame,
357
+ ) {
358
+ const sortArgument = twoColorMode ? 'Absolute Mean difference' : 'Mean difference';
359
+ if (twoColorMode)
360
+ await (statsDf.columns as DG.ColumnList).addNewCalculated('Absolute Mean difference', 'Abs(${Mean difference})');
361
+
362
+ const aarWeightsDf = statsDf.groupBy([aminoAcidResidue]).sum(sortArgument, 'weight').aggregate();
363
+ const aarList = aarWeightsDf.getCol(aminoAcidResidue).toList();
364
+ const getWeight = (aar: string) => aarWeightsDf
365
+ .groupBy(['weight'])
366
+ .where(`${aminoAcidResidue} = ${aar}`)
367
+ .aggregate()
368
+ .get('weight', 0);
369
+ aarList.sort((first, second) => getWeight(second) - getWeight(first));
370
+
371
+ matrixDf.getCol(aminoAcidResidue).setCategoryOrder(aarList);
372
+ }
373
+
374
+ function createVerticalTable(
375
+ statsDf: DG.DataFrame, aminoAcidResidue: string, positionColName: string, twoColorMode: boolean,
376
+ ) {
377
+ // TODO: aquire ALL of the positions
378
+ let sequenceDf = statsDf.groupBy(['Mean difference', aminoAcidResidue, positionColName, 'Count', 'Ratio', 'pValue'])
379
+ .where('pValue <= 0.1')
380
+ .aggregate();
381
+
382
+ let tempStats: DG.Stats;
383
+ const maxAtPos: {[index: string]: number} = {};
384
+ for (const pos of sequenceDf.getCol(positionColName).categories) {
385
+ tempStats = DG.Stats.fromColumn(
386
+ sequenceDf.getCol('Mean difference'),
387
+ DG.BitSet.create(sequenceDf.rowCount, (i) => sequenceDf.get(positionColName, i) === pos),
388
+ );
389
+ maxAtPos[pos] = twoColorMode ?
390
+ (tempStats.max > Math.abs(tempStats.min) ? tempStats.max : tempStats.min) : tempStats.max;
391
+ }
392
+ sequenceDf = sequenceDf.clone(DG.BitSet.create(sequenceDf.rowCount, (i) => {
393
+ return sequenceDf.get('Mean difference', i) === maxAtPos[sequenceDf.get(positionColName, i)];
394
+ }));
395
+
396
+ return sequenceDf;
397
+ }
398
+
399
+ function createGrids(
400
+ matrixDf: DG.DataFrame, aminoAcidResidue: string, positionColumns: string[], sequenceDf: DG.DataFrame,
401
+ positionColName: string, grouping: boolean,
402
+ ) {
403
+ const sarGrid = matrixDf.plot.grid();
404
+ sarGrid.sort([aminoAcidResidue]);
405
+ sarGrid.columns.setOrder([aminoAcidResidue].concat(positionColumns));
406
+
407
+ const sarVGrid = sequenceDf.plot.grid();
408
+ sarVGrid.sort([positionColName]);
409
+ sarVGrid.col('pValue')!.format = 'four digits after comma';
410
+ sarVGrid.col('pValue')!.name = 'P-Value';
411
+
412
+ if (!grouping) {
413
+ let tempCol = (matrixDf.columns as DG.ColumnList).byName(aminoAcidResidue);
414
+ if (tempCol)
415
+ PeptidesController.setAARRenderer(tempCol, sarGrid);
416
+
417
+ tempCol = (sequenceDf.columns as DG.ColumnList).byName(aminoAcidResidue);
418
+ if (tempCol)
419
+ PeptidesController.setAARRenderer(tempCol, sarGrid);
420
+ }
421
+
422
+ return [sarGrid, sarVGrid];
423
+ }
424
+
425
+ function setCellRendererFunc(
426
+ renderColNames: string[], positionColName: string, aminoAcidResidue: string, statsDf: DG.DataFrame,
427
+ twoColorMode: boolean, sarGrid: DG.Grid, sarVGrid: DG.Grid,
428
+ ) {
429
+ const mdCol = statsDf.getCol('Mean difference');
430
+ const cellRendererFunc = function(args: DG.GridCellRenderArgs) {
431
+ args.g.save();
432
+ args.g.beginPath();
433
+ args.g.rect(args.bounds.x, args.bounds.y, args.bounds.width, args.bounds.height);
434
+ args.g.clip();
435
+
436
+ if (args.cell.isRowHeader && args.cell.gridColumn.visible) {
437
+ args.cell.gridColumn.visible = false;
438
+ args.preventDefault();
439
+ return;
440
+ }
441
+
442
+ if (
443
+ args.cell.isTableCell &&
444
+ args.cell.tableRowIndex !== null &&
445
+ args.cell.tableColumn !== null &&
446
+ args.cell.cell.value !== null
447
+ ) {
448
+ if (renderColNames.indexOf(args.cell.tableColumn.name) !== -1) {
449
+ const currentPosition = args.cell.tableColumn.name !== 'Mean difference' ?
450
+ args.cell.tableColumn.name : args.cell.grid.table.get(positionColName, args.cell.tableRowIndex);
451
+ const query =
452
+ `${aminoAcidResidue} = ${args.cell.grid.table.get(aminoAcidResidue, args.cell.tableRowIndex)} ` +
453
+ `and ${positionColName} = ${currentPosition}`;
454
+
455
+ const pVal: number = statsDf.groupBy(['pValue']).where(query).aggregate().get('pValue', 0);
456
+
457
+ let coef;
458
+ const variant = args.cell.cell.value < 0;
459
+ if (pVal < 0.01)
460
+ coef = variant && twoColorMode ? '#FF7900' : '#299617';
461
+ else if (pVal < 0.05)
462
+ coef = variant && twoColorMode ? '#FFA500' : '#32CD32';
463
+ else if (pVal < 0.1)
464
+ coef = variant && twoColorMode ? '#FBCEB1' : '#98FF98';
465
+ else
466
+ coef = DG.Color.toHtml(DG.Color.lightLightGray);
467
+
468
+
469
+ const chooseMin = () => twoColorMode ? 0 : mdCol.min;
470
+ const chooseMax = () => twoColorMode ? Math.max(Math.abs(mdCol.min), mdCol.max) : mdCol.max;
471
+ const chooseCurrent = () => twoColorMode ? Math.abs(args.cell.cell.value) : args.cell.cell.value;
472
+
473
+ const rCoef = (chooseCurrent() - chooseMin()) / (chooseMax() - chooseMin());
474
+
475
+ const maxRadius = 0.9 * (args.bounds.width > args.bounds.height ? args.bounds.height : args.bounds.width) / 2;
476
+ const radius = Math.floor(maxRadius * rCoef);
477
+
478
+ args.g.beginPath();
479
+ args.g.fillStyle = coef;
480
+ args.g.arc(
481
+ args.bounds.x + args.bounds.width / 2, args.bounds.y + args.bounds.height / 2, radius < 3 ? 3 : radius, 0,
482
+ Math.PI * 2, true,
483
+ );
484
+ args.g.closePath();
485
+
486
+ args.g.fill();
487
+ args.preventDefault();
488
+ }
489
+ }
490
+ args.g.restore();
491
+ };
492
+ sarGrid.onCellRender.subscribe(cellRendererFunc);
493
+ sarVGrid.onCellRender.subscribe(cellRendererFunc);
494
+ }
495
+
496
+ function setTooltipFunc(
497
+ renderColNames: string[], statsDf: DG.DataFrame, aminoAcidResidue: string, positionColName: string,
498
+ peptidesCount: number, grouping: boolean, sarGrid: DG.Grid, sarVGrid: DG.Grid, sourceDf: DG.DataFrame,
499
+ ) {
500
+ const onCellTooltipFunc = async function(cell: DG.GridCell, x: number, y: number) {
501
+ if (
502
+ !cell.isRowHeader && !cell.isColHeader && cell.tableColumn !== null && cell.cell.value !== null &&
503
+ cell.tableRowIndex !== null && renderColNames.indexOf(cell.tableColumn.name) !== -1) {
504
+ const tooltipMap: { [index: string]: string } = {};
505
+
506
+ for (const col of (statsDf.columns as DG.ColumnList).names()) {
507
+ if (col !== aminoAcidResidue && col !== positionColName) {
508
+ const currentPosition = cell.tableColumn.name !== 'Mean difference' ?
509
+ cell.tableColumn.name : cell.grid.table.get(positionColName, cell.tableRowIndex);
510
+ const query =
511
+ `${aminoAcidResidue} = ${cell.grid.table.get(aminoAcidResidue, cell.tableRowIndex)} ` +
512
+ `and ${positionColName} = ${currentPosition}`;
513
+ const textNum = statsDf.groupBy([col]).where(query).aggregate().get(col, 0);
514
+ let text = `${col === 'Count' ? textNum : textNum.toFixed(5)}`;
515
+
516
+ if (col === 'Count')
517
+ text += ` / ${peptidesCount}`;
518
+ else if (col === 'pValue')
519
+ text = parseFloat(text) !== 0 ? text : '<0.01';
520
+
521
+
522
+ tooltipMap[col === 'pValue' ? 'p-value' : col] = text;
523
+ }
524
+ }
525
+
526
+ ui.tooltip.show(ui.tableFromMap(tooltipMap), x, y);
527
+ }
528
+ if (
529
+ !cell.isColHeader &&
530
+ cell.tableColumn !== null &&
531
+ cell.tableColumn.name == aminoAcidResidue &&
532
+ cell.cell.value !== null &&
533
+ cell.tableRowIndex !== null
534
+ ) {
535
+ if (grouping) {
536
+ const currentGroup = groupDescription[cell.cell.value];
537
+ const divText = ui.divText('Amino Acids in this group: ' + currentGroup['aminoAcids'].join(', '));
538
+ ui.tooltip.show(ui.divV([ui.h3(currentGroup['description']), divText]), x, y);
539
+ } else {
540
+ const monomerLib = sourceDf.temp[MonomerLibrary.id];
541
+ ChemPalette.showTooltip(cell, x, y, monomerLib);
542
+ }
543
+ }
544
+ return true;
545
+ };
546
+ sarGrid.onCellTooltip(onCellTooltipFunc);
547
+ sarVGrid.onCellTooltip(onCellTooltipFunc);
548
+ }
549
+
550
+ function postProcessGrids(
551
+ sourceGrid: DG.Grid, invalidIndexes: number[], grouping: boolean, aminoAcidResidue: string, sarGrid: DG.Grid,
552
+ sarVGrid: DG.Grid,
553
+ ) {
554
+ sourceGrid.onCellPrepare((cell: DG.GridCell) => {
555
+ const currentRowIndex = cell.tableRowIndex;
556
+ if (currentRowIndex && invalidIndexes.includes(currentRowIndex) && !cell.isRowHeader)
557
+ cell.style.backColor = DG.Color.lightLightGray;
558
+ });
559
+
560
+ const mdCol: DG.GridColumn = sarVGrid.col('Mean difference')!;
561
+ mdCol.name = 'Diff';
562
+
563
+ for (const grid of [sarGrid, sarVGrid]) {
564
+ grid.props.rowHeight = 20;
565
+ grid.columns.rowHeader!.width = 20;
566
+ for (let i = 0; i < grid.columns.length; ++i) {
567
+ const col = grid.columns.byIndex(i)!;
568
+ if (grid == sarVGrid && col.name !== 'Diff' && col.name !== 'AAR')
569
+ col.width = 45;
570
+ else
571
+ col.width = grid.props.rowHeight;
572
+ }
573
+ }
574
+
575
+ if (grouping) {
576
+ sarGrid.col(aminoAcidResidue)!.name = 'Groups';
577
+ sarVGrid.col(aminoAcidResidue)!.name = 'Groups';
578
+ }
579
+
580
+ sarGrid.props.allowEdit = false;
581
+ sarVGrid.props.allowEdit = false;
582
+ }