@datagrok/peptides 0.8.10 → 0.8.12

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,40 +1,57 @@
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 {Subject, Observable} from 'rxjs';
5
- import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
6
  import {addViewerToHeader, StackedBarChart} from './viewers/stacked-barchart-viewer';
7
7
  import {PeptidesController} from './peptides';
8
8
  import {tTest} from '@datagrok-libraries/statistics/src/tests';
9
9
  import {fdrcorrection} from '@datagrok-libraries/statistics/src/multiple-tests';
10
10
  import {ChemPalette} from './utils/chem-palette';
11
11
  import {MonomerLibrary} from './monomer-library';
12
-
12
+ import * as C from './utils/constants';
13
+ import * as type from './utils/types';
14
+ import {FilteringStatistics} from './utils/filtering-statistics';
13
15
 
14
16
  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';
17
+ static _modelName = 'peptidesModel';
18
+
19
+ _statsDataFrameSubject = new Subject<DG.DataFrame>();
20
+ _sarGridSubject = new Subject<DG.Grid>();
21
+ _sarVGridSubject = new Subject<DG.Grid>();
22
+ // _groupMappingSubject = new Subject<StringDictionary>();
23
+ _substitutionTableSubject = new Subject<DG.DataFrame>();
24
+
25
+ _isUpdating: boolean = false;
26
+ _isSubstInitialized = false;
27
+ isBitsetChangedInitialized = false;
28
+
29
+ //viewer properties
30
+ // _grouping!: boolean;
31
+ _filterMode!: boolean;
32
+ _twoColorMode!: boolean;
33
+ _activityScaling!: string;
34
+ _isSubstitutionOn!: boolean;
35
+ _activityLimit!: number;
36
+ _maxSubstitutions!: number;
37
+
38
+ _sarGrid!: DG.Grid;
39
+ _sarVGrid!: DG.Grid;
40
+ _sourceGrid!: DG.Grid;
41
+ _dataFrame: DG.DataFrame;
42
+ _substitutionTable!: DG.DataFrame;
43
+ splitCol!: DG.Column;
44
+ stackedBarchart!: StackedBarChart;
45
+
46
+ // _substTableTooltipData!: { [aar: string]: number[][][]; };
47
+ _casesTable!: type.SubstitutionCases;
48
+ _substTableTooltipData!: type.SubstitutionTooltips;
30
49
 
31
50
  private constructor(dataFrame: DG.DataFrame) {
32
51
  this._dataFrame = dataFrame;
33
- this._activityColumn = null;
34
- this._activityScaling = null;
35
- this._sourceGrid = null;
36
- this._twoColorMode = null;
37
- this._initialBitset = null;
52
+ this._dataFrame.temp[C.PEPTIDES_ANALYSIS] = true;
53
+
54
+ this.updateProperties();
38
55
  }
39
56
 
