@datagrok/peptides 1.8.2 → 1.9.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
3
  "friendlyName": "Peptides",
4
- "version": "1.8.2",
4
+ "version": "1.9.0",
5
5
  "author": {
6
6
  "name": "Volodymyr Dyma",
7
7
  "email": "vdyma@datagrok.ai"
@@ -16,8 +16,8 @@
16
16
  "@datagrok-libraries/bio": "^5.29.3",
17
17
  "@datagrok-libraries/ml": "^6.3.3",
18
18
  "@datagrok-libraries/statistics": "^1.1.7",
19
- "@datagrok-libraries/utils": "^4.0.2",
20
- "@datagrok-libraries/tutorials": "^1.3.1",
19
+ "@datagrok-libraries/utils": "^4.0.7",
20
+ "@datagrok-libraries/tutorials": "^1.3.2",
21
21
  "cash-dom": "^8.1.2",
22
22
  "datagrok-api": "^1.6.0",
23
23
  "file-loader": "^6.2.0",
package/src/demo/fasta.ts CHANGED
@@ -20,7 +20,7 @@ export async function macromoleculeSarFastaDemoUI(): Promise<void> {
20
20
  simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
21
21
  simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
22
22
  simpleAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
23
- const simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
23
+ const simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
24
24
 
25
25
  demo.step('Load data', async () => {grok.shell.addTableView(simpleTable);},
26
26
  {description: 'Load the dataset containing macromolecule sequences. Notice how Datagrok detects macromolecules ' +
package/src/model.ts CHANGED
@@ -19,8 +19,8 @@ import * as uuid from 'uuid';
19
19
 
20
20
  import * as C from './utils/constants';
21
21
  import * as type from './utils/types';
22
- import {calculateSelected, extractMonomerInfo, scaleActivity, getStatsSummary} from './utils/misc';
23
- import {MonomerPosition, MostPotentResiduesViewer} from './viewers/sar-viewer';
22
+ import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary} from './utils/misc';
23
+ import {MONOMER_POSITION_PROPERTIES, MonomerPosition, MostPotentResiduesViewer} from './viewers/sar-viewer';
24
24
  import * as CR from './utils/cell-renderer';
25
25
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
26
26
  import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap,
@@ -39,8 +39,8 @@ export type SummaryStats = {
39
39
  minPValue: number, maxPValue: number,
40
40
  minRatio: number, maxRatio: number,
41
41
  };
42
- export type PositionStats = { [monomer: string]: Stats } & { general: SummaryStats };
43
- export type MonomerPositionStats = { [position: string]: PositionStats } & { general: SummaryStats };
42
+ export type PositionStats = {[monomer: string]: Stats} & {general: SummaryStats};
43
+ export type MonomerPositionStats = {[position: string]: PositionStats} & {general: SummaryStats};
44
44
  export type ClusterStats = {[cluster: string]: Stats};
45
45
  export enum CLUSTER_TYPE {
46
46
  ORIGINAL = 'original',
@@ -78,11 +78,11 @@ export class PeptidesModel {
78
78
  _monomerPositionSelection!: type.PositionToAARList;
79
79
  _monomerPositionFilter!: type.PositionToAARList;
80
80
  _clusterSelection!: string[];
81
- _mutationCliffs?: type.MutationCliffs;
81
+ _mutationCliffs: type.MutationCliffs | null = null;
82
82
  isInitialized = false;
83
83
  _analysisView?: DG.TableView;
84
84
 
85
- monomerMap: { [key: string]: { molfile: string, fullName: string } } = {};
85
+ monomerMap: {[key: string]: {molfile: string, fullName: string}} = {};
86
86
  monomerLib: IMonomerLib | null = null; // To get monomers from lib(s)
87
87
  monomerWorks: MonomerWorks | null = null; // To get processed monomers
88
88
 
@@ -93,8 +93,8 @@ export class PeptidesModel {
93
93
  initBitset: DG.BitSet;
94
94
  isInvariantMapTrigger: boolean = false;
95
95
  headerSelectedMonomers: type.MonomerSelectionStats = {};
96
- webLogoBounds: { [positon: string]: { [monomer: string]: DG.Rect } } = {};
97
- cachedWebLogoTooltip: { bar: string; tooltip: HTMLDivElement | null; } = {bar: '', tooltip: null};
96
+ webLogoBounds: {[positon: string]: {[monomer: string]: DG.Rect}} = {};
97
+ cachedWebLogoTooltip: {bar: string, tooltip: HTMLDivElement | null} = {bar: '', tooltip: null};
98
98
  _monomerPositionDf?: DG.DataFrame;
99
99
  _alphabet?: string;
100
100
  _mostPotentResiduesDf?: DG.DataFrame;
@@ -170,18 +170,15 @@ export class PeptidesModel {
170
170
  return col.getTag(bioTAGS.alphabet);
171
171
  }
172
172
 
173
- get mutationCliffs(): type.MutationCliffs {
174
- if (this._mutationCliffs)
175
- return this._mutationCliffs;
173
+ get mutationCliffs(): type.MutationCliffs | null {
174
+ // if (this._mutationCliffs)
175
+ // return this._mutationCliffs;
176
176
 
177
- const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
178
- //TODO: set categories ordering the same to share compare indexes instead of strings
179
- const monomerColumns: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
180
- this._mutationCliffs = findMutations(scaledActivityCol.getRawData(), monomerColumns, this.settings);
181
- return this._mutationCliffs;
177
+ // this.updateMutationCliffs(false);
178
+ return this._mutationCliffs!;
182
179
  }
183
180
 
184
- set mutationCliffs(si: type.MutationCliffs) {
181
+ set mutationCliffs(si: type.MutationCliffs | null) {
185
182
  this._mutationCliffs = si;
186
183
  }
187
184
 
@@ -354,10 +351,7 @@ export class PeptidesModel {
354
351
  this.createScaledCol();
355
352
  break;
356
353
  case 'mutationCliffs':
357
- const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
358
- //TODO: set categories ordering the same to share compare indexes instead of strings
359
- const monomerCols: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractMonomerInfo);
360
- this.mutationCliffs = findMutations(scaledActivityCol.getRawData(), monomerCols, this.settings);
354
+ this.updateMutationCliffs();
361
355
  break;
362
356
  case 'stats':
363
357
  this.monomerPositionStats = this.calculateMonomerPositionStatistics();
@@ -390,6 +384,22 @@ export class PeptidesModel {
390
384
  this._settingsSubject.next(this.settings);
391
385
  }
392
386
 
387
+ updateMutationCliffs(notify: boolean = true): void {
388
+ const scaledActivityCol: DG.Column<number> = this.df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
389
+ //TODO: set categories ordering the same to share compare indexes instead of strings
390
+ const monomerCols: type.RawColumn[] = this.df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER).map(extractColInfo);
391
+ const targetCol = typeof this.settings.targetColumnName !== 'undefined' ?
392
+ extractColInfo(this.df.getCol(this.settings.targetColumnName)) : null;
393
+ const mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
394
+ const currentTarget = mpViewer?.getProperty(MONOMER_POSITION_PROPERTIES.TARGET)?.get(mpViewer);
395
+ const targetOptions = {targetCol: targetCol, currentTarget: currentTarget};
396
+ const mutationCliffs = findMutations(scaledActivityCol.getRawData(), monomerCols, this.settings, targetOptions);
397
+ if (notify)
398
+ this.mutationCliffs = mutationCliffs;
399
+ else
400
+ this._mutationCliffs = mutationCliffs;
401
+ }
402
+
393
403
  createMonomerPositionDf(): DG.DataFrame {
394
404
  const positions = this.splitSeqDf.columns.names();
395
405
  const matrixDf = this.matrixDf
@@ -1097,6 +1107,8 @@ export class PeptidesModel {
1097
1107
  this.updateGrid();
1098
1108
  this.fireBitsetChanged(true);
1099
1109
  this.analysisView.grid.invalidate();
1110
+ if (typeof this.settings.targetColumnName === 'undefined')
1111
+ this.updateMutationCliffs();
1100
1112
  }
1101
1113
 
1102
1114
  findViewer(viewerType: VIEWER_TYPE): DG.Viewer | null {
@@ -1121,6 +1133,12 @@ export class PeptidesModel {
1121
1133
  const [dockType, refNode, ratio] = mostPotentResidues === null ? [DG.DOCK_TYPE.DOWN, null, undefined] :
1122
1134
  [DG.DOCK_TYPE.LEFT, this.findViewerNode(VIEWER_TYPE.MOST_POTENT_RESIDUES), 0.7];
1123
1135
  dm.dock(monomerPosition, dockType, refNode, VIEWER_TYPE.MONOMER_POSITION, ratio);
1136
+ if (typeof this.settings.targetColumnName !== 'undefined') {
1137
+ const target = monomerPosition.getProperty(MONOMER_POSITION_PROPERTIES.TARGET)!;
1138
+ const choices = this.df.getCol(this.settings.targetColumnName!).categories;
1139
+ target.choices = choices;
1140
+ target.set(monomerPosition, choices[0]);
1141
+ }
1124
1142
  }
1125
1143
 
1126
1144
  async addMostPotentResidues(): Promise<void> {
package/src/package.ts CHANGED
@@ -176,6 +176,7 @@ export async function peptideSpacePanel(col: DG.Column): Promise<DG.Widget> {
176
176
  //name: Macromolecule SAR Analysis
177
177
  //description: Macromolecule SAR Analysis demo on peptide sequences in FASTA format
178
178
  //meta.demoPath: Bioinformatics | Macromolecule SAR Analysis
179
+ //meta.isDemoScript: True
179
180
  export async function macromoleculeSarFastaDemo(): Promise<void> {
180
181
  return macromoleculeSarFastaDemoUI();
181
182
  }
@@ -5,11 +5,13 @@ import {category, test, expect, before} from '@datagrok-libraries/utils/src/test
5
5
  import {_package} from '../package-test';
6
6
  import {findMutations} from '../utils/algorithms';
7
7
  import * as type from '../utils/types';
8
+ import {extractColInfo} from '../utils/misc';
8
9
 
9
10
  category('Algorithms', () => {
10
11
  let activityCol: type.RawData;
11
12
  let monomerColumns: type.RawColumn[];
12
13
  let settings: type.PeptidesSettings;
14
+ let targetCol: type.RawColumn;
13
15
 
14
16
  before(async () => {
15
17
  activityCol = DG.Column.fromList('int', 'test', [1, 2, 5]).getRawData();
@@ -22,29 +24,35 @@ category('Algorithms', () => {
22
24
  rawData: col.getRawData(),
23
25
  cat: col.categories,
24
26
  }));
27
+ targetCol = extractColInfo(DG.Column.fromList('string', 'target', ['1', '2', '2']));
25
28
  settings = {maxMutations: 1, minActivityDelta: 2};
26
29
  });
27
30
 
28
31
  test('MutationCliffs', async () => {
29
- const substInfo: type.MutationCliffs = findMutations(activityCol, monomerColumns, settings);
30
- expect(substInfo.has('C'), true);
31
- expect(substInfo.has('D'), true);
32
- expect(substInfo.has('A'), false);
32
+ // Check every pair
33
+ let mutationCliffsInfo: type.MutationCliffs = findMutations(activityCol, monomerColumns, settings);
34
+ expect(mutationCliffsInfo.has('C'), true, `MutationCliffsInfo should have key 'C'`);
35
+ expect(mutationCliffsInfo.has('D'), true, `MutationCliffsInfo should have key 'D'`);
36
+ expect(mutationCliffsInfo.has('A'), false, `MutationCliffsInfo should not have key 'A'`);
33
37
 
34
- const c = substInfo.get('C')!;
35
- const d = substInfo.get('D')!;
36
- expect(c.has('3'), true);
37
- expect(d.has('3'), true);
38
+ const c = mutationCliffsInfo.get('C')!;
39
+ const d = mutationCliffsInfo.get('D')!;
40
+ expect(c.has('3'), true, `MutationCliffsInfo['C'] should have key '3'`);
41
+ expect(d.has('3'), true, `MutationCliffsInfo['D'] should have key '3'`);
38
42
 
39
43
  const c3 = c.get('3')!;
40
44
  const d3 = d.get('3')!;
41
- expect(c3.has(1), true);
42
- expect(d3.has(2), true);
45
+ expect(c3.has(1), true, `MutationCliffsInfo['C']['3'] should have key 1`);
46
+ expect(d3.has(2), true, `MutationCliffsInfo['D']['3'] should have key 2`);
43
47
 
44
48
  const c31 = c3.get(1)!;
45
49
  const d32 = d3.get(2)!;
46
- expect(c31[0], 2);
47
- expect(d32[0], 1);
50
+ expect(c31[0], 2, `MutationCliffsInfo['C']['3'][1] should have value 2`);
51
+ expect(d32[0], 1, `MutationCliffsInfo['D']['3'][2] should have value 1`);
52
+
53
+ // Check with target
54
+ mutationCliffsInfo = findMutations(activityCol, monomerColumns, settings, {targetCol, currentTarget: '1'});
55
+ expect(mutationCliffsInfo.size, 0, `MutationCliffsInfo should be empty for target '1'`);
48
56
  });
49
57
 
50
58
  test('MutationCliffs - Benchmark 5k', async () => {
package/src/tests/core.ts CHANGED
@@ -33,7 +33,7 @@ category('Core', () => {
33
33
  simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
34
34
  simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
35
35
  simpleAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
36
- simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
36
+ simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
37
37
 
38
38
  model = await startAnalysis(
39
39
  simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, C.SCALING_METHODS.MINUS_LG);
@@ -53,7 +53,7 @@ category('Core', () => {
53
53
  complexAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
54
54
  complexAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
55
55
  complexAlignedSeqCol.setTag(C.TAGS.SEPARATOR, '/');
56
- complexScaledCol = scaleActivity(complexActivityCol, '-lg');
56
+ complexScaledCol = scaleActivity(complexActivityCol, C.SCALING_METHODS.MINUS_LG);
57
57
 
58
58
  model = await startAnalysis(
59
59
  complexActivityCol, complexAlignedSeqCol, null, complexTable, complexScaledCol, C.SCALING_METHODS.MINUS_LG);
@@ -72,7 +72,7 @@ category('Core', () => {
72
72
  simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
73
73
  simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
74
74
  simpleAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
75
- simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
75
+ simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
76
76
 
77
77
  model = await startAnalysis(
78
78
  simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, C.SCALING_METHODS.MINUS_LG);
@@ -104,7 +104,7 @@ category('Core', () => {
104
104
  test('Cluster stats - Benchmark HELM 5k', async () => {
105
105
  const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k_2.d42'))[0];
106
106
  const activityCol = df.getCol('Activity');
107
- const scaledActivityCol = scaleActivity(activityCol, 'none');
107
+ const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
108
108
  const clustersCol = df.getCol('Cluster');
109
109
  const sequenceCol = df.getCol('HELM');
110
110
  sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
@@ -119,7 +119,7 @@ category('Core', () => {
119
119
  test('Monomer Position stats - Benchmark HELM 5k', async () => {
120
120
  const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
121
121
  const activityCol = df.getCol('Activity');
122
- const scaledActivityCol = scaleActivity(activityCol, 'none');
122
+ const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
123
123
  const clustersCol = df.getCol('Cluster');
124
124
  const sequenceCol = df.getCol('HELM');
125
125
  sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
@@ -134,7 +134,7 @@ category('Core', () => {
134
134
  test('Analysis start - Benchmark HELM 5k', async () => {
135
135
  const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
136
136
  const activityCol = df.getCol('Activity');
137
- const scaledActivityCol = scaleActivity(activityCol, 'none');
137
+ const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
138
138
  const clustersCol = df.getCol('Cluster');
139
139
  const sequenceCol = df.getCol('HELM');
140
140
  sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
@@ -18,7 +18,7 @@ category('Viewers: Basic', () => {
18
18
  const viewers = DG.Func.find({package: 'Peptides', tags: ['viewer']}).map((f) => f.friendlyName);
19
19
  for (const v of viewers) {
20
20
  test(v, async () => {
21
- await testViewer(v, df.clone(), true);
21
+ await testViewer(v, df.clone(), {detectSemanticTypes: true});
22
22
  }, {skipReason: 'GROK-11534'});
23
23
  }
24
24
  });
@@ -5,20 +5,31 @@ import {getTypedArrayConstructor} from './misc';
5
5
  type MutationCliffInfo = {pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number};
6
6
 
7
7
  export function findMutations(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
8
- settings: type.PeptidesSettings = {}): type.MutationCliffs {
8
+ settings: type.PeptidesSettings = {},
9
+ targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {}): type.MutationCliffs {
9
10
  const nCols = monomerInfoArray.length;
10
11
  if (nCols == 0)
11
12
  throw new Error(`PepAlgorithmError: Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
12
13
 
14
+ settings.minActivityDelta ??= 0;
15
+ settings.maxMutations ??= 1;
16
+ const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
17
+
13
18
  const substitutionsInfo: type.MutationCliffs = new Map();
14
19
  const nRows = activityArray.length;
15
20
  for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
21
+ if (currentTargetIdx !== -1 && targetOptions.targetCol?.rawData[seq1Idx] !== currentTargetIdx)
22
+ continue;
23
+
16
24
  for (let seq2Idx = seq1Idx + 1; seq2Idx < nRows; seq2Idx++) {
25
+ if (currentTargetIdx !== -1 && targetOptions.targetCol?.rawData[seq2Idx] !== currentTargetIdx)
26
+ continue;
27
+
17
28
  let substCounter = 0;
18
29
  const activityValSeq1 = activityArray[seq1Idx];
19
30
  const activityValSeq2 = activityArray[seq2Idx];
20
31
  const delta = activityValSeq1 - activityValSeq2;
21
- if (Math.abs(delta) < (settings.minActivityDelta ?? 0))
32
+ if (Math.abs(delta) < settings.minActivityDelta)
22
33
  continue;
23
34
 
24
35
  let substCounterFlag = false;
@@ -30,7 +41,7 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
30
41
  continue;
31
42
 
32
43
  substCounter++;
33
- substCounterFlag = substCounter > (settings.maxMutations ?? 1);
44
+ substCounterFlag = substCounter > settings.maxMutations;
34
45
  if (substCounterFlag)
35
46
  break;
36
47
 
@@ -21,7 +21,7 @@ export function setAARRenderer(col: DG.Column, alphabet: string): void {
21
21
 
22
22
  export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D, currentAAR: string,
23
23
  currentPosition: string, monomerPositionStats: MonomerPositionStats, bound: DG.Rect,
24
- mutationCliffsSelection: types.PositionToAARList, substitutionsInfo: types.MutationCliffs,
24
+ mutationCliffsSelection: types.PositionToAARList, substitutionsInfo: types.MutationCliffs | null = null,
25
25
  twoColorMode: boolean = false): void {
26
26
  const positionStats = monomerPositionStats[currentPosition];
27
27
  const pVal: number = positionStats[currentAAR].pValue;
@@ -58,7 +58,7 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
58
58
  canvasContext.closePath();
59
59
 
60
60
  canvasContext.fill();
61
- if (substitutionsInfo.size > 0) {
61
+ if (substitutionsInfo !== null && substitutionsInfo.size > 0) {
62
62
  canvasContext.textBaseline = 'middle';
63
63
  canvasContext.textAlign = 'center';
64
64
  canvasContext.fillStyle = DG.Color.toHtml(DG.Color.black);
package/src/utils/misc.ts CHANGED
@@ -15,15 +15,16 @@ export function getSeparator(col: DG.Column<string>): string {
15
15
  return col.getTag(C.TAGS.SEPARATOR) ?? '';
16
16
  }
17
17
 
18
- export function scaleActivity(activityCol: DG.Column<number>, scaling: string = 'none'): DG.Column<number> {
18
+ export function scaleActivity(activityCol: DG.Column<number>, scaling: C.SCALING_METHODS = C.SCALING_METHODS.NONE,
19
+ ): DG.Column<number> {
19
20
  let formula = (x: number): number => x;
20
21
  switch (scaling) {
21
- case 'none':
22
+ case C.SCALING_METHODS.NONE:
22
23
  break;
23
- case 'lg':
24
+ case C.SCALING_METHODS.LG:
24
25
  formula = (x: number): number => Math.log10(x);
25
26
  break;
26
- case '-lg':
27
+ case C.SCALING_METHODS.MINUS_LG:
27
28
  formula = (x: number): number => -Math.log10(x);
28
29
  break;
29
30
  default:
@@ -62,7 +63,7 @@ export function calculateSelected(df: DG.DataFrame): type.MonomerSelectionStats
62
63
  // gc.cell.value == DG.INT_NULL || gc.cell.value == DG.FLOAT_NULL;
63
64
  // }
64
65
 
65
- export function extractMonomerInfo(col: DG.Column<string>): type.RawColumn {
66
+ export function extractColInfo(col: DG.Column<string>): type.RawColumn {
66
67
  return {
67
68
  name: col.name,
68
69
  cat: col.categories,
@@ -15,6 +15,7 @@ export type PeptidesSettings = {
15
15
  sequenceColumnName?: string,
16
16
  activityColumnName?: string,
17
17
  clustersColumnName?: string,
18
+ targetColumnName?: string,
18
19
  scaling?: SCALING_METHODS,
19
20
  isBidirectional?: boolean,
20
21
  showMonomerPosition?: boolean,
@@ -11,18 +11,29 @@ export enum MONOMER_POSITION_MODE {
11
11
  INVARIANT_MAP = 'Invariant Map',
12
12
  }
13
13
 
14
+ export enum MONOMER_POSITION_PROPERTIES {
15
+ COLOR_COLUMN_NAME = 'colorColumnName',
16
+ AGGREGATION = 'aggregation',
17
+ TARGET = 'target',
18
+ };
19
+
14
20
  /** Structure-activity relationship viewer */
15
21
  export class MonomerPosition extends DG.JsViewer {
16
- _titleHost = ui.divText('Mutation Cliffs', {id: 'pep-viewer-title'});
22
+ _titleHost = ui.divText(MONOMER_POSITION_MODE.MUTATION_CLIFFS, {id: 'pep-viewer-title'});
17
23
  _viewerGrid!: DG.Grid;
18
24
  _model!: PeptidesModel;
19
25
  colorColumnName: string;
20
26
  aggregation: string;
27
+ target: string;
21
28
 
22
29
  constructor() {
23
30
  super();
24
- this.colorColumnName = this.string('colorColumnName', C.COLUMNS_NAMES.ACTIVITY_SCALED, {category: 'Invariant Map'});
25
- this.aggregation = this.string('aggregation', 'avg', {category: 'Invariant Map', choices: Object.values(DG.AGG)});
31
+ this.target = this.string(MONOMER_POSITION_PROPERTIES.TARGET, null,
32
+ {category: MONOMER_POSITION_MODE.MUTATION_CLIFFS, choices: []});
33
+ this.colorColumnName = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
34
+ {category: MONOMER_POSITION_MODE.INVARIANT_MAP});
35
+ this.aggregation = this.string(MONOMER_POSITION_PROPERTIES.AGGREGATION, DG.AGG.AVG,
36
+ {category: MONOMER_POSITION_MODE.INVARIANT_MAP, choices: Object.values(DG.AGG)});
26
37
  }
27
38
 
28
39
  get name(): string {return VIEWER_TYPE.MONOMER_POSITION;}
@@ -65,6 +76,9 @@ export class MonomerPosition extends DG.JsViewer {
65
76
 
66
77
  onPropertyChanged(property: DG.Property): void {
67
78
  super.onPropertyChanged(property);
79
+ if (property.name === MONOMER_POSITION_PROPERTIES.TARGET)
80
+ this.model.updateMutationCliffs();
81
+
68
82
  this.render();
69
83
  }
70
84
 
@@ -105,14 +119,14 @@ export class MonomerPosition extends DG.JsViewer {
105
119
  mutationCliffsMode.value = true;
106
120
  this.mode = MONOMER_POSITION_MODE.MUTATION_CLIFFS;
107
121
  });
108
- mutationCliffsMode.addPostfix('Mutation Cliffs');
122
+ mutationCliffsMode.addPostfix(MONOMER_POSITION_MODE.MUTATION_CLIFFS);
109
123
  const invariantMapMode = ui.boolInput('', this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP);
110
124
  invariantMapMode.root.addEventListener('click', () => {
111
125
  mutationCliffsMode.value = false;
112
126
  invariantMapMode.value = true;
113
127
  this.mode = MONOMER_POSITION_MODE.INVARIANT_MAP;
114
128
  });
115
- invariantMapMode.addPostfix('Invariant Map');
129
+ invariantMapMode.addPostfix(MONOMER_POSITION_MODE.INVARIANT_MAP);
116
130
  const setDefaultProperties = (input: DG.InputBase): void => {
117
131
  $(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
118
132
  $(input.root).find('.ui-input-description').css('padding', '0px').css('padding-left', '5px');
@@ -236,7 +250,6 @@ export class MostPotentResiduesViewer extends DG.JsViewer {
236
250
  function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvariantMap?: boolean,
237
251
  colorCol?: DG.Column<number>, colorAgg?: DG.AggregationType): void {
238
252
  const renderColNames = [...model.splitSeqDf.columns.names(), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
239
- // const mdCol = model.monomerPositionStats.getCol(C.COLUMNS_NAMES.MEAN_DIFFERENCE);
240
253
  const canvasContext = args.g;
241
254
  const bound = args.bounds;
242
255
 
@@ -323,8 +336,12 @@ export function showTooltip(cell: DG.GridCell, x: number, y: number, model: Pept
323
336
 
324
337
  function chooseAction(aar: string, position: string, isShiftPressed: boolean, isFilter: boolean,
325
338
  model: PeptidesModel): void {
326
- if (!isShiftPressed)
327
- model.initMonomerPositionSelection({cleanInit: true, notify: false});
339
+ if (!isShiftPressed) {
340
+ if (isFilter)
341
+ model.initMonomerPositionFilter({cleanInit: true, notify: false});
342
+ else
343
+ model.initMonomerPositionSelection({cleanInit: true, notify: false});
344
+ }
328
345
 
329
346
  model.modifyMonomerPositionSelection(aar, position, isFilter);
330
347
  }
@@ -6,53 +6,62 @@ import {PeptidesModel} from '../model';
6
6
  import {getSeparator} from '../utils/misc';
7
7
 
8
8
  export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
9
- const currentFilter = table.filter.getSelectedIndexes();
9
+ const filteredIndexes = table.filter.getSelectedIndexes();
10
10
  const substInfo = model.mutationCliffs;
11
11
  const currentCell = model.monomerPositionSelection;
12
12
  const positions = Object.keys(currentCell);
13
13
 
14
- if (!positions.length)
14
+ if (!positions.length || substInfo === null)
15
15
  return new DG.Widget(ui.label('No mutations table generated'));
16
16
 
17
17
  const substitutionsArray: string[] = [];
18
18
  const deltaArray: number[] = [];
19
19
  const substitutedToArray: string[] = [];
20
20
  const alignedSeqCol = table.getCol(model.settings.sequenceColumnName!);
21
+ const alignedSeqColCategories = alignedSeqCol.categories;
22
+ const alignedSeqColData = alignedSeqCol.getRawData();
21
23
  const activityScaledCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
24
+ const activityScaledColData = activityScaledCol.getRawData();
22
25
  const seenIndexes = new Map<number, number[]>();
23
26
 
24
27
  for (const pos of positions) {
28
+ const posCol = table.getCol(pos);
29
+ const posColCategories = posCol.categories;
30
+ const posColData = posCol.getRawData();
31
+
25
32
  for (const aar of currentCell[pos]) {
26
33
  const substitutionsMap = substInfo.get(aar)?.get(pos) as Map<number, type.UTypedArray> | undefined;
27
34
  if (typeof substitutionsMap === 'undefined')
28
35
  continue;
29
36
 
30
- const posCol = table.getCol(pos);
31
37
  for (const [referenceIdx, indexArray] of substitutionsMap.entries()) {
32
- if (!currentFilter.includes(referenceIdx))
38
+ if (!filteredIndexes.includes(referenceIdx))
33
39
  continue;
34
40
 
35
41
  const forbiddentIndexes = seenIndexes.get(referenceIdx) ?? [];
36
- const baseSequence = alignedSeqCol.get(referenceIdx);
37
- const baseActivity = activityScaledCol.get(referenceIdx);
42
+ // const baseSequence = alignedSeqCol.get(referenceIdx);
43
+ const baseSequence = alignedSeqColCategories[alignedSeqColData[referenceIdx]];
44
+ // const baseActivity = activityScaledCol.get(referenceIdx);
45
+ const baseActivity = activityScaledColData[referenceIdx];
38
46
 
39
47
  for (const subIdx of indexArray) {
40
- if (forbiddentIndexes.includes(subIdx) || !currentFilter.includes(subIdx))
48
+ if (forbiddentIndexes.includes(subIdx) || !filteredIndexes.includes(subIdx))
41
49
  continue;
42
50
 
43
51
  if (!seenIndexes.has(subIdx))
44
52
  seenIndexes.set(subIdx, []);
53
+ const subSeq = alignedSeqColCategories[alignedSeqColData[subIdx]];
45
54
 
46
55
  seenIndexes.get(subIdx)!.push(referenceIdx);
47
- substitutionsArray.push(`${baseSequence}#${alignedSeqCol.get(subIdx)}`);
48
- deltaArray.push(baseActivity - activityScaledCol.get(subIdx));
49
- substitutedToArray.push(posCol.get(subIdx));
56
+ substitutionsArray.push(`${baseSequence}#${subSeq}`);
57
+ deltaArray.push(baseActivity - activityScaledColData[subIdx]);
58
+ substitutedToArray.push(posColCategories[posColData[subIdx]]);
50
59
  }
51
60
  }
52
61
  }
53
62
  }
54
63
 
55
- if (!substitutionsArray.length)
64
+ if (substitutionsArray.length === 0)
56
65
  return new DG.Widget(ui.label('No mutations table generated'));
57
66
 
58
67
  const substCol = DG.Column.fromStrings('Mutation', substitutionsArray);
@@ -72,8 +72,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
72
72
  const histogramHost = ui.div([], {id: 'pep-hist-host'});
73
73
 
74
74
  const activityScalingMethod = ui.choiceInput(
75
- 'Scaling', 'none', ['none', 'lg', '-lg'],
76
- async (currentMethod: string): Promise<void> => {
75
+ 'Scaling', C.SCALING_METHODS.NONE, Object.values(C.SCALING_METHODS),
76
+ async (currentMethod: C.SCALING_METHODS): Promise<void> => {
77
77
  scaledCol = scaleActivity(activityColumnChoice.value!, currentMethod);
78
78
 
79
79
  const hist = DG.DataFrame.fromColumns([scaledCol]).plot.histogram({
@@ -87,7 +87,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
87
87
  });
88
88
  histogramHost.lastChild?.remove();
89
89
  histogramHost.appendChild(hist.root);
90
- });
90
+ }) as DG.InputBase<C.SCALING_METHODS | null>;
91
91
  activityScalingMethod.setTooltip('Function to apply for each value in activity column');
92
92
 
93
93
  const activityScalingMethodState = (): void => {
@@ -101,23 +101,27 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
101
101
  activityColumnChoice.fireChanged();
102
102
  activityScalingMethod.fireChanged();
103
103
 
104
- const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice];
104
+ const targetColumnChoice = ui.columnInput('Target', df, null, () => {
105
+ if (targetColumnChoice.value?.type !== DG.COLUMN_TYPE.STRING) {
106
+ grok.shell.warning('Target column should be of string type');
107
+ targetColumnChoice.value = null;
108
+ }
109
+ });
110
+ targetColumnChoice.nullable = true;
111
+
112
+ const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice, targetColumnChoice];
105
113
  if (seqColInput !== null)
106
114
  inputsList.splice(0, 0, seqColInput);
107
115
 
108
- const bitsetChanged = df.filter.onChanged.subscribe(() => {
109
- activityScalingMethodState();
110
- });
116
+ const bitsetChanged = df.filter.onChanged.subscribe(() => activityScalingMethodState());
111
117
 
112
118
  const startAnalysisCallback = async (): Promise<boolean> => {
113
119
  const sequencesCol = col ?? seqColInput!.value;
120
+ bitsetChanged.unsubscribe();
114
121
  if (sequencesCol) {
115
122
  const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
116
- scaledCol, activityScalingMethod.stringValue as C.SCALING_METHODS ?? C.SCALING_METHODS.NONE);
117
- if (model != null) {
118
- bitsetChanged.unsubscribe();
119
- return true;
120
- }
123
+ scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, targetColumnChoice.value);
124
+ return model != null;
121
125
  }
122
126
  return false;
123
127
  };
@@ -151,10 +155,11 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
151
155
 
152
156
  export async function startAnalysis(activityColumn: DG.Column<number>, peptidesCol: DG.Column<string>,
153
157
  clustersColumn: DG.Column | null, currentDf: DG.DataFrame, scaledCol: DG.Column<number>, scaling: C.SCALING_METHODS,
154
- ): Promise<PeptidesModel | null> {
158
+ targetColumn: DG.Column<string> | null = null): Promise<PeptidesModel | null> {
155
159
  const progress = DG.TaskBarProgressIndicator.create('Loading SAR...');
156
160
  let model = null;
157
- if (activityColumn.type === DG.TYPE.FLOAT || activityColumn.type === DG.TYPE.INT) {
161
+ if (activityColumn.type === DG.COLUMN_TYPE.FLOAT || activityColumn.type === DG.COLUMN_TYPE.INT ||
162
+ activityColumn.type === DG.COLUMN_TYPE.BIG_INT || activityColumn.type === DG.COLUMN_TYPE.QNUM) {
158
163
  //prepare new DF
159
164
  const newDf = DG.DataFrame.create(currentDf.rowCount);
160
165
  const newDfCols = newDf.columns;
@@ -173,6 +178,9 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
173
178
  minActivityDelta: 0,
174
179
  showDendrogram: false,
175
180
  };
181
+ if (targetColumn !== null)
182
+ settings.targetColumnName = targetColumn.name;
183
+
176
184
  if (clustersColumn) {
177
185
  const clusterCol = newDf.getCol(clustersColumn.name);
178
186
  if (clusterCol.type != DG.COLUMN_TYPE.STRING)