@datagrok/peptides 1.14.1 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/196.js +3 -0
  3. package/dist/196.js.LICENSE.txt +51 -0
  4. package/dist/209.js +2 -2
  5. package/dist/361.js +2 -0
  6. package/dist/381.js +2 -0
  7. package/dist/436.js +2 -0
  8. package/dist/694.js +2 -0
  9. package/dist/831.js +2 -0
  10. package/dist/868.js +2 -0
  11. package/dist/package-test.js +3 -2
  12. package/dist/package-test.js.LICENSE.txt +51 -0
  13. package/dist/package.js +3 -2
  14. package/dist/package.js.LICENSE.txt +51 -0
  15. package/files/help/logo-summary-table.md +23 -0
  16. package/files/help/monomer-position.md +31 -0
  17. package/files/help/most-potent-residues.md +17 -0
  18. package/files/icons/ribbon/logo-summary-viewer.svg +8 -0
  19. package/files/icons/ribbon/peptide-sar-vertical-viewer.svg +10 -0
  20. package/files/icons/ribbon/peptide-sar-viewer.svg +11 -0
  21. package/files/tests/100k.d42 +0 -0
  22. package/files/tests/200k.d42 +0 -0
  23. package/files/tests/50k.d42 +0 -0
  24. package/files/tests/{aligned_5k.d42 → 5k.d42} +0 -0
  25. package/package.json +5 -5
  26. package/src/model.ts +85 -175
  27. package/src/package-test.ts +7 -6
  28. package/src/tests/benchmarks.ts +95 -0
  29. package/src/tests/core.ts +4 -75
  30. package/src/tests/{algorithms.ts → misc.ts} +3 -16
  31. package/src/tests/model.ts +3 -14
  32. package/src/tests/table-view.ts +4 -14
  33. package/src/tests/viewers.ts +7 -1
  34. package/src/tests/widgets.ts +9 -1
  35. package/src/utils/algorithms.ts +166 -16
  36. package/src/utils/cell-renderer.ts +12 -7
  37. package/src/utils/constants.ts +2 -0
  38. package/src/utils/misc.ts +2 -1
  39. package/src/utils/parallel-mutation-cliffs.ts +106 -0
  40. package/src/utils/types.ts +1 -1
  41. package/src/viewers/logo-summary.ts +9 -6
  42. package/src/viewers/sar-viewer.ts +28 -14
  43. package/src/widgets/mutation-cliffs.ts +2 -2
  44. package/src/widgets/peptides.ts +15 -5
  45. package/src/widgets/selection.ts +9 -0
  46. package/src/widgets/settings.ts +3 -8
  47. package/src/workers/mutation-cliffs-worker.ts +77 -0
  48. package/dist/521.js +0 -2
  49. package/dist/677.js +0 -2
  50. package/dist/729.js +0 -2
  51. package/dist/959.js +0 -2
package/src/tests/core.ts CHANGED
@@ -38,15 +38,9 @@ category('Core', () => {
38
38
  model = await startAnalysis(
39
39
  simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, C.SCALING_METHODS.MINUS_LG);
40
40
  expect(model instanceof PeptidesModel, true, 'Model is null');
41
- let overlayInit = false;
42
- grok.log.debug('Waiting for overlay...');
43
- model!._analysisView!.grid.onAfterDrawOverlay.subscribe(() => {
44
- overlayInit = true;
45
- grok.log.debug('Overlay initialized');
46
- });
47
41
 
48
42
  model!.mutationCliffsSelection = {'11': ['D']};
49
- await delay(500);
43
+ await delay(3000);
50
44
  });
51
45
 