40
57
  static getInstance(dataFrame: DG.DataFrame): PeptidesModel {
@@ -42,541 +59,931 @@ export class PeptidesModel {
42
59
  return dataFrame.temp[PeptidesModel.modelName];
43
60
  }
44
61
 
45
- get dataFrame(): DG.DataFrame {
46
- return this._dataFrame;
62
+ updateProperties() {
63
+ this._activityScaling = this._dataFrame.tags['scaling'];
64
+ this._filterMode = this.stringToBool(this._dataFrame.tags['filterMode']);
65
+ this._twoColorMode = this.stringToBool(this._dataFrame.tags['bidirectionalAnalysis']);
66
+ // this._grouping = this.stringToBool(this._dataFrame.tags['grouping']);
67
+ this._isSubstitutionOn = this.stringToBool(this._dataFrame.tags['showSubstitution']);
68
+ this._maxSubstitutions = parseInt(this._dataFrame.tags['maxSubstitutions']);
69
+ this._activityLimit = parseFloat(this._dataFrame.tags['activityLimit']);
47
70
  }
48
71
 
49
- get onStatsDataFrameChanged(): Observable<DG.DataFrame> {
50
- return this._statsDataFrameSubject.asObservable();
72
+ stringToBool(str: string) {
73
+ return str === 'true' ? true : false;
51
74
  }
52
75
 
53
- get onSARGridChanged(): Observable<DG.Grid> {
54
- return this._sarGridSubject.asObservable();
76
+ setProperties(
77
+ activityScaling: string, filterMode: boolean, twoColorMode: boolean, isSubstitutionOn: boolean,
78
+ maxSubstitutions: number, activityLimit: number, forceUpdate = false,
79
+ ) {
80
+ const chooseAction = (value: string, defaultValue: any) => forceUpdate ? value : defaultValue ?? value;
81
+ this._dataFrame.tags['scaling'] = chooseAction(`${activityScaling}`, this._dataFrame.tags['scaling']);
82
+ this._dataFrame.tags['filterMode'] = chooseAction(`${filterMode}`, this._dataFrame.tags['filterMode']);
83
+ this._dataFrame.tags['bidirectionalAnalysis'] = chooseAction(`${twoColorMode}`, this._dataFrame.tags['bidirectionalAnalysis']);
84
+ // this._dataFrame.tags['grouping'] = chooseAction(`${grouping}`, this._dataFrame.tags['grouping']);
85
+ this._dataFrame.tags['showSubstitution'] = chooseAction(`${isSubstitutionOn}`, this._dataFrame.tags['showSubstitution']);
86
+ this._dataFrame.tags['maxSubstitutions'] = chooseAction(`${maxSubstitutions}`, this._dataFrame.tags['maxSubstitutions']);
87
+ this._dataFrame.tags['activityLimit'] = chooseAction(`${activityLimit}`, this._dataFrame.tags['activityLimit']);
88
+
89
+ this.updateProperties();
55
90
  }
56
91
 
57
- get onSARVGridChanged(): Observable<DG.Grid> {
58
- return this._sarVGridSubject.asObservable();
59
- }
92
+ get dataFrame(): DG.DataFrame {return this._dataFrame;}
60
93
 
61
- get onGroupMappingChanged(): Observable<StringDictionary> {
62
- return this._groupMappingSubject.asObservable();
63
- }
94
+ get onStatsDataFrameChanged(): Observable<DG.DataFrame> {return this._statsDataFrameSubject.asObservable();}
64
95
 
65
- get onSubstFlagChanged(): Observable<boolean> {
66
- return this._substFlagSubject.asObservable();
67
- }
96
+ get onSARGridChanged(): Observable<DG.Grid> {return this._sarGridSubject.asObservable();}
97
+
98
+ get onSARVGridChanged(): Observable<DG.Grid> {return this._sarVGridSubject.asObservable();}
99
+
100
+ // get onGroupMappingChanged(): Observable<StringDictionary> {return this._groupMappingSubject.asObservable();}
101
+
102
+ get onSubstTableChanged(): Observable<DG.DataFrame> {return this._substitutionTableSubject.asObservable();}
103
+
104
+ get substTooltipData(): type.SubstitutionTooltips {return this._substTableTooltipData!;}
68
105
 
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;
106
+ async updateData(
107
+ activityScaling?: string, sourceGrid?: DG.Grid, twoColorMode?: boolean, activityLimit?: number,
108
+ maxSubstitutions?: number, isSubstitutionOn?: boolean, filterMode?: boolean,
109
+ ) {
110
+ //FIXME: threre are too many assignments, some are duplicating
72
111
  this._activityScaling = activityScaling ?? this._activityScaling;
73
112
  this._sourceGrid = sourceGrid ?? this._sourceGrid;
74
113
  this._twoColorMode = twoColorMode ?? this._twoColorMode;
75
- this._initialBitset = initialBitset ?? this._initialBitset;
76
- this._grouping = grouping ?? this._grouping;
114
+ // this._grouping = grouping ?? this._grouping;
115
+ this._activityLimit = activityLimit ?? this._activityLimit;
116
+ this._maxSubstitutions = maxSubstitutions ?? this._maxSubstitutions;
117
+ this._isSubstitutionOn = isSubstitutionOn ?? this._isSubstitutionOn;
118
+ this._filterMode = filterMode ?? this._filterMode;
119
+ this.setProperties(this._activityScaling, this._filterMode, this._twoColorMode, this._isSubstitutionOn,
120
+ this._maxSubstitutions, this._activityLimit, true);
121
+
77
122
  await this.updateDefault();
78
123
  }
79
124
 
80
125
  async updateDefault() {
81
- if (
82
- this._activityColumn && this._activityScaling && this._sourceGrid && this._twoColorMode !== null &&
83
- !this._isUpdating
84
- ) {
126
+ if (this._activityScaling && this._sourceGrid && this._twoColorMode !== null && !this._isUpdating) {
85
127
  this._isUpdating = true;
86
- const [viewerGrid, viewerVGrid, statsDf, groupMapping] = await this.describe(
87
- this._activityColumn, this._activityScaling, this._twoColorMode, this._grouping);
128
+ const [viewerGrid, viewerVGrid, statsDf, substTable] = await this.initializeViewersComponents();
129
+ //FIXME: modify during the initializeViewersComponents stages
88
130
  this._statsDataFrameSubject.next(statsDf);
89
- this._groupMappingSubject.next(groupMapping);
131
+ // this._groupMappingSubject.next(groupMapping);
90
132
  this._sarGridSubject.next(viewerGrid);
91
133
  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;
134
+ if (this._isSubstitutionOn) {
135
+ this._substitutionTableSubject.next(substTable);
136
+ this._isSubstInitialized = true;
137
+ }
98
138
  }
99
-
100
139
  await this.updateBarchart();
140
+ this.invalidateGrids();
141
+
142
+ this._isUpdating = false;
101
143
  }
102
144
 
103
145
  async updateBarchart() {
104
- const stackedBarchart = await this._dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
105
- if (stackedBarchart && this._sourceGrid)
106
- addViewerToHeader(this._sourceGrid, stackedBarchart);
146
+ this.stackedBarchart ??= await this._dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
147
+ if (this.stackedBarchart && this._sourceGrid)
148
+ addViewerToHeader(this._sourceGrid, this.stackedBarchart);
107
149
  }
108
150
 
109
- static get modelName() {
110
- return PeptidesModel._modelName;
111
- }
151
+ static get modelName() { return PeptidesModel._modelName; }
112
152
 
113
- async describe(
114
- activityColumn: string, activityScaling: string, twoColorMode: boolean, grouping: boolean,
115
- ): Promise<[DG.Grid, DG.Grid, DG.DataFrame, StringDictionary]> {
153
+ async initializeViewersComponents(): Promise<[DG.Grid, DG.Grid, DG.DataFrame, DG.DataFrame]> {
116
154
  if (this._sourceGrid === null)
117
155
  throw new Error(`Source grid is not initialized`);
118
156
 
119
157
  //Split the aligned sequence into separate AARs
120
158
  let splitSeqDf: DG.DataFrame | undefined;
121
159
  let invalidIndexes: number[];
122
- const col: DG.Column = (this._dataFrame.columns as DG.ColumnList).bySemType('alignedSequence')!;
160
+ const col: DG.Column = (this._dataFrame.columns as DG.ColumnList).bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE)!;
123
161
  [splitSeqDf, invalidIndexes] = PeptidesController.splitAlignedPeptides(col);
124
- splitSeqDf.name = 'Split sequence';
125
162
 
126
163
  const positionColumns = (splitSeqDf.columns as DG.ColumnList).names();
127
- const activityColumnScaled = `${activityColumn}Scaled`;
128
164
  const renderColNames: string[] = (splitSeqDf.columns as DG.ColumnList).names();
129
- const positionColName = 'Pos';
130
- const aminoAcidResidue = 'AAR';
131
165
 
132
- (splitSeqDf.columns as DG.ColumnList).add(this._dataFrame.getCol(activityColumn));
166
+ (splitSeqDf.columns as DG.ColumnList).add(this._dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY));
133
167
 
134
- joinDataFrames(this._dataFrame, positionColumns, splitSeqDf, activityColumn);
168
+ this.joinDataFrames(this._dataFrame, positionColumns, splitSeqDf);
135
169
 
136
170
  for (const dfCol of (this._dataFrame.columns as DG.ColumnList)) {
137
- if (splitSeqDf.col(dfCol.name) && dfCol.name != activityColumn)
171
+ if (splitSeqDf.col(dfCol.name) && dfCol.name != C.COLUMNS_NAMES.ACTIVITY)
138
172
  PeptidesController.setAARRenderer(dfCol, this._sourceGrid);
139
173
  }
140
174
 
141
- sortSourceGrid(this._sourceGrid);
175
+ this.sortSourceGrid(this._sourceGrid);
142
176
 
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);
177
+ await this.createScaledCol(this._activityScaling!, this._dataFrame, this._sourceGrid, splitSeqDf);
162
178
 
163
179
  //unpivot a table and handle duplicates
164
180
  splitSeqDf = splitSeqDf.groupBy(positionColumns)
165
- .add('med', activityColumnScaled, activityColumnScaled)
181
+ .add('med', C.COLUMNS_NAMES.ACTIVITY_SCALED, C.COLUMNS_NAMES.ACTIVITY_SCALED)
166
182
  .aggregate();
167
183
 
168
- const peptidesCount = splitSeqDf.getCol(activityColumnScaled).length;
184
+ const peptidesCount = splitSeqDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).length;
169
185
 
170
- let matrixDf = splitSeqDf.unpivot([activityColumnScaled], positionColumns, positionColName, aminoAcidResidue);
186
+ let matrixDf = splitSeqDf.unpivot(
187
+ [C.COLUMNS_NAMES.ACTIVITY_SCALED], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
171
188
 
172
189
  //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);
190
+ // let groupMapping: StringDictionary = {};
191
+ // if (this._grouping) {
192
+ // groupMapping = C.aarGroups;
193
+ // const aarCol = matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
194
+ // aarCol.init((index) => groupMapping[aarCol.get(index)[0]] ?? '-');
195
+ // aarCol.compact();
196
+ // } else
197
+ // Object.keys(C.aarGroups).forEach((value) => groupMapping[value] = value);
181
198
 
182
199
 
200
+ //FIXME: for some reason Mean difference is not calculated for all the AARs
183
201
  //statistics for specific AAR at a specific position
184
- const statsDf = await calculateStatistics(
185
- matrixDf, positionColName, aminoAcidResidue, activityColumnScaled, peptidesCount, splitSeqDf, groupMapping,
186
- );
202
+ const statsDf = await this.calculateStatistics(matrixDf, peptidesCount, splitSeqDf);
187
203
 
188
204
  // SAR matrix table
189
205
  //pivot a table to make it matrix-like
190
- matrixDf = statsDf.groupBy([aminoAcidResidue])
191
- .pivot(positionColName)
192
- .add('first', 'Mean difference', '')
206
+ matrixDf = statsDf.groupBy([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE])
207
+ .pivot(C.COLUMNS_NAMES.POSITION)
208
+ .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
193
209
  .aggregate();
194
210
  matrixDf.name = 'SAR';
195
211
 
196
212
  // Setting category order
197
- await setCategoryOrder(twoColorMode, statsDf, aminoAcidResidue, matrixDf);
213
+ await this.setCategoryOrder(this._twoColorMode!, statsDf, matrixDf);
198
214
 
199
215
  // 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');
216
+ const sequenceDf = this.createVerticalTable(statsDf, this._twoColorMode!);
217
+ renderColNames.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
218
+
219
+ let substTable: DG.DataFrame | null = null;
220
+ if (this._isSubstitutionOn || !this._isSubstInitialized)
221
+ substTable = this.calcSubstitutions();
202
222
 
203
- const [sarGrid, sarVGrid] = createGrids(
204
- matrixDf, aminoAcidResidue, positionColumns, sequenceDf, positionColName, grouping,
205
- );
223
+ //TODO: move everything below out to controller
224
+ const [sarGrid, sarVGrid] = this.createGrids(matrixDf, positionColumns, sequenceDf);
206
225
 
207
- setCellRendererFunc(
208
- renderColNames, positionColName, aminoAcidResidue, statsDf, twoColorMode, sarGrid, sarVGrid,
209
- );
226
+ this._sarGrid = sarGrid;
227
+ this._sarVGrid = sarVGrid;
228
+
229
+ this.setCellRenderers(
230
+ renderColNames, statsDf, this._twoColorMode, sarGrid, sarVGrid, this._isSubstitutionOn);
210
231
 
211
232
  // show all the statistics in a tooltip over cell
212
- setTooltipFunc(
213
- renderColNames, statsDf, aminoAcidResidue, positionColName, peptidesCount, grouping, sarGrid, sarVGrid,
214
- this._dataFrame,
215
- );
233
+ this.setTooltips(renderColNames, statsDf, peptidesCount, sarGrid, sarVGrid, this._dataFrame);
234
+
235
+ this.setInteractionCallback();
236
+
237
+ this.modifyOrCreateSplitCol(C.CATEGORIES.ALL, C.CATEGORIES.ALL);
238
+
239
+ this.setBitsetCallback();
216
240
 
217
- postProcessGrids(this._sourceGrid, invalidIndexes, grouping, aminoAcidResidue, sarGrid, sarVGrid);
241
+ this.postProcessGrids(this._sourceGrid, invalidIndexes, sarGrid, sarVGrid);
242
+
243
+ if (this.dataFrame.tags[C.TAGS.AAR] && this.dataFrame.tags[C.TAGS.POSITION]) {
244
+ const sarDf = sarGrid.dataFrame;
245
+ const rowCount = sarDf.rowCount;
246
+ let index = -1;
247
+ for (let i = 0; i < rowCount; i++) {
248
+ if (sarDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, i) === this.dataFrame.tags[C.TAGS.AAR]) {
249
+ index = i;
250
+ break;
251
+ }
252
+ }
253
+ sarDf.currentCell = sarDf.cell(index, this.dataFrame.tags[C.TAGS.POSITION]);
254
+ }
218
255
 
219
256
  //TODO: return class instead
220
- return [sarGrid, sarVGrid, statsDf, groupMapping];
257
+ return [sarGrid, sarVGrid, statsDf, substTable!];
221
258
  }
222
- }
223
259
 
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
- }
260
+ calcSubstitutions() {
261
+ // const col: DG.Column = this.dataFrame.columns.bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE);
262
+ // const values: number[] = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
263
+ const activityValues = this.dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
264
+ // const splitedMatrix = this.split(col);
265
+ const columnList = (this.dataFrame.columns as DG.ColumnList).toList()
266
+ .filter(col => col.semType === C.SEM_TYPES.AMINO_ACIDS);
267
+ if (columnList.length === 0)
268
+ throw new Error(`Couldn't find any column of semType '${C.SEM_TYPES.AMINO_ACIDS}'`);
269
+
270
+ const tableValues: { [aar: string]: number[]; } = {};
271
+ const tableTooltips: { [aar: string]: {}[][]; } = {};
272
+ const tableCases: { [aar: string]: number[][][]; } = {};
273
+
274
+ // const nRows = splitedMatrix.length;
275
+ // const nCols = splitedMatrix[0].length;
276
+ // const nColsArray = Array(nCols);
277
+ const nCols = columnList.length;
278
+ const nRows = this.dataFrame.rowCount;
279
+
280
+ //TODO: this looks **very** expensive
281
+ for (let i = 0; i < nRows - 1; i++) {
282
+ for (let j = i + 1; j < nRows; j++) {
283
+ let substCounter = 0;
284
+ const subst1: { [pos: number]: [string, {key: string, value: string, diff: number}] } = {};
285
+ const subst2: { [pos: number]: [string, {key: string, value: string, diff: number}] } = {};
286
+ const activityValI = activityValues.get(i) as number;
287
+ const activityValJ = activityValues.get(j) as number;
288
+ const strActivityValI = activityValI.toFixed(2);
289
+ const strActivityValJ = activityValJ.toFixed(2);
290
+ const delta = activityValI - activityValJ;
291
+
292
+ if (Math.abs(delta) < this._activityLimit)
293
+ continue;
294
+
295
+ for (let k = 0; k < nCols; k++) {
296
+ // const smik = splitedMatrix[i][k];
297
+ // const smjk = splitedMatrix[j][k];
298
+ const smik = columnList[k].get(i) as string;
299
+ const smjk = columnList[k].get(j) as string;
300
+
301
+ if (smik === smjk)
302
+ continue;
303
+
304
+ substCounter++;
305
+ subst1[k] = [
306
+ smik,
307
+ {
308
+ key: `${smik === '-' ? 'Empty' : smik} → ${smjk === '-' ? 'Empty' : smjk}`,
309
+ value: `${strActivityValI} ${strActivityValJ}`,
310
+ diff: -delta,
311
+ },
312
+ ];
313
+ subst2[k] = [
314
+ smjk,
315
+ {
316
+ key: `${smjk === '-' ? 'Empty' : smjk} → ${smik === '-' ? 'Empty' : smik}`,
317
+ value: `${strActivityValJ} → ${strActivityValI}`,
318
+ diff: delta,
319
+ },
320
+ ];
321
+ }
279
322
 
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;
323
+ if (substCounter > this._maxSubstitutions || substCounter === 0)
324
+ continue;
325
+
326
+ for (const subst of [subst1, subst2]) {
327
+ Object.keys(subst).forEach((pos) => {
328
+ const posInt = parseInt(pos);
329
+ const aar = subst[posInt][0];
330
+ if (!Object.keys(tableValues).includes(aar)) {
331
+ // tableValues[aar] = Array(...nColsArray).map(() => DG.INT_NULL);
332
+ // tableTooltips[aar] = Array(...nColsArray).map(() => []);
333
+ // tableCases[aar] = Array(...nColsArray).map(() => []);
334
+ tableValues[aar] = Array(nCols).fill(DG.INT_NULL);
335
+ tableTooltips[aar] = Array(nCols).fill([]);
336
+ tableCases[aar] = Array(nCols).fill([]);
337
+ }
338
+
339
+ tableValues[aar][posInt] = tableValues[aar][posInt] === DG.INT_NULL ? 1 : tableValues[aar][posInt] + 1;
340
+ tableTooltips[aar][posInt] = !tableTooltips[aar][posInt].length ?
341
+ [{key: 'Substitution', value: 'Values'}] : tableTooltips[aar][posInt];
342
+ tableTooltips[aar][posInt].push(subst[posInt][1]);
343
+ tableCases[aar][posInt].push([i, j, subst == subst1 ? delta : -delta]);
344
+ });
345
+ }
291
346
  }
292
- if (b.column!.semType == 'aminoAcids')
293
- return 1;
294
- return 0;
347
+ }
348
+
349
+ const tableValuesKeys = Object.keys(tableValues);
350
+ const dfLength = tableValuesKeys.length;
351
+ const cols = columnList.map(col => {
352
+ const newCol = DG.Column.int(`${col.name}`, dfLength);
353
+ newCol.semType = 'Substitution';
354
+ return newCol;
295
355
  });
296
- sourceGrid.columns.setOrder(colNames.map((v) => v.name));
356
+ const aarCol = DG.Column.string(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, dfLength);
357
+ cols.splice(0, 0, aarCol);
358
+ const table = DG.DataFrame.fromColumns(cols);
359
+
360
+ for (let i = 0; i < dfLength; i++) {
361
+ const aar = tableValuesKeys[i];
362
+ // tableValues[aar].splice(0, 1);
363
+ table.rows.setValues(i, [aar, ...tableValues[aar]]);
364
+ }
365
+
366
+ // let groupMapping: { [key: string]: string } = {};
367
+
368
+ //TODO: enable grouping
369
+ // Object.keys(aarGroups).forEach((value) => groupMapping[value] = value);
370
+ this._substTableTooltipData = tableTooltips;
371
+ this._casesTable = tableCases;
372
+ this.substitutionTable = table;
373
+
374
+ return table;
297
375
  }
