@datagrok/peptides 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "author": {
5
5
  "name": "Volodymyr Dyma",
6
6
  "email": "vdyma@datagrok.ai"
@@ -43,10 +43,6 @@
43
43
  "@types/node-fetch": "^2.6.2",
44
44
  "node-fetch": "^2.6.7"
45
45
  },
46
- "sources": [
47
- "common/ngl_viewer/ngl.js",
48
- "helm/JSDraw/Pistoia.HELM.js"
49
- ],
50
46
  "scripts": {
51
47
  "link-api": "npm link datagrok-api",
52
48
  "link-utils": "npm link @datagrok-libraries/utils",
@@ -46,15 +46,16 @@ it('TEST', async () => {
46
46
  const cMessage = df.columns.byName('result');
47
47
  const cCat = df.columns.byName('category');
48
48
  const cName = df.columns.byName('name');
49
+ const cTime = df.columns.byName('ms');
49
50
  let failed = false;
50
51
  let passReport = '';
51
52
  let failReport = '';
52
53
  for (let i = 0; i < df.rowCount; i++) {
53
54
  if (cStatus.get(i))
54
- passReport += `Test result : Success : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
55
+ passReport += `Test result : Success : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
55
56
  else {
56
57
  failed = true;
57
- failReport += `Test result : Failed : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
58
+ failReport += `Test result : Failed : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
58
59
  }
59
60
  }
60
61
  resolve({failReport, passReport, failed});
package/src/model.ts CHANGED
@@ -4,29 +4,25 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
6
6
 
7
- import {Subject, Observable} from 'rxjs';
7
+ import * as rxjs from 'rxjs';
8
+
8
9
  import * as C from './utils/constants';
9
10
  import * as type from './utils/types';
10
- import {calculateBarsData, getTypedArrayConstructor, scaleActivity} from './utils/misc';
11
+ import {calculateBarsData, getTypedArrayConstructor, isGridCellInvalid, scaleActivity} from './utils/misc';
11
12
  import {SARViewer, SARViewerBase, SARViewerVertical} from './viewers/sar-viewer';
12
13
  import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
13
14
  import {renderBarchart, renderSARCell, setAARRenderer} from './utils/cell-renderer';
14
- import {substitutionsWidget} from './widgets/subst-table';
15
+ import {mutationCliffsWidget} from './widgets/mutation-cliffs';
15
16
  import {getDistributionAndStats, getDistributionWidget} from './widgets/distribution';
16
- import {getStats, Stats} from './utils/filtering-statistics';
17
- import * as rxjs from 'rxjs';
18
-
17
+ import {getStats, Stats} from './utils/statistics';
19
18
 
20
19
  export class PeptidesModel {
21
20
  static modelName = 'peptidesModel';
22
21
 
23
- _statsDataFrameSubject = new Subject<DG.DataFrame>();
24
- _sarGridSubject = new Subject<DG.Grid>();
25
- _sarVGridSubject = new Subject<DG.Grid>();
26
- _substitutionTableSubject = new Subject<type.SubstitutionsInfo>();
22
+ _sarGridSubject = new rxjs.Subject<DG.Grid>();
23
+ _sarVGridSubject = new rxjs.Subject<DG.Grid>();
27
24
 
28
25
  _isUpdating: boolean = false;
29
- _isSubstInitialized = false;
30
26
  isBitsetChangedInitialized = false;
31
27
  isCellChanging = false;
32
28
 
@@ -65,13 +61,9 @@ export class PeptidesModel {
65
61
  return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
66
62
  }
67
63
 
68
- get onStatsDataFrameChanged(): Observable<DG.DataFrame> {return this._statsDataFrameSubject.asObservable();}
69
-
70
- get onSARGridChanged(): Observable<DG.Grid> {return this._sarGridSubject.asObservable();}
71
-
72
- get onSARVGridChanged(): Observable<DG.Grid> {return this._sarVGridSubject.asObservable();}
64
+ get onSARGridChanged(): rxjs.Observable<DG.Grid> {return this._sarGridSubject.asObservable();}
73
65
 
74
- get onSubstTableChanged(): Observable<type.SubstitutionsInfo> {return this._substitutionTableSubject.asObservable();}
66
+ get onSARVGridChanged(): rxjs.Observable<DG.Grid> {return this._sarVGridSubject.asObservable();}
75
67
 
76
68
  get currentSelection(): type.SelectionObject {
77
69
  this._currentSelection ??= JSON.parse(this.df.tags[C.TAGS.SELECTION] || '{}');
@@ -120,7 +112,7 @@ export class PeptidesModel {
120
112
  const acc = ui.accordion();
121
113
  acc.root.style.width = '100%';
122
114
  acc.addTitle(ui.h1(`${this.df.selection.trueCount} selected rows`));
123
- acc.addPane('Substitutions', () => substitutionsWidget(this.df, this).root, true);
115
+ acc.addPane('Substitutions', () => mutationCliffsWidget(this.df, this).root, true);
124
116
  acc.addPane('Distribtution', () => getDistributionWidget(this.df, this).root, true);
125
117
 
126
118
  return acc;
@@ -158,23 +150,17 @@ export class PeptidesModel {
158
150
  if ((this._sourceGrid && !this._isUpdating && proprtyChanged) || !this.isInitialized) {
159
151
  this.isInitialized = true;
160
152
  this._isUpdating = true;
161
- const [viewerGrid, viewerVGrid, statsDf] = this.initializeViewersComponents();
153
+ const [viewerGrid, viewerVGrid] = this.initializeViewersComponents();
162
154
  //FIXME: modify during the initializeViewersComponents stages
163
- this._statsDataFrameSubject.next(statsDf);
164
155
  this._sarGridSubject.next(viewerGrid);
165
156
  this._sarVGridSubject.next(viewerVGrid);
166
- if (viewer.showSubstitution) {
167
- this._substitutionTableSubject.next(this.substitutionsInfo);
168
- this._isSubstInitialized = true;
169
- }
170
157
 
171
158
  this.invalidateSelection();
172
-
173
159
  this._isUpdating = false;
174
160
  }
175
161
  }
176
162
 
177
- initializeViewersComponents(): [DG.Grid, DG.Grid, DG.DataFrame] {
163
+ initializeViewersComponents(): [DG.Grid, DG.Grid] {
178
164
  if (this._sourceGrid === null)
179
165
  throw new Error(`Source grid is not initialized`);
180
166
 
@@ -207,14 +193,14 @@ export class PeptidesModel {
207
193
  //unpivot a table and handle duplicates
208
194
  let matrixDf = splitSeqDf.groupBy(positionColumns).aggregate();
209
195
 
210
- matrixDf = matrixDf.unpivot([], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
196
+ matrixDf = matrixDf.unpivot([], positionColumns, C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER);
211
197
 
212
198
  //statistics for specific AAR at a specific position
213
199
  this.statsDf = this.calculateStatistics(matrixDf);
214
200
 
215
201
  // SAR matrix table
216
202
  //pivot a table to make it matrix-like
217
- matrixDf = this.statsDf.groupBy([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE])
203
+ matrixDf = this.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
218
204
  .pivot(C.COLUMNS_NAMES.POSITION)
219
205
  .add('first', C.COLUMNS_NAMES.MEAN_DIFFERENCE, '')
220
206
  .aggregate();
@@ -226,8 +212,7 @@ export class PeptidesModel {
226
212
  // SAR vertical table (naive, choose best Mean difference from pVals <= 0.01)
227
213
  const sequenceDf = this.createVerticalTable();
228
214
 
229
- if (viewer.showSubstitution || !this._isSubstInitialized)
230
- this.calcSubstitutions();
215
+ this.calcSubstitutions();
231
216
 
232
217
  const [sarGrid, sarVGrid] = this.createGrids(matrixDf, positionColumns, sequenceDf, alphabet);
233
218
 
@@ -250,7 +235,7 @@ export class PeptidesModel {
250
235
  this.postProcessGrids(sarGrid, sarVGrid);
251
236
 
252
237
  //TODO: return class instead
253
- return [sarGrid, sarVGrid, this.statsDf];
238
+ return [sarGrid, sarVGrid];
254
239
  }
255
240
 
256
241
  calcSubstitutions(): void {
@@ -387,7 +372,7 @@ export class PeptidesModel {
387
372
  }
388
373
 
389
374
  calculateStatistics(matrixDf: DG.DataFrame): DG.DataFrame {
390
- matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]).aggregate();
375
+ matrixDf = matrixDf.groupBy([C.COLUMNS_NAMES.POSITION, C.COLUMNS_NAMES.MONOMER]).aggregate();
391
376
 
392
377
  //calculate p-values based on t-test
393
378
  const matrixCols = matrixDf.columns;
@@ -395,7 +380,7 @@ export class PeptidesModel {
395
380
  const pValCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.P_VALUE);
396
381
  const countCol = matrixCols.addNewInt(C.COLUMNS_NAMES.COUNT);
397
382
  const ratioCol = matrixCols.addNewFloat(C.COLUMNS_NAMES.RATIO);
398
- const aarCol = matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
383
+ const aarCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
399
384
  const posCol = matrixDf.getCol(C.COLUMNS_NAMES.POSITION);
400
385
  const activityCol: number[] = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).toList();
401
386
  const sourceDfLen = activityCol.length;
@@ -428,22 +413,22 @@ export class PeptidesModel {
428
413
  absMDCol.init((i) => Math.abs(mdCol.get(i)));
429
414
  }
430
415
 
431
- const aarWeightsDf = this.statsDf.groupBy([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]).sum(sortArgument, 'weight')
416
+ const aarWeightsDf = this.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER]).sum(sortArgument, 'weight')
432
417
  .aggregate();
433
- const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE).toList();
418
+ const aarList = aarWeightsDf.getCol(C.COLUMNS_NAMES.MONOMER).toList();
434
419
  const getWeight = (aar: string): number => aarWeightsDf
435
420
  .groupBy(['weight'])
436
- .where(`${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ${aar}`)
421
+ .where(`${C.COLUMNS_NAMES.MONOMER} = ${aar}`)
437
422
  .aggregate()
438
423
  .get('weight', 0) as number;
439
424
  aarList.sort((first, second) => getWeight(second) - getWeight(first));
440
425
 
441
- matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE).setCategoryOrder(aarList);
426
+ matrixDf.getCol(C.COLUMNS_NAMES.MONOMER).setCategoryOrder(aarList);
442
427
  }
443
428
 
444
429
  createVerticalTable(): DG.DataFrame {
445
430
  // TODO: aquire ALL of the positions
446
- const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, C.COLUMNS_NAMES.POSITION,
431
+ const columns = [C.COLUMNS_NAMES.MEAN_DIFFERENCE, C.COLUMNS_NAMES.MONOMER, C.COLUMNS_NAMES.POSITION,
447
432
  'Count', 'Ratio', C.COLUMNS_NAMES.P_VALUE];
448
433
  let sequenceDf = this.statsDf.groupBy(columns)
449
434
  .where('pValue <= 0.1')
@@ -469,8 +454,8 @@ export class PeptidesModel {
469
454
  createGrids(
470
455
  matrixDf: DG.DataFrame, positionColumns: string[], sequenceDf: DG.DataFrame, alphabet: string): DG.Grid[] {
471
456
  const sarGrid = matrixDf.plot.grid();
472
- sarGrid.sort([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE]);
473
- sarGrid.columns.setOrder([C.COLUMNS_NAMES.AMINO_ACID_RESIDUE].concat(positionColumns as C.COLUMNS_NAMES[]));
457
+ sarGrid.sort([C.COLUMNS_NAMES.MONOMER]);
458
+ sarGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER].concat(positionColumns as C.COLUMNS_NAMES[]));
474
459
 
475
460
  const sarVGrid = sequenceDf.plot.grid();
476
461
  sarVGrid.sort([C.COLUMNS_NAMES.POSITION]);
@@ -478,11 +463,11 @@ export class PeptidesModel {
478
463
  pValGridCol.format = '#.000';
479
464
  pValGridCol.name = 'P-value';
480
465
 
481
- let tempCol = matrixDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
466
+ let tempCol = matrixDf.getCol(C.COLUMNS_NAMES.MONOMER);
482
467
  if (tempCol)
483
468
  setAARRenderer(tempCol, alphabet, sarGrid);
484
469
 
485
- tempCol = sequenceDf.getCol(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE);
470
+ tempCol = sequenceDf.getCol(C.COLUMNS_NAMES.MONOMER);
486
471
  if (tempCol)
487
472
  setAARRenderer(tempCol, alphabet, sarGrid);
488
473
 
@@ -563,11 +548,11 @@ export class PeptidesModel {
563
548
  const gridTable = cell.grid.table;
564
549
  const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
565
550
  tableColName : gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
566
- const currentAAR: string = gridTable.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
551
+ const currentAAR: string = gridTable.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
567
552
 
568
553
  const viewer = this.getViewer();
569
554
  renderSARCell(canvasContext, currentAAR, currentPosition, this.statsDf, viewer.bidirectionalAnalysis, mdCol,
570
- bound, cellValue, this.currentSelection, viewer.showSubstitution ? this.substitutionsInfo : null);
555
+ bound, cellValue, this.currentSelection, this.substitutionsInfo);
571
556
  }
572
557
  args.preventDefault();
573
558
  }
@@ -605,7 +590,7 @@ export class PeptidesModel {
605
590
 
606
591
  if (!cell.isRowHeader && !cell.isColHeader && tableCol && tableRowIndex != null) {
607
592
  const table = cell.grid.table;
608
- const currentAAR = table.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIndex);
593
+ const currentAAR = table.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
609
594
 
610
595
  if (tableCol.semType == C.SEM_TYPES.MONOMER)
611
596
  this.showMonomerTooltip(currentAAR, x, y);
@@ -676,26 +661,24 @@ export class PeptidesModel {
676
661
  const chooseAction = (aar: string, position: string, isShiftPressed: boolean): void =>
677
662
  isShiftPressed ? this.modifyCurrentSelection(aar, position) : this.initCurrentSelection(aar, position);
678
663
 
679
- const gridCellValidation = (gc: DG.GridCell | null): boolean => !gc || !gc.cell.value || !gc.tableColumn ||
680
- gc.tableRowIndex == null || gc.tableRowIndex == -1;
681
664
  this._sarGrid.root.addEventListener('click', (ev) => {
682
665
  const gridCell = this._sarGrid.hitTest(ev.offsetX, ev.offsetY);
683
- if (gridCellValidation(gridCell) || gridCell!.tableColumn!.name == C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)
666
+ if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name == C.COLUMNS_NAMES.MONOMER)
684
667
  return;
685
668
 
686
669
  const position = gridCell!.tableColumn!.name;
687
- const aar = sarDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, gridCell!.tableRowIndex!);
670
+ const aar = sarDf.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!);
688
671
  chooseAction(aar, position, ev.shiftKey);
689
672
  });
690
673
 
691
674
  this._sarVGrid.root.addEventListener('click', (ev) => {
692
675
  const gridCell = this._sarVGrid.hitTest(ev.offsetX, ev.offsetY);
693
- if (gridCellValidation(gridCell) || gridCell!.tableColumn!.name != C.COLUMNS_NAMES.MEAN_DIFFERENCE)
676
+ if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name != C.COLUMNS_NAMES.MEAN_DIFFERENCE)
694
677
  return;
695
678
 
696
679
  const tableRowIdx = gridCell!.tableRowIndex!;
697
680
  const position = sarVDf.get(C.COLUMNS_NAMES.POSITION, tableRowIdx);
698
- const aar = sarVDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, tableRowIdx);
681
+ const aar = sarVDf.get(C.COLUMNS_NAMES.MONOMER, tableRowIdx);
699
682
  chooseAction(aar, position, ev.shiftKey);
700
683
  });
701
684
 
@@ -805,7 +788,7 @@ export class PeptidesModel {
805
788
  for (let i = 0; i < colNum; ++i) {
806
789
  const col = girdCols.byIndex(i)!;
807
790
  const colName = col.name;
808
- if (grid == sarVGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.AMINO_ACID_RESIDUE)
791
+ if (grid == sarVGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER)
809
792
  col.width = 50;
810
793
  else
811
794
  col.width = gridProps.rowHeight + 10;
@@ -899,15 +882,18 @@ export class PeptidesModel {
899
882
 
900
883
  const sarViewersGroup: viewerTypes[] = [this.sarViewer, this.sarViewerVertical];
901
884
 
902
- if (this.df.rowCount <= 10000) {
903
- const peptideSpaceViewerOptions = {method: 'UMAP', measure: 'Levenshtein', cyclesCount: 100};
904
- const peptideSpaceViewer =
905
- await this.df.plot.fromType('peptide-space-viewer', peptideSpaceViewerOptions) as PeptideSpaceViewer;
906
- dockManager.dock(peptideSpaceViewer, DG.DOCK_TYPE.RIGHT, null, 'Peptide Space Viewer');
907
- }
885
+ // TODO: completely remove this viewer?
886
+ // if (this.df.rowCount <= 10000) {
887
+ // const peptideSpaceViewerOptions = {method: 'UMAP', measure: 'Levenshtein', cyclesCount: 100};
888
+ // const peptideSpaceViewer =
889
+ // await this.df.plot.fromType('peptide-space-viewer', peptideSpaceViewerOptions) as PeptideSpaceViewer;
890
+ // dockManager.dock(peptideSpaceViewer, DG.DOCK_TYPE.RIGHT, null, 'Peptide Space Viewer');
891
+ // }
908
892
 
909
893
  this.updateDefault();
910
894
 
895
+ this.currentView.filters({filters: [{type: 'Peptides:invariantMapFilter'}]});
896
+
911
897
  dockViewers(sarViewersGroup, DG.DOCK_TYPE.RIGHT, dockManager, DG.DOCK_TYPE.DOWN);
912
898
 
913
899
  this._sourceGrid.props.allowEdit = false;
package/src/package.ts CHANGED
@@ -11,6 +11,7 @@ import {manualAlignmentWidget} from './widgets/manual-alignment';
11
11
  import {SARViewer, SARViewerVertical} from './viewers/sar-viewer';
12
12
 
13
13
  import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
14
+ import {InvariantMap} from './utils/invariant-map';
14
15
 
15
16
  export const _package = new DG.Package();
16
17
  let currentTable: DG.DataFrame;
@@ -153,3 +154,10 @@ function getOrDefine(dataframe?: DG.DataFrame, column?: DG.Column | null): [DG.D
153
154
 
154
155
  return [dataframe, column];
155
156
  }
157
+
158
+ //name: Invariant Map Filter
159
+ //tags: filter
160
+ //output: filter result
161
+ export function invariantMapFilter() {
162
+ return new InvariantMap();
163
+ }
@@ -63,8 +63,8 @@ export function measureAAR(s: string): number {
63
63
 
64
64
  export function renderSARCell(canvasContext: CanvasRenderingContext2D, currentAAR: string, currentPosition: string,
65
65
  statsDf: DG.DataFrame, twoColorMode: boolean, mdCol: DG.Column<number>, bound: DG.Rect, cellValue: number,
66
- currentSelection: types.SelectionObject, substitutionsInfo: types.SubstitutionsInfo | null): void {
67
- const queryAAR = `${C.COLUMNS_NAMES.AMINO_ACID_RESIDUE} = ${currentAAR}`;
66
+ currentSelection: types.SelectionObject, substitutionsInfo: types.SubstitutionsInfo): void {
67
+ const queryAAR = `${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`;
68
68
  const query = `${queryAAR} and ${C.COLUMNS_NAMES.POSITION} = ${currentPosition}`;
69
69
  const pVal: number = statsDf
70
70
  .groupBy([C.COLUMNS_NAMES.P_VALUE])
@@ -101,7 +101,7 @@ export function renderSARCell(canvasContext: CanvasRenderingContext2D, currentAA
101
101
  canvasContext.closePath();
102
102
 
103
103
  canvasContext.fill();
104
- if (substitutionsInfo) {
104
+ if (substitutionsInfo.size > 0) {
105
105
  canvasContext.textBaseline = 'middle';
106
106
  canvasContext.textAlign = 'center';
107
107
  canvasContext.fillStyle = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(coef)));
@@ -3,7 +3,7 @@ export enum COLUMNS_NAMES {
3
3
  ACTIVITY = '~activity',
4
4
  ACTIVITY_SCALED = 'activity_scaled',
5
5
  ALIGNED_SEQUENCE = '~aligned_sequence',
6
- AMINO_ACID_RESIDUE = 'AAR',
6
+ MONOMER = 'AAR',
7
7
  POSITION = 'Pos',
8
8
  P_VALUE = 'pValue',
9
9
  MEAN_DIFFERENCE = 'Mean difference',
@@ -0,0 +1,163 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import * as C from './constants';
5
+ import {PeptidesModel} from '../model';
6
+ import {isGridCellInvalid} from './misc';
7
+
8
+ const CELL_SIZE = 20; // 20px cell height and width
9
+
10
+ export class InvariantMap extends DG.Filter {
11
+ model: PeptidesModel | null = null;
12
+ chosenCells: {[position: string]: string[]} = {};
13
+
14
+ constructor() {
15
+ super();
16
+ }
17
+
18
+ get caption(): string {return 'Invariant Map';}
19
+
20
+ get filterSummary(): string {
21
+ let summary = '';
22
+ for (const [pos, aarList] of Object.entries(this.chosenCells))
23
+ if (aarList.length > 0)
24
+ summary += `${pos}: ${aarList}\n`;
25
+
26
+ return summary;
27
+ }
28
+
29
+ get isFiltering(): boolean {return true && super.isFiltering;}
30
+
31
+ get isReadyToApplyFilter(): boolean {return this.model != null;}
32
+
33
+ async attach(df: DG.DataFrame): Promise<void> {
34
+ super.attach(df);
35
+ this.model = await PeptidesModel.getInstance(df);
36
+ this.render(true);
37
+ }
38
+
39
+ saveState(): any {
40
+ const state = super.saveState();
41
+ state.chosenCells = JSON.stringify(this.chosenCells);
42
+ return state;
43
+ }
44
+
45
+ applyState(state: any): void {
46
+ super.applyState(state);
47
+ if (state.chosenCells) {
48
+ this.chosenCells = JSON.parse(state.chosenCells);
49
+ this.render();
50
+ }
51
+ }
52
+
53
+ applyFilter(): void {
54
+ this.dataFrame?.filter.init((bitsetIndex) => {
55
+ for (const [position, aarList] of Object.entries(this.chosenCells)) {
56
+ if (aarList.length != 0 && !aarList.includes(this.dataFrame!.get(position, bitsetIndex)))
57
+ return false;
58
+ }
59
+ return true;
60
+ });
61
+ }
62
+
63
+ render(initChosenCells: boolean = false): void {
64
+ if (this.model == null)
65
+ return;
66
+
67
+ const invariantDf = this.model.statsDf.groupBy([C.COLUMNS_NAMES.MONOMER])
68
+ .pivot(C.COLUMNS_NAMES.POSITION)
69
+ .add('first', C.COLUMNS_NAMES.COUNT, '')
70
+ .aggregate();
71
+ invariantDf.getCol(C.COLUMNS_NAMES.MONOMER).semType = C.SEM_TYPES.MONOMER;
72
+ const orderedColNames = invariantDf.columns.names().sort((a, b) => {
73
+ const aInt = parseInt(a);
74
+ const bInt = parseInt(b);
75
+ if (isNaN(aInt))
76
+ return -1;
77
+ else if (isNaN(bInt))
78
+ return 1;
79
+ return aInt - bInt;
80
+ });
81
+
82
+ // Create grid and set properties
83
+ const invariantGrid = invariantDf.plot.grid();
84
+ const gridCols = invariantGrid.columns;
85
+ gridCols.rowHeader!.visible = false;
86
+ gridCols.setOrder(orderedColNames);
87
+
88
+ for (let gridColIndex = 0; gridColIndex < gridCols.length; ++gridColIndex) {
89
+ const gridCol = gridCols.byIndex(gridColIndex)!
90
+ if (gridCol.name != C.COLUMNS_NAMES.MONOMER)
91
+ gridCol.width = CELL_SIZE;
92
+ }
93
+
94
+ if (initChosenCells) {
95
+ this.chosenCells = {};
96
+ for (const col of invariantDf.columns)
97
+ if (col.name != C.COLUMNS_NAMES.MONOMER)
98
+ this.chosenCells[col.name] = [];
99
+ }
100
+
101
+ invariantGrid.root.addEventListener('click', (ev) => {
102
+ invariantDf.currentRowIdx = -1;
103
+ const gridCell = invariantGrid.hitTest(ev.offsetX, ev.offsetY);
104
+ if (isGridCellInvalid(gridCell) || gridCell!.tableColumn!.name == C.COLUMNS_NAMES.MONOMER)
105
+ return;
106
+
107
+ const position = gridCell!.tableColumn!.name;
108
+ const aar = invariantDf.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!);
109
+ const aarList = this.chosenCells[position];
110
+ const aarIndex = aarList.indexOf(aar);
111
+
112
+ if (aarIndex != -1)
113
+ aarList.splice(aarIndex, 1);
114
+ else
115
+ aarList.push(aar);
116
+
117
+ invariantGrid.invalidate();
118
+ this.applyFilter();
119
+ });
120
+
121
+ invariantGrid.onCellRender.subscribe((args) => {
122
+ //FIXME: for some reason it doesn't work when I set right away
123
+ const gridProps = invariantGrid.props;
124
+ gridProps.allowBlockSelection = false;
125
+ gridProps.allowColSelection = false;
126
+ gridProps.allowRowSelection = false;
127
+ gridProps.allowEdit = false;
128
+ gridProps.rowHeight = CELL_SIZE;
129
+
130
+ const gc = args.cell;
131
+ const tableColName = gc.tableColumn?.name;
132
+ const tableRowIndex = gc.tableRowIndex;
133
+
134
+ if (isGridCellInvalid(gc) || tableColName == C.COLUMNS_NAMES.MONOMER ||
135
+ tableRowIndex == null || tableRowIndex == -1)
136
+ return;
137
+
138
+ const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ?
139
+ tableColName : invariantDf.get(C.COLUMNS_NAMES.POSITION, tableRowIndex);
140
+ const currentAAR: string = invariantDf.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
141
+ const canvasContext = args.g;
142
+ const bound = args.bounds;
143
+
144
+ canvasContext.font = '10px Roboto';
145
+ canvasContext.textAlign = 'center';
146
+ canvasContext.textBaseline = 'middle';
147
+ canvasContext.fillStyle = '#000';
148
+ canvasContext.fillText(gc.cell.value, bound.x + (bound.width / 2), bound.y + (bound.height / 2), bound.width);
149
+
150
+ const aarSelection = this.chosenCells[currentPosition];
151
+ if (aarSelection.includes(currentAAR)) {
152
+ canvasContext.strokeStyle = '#000';
153
+ canvasContext.lineWidth = 1;
154
+ canvasContext.strokeRect(bound.x + 1, bound.y + 1, bound.width - 1, bound.height - 1);
155
+ }
156
+ args.preventDefault();
157
+ });
158
+
159
+ const gridHost = ui.box(invariantGrid.root);
160
+ gridHost.style.width = '100%';
161
+ this.root.appendChild(gridHost);
162
+ }
163
+ }
package/src/utils/misc.ts CHANGED
@@ -91,3 +91,8 @@ export function calculateSingleBarData(col: DG.Column<string>, selection: DG.Bit
91
91
 
92
92
  return colStats;
93
93
  }
94
+
95
+ export function isGridCellInvalid(gc: DG.GridCell | null): boolean {
96
+ return !gc || !gc.cell.value || !gc.tableColumn || gc.tableRowIndex == null || gc.tableRowIndex == -1 ||
97
+ gc.cell.value == DG.INT_NULL || gc.cell.value == DG.FLOAT_NULL;
98
+ }
@@ -15,7 +15,6 @@ export class SARViewerBase extends DG.JsViewer {
15
15
  model!: PeptidesModel;
16
16
  scaling: string;
17
17
  bidirectionalAnalysis: boolean;
18
- showSubstitution: boolean;
19
18
  maxSubstitutions: number;
20
19
  minActivityDelta: number;
21
20
  _titleHost = ui.divText('SAR Viewer', {id: 'pep-viewer-title'});
@@ -27,18 +26,15 @@ export class SARViewerBase extends DG.JsViewer {
27
26
 
28
27
  this.scaling = this.string('scaling', 'none', {choices: ['none', 'lg', '-lg']});
29
28
  this.bidirectionalAnalysis = this.bool('bidirectionalAnalysis', false);
30
- this.showSubstitution = this.bool('showSubstitution', true);
31
- this.maxSubstitutions = this.int('maxSubstitutions', 2);
32
- this.minActivityDelta = this.float('minActivityDelta', 1);
29
+ this.maxSubstitutions = this.int('maxSubstitutions', 1);
30
+ this.minActivityDelta = this.float('minActivityDelta', 0);
33
31
  }
34
32
 
35
33
  async onTableAttached(): Promise<void> {
36
34
  super.onTableAttached();
37
35
  this.sourceGrid = this.view?.grid ?? (grok.shell.v as DG.TableView).grid;
38
36
  this.model = await PeptidesModel.getInstance(this.dataFrame);
39
- // this.model.init(this.dataFrame);
40
37
  this.helpUrl = '/help/domains/bio/peptides.md';
41
- // await this.requestDataUpdate();
42
38
 
43
39
  this.initProperties();
44
40
  }
@@ -65,11 +61,6 @@ export class SARViewerBase extends DG.JsViewer {
65
61
  this.viewerGrid?.invalidate();
66
62
  }
67
63
 
68
- // async requestDataUpdate(): Promise<void> {
69
- // await this.model.updateData(this.scaling, this.sourceGrid, this.bidirectionalAnalysis,
70
- // this.minActivityDelta, this.maxSubstitutions, this.showSubstitution);
71
- // }
72
-
73
64
  onPropertyChanged(property: DG.Property): void {
74
65
  super.onPropertyChanged(property);
75
66
  this.dataFrame.tags[property.name] = `${property.get(this)}`;
@@ -89,10 +80,6 @@ export class SARViewerBase extends DG.JsViewer {
89
80
  }
90
81
  }
91
82
 
92
- if (!this.showSubstitution && ['maxSubstitutions', 'activityLimit'].includes(propName))
93
- return;
94
-
95
- // await this.requestDataUpdate();
96
83
  this.model.updateDefault();
97
84
  this.render(true);
98
85
  }
@@ -102,7 +89,7 @@ export class SARViewerBase extends DG.JsViewer {
102
89
  * Structure-activity relationship viewer.
103
90
  */
104
91
  export class SARViewer extends SARViewerBase {
105
- _titleHost = ui.divText('Monomer-Positions', {id: 'pep-viewer-title'});
92
+ _titleHost = ui.divText('Mutation Cliffs', {id: 'pep-viewer-title'});
106
93
  _name = 'Structure-Activity Relationship';
107
94
 
108
95
  constructor() {super();}
@@ -112,7 +99,6 @@ export class SARViewer extends SARViewerBase {
112
99
  async onTableAttached(): Promise<void> {
113
100
  await super.onTableAttached();
114
101
  this.model.sarViewer ??= this;
115
- // this.dataFrame.temp['sarViewer'] = this;
116
102
 
117
103
  this.subs.push(this.model.onSARGridChanged.subscribe((data) => {
118
104
  this.viewerGrid = data;
@@ -22,7 +22,7 @@ export async function analyzePeptidesWidget(currentDf: DG.DataFrame, col: DG.Col
22
22
  let funcs = DG.Func.find({package: 'Bio', name: 'webLogoViewer'});
23
23
  if (funcs.length == 0)
24
24
  return new DG.Widget(ui.label('Bio package is missing or out of date. Please install the latest version.'));
25
-
25
+
26
26
  funcs = DG.Func.find({package: 'Helm', name: 'getMonomerLib'});
27
27
  if (funcs.length == 0)
28
28
  return new DG.Widget(ui.label('Helm package is missing or out of date. Please install the latest version.'));
@@ -5,7 +5,7 @@ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations'
5
5
  import $ from 'cash-dom';
6
6
 
7
7
  import * as C from '../utils/constants';
8
- import {getStats, Stats} from '../utils/filtering-statistics';
8
+ import {getStats, Stats} from '../utils/statistics';
9
9
  import {PeptidesModel} from '../model';
10
10
 
11
11
  const allConst = 'All';
@@ -5,13 +5,13 @@ import * as type from '../utils/types';
5
5
  import {PeptidesModel} from '../model';
6
6
  import {getSeparator} from '../utils/misc';
7
7
 
8
- export function substitutionsWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
8
+ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
9
9
  const substInfo = model.substitutionsInfo;
10
10
  const currentCell = model.currentSelection;
11
11
  const positions = Object.keys(currentCell);
12
12
 
13
13
  if (!positions.length)
14
- return new DG.Widget(ui.label('No substitution table generated'));
14
+ return new DG.Widget(ui.label('No mutations table generated'));
15
15
 
16
16
  const substitutionsArray: string[] = [];
17
17
  const deltaArray: number[] = [];
@@ -49,9 +49,9 @@ export function substitutionsWidget(table: DG.DataFrame, model: PeptidesModel):
49
49
  }
50
50
 
51
51
  if (!substitutionsArray.length)
52
- return new DG.Widget(ui.label('No substitution table generated'));
52
+ return new DG.Widget(ui.label('No mutations table generated'));
53
53
 
54
- const substCol = DG.Column.fromStrings('Substiutions', substitutionsArray);
54
+ const substCol = DG.Column.fromStrings('Mutation', substitutionsArray);
55
55
  substCol.semType = C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;
56
56
  substCol.tags[C.TAGS.SEPARATOR] = getSeparator(alignedSeqCol);
57
57
  substCol.tags[DG.TAGS.UNITS] = alignedSeqCol.tags[DG.TAGS.UNITS];
@@ -61,7 +61,7 @@ export function substitutionsWidget(table: DG.DataFrame, model: PeptidesModel):
61
61
  const substTable =
62
62
  DG.DataFrame.fromColumns([substCol, DG.Column.fromList('double', 'Delta', deltaArray), hiddenSubstToAarCol]);
63
63
 
64
- const aminoToInput = ui.stringInput('Substituted to:', '', () => {
64
+ const aminoToInput = ui.stringInput('Mutated to:', '', () => {
65
65
  const substitutedToAar = aminoToInput.stringValue;
66
66
  if (substitutedToAar != '')
67
67
  substTable.filter.init((idx) => hiddenSubstToAarCol.get(idx) === substitutedToAar);