52
46
  test('Start analysis: сomplex', async () => {
@@ -64,16 +58,9 @@ category('Core', () => {
64
58
  model = await startAnalysis(
65
59
  complexActivityCol, complexAlignedSeqCol, null, complexTable, complexScaledCol, C.SCALING_METHODS.MINUS_LG);
66
60
  expect(model instanceof PeptidesModel, true, 'Model is null');
67
- let overlayInit = false;
68
- model!._analysisView!.grid.onAfterDrawOverlay.subscribe(() => {
69
- overlayInit = true;
70
- grok.log.debug('Overlay initialized');
71
- });
72
61
 
73
- if (model !== null)
74
- model.mutationCliffsSelection = {'13': ['-']};
75
-
76
- await delay(500);
62
+ model!.mutationCliffsSelection = {'13': ['-']};
63
+ await delay(3000);
77
64
  });
78
65
 
79
66
  test('Save and load project', async () => {
@@ -105,7 +92,7 @@ category('Core', () => {
105
92
  const sp = await grok.dapi.projects.save(project);
106
93
 
107
94
  v.close();
108
- await awaitCheck(() => typeof grok.shell.tableView('Peptides analysis') === 'undefined', 'Table never closed', 2000);
95
+ await awaitCheck(() => typeof grok.shell.tableView('Peptides analysis') === 'undefined', 'Table never closed', 3000);
109
96
 
110
97
  await sp.open();
111
98
  v = grok.shell.getTableView('Peptides analysis');
@@ -113,63 +100,5 @@ category('Core', () => {
113
100
  await grok.dapi.layouts.delete(sl);
114
101
  await grok.dapi.tables.delete(sti);
115
102
  await grok.dapi.projects.delete(sp);
116
-
117
- await delay(500);
118
103
  });
119
-
120
- test('Cluster stats - Benchmark HELM 5k', async () => {
121
- if (!DG.Test.isInBenchmark)
122
- return;
123
-
124
- const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k_2.d42'))[0];
125
- const activityCol = df.getCol('Activity');
126
- const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
127
- const clustersCol = df.getCol('Cluster');
128
- const sequenceCol = df.getCol('HELM');
129
- sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
130
- sequenceCol.setTag(DG.TAGS.UNITS, NOTATION.HELM);
131
- const model = await startAnalysis(activityCol, sequenceCol, clustersCol, df, scaledActivityCol, C.SCALING_METHODS.NONE);
132
-
133
- for (let i = 0; i < 5; ++i)
134
- DG.time('Cluster stats', () => model?.calculateClusterStatistics());
135
- }, {timeout: 10000});
136
-
137
- test('Monomer Position stats - Benchmark HELM 5k', async () => {
138
- if (!DG.Test.isInBenchmark)
139
- return;
140
-
141
- const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
142
- const activityCol = df.getCol('Activity');
143
- const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
144
- const clustersCol = df.getCol('Cluster');
145
- const sequenceCol = df.getCol('HELM');
146
- sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
147
- sequenceCol.setTag(DG.TAGS.UNITS, NOTATION.HELM);
148
- const model = await startAnalysis(activityCol, sequenceCol, clustersCol, df, scaledActivityCol, C.SCALING_METHODS.NONE);
149
-
150
- for (let i = 0; i < 5; ++i)
151
- DG.time('Monomer position stats', () => model?.calculateMonomerPositionStatistics());
152
- }, {timeout: 10000});
153
-
154
- test('Analysis start - Benchmark HELM 5k', async () => {
155
- if (!DG.Test.isInBenchmark)
156
- return;
157
-
158
- const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
159
- const activityCol = df.getCol('Activity');
160
- const scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
161
- const clustersCol = df.getCol('Cluster');
162
- const sequenceCol = df.getCol('HELM');
163
- sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
164
- sequenceCol.setTag(DG.TAGS.UNITS, NOTATION.HELM);
165
-
166
- for (let i = 0; i < 5; ++i) {
167
- await DG.timeAsync('Analysis start', async () => {
168
- const model = await startAnalysis(activityCol, sequenceCol, clustersCol, df, scaledActivityCol, C.SCALING_METHODS.NONE);
169
-
170
- if (model)
171
- grok.shell.closeTable(model.df);
172
- });
173
- }
174
- }, {timeout: 10000});
175
104
  });
@@ -7,6 +7,7 @@ import {findMutations} from '../utils/algorithms';
7
7
  import * as type from '../utils/types';
8
8
  import {extractColInfo} from '../utils/misc';
9
9
 
10
+
10
11
  category('Algorithms', () => {
11
12
  let activityCol: type.RawData;
12
13
  let monomerColumns: type.RawColumn[];
@@ -30,7 +31,7 @@ category('Algorithms', () => {
30
31
 
31
32
  test('MutationCliffs', async () => {
32
33
  // Check every pair
33
- let mutationCliffsInfo: type.MutationCliffs = findMutations(activityCol, monomerColumns, settings);
34
+ let mutationCliffsInfo: type.MutationCliffs = await findMutations(activityCol, monomerColumns, settings);
34
35
  expect(mutationCliffsInfo.has('C'), true, `MutationCliffsInfo should have key 'C'`);
35
36
  expect(mutationCliffsInfo.has('D'), true, `MutationCliffsInfo should have key 'D'`);
36
37
  expect(mutationCliffsInfo.has('A'), false, `MutationCliffsInfo should not have key 'A'`);
@@ -51,21 +52,7 @@ category('Algorithms', () => {
51
52
  expect(d32[0], 1, `MutationCliffsInfo['D']['3'][2] should have value 1`);
52
53
 
53
54
  // Check with target
54
- mutationCliffsInfo = findMutations(activityCol, monomerColumns, settings, {targetCol, currentTarget: '1'});
55
+ mutationCliffsInfo = await findMutations(activityCol, monomerColumns, settings, {targetCol, currentTarget: '1'});
55
56
  expect(mutationCliffsInfo.size, 0, `MutationCliffsInfo should be empty for target '1'`);
56
57
  });
57
-
58
- test('MutationCliffs - Benchmark 5k', async () => {
59
- if (!DG.Test.isInBenchmark)
60
- return;
61
-
62
- const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
63
- const activityCol: type.RawData = df.getCol('Activity').getRawData();
64
- const monomerCols: type.RawColumn[] = [];
65
- for (let i = 1; i < 16; ++i) {
66
- const col = df.getCol(i.toString());
67
- monomerCols.push({name: col.name, rawData: col.getRawData(), cat: col.categories});
68
- }
69
- DG.time('MutationCliffs', () => findMutations(activityCol, monomerCols));
70
- }, {timeout: 5000});
71
58
  });
@@ -1,6 +1,6 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
 
3
- import {category, test, before, expect, expectFloat, awaitCheck, delay} from '@datagrok-libraries/utils/src/test';
3
+ import {category, test, before, expect, expectFloat, awaitCheck, delay, after} from '@datagrok-libraries/utils/src/test';
4
4
  import {_package} from '../package-test';
5
5
  import {PeptidesModel, VIEWER_TYPE, getAggregatedColName} from '../model';
6
6
  import {startAnalysis} from '../widgets/peptides';
@@ -38,6 +38,8 @@ category('Model: Settings', () => {
38
38
  await delay(500);
39
39
  });
40
40
 
41
+ after(async () => await delay(3000));
42
+
41
43
  test('Activity scaling', async () => {
42
44
  const getError = (row: number, method: SCALING_METHODS): string =>
43
45
  `Activity mismatch at row ${row} for scaling method '${method}'`;
@@ -72,19 +74,6 @@ category('Model: Settings', () => {
72
74
  expectFloat(scaledActivityData[i], origActivityData[i], tolerance, getError(i, SCALING_METHODS.NONE));
73
75
  });
74
76
 
75
- test('Bidirectional analysis', async () => {
76
- // Check that bidirectional analysis is disabled by default
77
- expect(model.settings.isBidirectional, false, 'Bidirectional analysis is enabled by default');
78
-
79
- // Check that bidirectional analysis can be enabled
80
- model.settings = {isBidirectional: true};
81
- expect(model.settings.isBidirectional, true, 'Bidirectional analysis is disabled after enabling');
82
-
83
- // Check that bidirectional analysis can be disabled
84
- model.settings = {isBidirectional: false};
85
- expect(model.settings.isBidirectional, false, 'Bidirectional analysis is enabled after disabling');
86
- });
87
-
88
77
  test('Mutation Cliffs', async () => {
89
78
  // Check default mutation cliffs parameters
90
79
  expect(model.settings.maxMutations, mutationCliffsDefaultParams.maxMutations, `Max mutations mismatch: expected ` +
@@ -1,6 +1,6 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
 
3
- import {category, test, before, expect, delay} from '@datagrok-libraries/utils/src/test';
3
+ import {category, test, before, expect, delay, after} from '@datagrok-libraries/utils/src/test';
4
4
  import {_package} from '../package-test';
5
5
  import {CLUSTER_TYPE, PeptidesModel} from '../model';
6
6
  import {startAnalysis} from '../widgets/peptides';
@@ -39,6 +39,8 @@ category('Table view', () => {
39
39
  await delay(500);
40
40
  });
41
41
 
42
+ after(async () => await delay(3000));
43
+
42
44
  test('Tooltip', async () => {
43
45
  expect(model.showMonomerTooltip(firstPair.monomerOrCluster, 0, 0), true,
44
46
  `Couldn't structure for monomer ${firstPair.monomerOrCluster}`);
@@ -46,7 +48,7 @@ category('Table view', () => {
46
48
 
47
49
  test('Visible columns', async () => {
48
50
  const gridCols = model.analysisView.grid.columns;
49
- const posCols = model.splitSeqDf.columns.names();
51
+ const posCols = model.positionColumns.toArray().map((col) => col.name);
50
52
  for (let colIdx = 1; colIdx < gridCols.length; colIdx++) {
51
53
  const col = gridCols.byIndex(colIdx)!;
52
54
  const tableColName = col.column!.name;
@@ -65,7 +67,6 @@ category('Table view', () => {
65
67
 
66
68
  // Select first monomer-position pair
67
69
  model.modifyMutationCliffsSelection(firstPair);
68
- await delay(500);
69
70
  expect(model.mutationCliffsSelection[firstPair.positionOrClusterType].includes(firstPair.monomerOrCluster), true,
70
71
  `Monomer ${firstPair.monomerOrCluster} is not selected at position ${firstPair.positionOrClusterType}`);
71
72
  expect(selection.trueCount, firstPair.mcCount, `Selection count is not equal to ${firstPair.mcCount} ` +
@@ -73,7 +74,6 @@ category('Table view', () => {
73
74
 
74
75
  // Select second monomer-position pair
75
76
  model.modifyMutationCliffsSelection(secondPair, {shiftPressed: true, ctrlPressed: false});
76
- await delay(500);
77
77
  expect(model.mutationCliffsSelection[secondPair.positionOrClusterType].includes(secondPair.monomerOrCluster), true,
78
78
  `Monomer ${secondPair.monomerOrCluster} is not selected at position ${secondPair.positionOrClusterType}`);
79
79
  expect(selection.trueCount, secondPair.mcCount + firstPair.mcCount, `Selection count is not equal ` +
@@ -82,7 +82,6 @@ category('Table view', () => {
82
82
 
83
83
  // Deselect second monomer-position pair
84
84
  model.modifyMutationCliffsSelection(secondPair, {shiftPressed: true, ctrlPressed: true});
85
- await delay(500);
86
85
  expect(model.mutationCliffsSelection[secondPair.positionOrClusterType].includes(secondPair.monomerOrCluster), false,
87
86
  `Monomer ${secondPair.monomerOrCluster} is still selected at position ${secondPair.positionOrClusterType} after deselection`);
88
87
  expect(selection.trueCount, firstPair.mcCount, `Selection count is not equal to ${firstPair.mcCount} ` +
@@ -90,7 +89,6 @@ category('Table view', () => {
90
89
 
91
90
  // Clear monomer-position selection
92
91
  model.initMutationCliffsSelection();
93
- await delay(500);
94
92
  for (const [position, selectedMonomers] of Object.entries(model.mutationCliffsSelection)) {
95
93
  expect(selectedMonomers.length, 0, `Selection is not empty for position ${position} after clearing ` +
96
94
  `monomer-position selection`);
@@ -99,7 +97,6 @@ category('Table view', () => {
99
97
 
100
98
  // Select first cluster
101
99
  model.modifyClusterSelection(firstCluster);
102
- await delay(500);
103
100
  expect(model.clusterSelection[firstCluster.positionOrClusterType].includes(firstCluster.monomerOrCluster), true,
104
101
  `Cluster ${firstCluster.monomerOrCluster} is not selected`);
105
102
  expect(selection.trueCount, firstCluster.count, `Selection count is not equal to ${firstCluster.count} for ` +
@@ -107,7 +104,6 @@ category('Table view', () => {
107
104
 
108
105
  // Select second cluster
109
106
  model.modifyClusterSelection(secondCluster, {shiftPressed: true, ctrlPressed: false});
110
- await delay(500);
111
107
  expect(model.clusterSelection[secondCluster.positionOrClusterType].includes(secondCluster.monomerOrCluster), true,
112
108
  `Cluster ${secondCluster.monomerOrCluster} is not selected`);
113
109
  expect(selection.trueCount, firstCluster.count + secondCluster.count, `Selection count is not equal to ` +
@@ -115,7 +111,6 @@ category('Table view', () => {
115
111
 
116
112
  // Deselect first cluster
117
113
  model.modifyClusterSelection(firstCluster, {shiftPressed: true, ctrlPressed: true});
118
- await delay(500);
119
114
  expect(model.clusterSelection[firstCluster.positionOrClusterType].includes(firstCluster.monomerOrCluster), false,
120
115
  `Cluster ${firstCluster.monomerOrCluster} is still selected after deselection`);
121
116
  expect(selection.trueCount, secondCluster.count, `Selection count is not equal to ${secondCluster.count} for ` +
@@ -123,7 +118,6 @@ category('Table view', () => {
123
118
 
124
119
  // Clear selection
125
120
  model.initClusterSelection();
126
- await delay(500);
127
121
  expect(model.isClusterSelectionEmpty, true, `Selection is not empty after clearing cluster selection`);
128
122
  expect(selection.trueCount, 0, `Selection count is not equal to 0 after clearing cluster selection`);
129
123
  });
@@ -136,7 +130,6 @@ category('Table view', () => {
136
130
 
137
131
  // Select by second monomer-position pair
138
132
  model.modifyInvariantMapSelection(secondPair);
139
- await delay(500);
140
133
  expect(model.invariantMapSelection[secondPair.positionOrClusterType].includes(secondPair.monomerOrCluster), true,
141
134
  `Monomer ${secondPair.monomerOrCluster} is not filtered at position ${secondPair.positionOrClusterType}`);
142
135
  expect(selection.trueCount, secondPair.imCount, `Filter count is not equal to ${secondPair.imCount} ` +
@@ -144,7 +137,6 @@ category('Table view', () => {
144
137
 
145
138
  // Select by first monomer-position pair
146
139
  model.modifyInvariantMapSelection(firstPair, {shiftPressed: true, ctrlPressed: false});
147
- await delay(500);
148
140
  expect(model.invariantMapSelection[firstPair.positionOrClusterType].includes(firstPair.monomerOrCluster), true,
149
141
  `Monomer ${firstPair.monomerOrCluster} is not filtered at position ${firstPair.positionOrClusterType}`);
150
142
  expect(selection.trueCount, secondPair.imCount, `Filter count is not equal to ${secondPair.imCount} ` +
@@ -152,7 +144,6 @@ category('Table view', () => {
152
144
 
153
145
  // Deselect filter for second monomer-position pair
154
146
  model.modifyInvariantMapSelection(secondPair, {shiftPressed: true, ctrlPressed: true});
155
- await delay(500);
156
147
  expect(model.invariantMapSelection[secondPair.positionOrClusterType].includes(secondPair.monomerOrCluster), false,
157
148
  `Monomer ${secondPair.monomerOrCluster} is still filtered at position ${secondPair.positionOrClusterType} after ` +
158
149
  `deselection`);
@@ -162,7 +153,6 @@ category('Table view', () => {
162
153
 
163
154
  // Clear selection
164
155
  model.initInvariantMapSelection();
165
- await delay(500);
166
156
  expect(selection.trueCount, 0, `Filter count is not equal to ${0} after clearing monomer-position filter`);
167
157
 
168
158
  for (const [position, filteredMonomers] of Object.entries(model.invariantMapSelection)) {
@@ -1,6 +1,6 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
 
3
- import {before, category, delay, expect, test, testViewer} from '@datagrok-libraries/utils/src/test';
3
+ import {after, before, category, delay, expect, test, testViewer} from '@datagrok-libraries/utils/src/test';
4
4
  import {aligned1} from './test-data';
5
5
  import {CLUSTER_TYPE, PeptidesModel, VIEWER_TYPE} from '../model';
6
6
  import {_package} from '../package-test';
@@ -50,6 +50,8 @@ category('Viewers: Monomer-Position', () => {
50
50
  await delay(500);
51
51
  });
52
52
 
53
+ after(async () => await delay(3000));
54
+
53
55
  test('Tooltip', async () => {
54
56
  const cellCoordinates = {col: '9', row: 6};
55
57
  const gc = mpViewer.viewerGrid.cell(cellCoordinates.col, cellCoordinates.row);
@@ -102,6 +104,8 @@ category('Viewers: Most Potent Residues', () => {
102
104
  await delay(500);
103
105
  });
104
106
 
107
+ after(async () => await delay(3000));
108
+
105
109
  test('Tooltip', async () => {
106
110
  const cellCoordinates = {col: 'Diff', row: 6};
107
111
  const gc = mprViewer.viewerGrid.cell(cellCoordinates.col, cellCoordinates.row);
@@ -139,6 +143,8 @@ category('Viewers: Logo Summary Table', () => {
139
143
  await delay(500);
140
144
  });
141
145
 
146
+ after(async () => await delay(3000));
147
+
142
148
  test('Properties', async () => {
143
149
  // Change Logo Summary Table Web Logo Mode property to full
144
150
  const webLogoMode = lstViewer.getProperty(LST_PROPERTIES.WEB_LOGO_MODE);
@@ -1,7 +1,7 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
- import {category, test, before, expect, delay} from '@datagrok-libraries/utils/src/test';
4
+ import {category, test, before, expect, delay, after} from '@datagrok-libraries/utils/src/test';
5
5
  import {_package} from '../package-test';
6
6
  import {CLUSTER_TYPE, PeptidesModel, VIEWER_TYPE} from '../model';
7
7
  import {scaleActivity} from '../utils/misc';
@@ -40,6 +40,8 @@ category('Widgets: Settings', () => {
40
40
  await delay(500);
41
41
  });
42
42
 
43
+ after(async () => await delay(3000));
44
+
43
45
  test('UI', async () => {
44
46
  const settingsElements = getSettingsDialog(model);
45
47
 
@@ -83,6 +85,8 @@ category('Widgets: Distribution panel', () => {
83
85
  await delay(500);
84
86
  });
85
87
 
88
+ after(async () => await delay(3000));
89
+
86
90
  test('UI', async () => {
87
91
  getDistributionWidget(model.df, model);
88
92
  });
@@ -113,6 +117,8 @@ category('Widgets: Mutation cliffs', () => {
113
117
  await delay(500);
114
118
  });
115
119
 
120
+ after(async () => await delay(3000));
121
+
116
122
  test('UI', async () => {
117
123
  mutationCliffsWidget(model.df, model);
118
124
  });
@@ -143,6 +149,8 @@ category('Widgets: Actions', () => {
143
149
  await delay(500);
144
150
  });
145
151
 
152
+ after(async () => await delay(3000));
153
+
146
154
  test('New view', async () => {
147
155
  // Set compound bitset: filter out 2 rows and select 1 among them
148
156
  const filter = model.df.filter;
@@ -1,22 +1,32 @@
1
+ import * as DG from 'datagrok-api/dg';
1
2
  import * as C from './constants';
2
3
  import * as type from './types';
3
- import {getTypedArrayConstructor} from './misc';
4
+ import {ParallelMutationCliffs} from './parallel-mutation-cliffs';
5
+ import {CLUSTER_TYPE, ClusterStats, ClusterTypeStats,
6
+ MonomerPositionStats, PositionStats, SummaryStats} from '../model';
7
+ import BitArray from '@datagrok-libraries/utils/src/bit-array';
8
+ import {Stats, getStats} from './statistics';
4
9
 
5
- type MutationCliffInfo = {pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number};
6
10
 
7
- export function findMutations(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
11
+ export async function findMutations(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
8
12
  settings: type.PeptidesSettings = {},
9
- targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {}): type.MutationCliffs {
13
+ targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {},
14
+ ): Promise<type.MutationCliffs> {
10
15
  const nCols = monomerInfoArray.length;
11
16
  if (nCols === 0)
12
17
  throw new Error(`PepAlgorithmError: Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
13
18
 
14
19
  settings.minActivityDelta ??= 0;
15
20
  settings.maxMutations ??= 1;
16
- const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
21
+ //const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
17
22
 
18
- const substitutionsInfo: type.MutationCliffs = new Map();
19
- const nRows = activityArray.length;
23
+ //const substitutionsInfo: type.MutationCliffs = new Map();
24
+ //const nRows = activityArray.length;
25
+
26
+ const substitutionsInfo =
27
+ await new ParallelMutationCliffs().calc(activityArray, monomerInfoArray, settings, targetOptions);
28
+ return substitutionsInfo;
29
+ /*
20
30
  for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
21
31
  if (currentTargetIdx !== -1 && targetOptions.targetCol?.rawData[seq1Idx] !== currentTargetIdx)
22
32
  continue;
@@ -33,11 +43,12 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
33
43
  continue;
34
44
 
35
45
  let substCounterFlag = false;
36
- const tempData: MutationCliffInfo[] = [];
46
+ const tempData: MutationCliffInfo[] = new Array(monomerInfoArray.length);
47
+ let tempDataIdx = 0;
37
48
  for (const monomerInfo of monomerInfoArray) {
38
- const seq1category = monomerInfo.rawData[seq1Idx];
39
- const seq2category = monomerInfo.rawData[seq2Idx];
40
- if (seq1category === seq2category)
49
+ const seq1categoryIdx = monomerInfo.rawData[seq1Idx];
50
+ const seq2categoryIdx = monomerInfo.rawData[seq2Idx];
51
+ if (seq1categoryIdx === seq2categoryIdx)
41
52
  continue;
42
53
 
43
54
  substCounter++;
@@ -45,19 +56,22 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
45
56
  if (substCounterFlag)
46
57
  break;
47
58
 
48
- tempData.push({
59
+ tempData[tempDataIdx++] ={
49
60
  pos: monomerInfo.name,
50
- seq1monomer: monomerInfo.cat![seq1category],
51
- seq2monomer: monomerInfo.cat![seq2category],
61
+ seq1monomer: monomerInfo.cat![seq1categoryIdx],
62
+ seq2monomer: monomerInfo.cat![seq2categoryIdx],
52
63
  seq1Idx: seq1Idx,
53
64
  seq2Idx: seq2Idx,
54
- });
65
+ };
55
66
  }
56
67
 
57
68
  if (substCounterFlag || substCounter === 0)
58
69
  continue;
59
70
 
60
- for (const tempDataElement of tempData) {
71
+ // Separate processing loop in case substCOunter is 0 or out of restricted range to
72
+ // prevent unnecessary computations
73
+ for (let i = 0; i < tempDataIdx; i++) {
74
+ const tempDataElement = tempData[i];
61
75
  //Working with seq1monomer
62
76
  const seq1monomer = tempDataElement.seq1monomer;
63
77
  if (!substitutionsInfo.has(seq1monomer))
@@ -102,4 +116,140 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
102
116
  }
103
117
 
104
118
  return substitutionsInfo;
119
+ */
120
+ }
121
+
122
+ export function calculateMonomerPositionStatistics(df: DG.DataFrame, positionColumns: DG.Column<string>[],
123
+ options: {isFiltered?: boolean, columns?: string[]} = {}): MonomerPositionStats {
124
+ options.isFiltered ??= false;
125
+ const monomerPositionObject = {general: {}} as MonomerPositionStats & {general: SummaryStats};
126
+ let activityColData: Float64Array = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData() as Float64Array;
127
+ let sourceDfLen = df.rowCount;
128
+
129
+ if (options.isFiltered) {
130
+ sourceDfLen = df.filter.trueCount;
131
+ const tempActivityData = new Float64Array(sourceDfLen);
132
+ const selectedIndexes = df.filter.getSelectedIndexes();
133
+ for (let i = 0; i < sourceDfLen; ++i)
134
+ tempActivityData[i] = activityColData[selectedIndexes[i]];
135
+ activityColData = tempActivityData;
136
+ positionColumns = DG.DataFrame.fromColumns(positionColumns).clone(df.filter).columns.toList();
137
+ }
138
+ options.columns ??= positionColumns.map((col) => col.name);
139
+
140
+ for (const posCol of positionColumns) {
141
+ if (!options.columns.includes(posCol.name))
142
+ continue;
143
+ const posColData = posCol.getRawData();
144
+ const posColCateogries = posCol.categories;
145
+ const currentPositionObject = {general: {}} as PositionStats & {general: SummaryStats};
146
+
147
+ for (let categoryIndex = 0; categoryIndex < posColCateogries.length; ++categoryIndex) {
148
+ const monomer = posColCateogries[categoryIndex];
149
+ if (monomer === '')
150
+ continue;
151
+
152
+ const boolArray: boolean[] = new Array(sourceDfLen).fill(false);
153
+ for (let i = 0; i < sourceDfLen; ++i) {
154
+ if (posColData[i] === categoryIndex)
155
+ boolArray[i] = true;
156
+ }
157
+ const bitArray = BitArray.fromValues(boolArray);
158
+ const stats = bitArray.allFalse || bitArray.allTrue ?
159
+ {count: sourceDfLen, meanDifference: 0, ratio: 1.0, pValue: null, mask: bitArray} :
160
+ getStats(activityColData, bitArray);
161
+ currentPositionObject[monomer] = stats;
162
+ getSummaryStats(currentPositionObject.general, stats);
163
+ }
164
+ monomerPositionObject[posCol.name] = currentPositionObject;
165
+ getSummaryStats(monomerPositionObject.general, null, currentPositionObject.general);
166
+ }
167
+ return monomerPositionObject;
168
+ }
169
+
170
+ export function getSummaryStats(
171
+ genObj: SummaryStats, stats: Stats | null = null, summaryStats: SummaryStats | null = null,
172
+ ): void {
173
+ if (stats === null && summaryStats === null)
174
+ throw new Error(`MonomerPositionStatsError: either stats or summaryStats must be present`);
175
+
176
+ const possibleMaxCount = stats?.count ?? summaryStats!.maxCount;
177
+ genObj.maxCount ??= possibleMaxCount;
178
+ if (genObj.maxCount < possibleMaxCount)
179
+ genObj.maxCount = possibleMaxCount;
180
+
181
+ const possibleMinCount = stats?.count ?? summaryStats!.minCount;
182
+ genObj.minCount ??= possibleMinCount;
183
+ if (genObj.minCount > possibleMinCount)
184
+ genObj.minCount = possibleMinCount;
185
+
186
+ const possibleMaxMeanDifference = stats?.meanDifference ?? summaryStats!.maxMeanDifference;
187
+ genObj.maxMeanDifference ??= possibleMaxMeanDifference;
188
+ if (genObj.maxMeanDifference < possibleMaxMeanDifference)
189
+ genObj.maxMeanDifference = possibleMaxMeanDifference;
190
+
191
+ const possibleMinMeanDifference = stats?.meanDifference ?? summaryStats!.minMeanDifference;
192
+ genObj.minMeanDifference ??= possibleMinMeanDifference;
193
+ if (genObj.minMeanDifference > possibleMinMeanDifference)
194
+ genObj.minMeanDifference = possibleMinMeanDifference;
195
+
196
+ if (!isNaN(stats?.pValue ?? NaN)) {
197
+ const possibleMaxPValue = stats?.pValue ?? summaryStats!.maxPValue;
198
+ genObj.maxPValue ??= possibleMaxPValue;
199
+ if (genObj.maxPValue < possibleMaxPValue)
200
+ genObj.maxPValue = possibleMaxPValue;
201
+
202
+ const possibleMinPValue = stats?.pValue ?? summaryStats!.minPValue;
203
+ genObj.minPValue ??= possibleMinPValue;
204
+ if (genObj.minPValue > possibleMinPValue)
205
+ genObj.minPValue = possibleMinPValue;
206
+ }
207
+
208
+ const possibleMaxRatio = stats?.ratio ?? summaryStats!.maxRatio;
209
+ genObj.maxRatio ??= possibleMaxRatio;
210
+ if (genObj.maxRatio < possibleMaxRatio)
211
+ genObj.maxRatio = possibleMaxRatio;
212
+
213
+ const possibleMinRatio = stats?.ratio ?? summaryStats!.minRatio;
214
+ genObj.minRatio ??= possibleMinRatio;
215
+ if (genObj.minRatio > possibleMinRatio)
216
+ genObj.minRatio = possibleMinRatio;
217
+ }
218
+
219
+ export function calculateClusterStatistics(df: DG.DataFrame, clustersColumnName: string,
220
+ customClusters: DG.Column<boolean>[]): ClusterTypeStats {
221
+ const rowCount = df.rowCount;
222
+ const origClustCol = df.getCol(clustersColumnName);
223
+ const origClustColData = origClustCol.getRawData();
224
+ const origClustColCat = origClustCol.categories;
225
+ const origClustMasks: BitArray[] = Array.from({length: origClustColCat.length},
226
+ () => new BitArray(rowCount, false));
227
+ for (let rowIdx = 0; rowIdx < rowCount; ++rowIdx)
228
+ origClustMasks[origClustColData[rowIdx]].setTrue(rowIdx);
229
+
230
+ const customClustMasks = customClusters.map(
231
+ (v) => BitArray.fromUint32Array(rowCount, v.getRawData() as Uint32Array));
232
+ const customClustColNamesList = customClusters.map((v) => v.name);
233
+
234
+ const activityColData: type.RawData = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
235
+
236
+ const origClustStats: ClusterStats = {};
237
+ const customClustStats: ClusterStats = {};
238
+
239
+ for (const clustType of Object.values(CLUSTER_TYPE)) {
240
+ const masks = clustType === CLUSTER_TYPE.ORIGINAL ? origClustMasks : customClustMasks;
241
+ const clustNames = clustType === CLUSTER_TYPE.ORIGINAL ? origClustColCat : customClustColNamesList;
242
+ const resultStats = clustType === CLUSTER_TYPE.ORIGINAL ? origClustStats : customClustStats;
243
+ for (let maskIdx = 0; maskIdx < masks.length; ++maskIdx) {
244
+ const mask = masks[maskIdx];
245
+ const stats = mask.allTrue || mask.allFalse ?
246
+ {count: mask.length, meanDifference: 0, ratio: 1.0, pValue: null, mask: mask} : getStats(activityColData, mask);
247
+ resultStats[clustNames[maskIdx]] = stats;
248
+ }
249
+ }
250
+
251
+ const resultStats = {} as ClusterTypeStats;
252
+ resultStats[CLUSTER_TYPE.ORIGINAL] = origClustStats;
253
+ resultStats[CLUSTER_TYPE.CUSTOM] = customClustStats;
254
+ return resultStats;
105
255
  }
@@ -22,7 +22,7 @@ export function setMonomerRenderer(col: DG.Column, alphabet: string): void {
22
22
  export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
23
23
  currentPosition: string, monomerPositionStats: MonomerPositionStats, bound: DG.Rect,
24
24
  mutationCliffsSelection: types.Selection, substitutionsInfo: types.MutationCliffs | null = null,
25
- _twoColorMode: boolean = false, renderNums: boolean = true): void {
25
+ renderNums: boolean = true): void {
26
26
  const positionStats = monomerPositionStats[currentPosition];
27
27
  const pVal = positionStats![currentMonomer]!.pValue;
28
28
  const currentMeanDifference = positionStats![currentMonomer]!.meanDifference;
@@ -81,12 +81,16 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
81
81
  export function renderInvaraintMapCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
82
82
  currentPosition: string, invariantMapSelection: types.Selection, cellValue: number, bound: DG.Rect,
83
83
  color: number): void {
84
+ //FIXME: This is a hack, because `color` value sometimes comes incomplete. E.g. we found that here `color` value is
85
+ // 255 and its contrast color would be black, which is not visible on blue (color code) background. The full number
86
+ // is actually 4278190335.
87
+ color = DG.Color.fromHtml(DG.Color.toHtml(color));
84
88
  canvasContext.fillStyle = DG.Color.toHtml(color);
85
89
  canvasContext.fillRect(bound.x, bound.y, bound.width, bound.height);
86
90
  canvasContext.font = '13px Roboto, Roboto Local, sans-serif';
87
91
  canvasContext.textAlign = 'center';
88
92
  canvasContext.textBaseline = 'middle';
89
- canvasContext.fillStyle = '#000';
93
+ canvasContext.fillStyle = DG.Color.toHtml(DG.Color.getContrastColor(color));
90
94
  canvasContext.fillText(cellValue.toString(), bound.x + (bound.width / 2), bound.y + (bound.height / 2), bound.width);
91
95
 
92
96
  const monomerSelection = invariantMapSelection[currentPosition];
@@ -114,18 +118,19 @@ export function drawLogoInBounds(ctx: CanvasRenderingContext2D, bounds: DG.Rect,
114
118
  drawOptions.symbolStyle ??= '16px Roboto, Roboto Local, sans-serif';
115
119
  drawOptions.upperLetterHeight ??= 12.2;
116
120
  drawOptions.upperLetterAscent ??= 0.25;
117
- drawOptions.marginVertical ??= 5;
118
- drawOptions.marginHorizontal ??= 5;
121
+ drawOptions.marginVertical ??= 2;
122
+ drawOptions.marginHorizontal ??= 2;
123
+ drawOptions.selectionWidth ??= 1;
119
124
  drawOptions.textHeight ??= 13;
120
125
  drawOptions.headerStyle ??= `bold ${drawOptions.textHeight * pr}px Roboto, Roboto Local, sans-serif`;
121
126
 
122
127
  const totalSpace = (sortedOrder.length - 1) * drawOptions.upperLetterAscent; // Total space between letters
123
128
  const barHeight = (bounds.height - 2 * drawOptions.marginVertical - totalSpace - 1.25 * drawOptions.textHeight) * pr;
124
129
  const leftShift = drawOptions.marginHorizontal * 2;
125
- const barWidth = (bounds.width - leftShift * 2) * pr;
130
+ const barWidth = (bounds.width - (leftShift + drawOptions.marginHorizontal)) * pr;
126
131
  const xStart = (bounds.x + leftShift) * pr;
127
- const selectionWidth = 4 * pr;
128
- const xSelection = (bounds.x + 3) * pr;
132
+ const selectionWidth = Math.min(drawOptions.selectionWidth * pr, 1);
133
+ const xSelection = (bounds.x + 1) * pr;
129
134
  let currentY = (bounds.y + drawOptions.marginVertical) * pr;
130
135
 
131
136
  const monomerBounds: { [monomer: string]: DG.Rect } = {};
@@ -43,6 +43,8 @@ export enum TAGS {
43
43
  MULTIPLE_VIEWS = 'isMultipleViews',
44
44
  IDENTITY_TEMPLATE = 'Identity template',
45
45
  SIMILARITY_TEMPLATE = 'Similarity template',
46
+ ANALYSIS_COL = 'isAnalysisCol',
47
+ POSITION_COL = 'isPositionCol',
46
48
  }
47
49
 
48
50
  export enum SEM_TYPES {