298
- }
299
376
 
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;
377
+ get substitutionTable() { return this._substitutionTable; }
378
+ set substitutionTable(table: DG.DataFrame) {
379
+ if (!table)
380
+ throw new Error(`Substitution table cannot be set to null`);
381
+ this._substitutionTable = table;
382
+ this._substitutionTableSubject.next(table);
345
383
  }
346
384
 
347
- pvalues = fdrcorrection(pvalues)[1];
385
+ joinDataFrames(df: DG.DataFrame, positionColumns: string[], splitSeqDf: DG.DataFrame) {
386
+ // append splitSeqDf columns to source table and make sure columns are not added more than once
387
+ const dfColsSet = new Set((df.columns as DG.ColumnList).names());
388
+ if (!positionColumns.every((col: string) => dfColsSet.has(col))) {
389
+ df.join(
390
+ splitSeqDf, [C.COLUMNS_NAMES.ACTIVITY], [C.COLUMNS_NAMES.ACTIVITY], (df.columns as DG.ColumnList).names(),
391
+ positionColumns, 'inner', true);
392
+ }
393
+ }
348
394
 
349
- for (let i = 0; i < pvalues.length; ++i)
350
- pValCol.set(i, pvalues[i]);
395
+ sortSourceGrid(sourceGrid: DG.Grid) {
396
+ if (sourceGrid) {
397
+ const colNames: DG.GridColumn[] = [];
398
+ for (let i = 1; i < sourceGrid.columns.length; i++)
399
+ colNames.push(sourceGrid.columns.byIndex(i)!);
400
+
401
+ colNames.sort((a, b)=>{
402
+ if (a.column!.semType == C.SEM_TYPES.AMINO_ACIDS) {
403
+ if (b.column!.semType == C.SEM_TYPES.AMINO_ACIDS)
404
+ return 0;
405
+ return -1;
406
+ }
407
+ if (b.column!.semType == C.SEM_TYPES.AMINO_ACIDS)
408
+ return 1;
409
+ return 0;
410
+ });
411
+ sourceGrid.columns.setOrder(colNames.map((v) => v.name));
412
+ }
413
+ }
351
414
 
