@datagrok/peptides 1.17.4 → 1.17.6

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.17.4",
4
+ "version": "1.17.6",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@datagrok-libraries/bio": "^5.39.14",
17
- "@datagrok-libraries/ml": "^6.4.1",
17
+ "@datagrok-libraries/ml": "^6.4.10",
18
18
  "@datagrok-libraries/statistics": "^1.2.11",
19
19
  "@datagrok-libraries/utils": "^4.1.36",
20
20
  "@datagrok-libraries/tutorials": "^1.3.11",
package/src/model.ts CHANGED
@@ -51,6 +51,8 @@ import {showMonomerTooltip} from './utils/tooltips';
51
51
  import {AggregationColumns, MonomerPositionStats} from './utils/statistics';
52
52
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
53
53
  import {getDbscanWorker} from '@datagrok-libraries/math';
54
+ import {markovCluster} from '@datagrok-libraries/ml/src/MCL/clustering-view';
55
+ import {DistanceAggregationMethods} from '@datagrok-libraries/ml/src/distance-matrix/types';
54
56
 
55
57
  export enum VIEWER_TYPE {
56
58
  MONOMER_POSITION = 'Monomer-Position',
@@ -99,6 +101,8 @@ export class PeptidesModel {
99
101
  accordionSource: VIEWER_TYPE | null = null;
100
102
  // sequence space viewer
101
103
  _sequenceSpaceViewer: DG.ScatterPlotViewer | null = null;
104
+ //MCL viewer
105
+ _mclViewer: DG.ScatterPlotViewer | null = null;
102
106
  /**
103
107
  * @param {DG.DataFrame}dataFrame - DataFrame to use for analysis
104
108
  */
@@ -151,6 +155,7 @@ export class PeptidesModel {
151
155
  // Peptides analysis settings
152
156
  _settings: type.PeptidesSettings | null = null;
153
157
  _sequenceSpaceCols: string[] = [];
158
+ _mclCols: string[] = [];
154
159
 
155
160
  /**
156
161
  * @return {type.PeptidesSettings}- Peptides analysis settings
@@ -185,6 +190,9 @@ export class PeptidesModel {
185
190
  case 'showDendrogram':
186
191
  updateVars.add('dendrogram');
187
192
  break;
193
+ case 'showSequenceSpace':
194
+ updateVars.add('showSequenceSpace');
195
+ break;
188
196
  case 'showLogoSummaryTable':
189
197
  updateVars.add('logoSummaryTable');
190
198
  break;
@@ -199,6 +207,10 @@ export class PeptidesModel {
199
207
  break;
200
208
  case 'sequenceSpaceParams':
201
209
  updateVars.add('sequenceSpaceParams');
210
+ break;
211
+ case 'mclSettings':
212
+ updateVars.add('mclSettings');
213
+ break;
202
214
  }
203
215
  }
204
216
  // Write updated settings
@@ -216,6 +228,8 @@ export class PeptidesModel {
216
228
  updateVars.add('clusterParams');
217
229
  }
218
230
  }
231
+ if (updateVars.has('sequenceSpaceParams'))
232
+ updateVars.delete('clusterParams');
219
233
 
220
234
  // Apply new settings
221
235
  for (const variable of updateVars) {
@@ -258,11 +272,16 @@ export class PeptidesModel {
258
272
  mpr.render();
259
273
  break;
260
274
  case 'sequenceSpaceParams':
261
- this.addSequenceSpace({clusterEmbeddings: this.settings!.sequenceSpaceParams?.clusterEmbeddings});
275
+ case 'showSequenceSpace':
276
+ if (this.settings!.showSequenceSpace)
277
+ this.addSequenceSpace({clusterEmbeddings: this.settings!.sequenceSpaceParams?.clusterEmbeddings});
262
278
  break;
263
279
  case 'clusterParams':
264
280
  this.clusterEmbeddings();
265
281
  break;
282
+ case 'mclSettings':
283
+ this.addMCLClusters();
284
+ break;
266
285
  }
267
286
  }
268
287
  }
@@ -1170,6 +1189,67 @@ export class PeptidesModel {
1170
1189
  gridCol && (gridCol.visible = false);
1171
1190
  }
1172
1191
 
1192
+ async addMCLClusters(): Promise<void> {
1193
+ if (this._mclViewer !== null) {
1194
+ try {
1195
+ this._mclViewer?.detach();
1196
+ this._mclViewer?.close();
1197
+ } catch (_) {}
1198
+ }
1199
+ if (this._mclCols.length !== 0)
1200
+ this._mclCols.forEach((col) => this.df.columns.remove(col));
1201
+ this._mclCols = [];
1202
+ const seqCol = this.df.getCol(this.settings!.sequenceColumnName!);
1203
+ this.settings!.mclSettings ??= new type.MCLSettings();
1204
+ const mclParams = this.settings?.mclSettings;
1205
+
1206
+ let counter = 0;
1207
+ const addedColCount = 4;
1208
+ const columnAddedSub = this.df.onColumnsAdded.subscribe((colArgs: DG.ColumnsArgs) => {
1209
+ for (const col of colArgs.columns) {
1210
+ if (col.name.toLowerCase().startsWith('embed') ||
1211
+ col.name.toLowerCase().startsWith('cluster')) {
1212
+ const gridCol = this.analysisView.grid.col(col.name);
1213
+ if (gridCol == null || this._mclCols.includes(col.name))
1214
+ continue;
1215
+
1216
+ gridCol.visible = false;
1217
+ this._mclCols.push(col.name);
1218
+ counter++;
1219
+ }
1220
+ }
1221
+ if (counter === addedColCount)
1222
+ columnAddedSub.unsubscribe();
1223
+ });
1224
+ const mclAdditionSub = grok.events.onViewerAdded.subscribe((info) => {
1225
+ try {
1226
+ const v = info.args.viewer as DG.ScatterPlotViewer;
1227
+ if (v.type === DG.VIEWER.SCATTER_PLOT) {
1228
+ if (this._sequenceSpaceViewer && this.analysisView.dockManager.findNode(this._sequenceSpaceViewer.root)) {
1229
+ const rootNode = this.analysisView.dockManager.findNode(this._sequenceSpaceViewer.root);
1230
+ setTimeout(() => {
1231
+ this.analysisView.dockManager.dock(v, DG.DOCK_TYPE.FILL, rootNode);
1232
+ });
1233
+ }
1234
+ mclAdditionSub.unsubscribe();
1235
+ }
1236
+ } catch (e) {
1237
+ console.error(e);
1238
+ }
1239
+ });
1240
+
1241
+ const bioPreprocessingFunc = DG.Func.find({package: 'Bio', name: 'macromoleculePreprocessingFunction'})[0];
1242
+ const mclViewer = await markovCluster(
1243
+ this.df, [seqCol], [mclParams!.distanceF], [1],
1244
+ DistanceAggregationMethods.MANHATTAN, [bioPreprocessingFunc], [{
1245
+ gapOpen: mclParams!.gapOpen, gapExtend: mclParams!.gapExtend,
1246
+ fingerprintType: mclParams!.fingerprintType,
1247
+ }], mclParams!.threshold, mclParams!.maxIterations,
1248
+ );
1249
+ mclAdditionSub.unsubscribe();
1250
+ this._mclViewer = mclViewer ?? null;
1251
+ }
1252
+
1173
1253
  /**
1174
1254
  * Adds Sequence Space viewer to the analysis view
1175
1255
  */
@@ -1240,7 +1320,7 @@ export class PeptidesModel {
1240
1320
  if (col.name.toLowerCase().startsWith('embed_') ||
1241
1321
  ( seqSpaceSettings.clusterEmbeddings && col.name.toLowerCase().startsWith('cluster'))) {
1242
1322
  const gridCol = this.analysisView.grid.col(col.name);
1243
- if (gridCol == null)
1323
+ if (gridCol == null || this._sequenceSpaceCols.includes(col.name))
1244
1324
  continue;
1245
1325
 
1246
1326
  gridCol.visible = false;
@@ -1252,8 +1332,25 @@ export class PeptidesModel {
1252
1332
  columnAddedSub.unsubscribe();
1253
1333
  });
1254
1334
 
1335
+ const seqSpaceAdditionSub = grok.events.onViewerAdded.subscribe((info) => {
1336
+ try {
1337
+ const v = info.args.viewer as DG.ScatterPlotViewer;
1338
+ if (v.type === DG.VIEWER.SCATTER_PLOT) {
1339
+ if (this._mclViewer && this.analysisView.dockManager.findNode(this._mclViewer.root)) {
1340
+ const rootNode = this.analysisView.dockManager.findNode(this._mclViewer.root);
1341
+ setTimeout(() => {
1342
+ this.analysisView.dockManager.dock(v, DG.DOCK_TYPE.FILL, rootNode);
1343
+ });
1344
+ }
1345
+ seqSpaceAdditionSub.unsubscribe();
1346
+ }
1347
+ } catch (e) {
1348
+ console.error(e);
1349
+ }
1350
+ });
1255
1351
  const seqSpaceViewer: DG.ScatterPlotViewer | undefined =
1256
1352
  await grok.functions.call('Bio:sequenceSpaceTopMenu', seqSpaceParams);
1353
+ seqSpaceAdditionSub.unsubscribe();
1257
1354
  if (!(seqSpaceViewer instanceof DG.ScatterPlotViewer))
1258
1355
  return;
1259
1356
 
@@ -18,6 +18,10 @@ export {tests};
18
18
  //input: object testContext {optional: true}
19
19
  //output: dataframe result
20
20
  export async function test(category: string, test: string, testContext: TestContext): Promise<DG.DataFrame> {
21
+ // const helmInit = DG.Func.find({name: 'initHelm'})[0];
22
+ // if (helmInit)
23
+ // await helmInit.apply();
24
+ testContext.catchUnhandled = false;
21
25
  const data = await runTests({category, test, testContext});
22
26
  return DG.DataFrame.fromObjects(data)!;
23
27
  }
package/src/tests/core.ts CHANGED
@@ -12,29 +12,19 @@ import {ALIGNMENT, ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-librarie
12
12
  import {MonomerPosition} from '../viewers/sar-viewer';
13
13
 
14
14
  category('Core', () => {
15
- let simpleTable: DG.DataFrame;
16
- let simpleActivityCol: DG.Column<number>;
17
- let simpleAlignedSeqCol: DG.Column<string>;
18
- let simpleScaledCol: DG.Column<number>;
19
-
20
- let complexTable: DG.DataFrame;
21
- let complexActivityCol: DG.Column<number>;
22
- let complexAlignedSeqCol: DG.Column<string>;
23
- let complexScaledCol: DG.Column<number>;
24
15
  const alignedSequenceCol = 'AlignedSequence';
25
16
 
26
17
  let model: PeptidesModel | null = null;
27
-
28
18
  test('Start analysis: simple', async () => {
29
19
  const simpleActivityColName = 'IC50';
30
- simpleTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned.csv'));
31
- simpleActivityCol = simpleTable.getCol(simpleActivityColName);
32
- simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
20
+ const simpleTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned.csv'));
21
+ const simpleActivityCol = simpleTable.getCol(simpleActivityColName);
22
+ const simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
33
23
  simpleAlignedSeqCol.semType = DG.SEMTYPE.MACROMOLECULE;
34
24
  simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
35
25
  simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
36
26
  simpleAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
37
- simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
27
+ const simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
38
28
 
39
29
  model = await startAnalysis(
40
30
  simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, C.SCALING_METHODS.MINUS_LG);
@@ -47,15 +37,15 @@ category('Core', () => {
47
37
 
48
38
  test('Start analysis: сomplex', async () => {
49
39
  const complexActivityColName = 'Activity';
50
- complexTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned_2.csv'));
51
- complexActivityCol = complexTable.getCol(complexActivityColName);
52
- complexAlignedSeqCol = complexTable.getCol('MSA');
40
+ const complexTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned_2.csv'));
41
+ const complexActivityCol = complexTable.getCol(complexActivityColName);
42
+ const complexAlignedSeqCol = complexTable.getCol('MSA');
53
43
  complexAlignedSeqCol.semType = DG.SEMTYPE.MACROMOLECULE;
54
44
  complexAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.UN);
55
45
  complexAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
56
46
  complexAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
57
47
  complexAlignedSeqCol.setTag(C.TAGS.SEPARATOR, '/');
58
- complexScaledCol = scaleActivity(complexActivityCol, C.SCALING_METHODS.MINUS_LG);
48
+ const complexScaledCol = scaleActivity(complexActivityCol, C.SCALING_METHODS.MINUS_LG);
59
49
 
60
50
  model = await startAnalysis(
61
51
  complexActivityCol, complexAlignedSeqCol, null, complexTable, complexScaledCol, C.SCALING_METHODS.MINUS_LG);
@@ -68,14 +58,14 @@ category('Core', () => {
68
58
 
69
59
  test('Save and load project', async () => {
70
60
  const simpleActivityColName = 'IC50';
71
- simpleTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned.csv'));
72
- simpleActivityCol = simpleTable.getCol(simpleActivityColName);
73
- simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
61
+ const simpleTable = DG.DataFrame.fromCsv(await _package.files.readAsText('aligned.csv'));
62
+ const simpleActivityCol = simpleTable.getCol(simpleActivityColName);
63
+ const simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
74
64
  simpleAlignedSeqCol.semType = DG.SEMTYPE.MACROMOLECULE;
75
65
  simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
76
66
  simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
77
67
  simpleAlignedSeqCol.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
78
- simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
68
+ const simpleScaledCol = scaleActivity(simpleActivityCol, C.SCALING_METHODS.MINUS_LG);
79
69
 
80
70
  model = await startAnalysis(simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol,
81
71
  C.SCALING_METHODS.MINUS_LG);
@@ -106,4 +96,4 @@ category('Core', () => {
106
96
  await grok.dapi.tables.delete(sti);
107
97
  await grok.dapi.projects.delete(sp);
108
98
  }, {skipReason: 'ViewLayout should become ViewInfo in 1.18.'});
109
- });
99
+ }, {clear: true});
@@ -24,7 +24,8 @@ category('Viewers: Basic', () => {
24
24
  const viewers = DG.Func.find({package: 'Peptides', tags: ['viewer']}).map((f) => f.friendlyName);
25
25
  for (const v of viewers) {
26
26
  test(v, async () => {
27
- await testViewer(v, df.clone(), {detectSemanticTypes: true, arbitraryDfTest: false});
27
+ await testViewer(v, df.clone(), {detectSemanticTypes: true, arbitraryDfTest: false, readOnly: true});
28
+ await delay(1000);
28
29
  });
29
30
  }
30
31
  });
@@ -48,7 +48,7 @@ category('Widgets: Settings', () => {
48
48
 
49
49
  // Check number of panes
50
50
  const panes = settingsElements.accordion.panes.map((pane) => pane.name);
51
- expect(panes.length, 4, `Expected 4 panes, got ${settingsElements.accordion.panes.length}`);
51
+ expect(panes.length, 5, `Expected 5 panes, got ${settingsElements.accordion.panes.length}`);
52
52
  for (const paneName of Object.values(SETTINGS_PANES))
53
53
  expect(panes.includes(paneName), true, `Pane ${paneName} is missing`);
54
54
 
@@ -153,6 +153,7 @@ category('Widgets: Actions', () => {
153
153
 
154
154
  before(async () => {
155
155
  df = DG.DataFrame.fromCsv(await _package.files.readAsText('tests/HELM_small.csv'));
156
+ await df.meta.detectSemanticTypes();
156
157
  activityCol = df.getCol(TEST_COLUMN_NAMES.ACTIVITY);
157
158
  sequenceCol = df.getCol(TEST_COLUMN_NAMES.SEQUENCE);
158
159
  sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
@@ -179,13 +180,14 @@ category('Widgets: Actions', () => {
179
180
 
180
181
  const selection = model.df.selection;
181
182
  selection.set(0, true, false);
183
+ selection.set(1, true, false);
182
184
 
183
185
  const newViewId = model.createNewView();
184
186
  const currentTable = grok.shell.t;
185
187
 
186
188
  expect(currentTable.getTag(C.TAGS.MULTIPLE_VIEWS), '1', 'Current table is expected to have multiple views tag');
187
189
  expect(currentTable.getTag(DG.TAGS.ID), newViewId, 'Current table is expected to have the same UUID as new view');
188
- expect(currentTable.rowCount, 1, 'Current table is expected to have 1 row');
190
+ expect(currentTable.rowCount, 2, 'Current table is expected to have 2 rows');
189
191
 
190
192
  await delay(500);
191
193
  const currentTableModel = currentTable.temp[PeptidesModel.modelName] as PeptidesModel;
@@ -59,7 +59,7 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
59
59
 
60
60
  const x = currentMeanDifference >= 0 ? pValComplement : -pValComplement;
61
61
  const coef = DG.Color.toHtml(pVal === null ? DG.Color.lightLightGray :
62
- DG.Color.scaleColor(x, -centeredPValLimit, centeredPValLimit));
62
+ DG.Color.scaleColor(x, -centeredPValLimit, centeredPValLimit, 255));
63
63
 
64
64
  const halfWidth = bounds.width / 2;
65
65
  const maxMeanDifference = Math.max(Math.abs(viewer.monomerPositionStats.general.minMeanDifference),
@@ -110,13 +110,17 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
110
110
  * @param bounds - Cell bounds.
111
111
  * @param color - Cell color.
112
112
  */
113
+
114
+ function setAlpha(color: number, alpha: number): number {
115
+ return (color & 0x00ffffff | (alpha << 24)) >>> 0;
116
+ }
113
117
  export function renderInvariantMapCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
114
118
  currentPosition: string, invariantMapSelection: type.Selection, cellValue: number, bounds: DG.Rect,
115
119
  color: number): void {
116
120
  //FIXME: This is a hack, because `color` value sometimes comes incomplete. E.g. we found that here `color` value is
117
121
  // 255 and its contrast color would be black, which is not visible on blue (color code) background. The full number
118
122
  // is actually 4278190335.
119
- color = DG.Color.fromHtml(DG.Color.toHtml(color));
123
+ color = DG.Color.fromHtml(DG.Color.toHtml(setAlpha(color, 255)));
120
124
  canvasContext.fillStyle = DG.Color.toHtml(color);
121
125
  canvasContext.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
122
126
  canvasContext.font = '13px Roboto, Roboto Local, sans-serif';
package/src/utils/misc.ts CHANGED
@@ -271,6 +271,7 @@ export function modifySelection(selection: type.Selection, clusterOrMonomerPosit
271
271
  */
272
272
  export function highlightMonomerPosition(monomerPosition: type.SelectionItem, dataFrame: DG.DataFrame,
273
273
  monomerPositionStats: MonomerPositionStats): void {
274
+ if (!dataFrame) return;
274
275
  const bitArray = new BitArray(dataFrame.rowCount);
275
276
  if (monomerPosition.positionOrClusterType === C.COLUMNS_NAMES.MONOMER) {
276
277
  const positionStats = Object.values(monomerPositionStats);
@@ -387,3 +388,14 @@ export function isApplicableDataframe(table: DG.DataFrame, minRows: number = 2):
387
388
  return table.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE).length > 0 &&
388
389
  wu(table.columns.numerical).toArray().length > 0 && table.rowCount >= minRows;
389
390
  }
391
+
392
+ export function debounce<T extends Array<any>, K>(f: (...args: T) => Promise<K>, timeout: number = 500,
393
+ ): (...args: T) => Promise<K> {
394
+ let timer: NodeJS.Timeout | number | undefined;
395
+ return async (...args: T) => {
396
+ return new Promise<K>((resolve) => {
397
+ clearTimeout(timer);
398
+ timer = setTimeout(() => resolve(f(...args)), timeout);
399
+ });
400
+ };
401
+ }
@@ -20,8 +20,19 @@ export interface PeptidesSettings {
20
20
  showMostPotentResidues?: boolean,
21
21
  showLogoSummaryTable?: boolean,
22
22
  showDendrogram?: boolean,
23
+ showSequenceSpace?: boolean,
23
24
  columns?: AggregationColumns,
24
25
  sequenceSpaceParams: SequenceSpaceParams,
26
+ mclSettings: MCLSettings,
27
+ }
28
+
29
+ export class MCLSettings {
30
+ maxIterations: number = 5;
31
+ threshold: number = 80;
32
+ distanceF: MmDistanceFunctionsNames = MmDistanceFunctionsNames.NEEDLEMANN_WUNSCH;
33
+ gapOpen: number = 1;
34
+ gapExtend: number = 0.6;
35
+ fingerprintType: string = 'Morgan';
25
36
  }
26
37
 
27
38
  export class SequenceSpaceParams {
@@ -23,6 +23,7 @@ import {_package} from '../package';
23
23
  import {showTooltip} from '../utils/tooltips';
24
24
  import {calculateMonomerPositionStatistics, findMutations, MutationCliffsOptions} from '../utils/algorithms';
25
25
  import {
26
+ debounce,
26
27
  extractColInfo,
27
28
  getTotalAggColumns,
28
29
  highlightMonomerPosition,
@@ -91,6 +92,9 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
91
92
  maxMutations: number;
92
93
  _scaledActivityColumn: DG.Column | null = null;
93
94
  doRender: boolean = true;
95
+ mutationCliffsDebouncer: (
96
+ activityArray: type.RawData, monomerInfoArray: type.RawColumn[], options?: MutationCliffsOptions
97
+ ) => Promise<type.MutationCliffs>;
94
98
 
95
99
  /** Sets common properties for inheritor viewers. */
96
100
  protected constructor() {
@@ -111,11 +115,16 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
111
115
  this.minActivityDelta = this.float(SAR_PROPERTIES.MIN_ACTIVITY_DELTA, 0,
112
116
  {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 0, max: 100});
113
117
  this.maxMutations = this.int(SAR_PROPERTIES.MAX_MUTATIONS, 1,
114
- {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 1, max: 50});
118
+ {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 1, max: 20});
115
119
 
116
120
  this.columns = this.columnList(SAR_PROPERTIES.COLUMNS, [], {category: PROPERTY_CATEGORIES.AGGREGATION});
117
121
  this.aggregation = this.string(SAR_PROPERTIES.AGGREGATION, DG.AGG.AVG,
118
122
  {category: PROPERTY_CATEGORIES.AGGREGATION, choices: C.AGGREGATION_TYPES});
123
+
124
+ this.mutationCliffsDebouncer = debounce(
125
+ async (activityArray: type.RawData, monomerInfoArray: type.RawColumn[], options?: MutationCliffsOptions) => {
126
+ return await findMutations(activityArray, monomerInfoArray, options);
127
+ });
119
128
  }
120
129
 
121
130
  _viewerGrid: DG.Grid | null = null;
@@ -433,7 +442,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
433
442
  * @return - mutation cliffs.
434
443
  */
435
444
  async calculateMutationCliffs(): Promise<MutationCliffs> {
436
- const scaledActivityCol: DG.Column<number> = this.dataFrame.getCol(this.sequenceColumnName);
445
+ const scaledActivityCol: DG.Column<number> = this.dataFrame.getCol(this.activityColumnName);
437
446
  //TODO: set categories ordering the same to share compare indexes instead of strings
438
447
  const monomerCols: type.RawColumn[] = this.positionColumns.map(extractColInfo);
439
448
  const targetCol = this.targetColumnName ? extractColInfo(this.dataFrame.getCol(this.targetColumnName)) : null;
@@ -442,7 +451,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
442
451
  maxMutations: this.maxMutations, minActivityDelta: this.minActivityDelta,
443
452
  targetCol, currentTarget: this.targetCategory,
444
453
  };
445
- return await findMutations(scaledActivityCol.getRawData(), monomerCols, options);
454
+ return await this.mutationCliffsDebouncer(scaledActivityCol.getRawData(), monomerCols, options);
446
455
  }
447
456
  }
448
457
 
@@ -130,7 +130,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): Di
130
130
  bitsetChanged.unsubscribe();
131
131
  if (sequencesCol) {
132
132
  const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
133
- scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: true,
133
+ scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: false, addMCL: true,
134
134
  useEmbeddingsClusters: generateClustersInput.value ?? false});
135
135
  return model !== null;
136
136
  }
@@ -167,6 +167,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): Di
167
167
  type AnalysisOptions = {
168
168
  addSequenceSpace?: boolean,
169
169
  useEmbeddingsClusters?: boolean,
170
+ addMCL?: boolean,
170
171
  };
171
172
 
172
173
  /**
@@ -207,8 +208,9 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
207
208
 
208
209
  const settings: type.PeptidesSettings = {
209
210
  sequenceColumnName: peptidesCol.name, activityColumnName: activityColumn.name, activityScaling: scaling,
210
- columns: {}, showDendrogram: false,
211
+ columns: {}, showDendrogram: false, showSequenceSpace: false,
211
212
  sequenceSpaceParams: new type.SequenceSpaceParams(!!options.useEmbeddingsClusters && !clustersColumn),
213
+ mclSettings: new type.MCLSettings(),
212
214
  };
213
215
 
214
216
  if (clustersColumn) {
@@ -253,6 +255,25 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
253
255
  }, 100);
254
256
  }
255
257
  }
258
+ } else if (options.addMCL ?? false) {
259
+ await model.addMCLClusters();
260
+ if (!clustersColumn && (options.useEmbeddingsClusters ?? false)) {
261
+ const mclClusterCol = model._mclCols
262
+ .find(
263
+ (col) => model?.df.col(col) && col.toLowerCase().startsWith('cluster') && !col.toLowerCase().includes('size'),
264
+ );
265
+ if (mclClusterCol) {
266
+ const lstProps: ILogoSummaryTable = {
267
+ clustersColumnName: mclClusterCol, sequenceColumnName: peptidesCol.name, activityScaling: scaling,
268
+ activityColumnName: activityColumn.name,
269
+ };
270
+ await model.addLogoSummaryTable(lstProps);
271
+ setTimeout(() => {
272
+ model && (model?.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable)?.render &&
273
+ (model?.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable)?.render();
274
+ }, 100);
275
+ }
276
+ }
256
277
  }
257
278
 
258
279
 
@@ -19,6 +19,7 @@ export enum SETTINGS_PANES {
19
19
  VIEWERS = 'Viewers',
20
20
  COLUMNS = 'Columns',
21
21
  SEQUENCE_SPACE = 'Sequence space',
22
+ MCL = 'MCL',
22
23
  }
23
24
 
24
25
  export enum GENERAL_INPUTS {
@@ -44,12 +45,22 @@ export enum SEQUENCE_SPACE_INPUTS {
44
45
  FINGERPRINT_TYPE = 'Fingerprint type',
45
46
  }
46
47
 
48
+ export enum MCL_INPUTS {
49
+ DISTANCE_FUNCTION = 'Distance function',
50
+ GAP_OPEN = 'Gap open penalty',
51
+ GAP_EXTEND = 'Gap extend penalty',
52
+ FINGERPRINT_TYPE = 'Fingerprint type',
53
+ THRESHOLD = 'Similarity threshold',
54
+ MAX_ITERATIONS = 'Max iterations',
55
+ }
56
+
47
57
 
48
58
  export const PANES_INPUTS = {
49
59
  [SETTINGS_PANES.GENERAL]: GENERAL_INPUTS,
50
60
  [SETTINGS_PANES.VIEWERS]: VIEWERS_INPUTS,
51
61
  [SETTINGS_PANES.COLUMNS]: COLUMNS_INPUTS,
52
62
  [SETTINGS_PANES.SEQUENCE_SPACE]: SEQUENCE_SPACE_INPUTS,
63
+ [SETTINGS_PANES.MCL]: MCL_INPUTS,
53
64
  };
54
65
 
55
66
  /**
@@ -69,7 +80,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
69
80
  const result: type.PartialPeptidesSettings = {};
70
81
  const inputs: PaneInputs = {};
71
82
  const seqSpaceParams = settings?.sequenceSpaceParams ?? new type.SequenceSpaceParams();
72
-
83
+ const mclParams = settings?.mclSettings ?? new type.MCLSettings();
73
84
  // General pane options
74
85
  const activityCol = ui.columnInput(GENERAL_INPUTS.ACTIVITY, model.df,
75
86
  model.df.getCol(model.settings!.activityColumnName!), () => result.activityColumnName = activityCol.value!.name,
@@ -102,10 +113,23 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
102
113
  const isDendrogramEnabled = wu(model.analysisView.viewers).some((v) => v.type === VIEWER_TYPE.DENDROGRAM);
103
114
  const dendrogram = ui.boolInput(VIEWER_TYPE.DENDROGRAM, isDendrogramEnabled ?? false,
104
115
  () => result.showDendrogram = dendrogram.value) as DG.InputBase<boolean>;
116
+ const showSeqSpace = ui.boolInput('Sequence space', !!settings?.showSequenceSpace, () => {
117
+ result.showSequenceSpace = showSeqSpace.value ?? undefined;
118
+ if (showSeqSpace.value) {
119
+ seqSpacePane.root.style.display = 'flex';
120
+ if (!settings?.showSequenceSpace)
121
+ result.sequenceSpaceParams = seqSpaceParams;
122
+ } else {
123
+ seqSpacePane.root.style.display = 'none';
124
+ delete result.sequenceSpaceParams;
125
+ }
126
+ if (result.showSequenceSpace === settings?.showSequenceSpace)
127
+ delete result.showSequenceSpace;
128
+ });
105
129
  dendrogram.setTooltip('Show dendrogram viewer');
106
130
  dendrogram.enabled = getTreeHelperInstance() !== null;
107
131
 
108
- accordion.addPane(SETTINGS_PANES.VIEWERS, () => ui.inputs([dendrogram]), true);
132
+ accordion.addPane(SETTINGS_PANES.VIEWERS, () => ui.inputs([dendrogram, showSeqSpace]), true);
109
133
  inputs[SETTINGS_PANES.VIEWERS] = [dendrogram];
110
134
 
111
135
  // Columns to include pane options
@@ -182,7 +206,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
182
206
  input.root.style.display = 'none';
183
207
  });
184
208
  }
185
-
209
+ // SEQ SPACE INPUTS
186
210
  const distanceFunctionInput = ui.choiceInput(SEQUENCE_SPACE_INPUTS.DISTANCE_FUNCTION, seqSpaceParams.distanceF,
187
211
  [distFNames.NEEDLEMANN_WUNSCH, distFNames.HAMMING, distFNames.LEVENSHTEIN, distFNames.MONOMER_CHEMICAL_DISTANCE],
188
212
  () => onSeqSpaceParamsChange('distanceF', distanceFunctionInput.value));
@@ -203,7 +227,8 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
203
227
  () => onSeqSpaceParamsChange('minPts', minPtsInput.value));
204
228
  minPtsInput.setTooltip('Minimum number of points in a cluster');
205
229
  const fingerprintTypesInput = ui.choiceInput('Fingerprint type', seqSpaceParams.fingerprintType,
206
- ['Morgan', 'RDKit', 'Pattern'], () => onSeqSpaceParamsChange('fingerprintType', fingerprintTypesInput.value));
230
+ ['Morgan', 'RDKit', 'Pattern', 'AtomPair', 'MACCS', 'TopologicalTorsion'],
231
+ () => onSeqSpaceParamsChange('fingerprintType', fingerprintTypesInput.value));
207
232
  function correctSeqSpaceInputs(): void {
208
233
  toggleInputs([gapOpenInput, gapExtendInput], distanceFunctionInput.value === distFNames.NEEDLEMANN_WUNSCH);
209
234
  toggleInputs([epsilonInput, minPtsInput], clusterEmbeddingsInput.value === true);
@@ -212,14 +237,70 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
212
237
  distanceFunctionInput.value === distFNames.NEEDLEMANN_WUNSCH);
213
238
  }
214
239
  correctSeqSpaceInputs();
240
+ // END OF SEQ SPACE INPUTS
241
+
242
+ //MCL INPUTS
243
+
244
+ const modifiedMCLParams: Partial<type.MCLSettings> = {};
245
+ function onMCLParamsChange(fieldName: keyof type.MCLSettings, value: any): void {
246
+ correctMCLInputs();
247
+ //correctSeqSpaceInputs();
248
+ if (value === null || value === undefined || value === '')
249
+ return;
250
+ modifiedMCLParams[fieldName] = value;
251
+ let isAllSame = true;
252
+ for (const [key, val] of Object.entries(modifiedMCLParams)) {
253
+ if (val !== mclParams[key as keyof type.MCLSettings]) {
254
+ isAllSame = false;
255
+ break;
256
+ }
257
+ }
258
+ if (isAllSame)
259
+ delete result.mclSettings;
260
+ else
261
+ result.mclSettings = {...mclParams, ...modifiedMCLParams};
262
+ }
263
+
264
+ function correctMCLInputs(): void {
265
+ toggleInputs([mclGapOpenInput, mclGapExtendInput], mclDistanceFunctionInput.value === distFNames.NEEDLEMANN_WUNSCH);
266
+ toggleInputs([mclFingerprintTypesInput],
267
+ mclDistanceFunctionInput.value === distFNames.MONOMER_CHEMICAL_DISTANCE ||
268
+ mclDistanceFunctionInput.value === distFNames.NEEDLEMANN_WUNSCH);
269
+ }
270
+
271
+ const mclDistanceFunctionInput = ui.choiceInput(SEQUENCE_SPACE_INPUTS.DISTANCE_FUNCTION, mclParams.distanceF,
272
+ [distFNames.NEEDLEMANN_WUNSCH, distFNames.MONOMER_CHEMICAL_DISTANCE, distFNames.HAMMING, distFNames.LEVENSHTEIN],
273
+ () => onMCLParamsChange('distanceF', mclDistanceFunctionInput.value));
274
+ const mclGapOpenInput = ui.floatInput(SEQUENCE_SPACE_INPUTS.GAP_OPEN, mclParams.gapOpen,
275
+ () => onMCLParamsChange('gapOpen', mclGapOpenInput.value));
276
+ const mclGapExtendInput = ui.floatInput(SEQUENCE_SPACE_INPUTS.GAP_EXTEND, mclParams.gapExtend,
277
+ () => onMCLParamsChange('gapExtend', mclGapExtendInput.value));
278
+ const mclFingerprintTypesInput = ui.choiceInput('Fingerprint type', mclParams.fingerprintType,
279
+ ['Morgan', 'RDKit', 'Pattern', 'AtomPair', 'MACCS', 'TopologicalTorsion'],
280
+ () => onMCLParamsChange('fingerprintType', mclFingerprintTypesInput.value));
281
+ const mclThresholdInput = ui.intInput('Similarity threshold', mclParams.threshold ?? 80,
282
+ () => onMCLParamsChange('threshold', mclThresholdInput.value));
283
+ const mclMaxIterationsInput = ui.intInput('Max iterations', mclParams.maxIterations ?? 5,
284
+ () => onMCLParamsChange('maxIterations', mclMaxIterationsInput.value));
285
+ correctMCLInputs();
286
+
287
+ const mclInputs = [mclThresholdInput, mclDistanceFunctionInput, mclFingerprintTypesInput,
288
+ mclGapOpenInput, mclGapExtendInput, mclMaxIterationsInput];
289
+
290
+ accordion.addPane(SETTINGS_PANES.MCL, () => ui.inputs(mclInputs), true);
291
+ inputs[SETTINGS_PANES.MCL] = mclInputs;
292
+ // END OF MCL INPUTS
215
293
 
216
294
  const seqSpaceInputs = [distanceFunctionInput, fingerprintTypesInput, gapOpenInput,
217
295
  gapExtendInput, clusterEmbeddingsInput, epsilonInput, minPtsInput];
218
- accordion.addPane(SETTINGS_PANES.SEQUENCE_SPACE, () => ui.inputs(seqSpaceInputs), true);
296
+ const seqSpacePane = accordion.addPane(SETTINGS_PANES.SEQUENCE_SPACE, () => ui.inputs(seqSpaceInputs), true);
219
297
  inputs[SETTINGS_PANES.SEQUENCE_SPACE] = seqSpaceInputs;
298
+ showSeqSpace.fireChanged();
220
299
  const dialog = ui.dialog('Peptides settings').add(accordion);
221
300
  dialog.root.style.width = '400px';
222
- dialog.onOK(() => model.settings = result);
301
+ dialog.onOK(() => {
302
+ model.settings = result;
303
+ });
223
304
  dialog.show();
224
305
 
225
306
  return {dialog, accordion, inputs};