@datagrok/peptides 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/package-test.js +8275 -3759
  2. package/dist/package.js +19735 -14773
  3. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +306 -215
  4. package/files/aligned.csv +648 -648
  5. package/files/aligned_2.csv +541 -10275
  6. package/files/aligned_3.csv +335 -0
  7. package/helm/JSDraw/Pistoia.HELM.js +27 -0
  8. package/package.json +17 -12
  9. package/src/__jest__/remote.test.ts +31 -13
  10. package/src/model.ts +321 -342
  11. package/src/package-test.ts +0 -1
  12. package/src/package.ts +13 -109
  13. package/src/tests/core.ts +48 -16
  14. package/src/tests/utils.ts +0 -11
  15. package/src/utils/cell-renderer.ts +92 -275
  16. package/src/utils/constants.ts +5 -4
  17. package/src/utils/misc.ts +103 -16
  18. package/src/utils/multiple-sequence-alignment.ts +1 -1
  19. package/src/utils/peptide-similarity-space.ts +1 -1
  20. package/src/utils/types.ts +7 -5
  21. package/src/viewers/peptide-space-viewer.ts +3 -3
  22. package/src/viewers/sar-viewer.ts +34 -23
  23. package/src/widgets/analyze-peptides.ts +23 -17
  24. package/src/widgets/distribution.ts +0 -1
  25. package/src/widgets/manual-alignment.ts +4 -4
  26. package/src/widgets/subst-table.ts +5 -2
  27. package/{test-Peptides-34f75e5127b8-4210edfc.html → test-Peptides-4f0c8bae6479-74cbfe68.html} +8 -8
  28. package/detectors.js +0 -9
  29. package/src/monomer-library.ts +0 -193
  30. package/src/tests/msa-tests.ts +0 -27
  31. package/src/utils/chem-palette.ts +0 -280
  32. package/src/viewers/stacked-barchart-viewer.ts +0 -321
  33. package/src/widgets/multiple-sequence-alignment.ts +0 -9
  34. package/src/widgets/peptide-molecule.ts +0 -82
package/src/model.ts CHANGED
@@ -3,24 +3,21 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {Subject, Observable} from 'rxjs';
6
- import {addViewerToHeader, StackedBarChart} from './viewers/stacked-barchart-viewer';
7
- import {tTest} from '@datagrok-libraries/statistics/src/tests';
8
- import {fdrcorrection} from '@datagrok-libraries/statistics/src/multiple-tests';
9
- import {ChemPalette} from './utils/chem-palette';
10
- import {MonomerLibrary} from './monomer-library';
11
6
  import * as C from './utils/constants';
12
7
  import * as type from './utils/types';
13
- import {getSeparator, getTypedArrayConstructor, stringToBool} from './utils/misc';
8
+ import {calculateBarsData, getTypedArrayConstructor, scaleActivity, splitAlignedPeptides} from './utils/misc';
14
9
  import {_package} from './package';
15
- import {SARViewer, SARViewerVertical} from './viewers/sar-viewer';
10
+ import {SARViewer, SARViewerBase, SARViewerVertical} from './viewers/sar-viewer';
16
11
  import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
17
- import {renderSARCell, setAARRenderer} from './utils/cell-renderer';
12
+ import {renderBarchart, renderSARCell, setAARRenderer} from './utils/cell-renderer';
18
13
  import {substitutionsWidget} from './widgets/subst-table';
19
14
  import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
20
15
  import {getStats, Stats} from './utils/filtering-statistics';
16
+ import * as rxjs from 'rxjs';
17
+
21
18
 