352
- return matrixDf.clone();
353
- }
415
+ async createScaledCol(
416
+ activityScaling: string, df: DG.DataFrame, sourceGrid: DG.Grid, splitSeqDf: DG.DataFrame,
417
+ ) {
418
+ const [scaledDf, newColName] = await PeptidesController.scaleActivity(
419
+ activityScaling, df, df.temp[C.COLUMNS_NAMES.ACTIVITY]);
420
+ //TODO: make another func
421
+ const scaledCol = scaledDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
422
+ (splitSeqDf.columns as DG.ColumnList).add(scaledCol);
423
+ const oldScaledCol = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
424
+ (df.columns as DG.ColumnList).replace(oldScaledCol, scaledCol);
425
+ const gridCol = sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED);
426
+ if (gridCol !== null) {
427
+ gridCol.name = newColName;
428
+ df.temp[C.COLUMNS_NAMES.ACTIVITY_SCALED] = newColName;
429
+ }
354
430
 
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
- }
431
+ sourceGrid.columns.setOrder([newColName]);
432
+ }
373
433
 
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;
434
+ async calculateStatistics(matrixDf: DG.DataFrame, peptidesCount: number, splitSeqDf: DG.DataFrame) {
435
+ matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE])
436
+ .add('count', C.COLUMNS_NAMES.ACTIVITY_SCALED, 'Count')
437
+ .aggregate();
438
+
439
+ const countThreshold = 4;
440
+ //@ts-ignore: never gets old
441
+ matrixDf.rows.filter((row) => row.Count >= countThreshold && row.Count <= peptidesCount - countThreshold);
442
+ matrixDf = matrixDf.clone(matrixDf.filter);
443
+
444
+ // calculate additional stats
445
+ await (matrixDf.columns as DG.ColumnList).addNewCalculated('Ratio', '${count}/'.concat(`${peptidesCount}`));
446
+
447
+ //calculate p-values based on t-test
448
+ let pvalues: Float32Array = new Float32Array(matrixDf.rowCount).fill(1);
449
+ const mdCol: DG.Column = (matrixDf.columns as DG.ColumnList).addNewFloat(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
450
+ const pValCol: DG.Column = (matrixDf.columns as DG.ColumnList).addNewFloat(C.COLUMNS_NAMES.P_VALUE);
451
+ for (let i = 0; i < matrixDf.rowCount; i++) {
452
+ const position = matrixDf.get(C.COLUMNS_NAMES.POSITION, i);
453
+ const aar = matrixDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, i);
454
+
455
+ //@ts-ignore
456
+ splitSeqDf.rows.select((row) => row[position] === aar);
457
+ const currentActivity: number[] = splitSeqDf
458
+ .clone(splitSeqDf.selection, [C.COLUMNS_NAMES.ACTIVITY_SCALED])
459
+ .getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED)
460
+ .toList();
461
+
462
+ //@ts-ignore
463
+ splitSeqDf.rows.select((row) => row[position] !== aar);
464
+ const otherActivity: number[] = splitSeqDf
465
+ .clone(splitSeqDf.selection, [C.COLUMNS_NAMES.ACTIVITY_SCALED])
466
+ .getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED)
467
+ .toList();
468
+
469
+ const testResult = tTest(currentActivity, otherActivity);
470
+ // testResult = uTest(currentActivity, otherActivity);
471
+ const currentMeanDiff = testResult[C.COLUMNS_NAMES.MEAN_DIFFERENCE]!;
472
+ const pvalue = testResult[currentMeanDiff >= 0 ? 'p-value more' : 'p-value less'];
473
+
474
+ mdCol.set(i, currentMeanDiff);
475
+ pvalues[i] = pvalue;
476
+ }
477
+
478
+ pvalues = fdrcorrection(pvalues)[1];
479
+
480
+ for (let i = 0; i < pvalues.length; ++i)
481
+ pValCol.set(i, pvalues[i]);
482
+
483
+ return matrixDf.clone();
391
484
  }
