@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.
- package/CHANGELOG.md +29 -0
- package/dist/196.js +3 -0
- package/dist/196.js.LICENSE.txt +51 -0
- package/dist/209.js +2 -2
- package/dist/361.js +2 -0
- package/dist/381.js +2 -0
- package/dist/436.js +2 -0
- package/dist/694.js +2 -0
- package/dist/831.js +2 -0
- package/dist/868.js +2 -0
- package/dist/package-test.js +3 -2
- package/dist/package-test.js.LICENSE.txt +51 -0
- package/dist/package.js +3 -2
- package/dist/package.js.LICENSE.txt +51 -0
- package/files/help/logo-summary-table.md +23 -0
- package/files/help/monomer-position.md +31 -0
- package/files/help/most-potent-residues.md +17 -0
- package/files/icons/ribbon/logo-summary-viewer.svg +8 -0
- package/files/icons/ribbon/peptide-sar-vertical-viewer.svg +10 -0
- package/files/icons/ribbon/peptide-sar-viewer.svg +11 -0
- package/files/tests/100k.d42 +0 -0
- package/files/tests/200k.d42 +0 -0
- package/files/tests/50k.d42 +0 -0
- package/files/tests/{aligned_5k.d42 → 5k.d42} +0 -0
- package/package.json +5 -5
- package/src/model.ts +85 -175
- package/src/package-test.ts +7 -6
- package/src/tests/benchmarks.ts +95 -0
- package/src/tests/core.ts +4 -75
- package/src/tests/{algorithms.ts → misc.ts} +3 -16
- package/src/tests/model.ts +3 -14
- package/src/tests/table-view.ts +4 -14
- package/src/tests/viewers.ts +7 -1
- package/src/tests/widgets.ts +9 -1
- package/src/utils/algorithms.ts +166 -16
- package/src/utils/cell-renderer.ts +12 -7
- package/src/utils/constants.ts +2 -0
- package/src/utils/misc.ts +2 -1
- package/src/utils/parallel-mutation-cliffs.ts +106 -0
- package/src/utils/types.ts +1 -1
- package/src/viewers/logo-summary.ts +9 -6
- package/src/viewers/sar-viewer.ts +28 -14
- package/src/widgets/mutation-cliffs.ts +2 -2
- package/src/widgets/peptides.ts +15 -5
- package/src/widgets/selection.ts +9 -0
- package/src/widgets/settings.ts +3 -8
- package/src/workers/mutation-cliffs-worker.ts +77 -0
- package/dist/521.js +0 -2
- package/dist/677.js +0 -2
- package/dist/729.js +0 -2
- 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(
|
|
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
|
-
|
|
74
|
-
|
|
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',
|
|
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
|
});
|
package/src/tests/model.ts
CHANGED
|
@@ -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 ` +
|
package/src/tests/table-view.ts
CHANGED
|
@@ -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.
|
|
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)) {
|
package/src/tests/viewers.ts
CHANGED
|
@@ -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);
|
package/src/tests/widgets.ts
CHANGED
|
@@ -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;
|
package/src/utils/algorithms.ts
CHANGED
|
@@ -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 {
|
|
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} = {}
|
|
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
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
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
|
|
59
|
+
tempData[tempDataIdx++] ={
|
|
49
60
|
pos: monomerInfo.name,
|
|
50
|
-
seq1monomer: monomerInfo.cat![
|
|
51
|
-
seq2monomer: monomerInfo.cat![
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 ??=
|
|
118
|
-
drawOptions.marginHorizontal ??=
|
|
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
|
|
130
|
+
const barWidth = (bounds.width - (leftShift + drawOptions.marginHorizontal)) * pr;
|
|
126
131
|
const xStart = (bounds.x + leftShift) * pr;
|
|
127
|
-
const selectionWidth =
|
|
128
|
-
const xSelection = (bounds.x +
|
|
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 } = {};
|
package/src/utils/constants.ts
CHANGED