22
19
  export class PeptidesModel {
23
- static _modelName = 'peptidesModel';
20
+ static modelName = 'peptidesModel';
24
21
 
25
22
  _statsDataFrameSubject = new Subject<DG.DataFrame>();
26
23
  _sarGridSubject = new Subject<DG.Grid>();
@@ -32,20 +29,11 @@ export class PeptidesModel {
32
29
  isBitsetChangedInitialized = false;
33
30
  isCellChanging = false;
34
31
 
35
- //viewer properties
36
- _filterMode!: boolean;
37
- _twoColorMode!: boolean;
38
- _activityScaling!: string;
39
- _isSubstitutionOn!: boolean;
40
- _activityLimit!: number;
41
- _maxSubstitutions!: number;
42
-
43
32
  _sarGrid!: DG.Grid;
44
33
  _sarVGrid!: DG.Grid;
45
34
  _sourceGrid!: DG.Grid;
46
- _dataFrame: DG.DataFrame;
35
+ df: DG.DataFrame;
47
36
  splitCol!: DG.Column<boolean>;
48
- stackedBarchart!: StackedBarChart;
49
37
  edf: DG.DataFrame | null = null;
50
38
  statsDf!: DG.DataFrame;
51
39
  _currentSelection!: type.SelectionObject;
@@ -55,28 +43,27 @@ export class PeptidesModel {
55
43
 
56
44
  isPeptideSpaceChangingBitset = false;
57
45
  isChangingEdfBitset = false;
58
- splitByPos = false;
59
- splitByAAR = false;
46
+
47
+ sarViewer!: SARViewer;
48
+ sarViewerVertical!: SARViewerVertical;
49
+
50
+ _usedProperties: {[propName: string]: string | number | boolean} = {};
51
+ monomerMap: {[key: string]: {molfile: string, fullName: string}} = {};
52
+ barData: type.MonomerDfStats = {};
53
+ barsBounds: {[position: string]: type.BarCoordinates} = {};
54
+ cachedBarchartTooltip: {bar: string, tooltip: null | HTMLDivElement} = {bar: '', tooltip: null};
55
+ monomerLib: any;
60
56
 
61
57
  private constructor(dataFrame: DG.DataFrame) {
62
- this._dataFrame = dataFrame;
63
- this.updateProperties();
58
+ this.df = dataFrame;
64
59
  }
65
60
 
66
- static async getInstance(dataFrame: DG.DataFrame, dgPackage?: DG.Package): Promise<PeptidesModel> {
67
- if (dataFrame.temp[MonomerLibrary.id] === null) {
68
- const sdf = await (dgPackage ?? _package).files.readAsText('HELMMonomers_June10.sdf');
69
- dataFrame.temp[MonomerLibrary.id] ??= new MonomerLibrary(sdf);
70
- }
61
+ static async getInstance(dataFrame: DG.DataFrame): Promise<PeptidesModel> {
71
62
  dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
72
63
  await (dataFrame.temp[PeptidesModel.modelName] as PeptidesModel).init();
73
64
  return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
74
65
  }
75
66
 
76
- static get modelName(): string {return PeptidesModel._modelName;}
77
-
78
- static get chemPalette(): typeof ChemPalette {return ChemPalette;}
79
-
80
67
  get onStatsDataFrameChanged(): Observable<DG.DataFrame> {return this._statsDataFrameSubject.asObservable();}
81
68
 
82
69
  get onSARGridChanged(): Observable<DG.Grid> {return this._sarGridSubject.asObservable();}
@@ -86,13 +73,41 @@ export class PeptidesModel {
86
73
  get onSubstTableChanged(): Observable<type.SubstitutionsInfo> {return this._substitutionTableSubject.asObservable();}
87
74
 
88
75
  get currentSelection(): type.SelectionObject {
89
- this._currentSelection ??= JSON.parse(this._dataFrame.tags[C.TAGS.SELECTION] || '{}');
76
+ this._currentSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
90
77
  return this._currentSelection;
91
78
  }
92
79
  set currentSelection(selection: type.SelectionObject) {
93
80
  this._currentSelection = selection;
94
- this._dataFrame.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
81
+ this.df.tags[C.TAGS.SELECTION] = JSON.stringify(selection);
95
82
  this.invalidateSelection();
83
+ this.barData = calculateBarsData(this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER), this.df.selection);
84
+ }
85
+
86
+ get usedProperties(): {[propName: string]: string | number | boolean} {
87
+ this._usedProperties = JSON.parse(this.df.tags['sarProperties'] ?? '{}');
88
+ return this._usedProperties;
89
+ }
90
+ set usedProperties(properties: {[propName: string]: string | number | boolean}) {
91
+ this.df.tags['sarProperties'] = JSON.stringify(properties);
92
+ this._usedProperties = properties;
93
+ }
94
+
95
+ get splitByPos() {
96
+ const splitByPosFlag = (this.df.tags['distributionSplit'] ?? '00')[0];
97
+ return splitByPosFlag == '1' ? true : false;
98
+ }
99
+ set splitByPos(flag: boolean) {
100
+ const splitByAARFlag = (this.df.tags['distributionSplit'] ?? '00')[1];
101
+ this.df.tags['distributionSplit'] = `${flag ? 1 : 0}${splitByAARFlag}`;
102
+ }
103
+
104
+ get splitByAAR() {
105
+ const splitByPosFlag = (this.df.tags['distributionSplit'] ?? '00')[1];
106
+ return splitByPosFlag == '1' ? true : false;
107
+ }
108
+ set splitByAAR(flag: boolean) {
109
+ const splitByAARFlag = (this.df.tags['distributionSplit'] ?? '00')[0];
110
+ this.df.tags['distributionSplit'] = `${splitByAARFlag}${flag ? 1 : 0}`;
96
111
  }
97
112
 
98
113
  invalidateSelection(): void {
@@ -103,111 +118,87 @@ export class PeptidesModel {
103
118
  createAccordion() {
104
119
  const acc = ui.accordion();
105
120
  acc.root.style.width = '100%';
106
- acc.addTitle(ui.h1(`${this.getBiteset().trueCount} selected rows`));
107
- acc.addPane('Substitutions', () => substitutionsWidget(this._dataFrame, this).root, true);
108
- acc.addPane('Distribtution', () => getDistributionWidget(this._dataFrame, this).root, true);
121
+ acc.addTitle(ui.h1(`${this.df.selection.trueCount} selected rows`));
122
+ acc.addPane('Substitutions', () => substitutionsWidget(this.df, this).root, true);
123
+ acc.addPane('Distribtution', () => getDistributionWidget(this.df, this).root, true);
109
124
 
110
125
  return acc;
111
126
  }
112
127
 
113
- updateProperties(): void {
114
- this._activityScaling = this._dataFrame.tags['scaling'];
115
- this._filterMode = stringToBool(this._dataFrame.tags['filterMode']);
116
- this._twoColorMode = stringToBool(this._dataFrame.tags['bidirectionalAnalysis']);
117
- this._isSubstitutionOn = stringToBool(this._dataFrame.tags['showSubstitution']);
118
- this._maxSubstitutions = parseInt(this._dataFrame.tags['maxSubstitutions']);
119
- this._activityLimit = parseFloat(this._dataFrame.tags['activityLimit']);
120
- }
121
-
122
- setProperties(
123
- activityScaling: string, filterMode: boolean, twoColorMode: boolean, isSubstitutionOn: boolean,
124
- maxSubstitutions: number, activityLimit: number, forceUpdate = false,
125
- ): void {
126
- const chooseAction =
127
- (value: string, defaultValue: string | boolean | number): string | boolean | number =>
128
- forceUpdate ? value : defaultValue ?? value;
129
- this._dataFrame.tags['scaling'] = chooseAction(`${activityScaling}`, this._dataFrame.tags['scaling']);
130
- this._dataFrame.tags['filterMode'] = chooseAction(`${filterMode}`, this._dataFrame.tags['filterMode']);
131
- this._dataFrame.tags['bidirectionalAnalysis'] =
132
- chooseAction(`${twoColorMode}`, this._dataFrame.tags['bidirectionalAnalysis']);
133
- this._dataFrame.tags['showSubstitution'] =
134
- chooseAction(`${isSubstitutionOn}`, this._dataFrame.tags['showSubstitution']);
135
- this._dataFrame.tags['maxSubstitutions'] =
136
- chooseAction(`${maxSubstitutions}`, this._dataFrame.tags['maxSubstitutions']);
137
- this._dataFrame.tags['activityLimit'] = chooseAction(`${activityLimit}`, this._dataFrame.tags['activityLimit']);
138
-
139
- this.updateProperties();
140
- }
141
-
142
- async updateData(
143
- activityScaling?: string, sourceGrid?: DG.Grid, twoColorMode?: boolean, activityLimit?: number,
144
- maxSubstitutions?: number, isSubstitutionOn?: boolean, filterMode?: boolean,
145
- ): Promise<void> {
146
- //FIXME: threre are too many assignments, some are duplicating
147
- this._activityScaling = activityScaling ?? this._activityScaling;
148
- this._sourceGrid = sourceGrid ?? this._sourceGrid;
149
- this._twoColorMode = twoColorMode ?? this._twoColorMode;
150
- this._activityLimit = activityLimit ?? this._activityLimit;
151
- this._maxSubstitutions = maxSubstitutions ?? this._maxSubstitutions;
152
- this._isSubstitutionOn = isSubstitutionOn ?? this._isSubstitutionOn;
153
- this._filterMode = filterMode ?? this._filterMode;
154
- this.setProperties(this._activityScaling, this._filterMode, this._twoColorMode, this._isSubstitutionOn,
155
- this._maxSubstitutions, this._activityLimit, true);
156
-
157
- await this.updateDefault();
158
- }
159
-
160
- async updateDefault(): Promise<void> {
161
- if (this._activityScaling && this._sourceGrid && this._twoColorMode !== null && !this._isUpdating) {
128
+ getViewer(): SARViewerBase {
129
+ const viewer = this.sarViewer ?? this.sarViewerVertical;
130
+ if (!viewer)
131
+ throw new Error('ViewerError: none of the SAR viewers is initialized');
132
+ return viewer;
133
+ }
134
+
135
+ isPropertyChanged(viewer: SARViewerBase): boolean {
136
+ // const viewer = this.getViewer();
137
+ const viewerProps = viewer.props.getProperties();
138
+ let result = false;
139
+ const tempProps = this.usedProperties;
140
+ for (const property of viewerProps) {
141
+ const propName = property.name;
142
+ const propVal = property.get(viewer);
143
+ if (tempProps[propName] != propVal) {
144
+ tempProps[propName] = propVal;
145
+ result = true;
146
+ }
147
+ }
148
+ this.usedProperties = tempProps;
149
+ return result;
150
+ }
151
+
152
+ updateDefault(): void {
153
+ const viewer = this.getViewer();
154
+ const proprtyChanged = this.isPropertyChanged(this.sarViewer) || this.isPropertyChanged(this.sarViewerVertical);
155
+ if ((this._sourceGrid && !this._isUpdating && proprtyChanged) || !this.isInitialized) {
156
+ this.isInitialized = true;
162
157
  this._isUpdating = true;
163
- const [viewerGrid, viewerVGrid, statsDf] = await this.initializeViewersComponents();
158
+ const [viewerGrid, viewerVGrid, statsDf] = this.initializeViewersComponents();
164
159
  //FIXME: modify during the initializeViewersComponents stages
165
160
  this._statsDataFrameSubject.next(statsDf);
166
161
  this._sarGridSubject.next(viewerGrid);
167
162
  this._sarVGridSubject.next(viewerVGrid);
168
- if (this._isSubstitutionOn) {
163
+ if (viewer.showSubstitution) {
169
164
  this._substitutionTableSubject.next(this.substitutionsInfo);
170
165
  this._isSubstInitialized = true;
171
166
  }
172
- }
173
- await this.updateBarchart();
174
- this.invalidateSelection();
175
167
 
176
- this._isUpdating = false;
177
- }
168
+ this.invalidateSelection();
178
169
 
179
- async updateBarchart(): Promise<void> {
180
- this.stackedBarchart ??= await this._dataFrame?.plot.fromType('StackedBarChartAA') as StackedBarChart;
181
- if (this.stackedBarchart && this._sourceGrid)
182
- addViewerToHeader(this._sourceGrid, this.stackedBarchart);
170
+ this._isUpdating = false;
171
+ }
183
172
  }
184
173
 
185
- async initializeViewersComponents(): Promise<[DG.Grid, DG.Grid, DG.DataFrame]> {
174
+ initializeViewersComponents(): [DG.Grid, DG.Grid, DG.DataFrame] {
186
175
  if (this._sourceGrid === null)
187
176
  throw new Error(`Source grid is not initialized`);
188
177
 
189
178
  //Split the aligned sequence into separate AARs
190
- let splitSeqDf: DG.DataFrame | undefined;
191
- let invalidIndexes: number[];
192
- const col: DG.Column = this._dataFrame.columns.bySemType(C.SEM_TYPES.ALIGNED_SEQUENCE)!;
193
- [splitSeqDf, invalidIndexes] = PeptidesModel.splitAlignedPeptides(col);
179
+ const col: DG.Column = this.df.columns.bySemType(C.SEM_TYPES.MACROMOLECULE)!;
180
+ const alphabet = col.tags[DG.TAGS.UNITS].split(':')[2];
181
+ const splitSeqDf = splitAlignedPeptides(col);
182
+
183
+ this.barData = calculateBarsData(splitSeqDf.columns.toList(), this.df.selection);
194
184
 
195
185
  const positionColumns = splitSeqDf.columns.names();
196
- const renderColNames: string[] = splitSeqDf.columns.names();
197
186
 
198
- const activityCol = this._dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
187
+ const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
199
188
  splitSeqDf.columns.add(activityCol);
200
189
 
201
190
  this.joinDataFrames(positionColumns, splitSeqDf);
202
191
 
203
- for (const dfCol of this._dataFrame.columns) {
192
+ for (const dfCol of this.df.columns) {
204
193
  if (positionColumns.includes(dfCol.name))
205
- setAARRenderer(dfCol, this._sourceGrid);
194
+ setAARRenderer(dfCol, alphabet, this._sourceGrid);
206
195
  }
207
196
 
208
- this.sortSourceGrid(this._sourceGrid);
197
+ this.sortSourceGrid();
209
198
 
210
- await this.createScaledCol(this._activityScaling, this._dataFrame, this._sourceGrid, splitSeqDf);
199
+ const viewer = this.getViewer();
200
+
201
+ this.createScaledCol(viewer.scaling, splitSeqDf);
211
202
 
212
203
  //unpivot a table and handle duplicates
213
204
  let matrixDf = splitSeqDf.groupBy(positionColumns).aggregate();
@@ -226,66 +217,68 @@ export class PeptidesModel {
226
217
  matrixDf.name = 'SAR';
227
218
 
228
219
  // Setting category order
229
- await this.setCategoryOrder(matrixDf);
220
+ this.setCategoryOrder(matrixDf);
230
221
 
231
222
  // SAR vertical table (naive, choose best Mean difference from pVals <= 0.01)
232
223
  const sequenceDf = this.createVerticalTable();
233
- renderColNames.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
234
224
 
235
- if (this._isSubstitutionOn || !this._isSubstInitialized)
225
+ if (viewer.showSubstitution || !this._isSubstInitialized)
236
226
  this.calcSubstitutions();
237
227
 
238
- //TODO: move everything below out to controller
239
- const [sarGrid, sarVGrid] = this.createGrids(matrixDf, positionColumns, sequenceDf);
228
+ const [sarGrid, sarVGrid] = this.createGrids(matrixDf, positionColumns, sequenceDf, alphabet);
240
229
 
241
230
  this._sarGrid = sarGrid;
242
231
  this._sarVGrid = sarVGrid;
243
232
 
244
- this.setCellRenderers(renderColNames, sarGrid, sarVGrid);
233
+ positionColumns.push(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
234
+
235
+ this.setBarChartInteraction();
236
+
237
+ this.setCellRenderers(positionColumns, sarGrid, sarVGrid);
245
238
 
246
239
  // show all the statistics in a tooltip over cell
247
- this.setTooltips(renderColNames, sarGrid, sarVGrid);
240
+ this.setTooltips(positionColumns, sarGrid, sarVGrid);
248
241
 
249
242
  this.setInteractionCallback();
250
243
 
251
244
  this.setBitsetCallback();
252
245
 
253
- this.postProcessGrids(this._sourceGrid, invalidIndexes, sarGrid, sarVGrid);
246
+ this.postProcessGrids(sarGrid, sarVGrid);
254
247
 
255
248
  //TODO: return class instead
256
249
  return [sarGrid, sarVGrid, this.statsDf];
257
250
  }
258
251
 
259
- //TODO: move to controller?
260
252
  calcSubstitutions(): void {
261
- const activityValues: DG.Column<number> = this._dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
262
- const columnList: DG.Column<string>[] = this._dataFrame.columns.bySemTypeAll(C.SEM_TYPES.AMINO_ACIDS);
253
+ const activityValues: DG.Column<number> = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
254
+ const columnList: DG.Column<string>[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER);
263
255
  const nCols = columnList.length;
264
256
  if (nCols == 0)
265
- throw new Error(`Couldn't find any column of semType '${C.SEM_TYPES.AMINO_ACIDS}'`);
257
+ throw new Error(`Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
266
258
 
259
+ const viewer = this.getViewer();
267
260
  this.substitutionsInfo = new Map();
268
- const nRows = this._dataFrame.rowCount;
261
+ const nRows = this.df.rowCount;
269
262
  for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
270
263
  for (let seq2Idx = seq1Idx + 1; seq2Idx < nRows; seq2Idx++) {
271
264
  let substCounter = 0;
272
- const activityValSeq1 = activityValues.get(seq1Idx);
273
- const activityValSeq2 = activityValues.get(seq2Idx);
265
+ const activityValSeq1 = activityValues.get(seq1Idx)!;
266
+ const activityValSeq2 = activityValues.get(seq2Idx)!;
274
267
  const delta = activityValSeq1 - activityValSeq2;
275
- if (Math.abs(delta) < this._activityLimit)
268
+ if (Math.abs(delta) < viewer.minActivityDelta)
276
269
  continue;
277
270
 
278
271
  let substCounterFlag = false;
279
272
  const tempData: {pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number}[] =
280
273
  [];
281
274
  for (const currentPosCol of columnList) {
282
- const seq1monomer = currentPosCol.get(seq1Idx);
283
- const seq2monomer = currentPosCol.get(seq2Idx);
275
+ const seq1monomer = currentPosCol.get(seq1Idx)!;
276
+ const seq2monomer = currentPosCol.get(seq2Idx)!;
284
277
  if (seq1monomer == seq2monomer)
285
278
  continue;
286
279
 
287
280
  substCounter++;
288
- substCounterFlag = substCounter > this._maxSubstitutions;
281
+ substCounterFlag = substCounter > viewer.maxSubstitutions;
289
282
  if (substCounterFlag)
290
283
  break;
291
284
 
@@ -343,58 +336,52 @@ export class PeptidesModel {
343
336
 
344
337
  joinDataFrames(positionColumns: string[], splitSeqDf: DG.DataFrame): void {
345
338
  // append splitSeqDf columns to source table and make sure columns are not added more than once
346
- const name = this._dataFrame.name;
347
- const dfColsSet = new Set(this._dataFrame.columns.names());
339
+ const name = this.df.name;
340
+ const dfColsSet = new Set(this.df.columns.names());
348
341
  if (!positionColumns.every((col: string) => dfColsSet.has(col))) {
349
- this._dataFrame.join(splitSeqDf, [C.COLUMNS_NAMES.ACTIVITY], [C.COLUMNS_NAMES.ACTIVITY],
350
- this._dataFrame.columns.names(), positionColumns, 'inner', true);
342
+ this.df.join(splitSeqDf, [C.COLUMNS_NAMES.ACTIVITY], [C.COLUMNS_NAMES.ACTIVITY],
343
+ this.df.columns.names(), positionColumns, 'inner', true);
351
344
  }
352
- this._dataFrame.name = name;
345
+ this.df.name = name;
353
346
  this.currentView.name = name;
354
347
  }
355
348
 
356
- sortSourceGrid(sourceGrid: DG.Grid): void {
357
- if (sourceGrid) {
358
- const colNames: DG.GridColumn[] = [];
359
- for (let i = 1; i < sourceGrid.columns.length; i++)
360
- colNames.push(sourceGrid.columns.byIndex(i)!);
349
+ sortSourceGrid(): void {
350
+ const colNames: DG.GridColumn[] = [];
351
+ for (let i = 1; i < this._sourceGrid.columns.length; i++)
352
+ colNames.push(this._sourceGrid.columns.byIndex(i)!);
361
353
 
362
- colNames.sort((a, b)=>{
363
- if (a.column!.semType == C.SEM_TYPES.AMINO_ACIDS) {
364
- if (b.column!.semType == C.SEM_TYPES.AMINO_ACIDS)
365
- return 0;
366
- return -1;
367
- }
368
- if (b.column!.semType == C.SEM_TYPES.AMINO_ACIDS)
369
- return 1;
370
- return 0;
371
- });
372
- sourceGrid.columns.setOrder(colNames.map((v) => v.name));
373
- }
354
+ colNames.sort((a, b)=>{
355
+ if (a.column!.semType == C.SEM_TYPES.MONOMER) {
356
+ if (b.column!.semType == C.SEM_TYPES.MONOMER)
357
+ return 0;
358
+ return -1;
359
+ }
360
+ if (b.column!.semType == C.SEM_TYPES.MONOMER)
361
+ return 1;
362
+ return 0;
363
+ });
364
+ this._sourceGrid.columns.setOrder(colNames.map((v) => v.name));
374
365
  }
375
366
 
376
- //TODO: move out, merge with controller's scaleActivity
377
- async createScaledCol(
378
- activityScaling: string, df: DG.DataFrame, sourceGrid: DG.Grid, splitSeqDf: DG.DataFrame,
379
- ): Promise<void> {
380
- const [scaledDf, newColName] = await PeptidesModel.scaleActivity(
381
- activityScaling, df, df.tags[C.COLUMNS_NAMES.ACTIVITY]);
367
+ createScaledCol(activityScaling: string, splitSeqDf: DG.DataFrame): void {
368
+ const [scaledDf, newColName] =
369
+ scaleActivity(activityScaling, this.df, this.df.tags[C.COLUMNS_NAMES.ACTIVITY]);
382
370
  //TODO: make another func
383
371
  const scaledCol = scaledDf.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
384
372
  scaledCol.semType = C.SEM_TYPES.ACTIVITY_SCALED;
385
373
  splitSeqDf.columns.add(scaledCol);
386
- const oldScaledCol = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
387
- df.columns.replace(oldScaledCol, scaledCol);
388
- const gridCol = sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED);
374
+ const oldScaledCol = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
375
+ this.df.columns.replace(oldScaledCol, scaledCol);
376
+ const gridCol = this._sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED);
389
377
  if (gridCol !== null) {
390
378
  gridCol.name = newColName;
391
- df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED] = newColName;
379
+ this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED] = newColName;
392
380
  }
393
381
 
394
- sourceGrid.columns.setOrder([newColName]);
382
+ this._sourceGrid.columns.setOrder([newColName]);
395
383
  }
396
384
 
397
- //TODO: move out
398
385
  calculateStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
399
386
  matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]).aggregate();
400
387
 
@@ -406,16 +393,15 @@ export class PeptidesModel {
406
393
  const ratioCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
407
394
  const aarCol = matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
408
395
  const posCol = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
409
- const activityCol: number[] = this._dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
396
+ const activityCol: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
410
397
  const sourceDfLen = activityCol.length;
411
398
 
412
399
  for (let i = 0; i < matrixDf.rowCount; i++) {
413
400
  const position: string = posCol.get(i);
414
401
  const aar: string = aarCol.get(i);
415
- const mask = DG.BitSet.create(sourceDfLen, (j) => this._dataFrame.get(position, j) == aar);
402
+ const mask = DG.BitSet.create(sourceDfLen, (j) => this.df.get(position, j) == aar);
416
403
  const stats = getStats(activityCol, mask);
417
404
 
418
- //TODO: store as object in a single column
419
405
  mdCol.set(i, stats.meanDifference);
420
406
  pValCol.set(i, stats.pValue);
421
407
  countCol.set(i, stats.count);
@@ -429,11 +415,14 @@ export class PeptidesModel {
429
415
  return matrixDf as DG.DataFrame;
430
416
  }
431
417
 
432
- async setCategoryOrder(matrixDf: DG.DataFrame): Promise<void> {
433
- const absMD = 'Absolute Mean difference';
434
- const sortArgument = this._twoColorMode ? absMD : C.COLUMNS_NAMES.MEAN_DIFFERENCE;
435
- if (this._twoColorMode)
436
- await this.statsDf.columns.addNewCalculated(absMD, 'Abs(${Mean difference})');
418
+ setCategoryOrder(matrixDf: DG.DataFrame): void {
419
+ let sortArgument: string = C.COLUMNS_NAMES.MEAN_DIFFERENCE;
420
+ if (this.getViewer().bidirectionalAnalysis) {
421
+ const mdCol = this.statsDf.getCol(sortArgument);
422
+ sortArgument = 'Absolute Mean difference';
423
+ const absMDCol = this.statsDf.columns.addNewFloat(sortArgument);
424
+ absMDCol.init((i) => Math.abs(mdCol.get(i)));
425
+ }
437
426
 
438
427
  const aarWeightsDf = this.statsDf.groupBy([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]).sum(sortArgument, 'weight')
439
428
  .aggregate();
@@ -464,7 +453,7 @@ export class PeptidesModel {
464
453
  const rowCount = sequenceDf.rowCount;
465
454
  for (const pos of posColCategories) {
466
455
  tempStats = DG.Stats.fromColumn(mdCol, DG.BitSet.create(rowCount, (i) => posCol.get(i) === pos));
467
- maxAtPos[pos] = this._twoColorMode ?
456
+ maxAtPos[pos] = this.getViewer().bidirectionalAnalysis ?
468
457
  (tempStats.max > Math.abs(tempStats.min) ? tempStats.max : tempStats.min) :
469
458
  tempStats.max;
470
459
  }
@@ -473,7 +462,8 @@ export class PeptidesModel {
473
462
  return sequenceDf;
474
463
  }
475
464
 
476
- createGrids(matrixDf: DG.DataFrame, positionColumns: string[], sequenceDf: DG.DataFrame): DG.Grid[] {
465
+ createGrids(
466
+ matrixDf: DG.DataFrame, positionColumns: string[], sequenceDf: DG.DataFrame, alphabet: string): DG.Grid[] {
477
467
  const sarGrid = matrixDf.plot.grid();
478
468
  sarGrid.sort([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]);
479
469
  sarGrid.columns.setOrder([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE].concat(positionColumns as C.COLUMNS_NAMES[]));
@@ -486,16 +476,58 @@ export class PeptidesModel {
486
476
 
487
477
  let tempCol = matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
488
478
  if (tempCol)
489
- setAARRenderer(tempCol, sarGrid);
479
+ setAARRenderer(tempCol, alphabet, sarGrid);
490
480
 
491
481
  tempCol = sequenceDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
492
482
  if (tempCol)
493
- setAARRenderer(tempCol, sarGrid);
483
+ setAARRenderer(tempCol, alphabet, sarGrid);
494
484
 
495
485
  return [sarGrid, sarVGrid];
496
486
  }
497
487
 
498
- //TODO: move out
488
+ setBarChartInteraction() {
489
+ const eventAction = (ev: MouseEvent): void => {
490
+ const cell = this._sourceGrid.hitTest(ev.offsetX, ev.offsetY);
491
+ if (cell?.isColHeader && cell.tableColumn?.semType == C.SEM_TYPES.MONOMER) {
492
+ const newBarPart = this.findAARandPosition(cell, ev);
493
+ this.requestBarchartAction(ev, newBarPart);
494
+ }
495
+ };
496
+
497
+ // The following events makes the barchart interactive
498
+ rxjs.fromEvent<MouseEvent>(this._sourceGrid.overlay, 'mousemove').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
499
+ rxjs.fromEvent<MouseEvent>(this._sourceGrid.overlay, 'click').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
500
+ }
501
+
502
+ findAARandPosition(cell: DG.GridCell, ev: MouseEvent) {
503
+ const barCoords = this.barsBounds[cell.tableColumn!.name];
504
+ for (const [monomer, coords] of Object.entries(barCoords)) {
505
+ const isIntersectingX = ev.offsetX >= coords.x && ev.offsetX <= coords.x + coords.width;
506
+ const isIntersectingY = ev.offsetY >= coords.y && ev.offsetY <= coords.y + coords.height;
507
+ if (isIntersectingX && isIntersectingY)
508
+ return {monomer: monomer, position: cell.tableColumn!.name};
509
+ }
510
+
511
+ return null;
512
+ }
513
+
514
+ requestBarchartAction(ev: MouseEvent, barPart: {position: string, monomer: string} | null): void {
515
+ if (!barPart)
516
+ return;
517
+ const monomer = barPart.monomer;
518
+ const position = barPart.position;
519
+ if (ev.type === 'click') {
520
+ ev.shiftKey ? this.modifyCurrentSelection(monomer, position) :
521
+ this.initCurrentSelection(monomer, position);
522
+ } else {
523
+ const bar = `${monomer}:${position}`;
524
+ if (this.cachedBarchartTooltip.bar == bar)
525
+ ui.tooltip.show(this.cachedBarchartTooltip.tooltip!, ev.clientX, ev.clientY);
526
+ else
527
+ this.cachedBarchartTooltip = {bar: bar, tooltip: this.showTooltipAt(monomer, position, ev.clientX, ev.clientY)};
528
+ }
529
+ }
530
+
499
531
  setCellRenderers(renderColNames: string[], sarGrid: DG.Grid, sarVGrid: DG.Grid): void {
500
532
  const mdCol = this.statsDf.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
501
533
  //decompose into two different renering funcs
@@ -527,8 +559,9 @@ export class PeptidesModel {
527
559
  tableColName : gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
528
560
  const currentAAR: string = gridTable.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
529
561
 
530
- renderSARCell(canvasContext, currentAAR, currentPosition, this.statsDf, this._twoColorMode, mdCol, bound,
531
- cellValue, this.currentSelection, this._isSubstitutionOn ? this.substitutionsInfo : null);
562
+ const viewer = this.getViewer();
563
+ renderSARCell(canvasContext, currentAAR, currentPosition, this.statsDf, viewer.bidirectionalAnalysis, mdCol,
564
+ bound, cellValue, this.currentSelection, viewer.showSubstitution ? this.substitutionsInfo : null);
532
565
  }
533
566
  args.preventDefault();
534
567
  }
@@ -536,26 +569,45 @@ export class PeptidesModel {
536
569
  };
537
570
  sarGrid.onCellRender.subscribe(renderCell);
538
571
  sarVGrid.onCellRender.subscribe(renderCell);
572
+
573
+ this._sourceGrid.setOptions({'colHeaderHeight': 130});
574
+ this._sourceGrid.onCellRender.subscribe((gcArgs) => {
575
+ const context = gcArgs.g;
576
+ const bounds = gcArgs.bounds;
577
+ const col = gcArgs.cell.tableColumn;
578
+
579
+ context.save();
580
+ context.beginPath();
581
+ context.rect(bounds.x, bounds.y, bounds.width, bounds.height);
582
+ context.clip();
583
+
584
+ if (gcArgs.cell.isColHeader && col?.semType == C.SEM_TYPES.MONOMER) {
585
+ const barBounds = renderBarchart(context, col, this.barData[col.name], bounds, this.df.filter.trueCount);
586
+ this.barsBounds[col.name] = barBounds;
587
+ gcArgs.preventDefault();
588
+ }
589
+
590
+ context.restore();
591
+ });
539
592
  }
540
593
 
541
594
  setTooltips(renderColNames: string[], sarGrid: DG.Grid, sarVGrid: DG.Grid): void {
542
595
  const showTooltip = (cell: DG.GridCell, x: number, y: number): boolean => {
543
596
  const tableCol = cell.tableColumn;
544
597
  const tableColName = tableCol?.name;
598
+ const tableRowIndex = cell.tableRowIndex;
545
599
 
546
- if (!cell.isRowHeader && !cell.isColHeader && tableCol !== null) {
547
- const tableRowIndex = cell.tableRowIndex;
600
+ if (!cell.isRowHeader && !cell.isColHeader && tableCol && tableRowIndex) {
601
+ const table = cell.grid.table;
602
+ const currentAAR = table.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
548
603
 
549
- if (tableCol.semType == C.SEM_TYPES.AMINO_ACIDS) {
550
- const monomerLib = this._dataFrame.temp[MonomerLibrary.id];
551
- ChemPalette.showTooltip(cell, x, y, monomerLib);
552
- } else if (cell.cell.value !== null && tableRowIndex !== null && renderColNames.includes(tableColName!)) {
553
- const table = cell.grid.table;
604
+ if (tableCol.semType == C.SEM_TYPES.MONOMER)
605
+ this.showMonomerTooltip(currentAAR, x, y);
606
+ else if (cell.cell.value && renderColNames.includes(tableColName!)) {
554
607
  const currentPosition = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
555
608
  table.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
556
- const currentAAR = table.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
557
609
 
558
- this.showTooltip(currentAAR, currentPosition, x, y);
610
+ this.showTooltipAt(currentAAR, currentPosition, x, y);
559
611
  }
560
612
  }
561
613
  return true;
@@ -563,13 +615,36 @@ export class PeptidesModel {
563
615
 
564
616
  sarGrid.onCellTooltip(showTooltip);
565
617
  sarVGrid.onCellTooltip(showTooltip);
618
+ this._sourceGrid.onCellTooltip((cell, x, y) => {
619
+ const col = cell.tableColumn;
620
+ const cellValue = cell.cell.value;
621
+ if (cellValue && col && col.semType === C.SEM_TYPES.MONOMER)
622
+ this.showMonomerTooltip(cellValue, x, y);
623
+ return true;
624
+ });
625
+ }
626
+
627
+ showMonomerTooltip(aar: string, x: number, y: number): void {
628
+ const tooltipElements: HTMLDivElement[] = [];
629
+ //@ts-ignore: no types for org
630
+ // const monomer: type.HELMMonomer = org.helm.webeditor.monomers.getMonomer('HELM_AA', aar);
631
+ const monomer: type.HELMMonomer = this.monomerLib[aar.toLowerCase()];
632
+
633
+ if (monomer) {
634
+ tooltipElements.push(ui.div(monomer.n));
635
+ const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
636
+ tooltipElements.push(grok.chem.svgMol(monomer.m, undefined, undefined, options));
637
+ } else
638
+ tooltipElements.push(ui.div(aar));
639
+
640
+ ui.tooltip.show(ui.divV(tooltipElements), x, y);
566
641
  }
567
642
 
568
- showTooltip(aar: string, position: string, x: number, y: number): void {
643
+ showTooltipAt(aar: string, position: string, x: number, y: number): HTMLDivElement | null {
569
644
  const currentStatsDf = this.statsDf.rows.match({Pos: position, AAR: aar}).toDataFrame();
570
- const activityCol = this._dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
645
+ const activityCol = this.df.columns.bySemType(C.SEM_TYPES.ACTIVITY_SCALED)!;
571
646
  const splitCol = DG.Column.bool(C.COLUMNS_NAMES.SPLIT_COL, activityCol.length);
572
- const currentPosCol = this._dataFrame.getCol(position);
647
+ const currentPosCol = this.df.getCol(position);
573
648
  splitCol.init((i) => currentPosCol.get(i) == aar);
574
649
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
575
650
  const stats: Stats = {
@@ -578,13 +653,16 @@ export class PeptidesModel {
578
653
  pValue: currentStatsDf.get(C.COLUMNS_NAMES.P_VALUE, 0),
579
654
  meanDifference: currentStatsDf.get(C.COLUMNS_NAMES.MEAN_DIFFERENCE, 0),
580
655
  };
581
- const tooltip = getDistributionAndStats(
582
- distributionTable, stats, `${position} : ${aar}`, 'Other', true);
656
+ if (!stats.count)
657
+ return null;
658
+
659
+ const tooltip = getDistributionAndStats(distributionTable, stats, `${position} : ${aar}`, 'Other', true);
583
660
 
584
661
  ui.tooltip.show(tooltip, x, y);
662
+
663
+ return tooltip;
585
664
  }
586
665
 
587
- //TODO: think about it, move out?
588
666
  setInteractionCallback(): void {
589
667
  const sarDf = this._sarGrid.dataFrame;
590
668
  const sarVDf = this._sarVGrid.dataFrame;
@@ -649,7 +727,7 @@ export class PeptidesModel {
649
727
  }
650
728
 
651
729
  invalidateGrids(): void {
652
- this.stackedBarchart?.computeData();
730
+ // this.stackedBarchart?.computeData();
653
731
  this._sarGrid.invalidate();
654
732
  this._sarVGrid.invalidate();
655
733
  this._sourceGrid?.invalidate();
@@ -659,12 +737,9 @@ export class PeptidesModel {
659
737
  setBitsetCallback(): void {
660
738
  if (this.isBitsetChangedInitialized)
661
739
  return;
662
- const filter = this._dataFrame.filter;
663
- const selection = this._dataFrame.selection;
664
-
665
- const changeBitset = (currentBitset: DG.BitSet, previousBitset: DG.BitSet): void => {
666
- previousBitset.setAll(!this._filterMode, false);
740
+ const selection = this.df.selection;
667
741
 
742
+ const changeBitset = (currentBitset: DG.BitSet): void => {
668
743
  const edfSelection = this.edf?.selection;
669
744
  if (this.isPeptideSpaceChangingBitset) {
670
745
  if (edfSelection == null)
@@ -690,8 +765,8 @@ export class PeptidesModel {
690
765
  //TODO: move out
691
766
  const getBitAt = (i: number) => {
692
767
  for (const position of positionList) {
693
- const positionCol: DG.Column<string> = this._dataFrame.getCol(position);
694
- if (this._currentSelection[position].includes(positionCol.get(i)))
768
+ const positionCol: DG.Column<string> = this.df.getCol(position);
769
+ if (this._currentSelection[position].includes(positionCol.get(i)!))
695
770
  return true;
696
771
  }
697
772
  return false;
@@ -701,29 +776,19 @@ export class PeptidesModel {
701
776
  updateEdfSelection();
702
777
  };
703
778
 
704
- filter.onChanged.subscribe(() => changeBitset(filter, selection));
705
- selection.onChanged.subscribe(() => changeBitset(selection, filter));
779
+ selection.onChanged.subscribe(() => changeBitset(selection));
706
780
  this.isBitsetChangedInitialized = true;
707
781
  }
708
782
 
709
783
  fireBitsetChanged(isPeptideSpaceSource: boolean = false): void {
710
784
  this.isPeptideSpaceChangingBitset = isPeptideSpaceSource;
711
- this.getBiteset().fireChanged();
785
+ this.df.selection.fireChanged();
712
786
  this.modifyOrCreateSplitCol();
713
787
  grok.shell.o = this.createAccordion().root;
714
788
  this.isPeptideSpaceChangingBitset = false;
715
789
  }
716
790
 
717
- getBiteset(): DG.BitSet {return this._filterMode ? this._dataFrame.filter : this._dataFrame.selection;}
718
-
719
- //TODO: move out
720
- postProcessGrids(sourceGrid: DG.Grid, invalidIndexes: number[], sarGrid: DG.Grid, sarVGrid: DG.Grid): void {
721
- sourceGrid.onCellPrepare((cell: DG.GridCell) => {
722
- const currentRowIndex = cell.tableRowIndex;
723
- if (currentRowIndex && invalidIndexes.includes(currentRowIndex) && !cell.isRowHeader)
724
- cell.style.backColor = DG.Color.lightLightGray;
725
- });
726
-
791
+ postProcessGrids(sarGrid: DG.Grid, sarVGrid: DG.Grid): void {
727
792
  const mdCol: DG.GridColumn = sarVGrid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
728
793
  mdCol.name = 'Diff';
729
794
 
@@ -753,141 +818,54 @@ export class PeptidesModel {
753
818
  }
754
819
 
755
820
  getSplitColValueAt(index: number, aar: string, position: string, aarLabel: string): string {
756
- const currentAAR = this._dataFrame.get(position, index) as string;
821
+ const currentAAR = this.df.get(position, index) as string;
757
822
  return currentAAR === aar ? aarLabel : C.CATEGORIES.OTHER;
758
823
  }
759
824
 
760
825
  modifyOrCreateSplitCol(): void {
761
- const bs = this.getBiteset();
762
- this.splitCol = this._dataFrame.col(C.COLUMNS_NAMES.SPLIT_COL) ??
763
- this._dataFrame.columns.addNewBool(C.COLUMNS_NAMES.SPLIT_COL);
826
+ const bs = this.df.selection;
827
+ this.splitCol = this.df.col(C.COLUMNS_NAMES.SPLIT_COL) ??
828
+ this.df.columns.addNewBool(C.COLUMNS_NAMES.SPLIT_COL);
764
829
  this.splitCol.init((i) => bs.get(i));
765
830
  this.splitCol.compact();
766
831
  }
767
832
 
768
- static async scaleActivity(
769
- activityScaling: string, df: DG.DataFrame, originalActivityName?: string, cloneBitset = false,
770
- ): Promise<[DG.DataFrame, string]> {
771
- let currentActivityColName = originalActivityName ?? C.COLUMNS_NAMES.ACTIVITY;
772
- const flag = df.columns.names().includes(currentActivityColName) &&
773
- currentActivityColName === originalActivityName;
774
- currentActivityColName = flag ? currentActivityColName : C.COLUMNS_NAMES.ACTIVITY;
775
- const tempDf = df.clone(cloneBitset ? df.filter : null, [currentActivityColName]);
776
-
777
- let formula = '${' + currentActivityColName + '}';
778
- let newColName = 'activity';
779
- switch (activityScaling) {
780
- case 'none':
781
- break;
782
- case 'lg':
783
- formula = `Log10(${formula})`;
784
- newColName = `Log10(${newColName})`;
785
- break;
786
- case '-lg':
787
- formula = `-1*Log10(${formula})`;
788
- newColName = `-Log10(${newColName})`;
789
- break;
790
- default:
791
- throw new Error(`ScalingError: method \`${activityScaling}\` is not available.`);
792
- }
793
-
794
- await tempDf.columns.addNewCalculated(C.COLUMNS_NAMES.ACTIVITY_SCALED, formula);
795
- df.tags['scaling'] = activityScaling;
796
-
797
- return [tempDf, newColName];
798
- }
799
-
800
- static splitAlignedPeptides(peptideColumn: DG.Column<string>, filter: boolean = true): [DG.DataFrame, number[]] {
801
- const separator = peptideColumn.tags[C.TAGS.SEPARATOR] ?? getSeparator(peptideColumn);
802
- const splitPeptidesArray: string[][] = [];
803
- let currentSplitPeptide: string[];
804
- let modeMonomerCount = 0;
805
- let currentLength;
806
- const colLength = peptideColumn.length;
807
-
808
- // splitting data
809
- const monomerLengths: {[index: string]: number} = {};
810
- for (let i = 0; i < colLength; i++) {
811
- currentSplitPeptide = peptideColumn.get(i).split(separator).map((value: string) => value ? value : '-');
812
- splitPeptidesArray.push(currentSplitPeptide);
813
- currentLength = currentSplitPeptide.length;
814
- monomerLengths[currentLength + ''] =
815
- monomerLengths[currentLength + ''] ? monomerLengths[currentLength + ''] + 1 : 1;
816
- }
817
- modeMonomerCount =
818
- parseInt(Object.keys(monomerLengths).reduce((a, b) => monomerLengths[a] > monomerLengths[b] ? a : b));
819
-
820
- // making sure all of the sequences are of the same size
821
- // and marking invalid sequences
822
- let nTerminal: string;
823
- const invalidIndexes: number[] = [];
824
- let splitColumns: string[][] = Array.from({length: modeMonomerCount}, (_) => []);
825
- modeMonomerCount--; // minus N-terminal
826
- for (let i = 0; i < colLength; i++) {
827
- currentSplitPeptide = splitPeptidesArray[i];
828
- nTerminal = currentSplitPeptide.pop()!; // it is guaranteed that there will be at least one element
829
- currentLength = currentSplitPeptide.length;
830
- if (currentLength !== modeMonomerCount)
831
- invalidIndexes.push(i);
832
-
833
- for (let j = 0; j < modeMonomerCount; j++)
834
- splitColumns[j].push(j < currentLength ? currentSplitPeptide[j] : '-');
835
-
836
- splitColumns[modeMonomerCount].push(nTerminal);
837
- }
838
- modeMonomerCount--; // minus C-terminal
839
-
840
- //create column names list
841
- const columnNames = Array.from({length: modeMonomerCount}, (_, index) => `${index + 1 < 10 ? 0 : ''}${index + 1 }`);
842
- columnNames.splice(0, 0, 'N');
843
- columnNames.push('C');
844
-
845
- // filter out the columns with the same values
846
- if (filter) {
847
- splitColumns = splitColumns.filter((positionArray, index) => {
848
- const isRetained = new Set(positionArray).size > 1;
849
- if (!isRetained)
850
- columnNames.splice(index, 1);
851
-
852
- return isRetained;
853
- });
854
- }
855
-
856
- return [
857
- DG.DataFrame.fromColumns(splitColumns.map((positionArray, index) => {
858
- return DG.Column.fromList('string', columnNames[index], positionArray);
859
- })),
860
- invalidIndexes,
861
- ];
862
- }
863
-
864
833
  syncProperties(isSourceSAR = true): void {
865
- const sarViewer: SARViewer = this._dataFrame.temp['sarViewer'];
866
- const sarViewerVertical: SARViewerVertical = this._dataFrame.temp['sarViewerVertical'];
867
- const sourceViewer = isSourceSAR ? sarViewer : sarViewerVertical;
868
- const targetViewer = isSourceSAR ? sarViewerVertical : sarViewer;
869
- const properties = sourceViewer.props.getProperties();
870
- for (const property of properties)
871
- targetViewer.props.set(property.name, property.get(sourceViewer));
834
+ if (this.sarViewer && this.sarViewerVertical) {
835
+ const [sourceViewer, targetViewer] = isSourceSAR ? [this.sarViewer, this.sarViewerVertical] :
836
+ [this.sarViewerVertical, this.sarViewer];
837
+ const properties = sourceViewer.props.getProperties();
838
+ const newProps: {[propName: string]: string | number | boolean} = {};
839
+ for (const property of properties) {
840
+ const propName = property.name;
841
+ const propVal = property.get(sourceViewer);
842
+ targetViewer.props.set(propName, propVal);
843
+ newProps[propName] = propVal;
844
+ }
845
+ this.usedProperties = newProps;
846
+ } else
847
+ console.warn('Warning: could not sync viewer properties, one of the viewers is not initialized');
872
848
  }
873
849
 
874
850
  /** Class initializer */
875
851
  async init(): Promise<void> {
876
852
  if (this.isInitialized)
877
853
  return;
878
- this.isInitialized = true;
879
854
 
880
- this.currentView = this._dataFrame.tags[C.PEPTIDES_ANALYSIS] == 'true' ? grok.shell.v as DG.TableView :
881
- grok.shell.addTableView(this._dataFrame);
882
- const sourceGrid = this.currentView.grid;
883
- if (this._dataFrame.tags[C.PEPTIDES_ANALYSIS] == 'true')
855
+ this.monomerLib =
856
+ JSON.parse(await grok.functions.call('Helm:getMonomerLib', {type: this.df.getTag('monomerType')}));
857
+
858
+ this.currentView = this.df.tags[C.PEPTIDES_ANALYSIS] == 'true' ? grok.shell.v as DG.TableView :
859
+ grok.shell.addTableView(this.df);
860
+ this._sourceGrid = this.currentView.grid;
861
+ if (this.df.tags[C.PEPTIDES_ANALYSIS] == 'true')
884
862
  return;
885
863
 
886
- this._dataFrame.tags[C.PEPTIDES_ANALYSIS] = 'true';
887
- sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!.name = this._dataFrame.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED];
888
- sourceGrid.columns.setOrder([this._dataFrame.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED]]);
864
+ this.df.tags[C.PEPTIDES_ANALYSIS] = 'true';
865
+ this._sourceGrid.col(C.COLUMNS_NAMES.ACTIVITY_SCALED)!.name = this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED];
866
+ this._sourceGrid.columns.setOrder([this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED]]);
889
867
 
890
- this._dataFrame.temp[C.EMBEDDING_STATUS] = false;
868
+ this.df.temp[C.EMBEDDING_STATUS] = false;
891
869
  const adjustCellSize = (grid: DG.Grid): void => {
892
870
  const colNum = grid.columns.length;
893
871
  for (let i = 0; i < colNum; ++i) {
@@ -897,36 +875,37 @@ export class PeptidesModel {
897
875
  grid.props.rowHeight = 20;
898
876
  };
899
877
 
900
- for (let i = 0; i < sourceGrid.columns.length; i++) {
901
- const aarCol = sourceGrid.columns.byIndex(i);
902
- if (aarCol && aarCol.name && aarCol.column?.semType !== C.SEM_TYPES.AMINO_ACIDS &&
903
- aarCol.name !== this._dataFrame.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED])
878
+ for (let i = 0; i < this._sourceGrid.columns.length; i++) {
879
+ const aarCol = this._sourceGrid.columns.byIndex(i);
880
+ if (aarCol && aarCol.name && aarCol.column?.semType !== C.SEM_TYPES.MONOMER &&
881
+ aarCol.name !== this.df.tags[C.COLUMNS_NAMES.ACTIVITY_SCALED])
904
882
  aarCol.visible = false;
905
883
  }
906
884
 
907
- const options = {scaling: this._dataFrame.tags['scaling']};
908
- await this.updateData(this._dataFrame.tags['scaling'], sourceGrid, false, 1, 2, false, false);
885
+ const options = {scaling: this.df.tags['scaling']};
909
886
 
910
887
  const dockManager = this.currentView.dockManager;
911
888
 
912
- const sarViewer = await this._dataFrame.plot.fromType('peptide-sar-viewer', options) as SARViewer;
889
+ this.sarViewer = await this.df.plot.fromType('peptide-sar-viewer', options) as SARViewer;
913
890
 
914
- const sarViewerVertical =
915
- await this._dataFrame.plot.fromType('peptide-sar-viewer-vertical', options) as SARViewerVertical;
891
+ this.sarViewerVertical =
892
+ await this.df.plot.fromType('peptide-sar-viewer-vertical', options) as SARViewerVertical;
916
893
 
917
- const sarViewersGroup: viewerTypes[] = [sarViewer, sarViewerVertical];
894
+ const sarViewersGroup: viewerTypes[] = [this.sarViewer, this.sarViewerVertical];
918
895
 
919
- if (this._dataFrame.rowCount <= 10000) {
896
+ if (this.df.rowCount <= 10000) {
920
897
  const peptideSpaceViewerOptions = {method: 'UMAP', measure: 'Levenshtein', cyclesCount: 100};
921
898
  const peptideSpaceViewer =
922
- await this._dataFrame.plot.fromType('peptide-space-viewer', peptideSpaceViewerOptions) as PeptideSpaceViewer;
899
+ await this.df.plot.fromType('peptide-space-viewer', peptideSpaceViewerOptions) as PeptideSpaceViewer;
923
900
  dockManager.dock(peptideSpaceViewer, DG.DOCK_TYPE.RIGHT, null, 'Peptide Space Viewer');
924
901
  }
925
902
 
903
+ this.updateDefault();
904
+
926
905
  dockViewers(sarViewersGroup, DG.DOCK_TYPE.RIGHT, dockManager, DG.DOCK_TYPE.DOWN);
927
906
 
928
- sourceGrid.props.allowEdit = false;
929
- adjustCellSize(sourceGrid);
907
+ this._sourceGrid.props.allowEdit = false;
908
+ adjustCellSize(this._sourceGrid);
930
909
 
931
910
  this.invalidateGrids();
932
911
  }