392
- sequenceDf = sequenceDf.clone(DG.BitSet.create(sequenceDf.rowCount, (i) => {
393
- return sequenceDf.get('Mean difference', i) === maxAtPos[sequenceDf.get(positionColName, i)];
394
- }));
395
485
 
396
- return sequenceDf;
397
- }
486
+ async setCategoryOrder(twoColorMode: boolean, statsDf: DG.DataFrame, matrixDf: DG.DataFrame) {
487
+ const absMD = 'Absolute Mean difference';
488
+ const sortArgument = twoColorMode ? absMD : C.COLUMNS_NAMES.MEAN_DIFFERENCE;
489
+ if (twoColorMode)
490
+ await (statsDf.columns as DG.ColumnList).addNewCalculated(absMD, 'Abs(${Mean difference})');
491
+
492
+ const aarWeightsDf = statsDf.groupBy([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]).sum(sortArgument, 'weight').aggregate();
493
+ const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE).toList();
494
+ const getWeight = (aar: string) => aarWeightsDf
495
+ .groupBy(['weight'])
496
+ .where(`${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ${aar}`)
497
+ .aggregate()
498
+ .get('weight', 0);
499
+ aarList.sort((first, second) => getWeight(second) - getWeight(first));
500
+
501
+ matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE).setCategoryOrder(aarList);
502
+ }
503
+
504
+ createVerticalTable(statsDf: DG.DataFrame, twoColorMode: boolean) {
505
+ // TODO: aquire ALL of the positions
506
+ const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, C.COLUMNS_NAMES.POSITION,
507
+ 'Count', 'Ratio', C.COLUMNS_NAMES.P_VALUE];
508
+ let sequenceDf = statsDf.groupBy(columns)
509
+ .where('pValue <= 0.1')
510
+ .aggregate();
511
+
512
+ let tempStats: DG.Stats;
513
+ const maxAtPos: {[index: string]: number} = {};
514
+ for (const pos of sequenceDf.getCol(C.COLUMNS_NAMES.POSITION).categories) {
515
+ tempStats = DG.Stats.fromColumn(
516
+ sequenceDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE),
517
+ DG.BitSet.create(sequenceDf.rowCount, (i) => sequenceDf.get(C.COLUMNS_NAMES.POSITION, i) === pos),
518
+ );
519
+ maxAtPos[pos] = twoColorMode ?
520
+ (tempStats.max > Math.abs(tempStats.min) ? tempStats.max : tempStats.min) : tempStats.max;
521
+ }
522
+ sequenceDf = sequenceDf.clone(DG.BitSet.create(sequenceDf.rowCount, (i) =>
523
+ sequenceDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, i) === maxAtPos[sequenceDf.get(C.COLUMNS_NAMES.POSITION, i)]));
524
+
525
+ return sequenceDf;
526
+ }
527
+
528
+ createGrids(matrixDf: DG.DataFrame, positionColumns: string[], sequenceDf: DG.DataFrame) {
529
+ const sarGrid = matrixDf.plot.grid();
530
+ sarGrid.sort([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]);
531
+ sarGrid.columns.setOrder([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE].concat(positionColumns as C.COLUMNS_NAMES[]));
398
532
 
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);
533
+ const sarVGrid = sequenceDf.plot.grid();
534
+ sarVGrid.sort([C.COLUMNS_NAMES.POSITION]);
535
+ sarVGrid.col(C.COLUMNS_NAMES.P_VALUE)!.format = 'four digits after comma';
536
+ sarVGrid.col(C.COLUMNS_NAMES.P_VALUE)!.name = 'P-Value';
537
+
538
+ let tempCol = (matrixDf.columns as DG.ColumnList).byName(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
414
539
  if (tempCol)
415
540
  PeptidesController.setAARRenderer(tempCol, sarGrid);
416
541
 
417
- tempCol = (sequenceDf.columns as DG.ColumnList).byName(aminoAcidResidue);
542
+ tempCol = (sequenceDf.columns as DG.ColumnList).byName(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
418
543
  if (tempCol)
419
544
  PeptidesController.setAARRenderer(tempCol, sarGrid);
545
+
546
+ return [sarGrid, sarVGrid];
420
547
  }
421
548
 
422
- return [sarGrid, sarVGrid];
423
- }
549
+ setCellRenderers(
550
+ renderColNames: string[], statsDf: DG.DataFrame, twoColorMode: boolean, sarGrid: DG.Grid, sarVGrid: DG.Grid,
551
+ isSubstitutionOn: boolean,
552
+ ) {
553
+ const mdCol = statsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
554
+ const cellRendererAction = (args: DG.GridCellRenderArgs) => {
555
+ const canvasContext = args.g;
556
+ const bound = args.bounds;
557
+ const cell = args.cell;
558
+ const tableColName = cell.tableColumn?.name;
559
+ const tableRowIndex = cell.tableRowIndex!;
560
+ const cellValue = cell.cell.value;
561
+ const midX = bound.x + bound.width / 2;
562
+ const midY = bound.y + bound.height / 2;
563
+
564
+ canvasContext.save();
565
+ canvasContext.beginPath();
566
+ canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
567
+ canvasContext.clip();
568
+
569
+ if (cell.isRowHeader && cell.gridColumn.visible) {
570
+ cell.gridColumn.visible = false;
571
+ args.preventDefault();
572
+ return;
573
+ }
424
574
 
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
- }
575
+ if (cell.isTableCell && tableColName && tableRowIndex !== null && renderColNames.indexOf(tableColName) !== -1) {
576
+ const gridTable = cell.grid.table;
577
+ const currentPosition = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
578
+ tableColName : gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
579
+ const currentAAR = gridTable.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
580
+ if (currentAAR === 'Aib' && currentPosition === '02')
581
+ console.log('stop');
582
+
583
+ const queryAAR = `${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ${currentAAR}`;
584
+ if (cellValue) {
585
+ const query = `${queryAAR} and ${C.COLUMNS_NAMES.POSITION} = ${currentPosition}`;
586
+ const pVal: number = statsDf
587
+ .groupBy([C.COLUMNS_NAMES.P_VALUE])
588
+ .where(query)
589
+ .aggregate()
590
+ .get(C.COLUMNS_NAMES.P_VALUE, 0);
591
+
592
+ let coef: string;
593
+ const variant = cellValue < 0;
594
+ if (pVal < 0.01)
595
+ coef = variant && twoColorMode ? '#FF7900' : '#299617';
596
+ else if (pVal < 0.05)
597
+ coef = variant && twoColorMode ? '#FFA500' : '#32CD32';
598
+ else if (pVal < 0.1)
599
+ coef = variant && twoColorMode ? '#FBCEB1' : '#98FF98';
600
+ else
601
+ coef = DG.Color.toHtml(DG.Color.lightLightGray);
602
+
603
+
604
+ const chooseMin = () => twoColorMode ? 0 : mdCol.min;
605
+ const chooseMax = () => twoColorMode ? Math.max(Math.abs(mdCol.min), mdCol.max) : mdCol.max;
606
+ const chooseCurrent = () => twoColorMode ? Math.abs(cellValue) : cellValue;
607
+
608
+ const rCoef = (chooseCurrent() - chooseMin()) / (chooseMax() - chooseMin());
609
+
610
+ const maxRadius = 0.9 * (bound.width > bound.height ? bound.height : bound.width) / 2;
611
+ const radius = Math.floor(maxRadius * rCoef);
612
+
613
+ canvasContext.beginPath();
614
+ canvasContext.fillStyle = coef;
615
+ canvasContext.arc(midX, midY, radius < 3 ? 3 : radius, 0, Math.PI * 2, true);
616
+ canvasContext.closePath();
617
+
618
+ canvasContext.fill();
619
+ if (isSubstitutionOn) {
620
+ canvasContext.textBaseline = 'middle';
621
+ canvasContext.textAlign = 'center';
622
+ canvasContext.fillStyle = DG.Color.toHtml(DG.Color.black);
623
+ // DG.Color.getContrastColor()
624
+ canvasContext.font = '13px Roboto, Roboto Local, sans-serif';
625
+ const substValue = this.substitutionTable.groupBy([currentPosition])
626
+ .where(queryAAR)
627
+ .aggregate()
628
+ .get(currentPosition, 0);
629
+ if (substValue && substValue !== DG.INT_NULL)
630
+ canvasContext.fillText(substValue, midX, midY);
631
+ }
632
+ }
633
+ args.preventDefault();
634
+ }
635
+ canvasContext.restore();
636
+ };
637
+ sarGrid.onCellRender.subscribe(cellRendererAction);
638
+ sarVGrid.onCellRender.subscribe(cellRendererAction);
639
+ }
441
640
 
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);
641
+ setTooltips(
642
+ renderColNames: string[], statsDf: DG.DataFrame, peptidesCount: number, sarGrid: DG.Grid, sarVGrid: DG.Grid,
643
+ sourceDf: DG.DataFrame,
644
+ ) {
645
+ const onCellTooltipAction = async (cell: DG.GridCell, x: number, y: number) => {
646
+ if (
647
+ !cell.isRowHeader && !cell.isColHeader && cell.tableColumn !== null && cell.cell.value !== null &&
648
+ cell.tableRowIndex !== null && renderColNames.indexOf(cell.tableColumn.name) !== -1) {
649
+ const tooltipMap: { [index: string]: string } = {};
650
+
651
+ for (const col of (statsDf.columns as DG.ColumnList).names()) {
652
+ if (col !== C.COLUMNS_NAMES.AMINO_ACID_RESIDUE && col !== C.COLUMNS_NAMES.POSITION) {
653
+ const currentPosition = cell.tableColumn.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
654
+ cell.tableColumn.name : cell.grid.table.get(C.COLUMNS_NAMES.POSITION, cell.tableRowIndex);
655
+ const query =
656
+ `${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ` +
657
+ `${cell.grid.table.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, cell.tableRowIndex)} ` +
658
+ `and ${C.COLUMNS_NAMES.POSITION} = ${currentPosition}`;
659
+ const textNum = statsDf.groupBy([col]).where(query).aggregate().get(col, 0);
660
+ let text = `${col === 'Count' ? textNum : textNum.toFixed(5)}`;
661
+
662
+ if (col === 'Count')
663
+ text += ` / ${peptidesCount}`;
664
+ else if (col === C.COLUMNS_NAMES.P_VALUE)
665
+ text = parseFloat(text) !== 0 ? text : '<0.01';
666
+
667
+
668
+ tooltipMap[col === C.COLUMNS_NAMES.P_VALUE ? 'p-value' : col] = text;
669
+ }
670
+ }
467
671
 
672
+ ui.tooltip.show(ui.tableFromMap(tooltipMap), x, y);
673
+ }
674
+ if (!cell.isColHeader && cell.tableColumn?.name == C.COLUMNS_NAMES.AMINO_ACID_RESIDUE) {
675
+ // if (grouping) {
676
+ // const currentGroup = C.groupDescription[cell.cell.value];
677
+ // const divText = ui.divText('Amino Acids in this group: ' + currentGroup[C.SEM_TYPES.AMINO_ACIDS].join(', '));
678
+ // ui.tooltip.show(ui.divV([ui.h3(currentGroup['description']), divText]), x, y);
679
+ // } else {
680
+ const monomerLib = sourceDf.temp[MonomerLibrary.id];
681
+ ChemPalette.showTooltip(cell, x, y, monomerLib);
682
+ }
683
+ return true;
684
+ };
685
+ sarGrid.onCellTooltip(onCellTooltipAction);
686
+ sarVGrid.onCellTooltip(onCellTooltipAction);
687
+ }
468
688
 
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;
689
+ setInteractionCallback() {
690
+ const sarDf = this._sarGrid.dataFrame;
691
+ const sarVDf = this._sarVGrid.dataFrame;
692
+
693
+ const getAARandPosition = (isVertical = false): [string, string] => {
694
+ let aar : string;
695
+ let position: string;
696
+ if (isVertical) {
697
+ const currentRowIdx = sarVDf.currentRowIdx;
698
+ aar = sarVDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, currentRowIdx);
699
+ position = sarVDf.get(C.COLUMNS_NAMES.POSITION, currentRowIdx);
700
+ } else {
701
+ aar = sarDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, sarDf.currentRowIdx);
702
+ position = sarDf.currentCol.name;
703
+ }
704
+ return [aar, position];
705
+ };
706
+
707
+ this._sarGrid.onCurrentCellChanged.subscribe((gc) => {
708
+ const isNegativeRowIndex = sarDf.currentRowIdx === -1;
709
+ if (!sarDf.currentCol || (!sarDf.currentCell.value && !isNegativeRowIndex))
710
+ return;
711
+ this.syncGrids(false, sarDf, sarVDf);
712
+ let aar: string = C.CATEGORIES.ALL;
713
+ let position: string = C.CATEGORIES.ALL;
714
+ if (!isNegativeRowIndex) {
715
+ [aar, position] = getAARandPosition();
716
+ this.dataFrame.tags[C.TAGS.AAR] = aar;
717
+ this.dataFrame.tags[C.TAGS.POSITION] = position;
718
+ } else {
719
+ this.dataFrame.tags[C.TAGS.AAR] = this.dataFrame.tags[C.TAGS.POSITION] = null;
720
+ }
721
+ this.dataFrame.temp['substTable'] = this.getSubstitutionTable();
722
+ this.modifyOrCreateSplitCol(aar, position);
723
+ this.fireBitsetChanged();
724
+ this.invalidateGrids();
725
+ grok.shell.o = this.dataFrame;
726
+ });
472
727
 
