@datagrok/peptides 0.8.9 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "",
5
5
  "dependencies": {
6
6
  "@biowasm/aioli": ">=2.4.0",
package/src/model.ts CHANGED
@@ -1,125 +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
4
  import {Subject, Observable} from 'rxjs';
5
5
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
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';
12
+
7
13
 
8
14
  export class PeptidesModel {
9
- // private _viewerGrid: DG.Grid;
10
- // private viewerVGrid: DG.Grid;
11
- // private _statsDf: DG.DataFrame;
12
- // private groupMapping: StringDictionary;
13
- private dataFrame: DG.DataFrame | null;
14
- private activityColumn: string | null;
15
- private activityScaling: string | null;
16
- private sourceGrid: DG.Grid | null;
17
- private twoColorMode: boolean | null;
18
- private initialBitset: DG.BitSet | null;
19
- private isUpdating: boolean = false;
20
- private grouping: boolean = false;
21
- private substFlag = false;
22
- private statsDataFrameSubject = new Subject<DG.DataFrame>();
23
- private sarGridSubject = new Subject<DG.Grid>();
24
- private sarVGridSubject = new Subject<DG.Grid>();
25
- private groupMappingSubject = new Subject<StringDictionary>();
26
- private substFlagSubject = new Subject<boolean>();
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>();
27
29
  private static _modelName = 'peptidesModel';
28
30
 
29
31
  private constructor(dataFrame: DG.DataFrame) {
30
- this.dataFrame = dataFrame;
31
- this.activityColumn = null;
32
- this.activityScaling = null;
33
- this.sourceGrid = null;
34
- this.twoColorMode = null;
35
- this.initialBitset = null;
36
-
37
- // this._statsDf = DG.DataFrame.create();
38
- // this._viewerGrid = DG.Grid.create(this.statsDf);
39
- // this.viewerVGrid = DG.Grid.create(this.statsDf);
40
- // this.groupMapping = {};
41
-
42
- // this.statsDataFrameObservable = new Observable(subject => subject.next(this.statsDf));
43
- // this.sarGridObservable = new Observable(subject => subject.next(this.viewerGrid));
44
- // this.sarVGridObservable = new Observable(subject => subject.next(this.viewerVGrid));
45
- // this.groupMappingObservable = new Observable(subject => subject.next(this.groupMapping));
32
+ this._dataFrame = dataFrame;
33
+ this._activityColumn = null;
34
+ this._activityScaling = null;
35
+ this._sourceGrid = null;
36
+ this._twoColorMode = null;
37
+ this._initialBitset = null;
46
38
  }
47
39
 
48
- // get statsDf() {
49
- // return this._statsDf;
50
- // }
40
+ static getInstance(dataFrame: DG.DataFrame): PeptidesModel {
41
+ dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
42
+ return dataFrame.temp[PeptidesModel.modelName];
43
+ }
51
44
 
52
- // get viewerGrid() {
53
- // return this._viewerGrid;
54
- // }
45
+ get dataFrame(): DG.DataFrame {
46
+ return this._dataFrame;
47
+ }
55
48
 
56
49
  get onStatsDataFrameChanged(): Observable<DG.DataFrame> {
57
- return this.statsDataFrameSubject.asObservable();
50
+ return this._statsDataFrameSubject.asObservable();
58
51
  }
59
52
 
60
53
  get onSARGridChanged(): Observable<DG.Grid> {
61
- return this.sarGridSubject.asObservable();
54
+ return this._sarGridSubject.asObservable();
62
55
  }
63
56
 
64
57
  get onSARVGridChanged(): Observable<DG.Grid> {
65
- return this.sarVGridSubject.asObservable();
58
+ return this._sarVGridSubject.asObservable();
66
59
  }
67
60
 
68
61
  get onGroupMappingChanged(): Observable<StringDictionary> {
69
- return this.groupMappingSubject.asObservable();
62
+ return this._groupMappingSubject.asObservable();
70
63
  }
71
64
 
72
65
  get onSubstFlagChanged(): Observable<boolean> {
73
- return this.substFlagSubject.asObservable();
66
+ return this._substFlagSubject.asObservable();
74
67
  }
75
68
 
76
- async updateData(
77
- df: DG.DataFrame | null, activityCol: string | null, activityScaling: string | null, sourceGrid: DG.Grid | null,
69
+ async updateData(activityCol: string | null, activityScaling: string | null, sourceGrid: DG.Grid | null,
78
70
  twoColorMode: boolean | null, initialBitset: DG.BitSet | null, grouping: boolean | null) {
79
- this.dataFrame = df ?? this.dataFrame;
80
- this.activityColumn = activityCol ?? this.activityColumn;
81
- this.activityScaling = activityScaling ?? this.activityScaling;
82
- this.sourceGrid = sourceGrid ?? this.sourceGrid;
83
- this.twoColorMode = twoColorMode ?? this.twoColorMode;
84
- this.initialBitset = initialBitset ?? this.initialBitset;
85
- this.grouping = grouping ?? this.grouping;
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;
86
77
  await this.updateDefault();
87
78
  }
88
79
 
89
80
  async updateDefault() {
90
- if (this.dataFrame && this.activityColumn && this.activityScaling && this.sourceGrid &&
91
- this.twoColorMode !== null && !this.isUpdating) {
92
- this.isUpdating = true;
93
- const [viewerGrid, viewerVGrid, statsDf, groupMapping] = await describe(
94
- this.dataFrame, this.activityColumn, this.activityScaling, this.sourceGrid, this.twoColorMode,
95
- this.initialBitset, this.grouping);
96
- this.statsDataFrameSubject.next(statsDf);
97
- this.groupMappingSubject.next(groupMapping);
98
- this.sarGridSubject.next(viewerGrid);
99
- this.sarVGridSubject.next(viewerVGrid);
100
- this.substFlag = !this.substFlag;
101
- this.substFlagSubject.next(this.substFlag);
102
-
103
- this.sourceGrid.invalidate();
104
-
105
- this.isUpdating = false;
81
+ if (
82
+ this._activityColumn && this._activityScaling && this._sourceGrid && this._twoColorMode !== null &&
83
+ !this._isUpdating
84
+ ) {
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;
106
98
  }
107
99
 
108
100
  await this.updateBarchart();
109
101
  }
110
102
 
111
103
  async updateBarchart() {
112
- const stackedBarchart = await this.dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
113
- if (stackedBarchart && this.sourceGrid)
114
- addViewerToHeader(this.sourceGrid, stackedBarchart);
104
+ const stackedBarchart = await this._dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
105
+ if (stackedBarchart && this._sourceGrid)
106
+ addViewerToHeader(this._sourceGrid, stackedBarchart);
115
107
  }
116
108
 
117
109
  static get modelName() {
118
110
  return PeptidesModel._modelName;
119
111
  }
120
112
 
121
- static getOrInit(dataFrame: DG.DataFrame): PeptidesModel {
122
- dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
123
- return dataFrame.temp[PeptidesModel.modelName];
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);
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));
297
+ }
298
+ }
299
+
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);
124
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;
125
582
  }