473
- const rCoef = (chooseCurrent() - chooseMin()) / (chooseMax() - chooseMin());
728
+ this._sarVGrid.onCurrentCellChanged.subscribe((gc) => {
729
+ if (!sarVDf.currentCol || sarVDf.currentRowIdx === -1)
730
+ return;
731
+ this.syncGrids(true, sarDf, sarVDf);
732
+ });
733
+ }
474
734
 
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);
735
+ invalidateGrids() {
736
+ this.stackedBarchart?.computeData();
737
+ this._sarGrid.invalidate();
738
+ this._sarVGrid.invalidate();
739
+ this._sourceGrid?.invalidate();
740
+ //TODO: this.peptideSpaceGrid.invalidate();
741
+ }
477
742
 
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();
743
+ setBitsetCallback() {
744
+ if (this.isBitsetChangedInitialized)
745
+ return;
746
+ const filter = this.dataFrame.filter;
747
+ const selection = this.dataFrame.selection;
748
+
749
+ const changeBitset = (currentBitset: DG.BitSet, previousBitset: DG.BitSet) => {
750
+ previousBitset.setAll(!this._filterMode, false);
751
+ currentBitset.init((i) => {
752
+ const currentCategory = this.splitCol.get(i);
753
+ return currentCategory !== C.CATEGORIES.OTHER && currentCategory !== C.CATEGORIES.ALL;
754
+ }, false);
755
+ };
756
+
757
+ const recalculateStatistics =
758
+ (bitset: DG.BitSet) => (this.dataFrame.temp[C.STATS] as FilteringStatistics).setMask(bitset);
759
+
760
+ filter.onChanged.subscribe(() => {
761
+ changeBitset(filter, selection);
762
+ recalculateStatistics(filter);
763
+ });
764
+ selection.onChanged.subscribe(() => {
765
+ changeBitset(selection, filter);
766
+ recalculateStatistics(selection);
767
+ });
768
+ this.isBitsetChangedInitialized = true;
769
+ }
485
770
 
486
- args.g.fill();
487
- args.preventDefault();
771
+ fireBitsetChanged() {(this._filterMode ? this._dataFrame.filter : this._dataFrame.selection).fireChanged();}
772
+
773
+ postProcessGrids(sourceGrid: DG.Grid, invalidIndexes: number[], sarGrid: DG.Grid, sarVGrid: DG.Grid) {
774
+ sourceGrid.onCellPrepare((cell: DG.GridCell) => {
775
+ const currentRowIndex = cell.tableRowIndex;
776
+ if (currentRowIndex && invalidIndexes.includes(currentRowIndex) && !cell.isRowHeader)
777
+ cell.style.backColor = DG.Color.lightLightGray;
778
+ });
779
+
780
+ const mdCol: DG.GridColumn = sarVGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
781
+ mdCol.name = 'Diff';
782
+
783
+ for (const grid of [sarGrid, sarVGrid]) {
784
+ grid.props.rowHeight = 20;
785
+ grid.columns.rowHeader!.width = 20;
786
+ for (let i = 0; i < grid.columns.length; ++i) {
787
+ const col = grid.columns.byIndex(i)!;
788
+ if (grid == sarVGrid && col.name !== 'Diff' && col.name !== C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)
789
+ col.width = 45;
790
+ else
791
+ col.width = grid.props.rowHeight;
488
792
  }
489
793
  }
490
- args.g.restore();
491
- };
492
- sarGrid.onCellRender.subscribe(cellRendererFunc);
493
- sarVGrid.onCellRender.subscribe(cellRendererFunc);
494
- }
495
794
 
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
- }
795
+ // if (grouping) {
796
+ // sarGrid.col(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)!.name = 'Groups';
797
+ // sarVGrid.col(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)!.name = 'Groups';
798
+ // }
525
799
 
526
- ui.tooltip.show(ui.tableFromMap(tooltipMap), x, y);
800
+ sarGrid.props.allowEdit = false;
801
+ sarVGrid.props.allowEdit = false;
802
+ }
803
+
804
+ split(peptideColumn: DG.Column, filter: boolean = true): string[][] {
805
+ const splitPeptidesArray: string[][] = [];
806
+ let currentSplitPeptide: string[];
807
+ let modeMonomerCount = 0;
808
+ let currentLength;
809
+ const colLength = peptideColumn.length;
810
+
811
+ // splitting data
812
+ const monomerLengths: { [index: string]: number } = {};
813
+ for (let i = 0; i < colLength; i++) {
814
+ currentSplitPeptide = peptideColumn.get(i).split('-').map((value: string) => value ? value : '-');
815
+ splitPeptidesArray.push(currentSplitPeptide);
816
+ currentLength = currentSplitPeptide.length;
817
+ monomerLengths[currentLength + ''] =
818
+ monomerLengths[currentLength + ''] ? monomerLengths[currentLength + ''] + 1 : 1;
527
819
  }
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);
820
+ //@ts-ignore: what I do here is converting string to number the most effective way I could find. parseInt is slow
821
+ modeMonomerCount = 1 * Object.keys(monomerLengths).reduce((a, b) => monomerLengths[a] > monomerLengths[b] ? a : b);
822
+
823
+ // making sure all of the sequences are of the same size
824
+ // and marking invalid sequences
825
+ let nTerminal: string;
826
+ const invalidIndexes: number[] = [];
827
+ let splitColumns: string[][] = Array.from({length: modeMonomerCount}, (_) => []);
828
+ modeMonomerCount--; // minus N-terminal
829
+ for (let i = 0; i < colLength; i++) {
830
+ currentSplitPeptide = splitPeptidesArray[i];
831
+ nTerminal = currentSplitPeptide.pop()!; // it is guaranteed that there will be at least one element
832
+ currentLength = currentSplitPeptide.length;
833
+ if (currentLength !== modeMonomerCount)
834
+ invalidIndexes.push(i);
835
+
836
+ for (let j = 0; j < modeMonomerCount; j++)
837
+ splitColumns[j].push(j < currentLength ? currentSplitPeptide[j] : '-');
838
+
839
+ splitColumns[modeMonomerCount].push(nTerminal);
840
+ }
841
+ modeMonomerCount--; // minus C-terminal
842
+
843
+ //create column names list
844
+ const columnNames = Array.from({length: modeMonomerCount}, (_, index) => `${index + 1 < 10 ? 0 : ''}${index + 1}`);
845
+ columnNames.splice(0, 0, 'N-terminal');
846
+ columnNames.push('C-terminal');
847
+
848
+ // filter out the columns with the same values
849
+ if (filter) {
850
+ splitColumns = splitColumns.filter((positionArray, index) => {
851
+ const isRetained = new Set(positionArray).size > 1;
852
+ if (!isRetained)
853
+ columnNames.splice(index, 1);
854
+
855
+ return isRetained;
856
+ });
857
+ }
858
+
859
+ return splitPeptidesArray;
860
+ }
861
+
862
+ getSubstitutionTable() {
863
+ if (!this._casesTable)
864
+ this.calcSubstitutions();
865
+ const sarDf = this._sarGrid.dataFrame;
866
+ const sourceDf = this._sourceGrid.dataFrame;
867
+ if (sarDf.currentRowIdx === -1)
868
+ return null;
869
+ const currentColName = sarDf.currentCol.name;
870
+ if (currentColName !== C.COLUMNS_NAMES.AMINO_ACID_RESIDUE) {
871
+ const col: DG.Column = sourceDf.columns.bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE);
872
+ const aar = sarDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, sarDf.currentRowIdx);
873
+ const pos = parseInt(currentColName);
874
+ const substitutionsCount = this.substitutionTable.groupBy([currentColName])
875
+ .where(`${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ${aar}`)
876
+ .aggregate()
877
+ .get(currentColName, 0);
878
+ if (substitutionsCount === DG.INT_NULL)
879
+ return null;
880
+ const currentCase = this._casesTable[aar][pos];
881
+ const tempDfLength = currentCase.length;
882
+ const initCol = DG.Column.string('Initial', tempDfLength);
883
+ const subsCol = DG.Column.string('Substituted', tempDfLength);
884
+
885
+ const tempDf = DG.DataFrame.fromColumns([
886
+ initCol,
887
+ subsCol,
888
+ DG.Column.float('Difference', tempDfLength),
889
+ ]);
890
+
891
+ for (let i = 0; i < tempDfLength; i++) {
892
+ const row = currentCase[i];
893
+ tempDf.rows.setValues(i, [col.get(row[0]), col.get(row[1]), row[2]]);
542
894
  }
895
+
896
+ // tempDf.temp['isReal'] = true;
897
+
898
+ initCol.semType = C.SEM_TYPES.ALIGNED_SEQUENCE;
899
+ initCol.temp['isAnalysisApplicable'] = false;
900
+ subsCol.semType = C.SEM_TYPES.ALIGNED_SEQUENCE;
901
+ subsCol.temp['isAnalysisApplicable'] = false;
902
+
903
+ // grok.shell.o = DG.SemanticValue.fromValueType(tempDf, 'Substitution');
904
+ return tempDf;
543
905
  }
544
- return true;
545
- };
546
- sarGrid.onCellTooltip(onCellTooltipFunc);
547
- sarVGrid.onCellTooltip(onCellTooltipFunc);
548
- }
906
+ return null;
907
+ }
908
+
909
+ //TODO: refactor, use this.sarDf and accept aar & position as parameters
910
+ syncGrids(sourceVertical: boolean, sarDf: DG.DataFrame, sarVDf: DG.DataFrame) {
911
+ let otherColName: string;
912
+ let otherRowIndex: number;
913
+ const otherDf = sourceVertical ? sarDf : sarVDf;
914
+
915
+ if (otherDf.temp[C.FLAGS.CELL_CHANGING])
916
+ return;
549
917
 
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;
918
+ //on vertical SAR viewer click
919
+ if (sourceVertical) {
920
+ const currentRowIdx = sarVDf.currentRowIdx;
921
+ const currentColName = sarVDf.currentCol.name;
922
+ if (currentColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE)
923
+ return;
924
+
925
+ otherColName = sarVDf.get(C.COLUMNS_NAMES.POSITION, currentRowIdx);
926
+ const otherRowName: string = sarVDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, currentRowIdx);
927
+ otherRowIndex = -1;
928
+ const rows = otherDf.rowCount;
929
+ for (let i = 0; i < rows; i++) {
930
+ if (otherDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, i) === otherRowName) {
931
+ otherRowIndex = i;
932
+ break;
933
+ }
934
+ }
935
+ //on SAR viewer click
936
+ } else {
937
+ otherColName = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
938
+ const otherPos: string = sarDf.currentCol.name;
939
+ if (otherPos === C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)
940
+ return;
941
+
942
+ const otherAAR: string =
943
+ sarDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, sarDf.currentRowIdx);
944
+ otherRowIndex = -1;
945
+ for (let i = 0; i < sarVDf.rowCount; i++) {
946
+ if (
947
+ sarVDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, i) === otherAAR &&
948
+ sarVDf.get(C.COLUMNS_NAMES.POSITION, i) === otherPos
949
+ ) {
950
+ otherRowIndex = i;
951
+ break;
952
+ }
953
+ }
572
954
  }
955
+ otherDf.temp[C.FLAGS.CELL_CHANGING] = true;
956
+ otherDf.currentCell = otherDf.cell(otherRowIndex, otherColName);
957
+ otherDf.temp[C.FLAGS.CELL_CHANGING] = false;
573
958
  }
574
959
 
575
- if (grouping) {
576
- sarGrid.col(aminoAcidResidue)!.name = 'Groups';
577
- sarVGrid.col(aminoAcidResidue)!.name = 'Groups';
960
+ getSplitColValueAt(index: number, aar: string, position: string, aarLabel: string): string {
961
+ const currentAAR = this.dataFrame.get(position, index) as string;
962
+ return currentAAR === aar ? aarLabel : C.CATEGORIES.OTHER;
578
963
  }
579
964
 
580
- sarGrid.props.allowEdit = false;
581
- sarVGrid.props.allowEdit = false;
965
+ modifyOrCreateSplitCol(aar: string, position: string): void {
966
+ const df = this.dataFrame;
967
+ this.splitCol = df.col(C.COLUMNS_NAMES.SPLIT_COL) ??
968
+ df.columns.addNew(C.COLUMNS_NAMES.SPLIT_COL, 'string') as DG.Column;
969
+
970
+ if (aar === C.CATEGORIES.ALL && position === C.CATEGORIES.ALL) {
971
+ this.splitCol.init(() => C.CATEGORIES.ALL);
972
+ return;
973
+ }
974
+
975
+ const aarLabel = `${aar === '-' ? 'Gap' : aar} : ${position}`;
976
+ this.splitCol.init((i) => this.getSplitColValueAt(i, aar, position, aarLabel));
977
+
978
+ // splitCol.init((i) => bitset.get(i) ? aarLabel : C.CATEGORY_OTHER);
979
+ this.splitCol.setCategoryOrder([aarLabel]);
980
+ this.splitCol.compact();
981
+
982
+ const colorMap: {[index: string]: string | number} = {};
983
+
984
+ colorMap[C.CATEGORIES.OTHER] = DG.Color.blue;
985
+ colorMap[aarLabel] = DG.Color.orange;
986
+ // colorMap[currentAAR] = cp.getColor(currentAAR);
987
+ df.getCol(C.COLUMNS_NAMES.SPLIT_COL).colors.setCategorical(colorMap);
988
+ }
582
989
  }