@datagrok/peptides 1.11.3 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -9
- package/dist/package-test.js +2 -2
- package/dist/package.js +2 -2
- package/package.json +2 -2
- package/src/model.ts +57 -86
- package/src/package.ts +0 -1
- package/src/tests/widgets.ts +2 -54
- package/src/utils/misc.ts +2 -12
- package/src/viewers/sar-viewer.ts +2 -0
- package/src/widgets/distribution.ts +2 -0
- package/src/widgets/manual-alignment.ts +3 -7
- package/src/widgets/peptides.ts +18 -17
- package/src/widgets/selection.ts +31 -0
- package/src/widgets/settings.ts +11 -1
- package/src/widgets/similarity.ts +0 -39
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/peptides",
|
|
3
3
|
"friendlyName": "Peptides",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.12.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Volodymyr Dyma",
|
|
7
7
|
"email": "vdyma@datagrok.ai"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"directory": "packages/Peptides"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@datagrok-libraries/bio": "^5.
|
|
16
|
+
"@datagrok-libraries/bio": "^5.37.0",
|
|
17
17
|
"@datagrok-libraries/ml": "^6.3.39",
|
|
18
18
|
"@datagrok-libraries/statistics": "^1.2.2",
|
|
19
19
|
"@datagrok-libraries/utils": "^4.1.4",
|
package/src/model.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
|
|
|
7
7
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
8
8
|
import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
|
|
9
9
|
import {pickUpPalette, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
10
|
+
import {calculateScores, SCORE} from '@datagrok-libraries/bio/src/utils/macromolecule/scoring';
|
|
10
11
|
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
11
12
|
import {DistanceMatrix} from '@datagrok-libraries/ml/src/distance-matrix';
|
|
12
13
|
import {StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
|
|
@@ -21,20 +22,18 @@ import $ from 'cash-dom';
|
|
|
21
22
|
|
|
22
23
|
import * as C from './utils/constants';
|
|
23
24
|
import * as type from './utils/types';
|
|
24
|
-
import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary, prepareTableForHistogram
|
|
25
|
+
import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary, prepareTableForHistogram} from './utils/misc';
|
|
25
26
|
import {MONOMER_POSITION_PROPERTIES, MonomerPosition, MostPotentResidues} from './viewers/sar-viewer';
|
|
26
27
|
import * as CR from './utils/cell-renderer';
|
|
27
28
|
import {mutationCliffsWidget} from './widgets/mutation-cliffs';
|
|
28
|
-
import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap
|
|
29
|
-
} from './widgets/distribution';
|
|
29
|
+
import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap} from './widgets/distribution';
|
|
30
30
|
import {getAggregatedValue, getStats, Stats} from './utils/statistics';
|
|
31
31
|
import {LogoSummaryTable} from './viewers/logo-summary';
|
|
32
32
|
import {getSettingsDialog} from './widgets/settings';
|
|
33
33
|
import {_package, getMonomerWorksInstance, getTreeHelperInstance} from './package';
|
|
34
34
|
import {findMutations} from './utils/algorithms';
|
|
35
35
|
import {createDistanceMatrixWorker} from './utils/worker-creator';
|
|
36
|
-
import {
|
|
37
|
-
import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
36
|
+
import {getSelectionWidget} from './widgets/selection';
|
|
38
37
|
|
|
39
38
|
export type SummaryStats = {
|
|
40
39
|
minCount: number, maxCount: number,
|
|
@@ -98,7 +97,8 @@ export class PeptidesModel {
|
|
|
98
97
|
_distanceMatrix!: DistanceMatrix;
|
|
99
98
|
_dm!: DistanceMatrix;
|
|
100
99
|
_layoutEventInitialized = false;
|
|
101
|
-
|
|
100
|
+
|
|
101
|
+
subs: rxjs.Subscription[] = [];
|
|
102
102
|
|
|
103
103
|
private constructor(dataFrame: DG.DataFrame) {
|
|
104
104
|
this.df = dataFrame;
|
|
@@ -442,26 +442,29 @@ export class PeptidesModel {
|
|
|
442
442
|
acc.addPane('Actions', () => {
|
|
443
443
|
const newView = ui.label('New view');
|
|
444
444
|
$(newView).addClass('d4-link-action');
|
|
445
|
-
newView.onclick = () => trueModel.createNewView();
|
|
446
|
-
newView.onmouseover =
|
|
445
|
+
newView.onclick = (): string => trueModel.createNewView();
|
|
446
|
+
newView.onmouseover =
|
|
447
|
+
(ev): void => ui.tooltip.show('Creates a new view from current selection', ev.clientX + 5, ev.clientY + 5);
|
|
447
448
|
const newCluster = ui.label('New cluster');
|
|
448
449
|
$(newCluster).addClass('d4-link-action');
|
|
449
|
-
newCluster.onclick = () => {
|
|
450
|
+
newCluster.onclick = (): void => {
|
|
450
451
|
const lstViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
451
452
|
if (lstViewer === null)
|
|
452
453
|
throw new Error('Logo summary table viewer is not found');
|
|
453
454
|
lstViewer.clusterFromSelection();
|
|
454
455
|
};
|
|
455
|
-
newCluster.onmouseover =
|
|
456
|
+
newCluster.onmouseover =
|
|
457
|
+
(ev): void => ui.tooltip.show('Creates a new cluster from selection', ev.clientX + 5, ev.clientY + 5);
|
|
456
458
|
const removeCluster = ui.label('Remove cluster');
|
|
457
459
|
$(removeCluster).addClass('d4-link-action');
|
|
458
|
-
removeCluster.onclick = () => {
|
|
460
|
+
removeCluster.onclick = (): void => {
|
|
459
461
|
const lstViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
460
462
|
if (lstViewer === null)
|
|
461
463
|
throw new Error('Logo summary table viewer is not found');
|
|
462
464
|
lstViewer.removeCluster();
|
|
463
465
|
};
|
|
464
|
-
removeCluster.onmouseover =
|
|
466
|
+
removeCluster.onmouseover =
|
|
467
|
+
(ev): void => ui.tooltip.show('Removes currently selected custom cluster', ev.clientX + 5, ev.clientY + 5);
|
|
465
468
|
removeCluster.style.visibility = trueModel.clusterSelection.length === 0 ||
|
|
466
469
|
!wu(this.customClusters).some((c) => trueModel.clusterSelection.includes(c.name)) ? 'hidden' : 'visible';
|
|
467
470
|
return ui.divV([newView, newCluster, removeCluster]);
|
|
@@ -470,6 +473,7 @@ export class PeptidesModel {
|
|
|
470
473
|
const table = trueModel.df.filter.anyFalse ? trueModel.df.clone(trueModel.df.filter, null, true) : trueModel.df;
|
|
471
474
|
acc.addPane('Mutation Cliffs pairs', () => mutationCliffsWidget(trueModel.df, trueModel).root);
|
|
472
475
|
acc.addPane('Distribution', () => getDistributionWidget(table, trueModel).root);
|
|
476
|
+
acc.addPane('Selection', () => getSelectionWidget(trueModel.df, trueModel).root);
|
|
473
477
|
|
|
474
478
|
return acc;
|
|
475
479
|
}
|
|
@@ -1107,80 +1111,47 @@ export class PeptidesModel {
|
|
|
1107
1111
|
this.updateGrid();
|
|
1108
1112
|
}
|
|
1109
1113
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
calculateIdentityBtn.disabled = true;
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
} else {
|
|
1155
|
-
const rowIndex = parseInt(templateInput.value) - 1;
|
|
1156
|
-
const selectedIndexes = this.df.filter.getSelectedIndexes();
|
|
1157
|
-
if (rowIndex < 0 || rowIndex >= selectedIndexes.length) {
|
|
1158
|
-
grok.shell.warning('Invalid row index');
|
|
1159
|
-
calculateIdentityBtn.disabled = true;
|
|
1160
|
-
calculateSimilarityBtn.disabled = true;
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
this.identityTemplate = sequencesCol.get(selectedIndexes[rowIndex]);
|
|
1164
|
-
}
|
|
1165
|
-
try {
|
|
1166
|
-
template = await getTemplate(this.identityTemplate, sequencesCol);
|
|
1167
|
-
} catch (e) {
|
|
1168
|
-
grok.shell.warning('Couldn\'t recognize sequence format.');
|
|
1169
|
-
grok.log.warning(e as string);
|
|
1170
|
-
calculateIdentityBtn.disabled = true;
|
|
1171
|
-
calculateSimilarityBtn.disabled = true;
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
calculateIdentityBtn.disabled = false;
|
|
1175
|
-
calculateSimilarityBtn.disabled = false;
|
|
1176
|
-
}, {placeholder: 'Sequence or row index...'});
|
|
1177
|
-
templateInput.setTooltip('Template sequence. Can be row index, peptide ID or sequence.');
|
|
1178
|
-
templateInput.fireChanged();
|
|
1179
|
-
const acc = this.analysisView.toolboxPage.accordion;
|
|
1180
|
-
acc.addPane('Sequence Identity and Similarity',
|
|
1181
|
-
() => ui.divV([ui.form([templateInput]), calculateIdentityBtn, calculateSimilarityBtn]), true, acc.panes[0]);
|
|
1182
|
-
this.isToolboxSet = true;
|
|
1183
|
-
}
|
|
1114
|
+
this.subs.push(grok.events.onAccordionConstructed.subscribe((acc) => {
|
|
1115
|
+
if (!(grok.shell.o instanceof DG.SemanticValue || (grok.shell.o instanceof DG.Column && this.df.columns.toList().includes(grok.shell.o))))
|
|
1116
|
+
return;
|
|
1117
|
+
|
|
1118
|
+
const actionsPane = acc.getPane('Actions');
|
|
1119
|
+
|
|
1120
|
+
const actionsHost = $(actionsPane.root).find('.d4-flex-col');
|
|
1121
|
+
const calculateIdentity = ui.label('Calculate identity');
|
|
1122
|
+
calculateIdentity.classList.add('d4-link-action');
|
|
1123
|
+
ui.tooltip.bind(calculateIdentity, 'Adds a column with fractions of matching monomers against sequence in the current row');
|
|
1124
|
+
calculateIdentity.onclick = (): void => {
|
|
1125
|
+
const seqCol = this.df.getCol(this.settings.sequenceColumnName!);
|
|
1126
|
+
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.IDENTITY);
|
|
1127
|
+
};
|
|
1128
|
+
actionsHost.append(ui.span([calculateIdentity], 'd4-markdown-row'));
|
|
1129
|
+
|
|
1130
|
+
const calculateSimilarity = ui.label('Calculate similarity');
|
|
1131
|
+
calculateSimilarity.classList.add('d4-link-action');
|
|
1132
|
+
ui.tooltip.bind(calculateSimilarity, 'Adds a column with sequence similarity scores against sequence in the current row');
|
|
1133
|
+
calculateSimilarity.onclick = (): void => {
|
|
1134
|
+
const seqCol = this.df.getCol(this.settings.sequenceColumnName!);
|
|
1135
|
+
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.SIMILARITY);
|
|
1136
|
+
};
|
|
1137
|
+
actionsHost.append(ui.span([calculateSimilarity], 'd4-markdown-row'));
|
|
1138
|
+
}));
|
|
1139
|
+
|
|
1140
|
+
this.subs.push(grok.events.onViewRemoved.subscribe((view) => {
|
|
1141
|
+
if (view.id === this.analysisView.id)
|
|
1142
|
+
this.subs.forEach((v) => v.unsubscribe());
|
|
1143
|
+
grok.log.debug(`Peptides: view ${view.name} removed`);
|
|
1144
|
+
}));
|
|
1145
|
+
this.subs.push(grok.events.onTableRemoved.subscribe((table: DG.DataFrame) => {
|
|
1146
|
+
if (table.id === this.df.id)
|
|
1147
|
+
this.subs.forEach((v) => v.unsubscribe());
|
|
1148
|
+
grok.log.debug(`Peptides: table ${table.name} removed`);
|
|
1149
|
+
}));
|
|
1150
|
+
this.subs.push(grok.events.onProjectClosed.subscribe((project: DG.Project) => {
|
|
1151
|
+
if (project.id === grok.shell.project.id)
|
|
1152
|
+
this.subs.forEach((v) => v.unsubscribe());
|
|
1153
|
+
grok.log.debug(`Peptides: project ${project.name} closed`);
|
|
1154
|
+
}));
|
|
1184
1155
|
|
|
1185
1156
|
this.fireBitsetChanged(true);
|
|
1186
1157
|
if (typeof this.settings.targetColumnName === 'undefined')
|
package/src/package.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {PeptideSimilaritySpaceWidget} from './utils/peptide-similarity-space';
|
|
|
8
8
|
import {manualAlignmentWidget} from './widgets/manual-alignment';
|
|
9
9
|
import {MonomerPosition, MostPotentResidues} from './viewers/sar-viewer';
|
|
10
10
|
import {getTreeHelper, ITreeHelper} from '@datagrok-libraries/bio/src/trees/tree-helper';
|
|
11
|
-
import {IDendrogramService, getDendrogramService} from '@datagrok-libraries/bio/src/trees/dendrogram';
|
|
12
11
|
import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
|
|
13
12
|
import {LogoSummaryTable} from './viewers/logo-summary';
|
|
14
13
|
import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
|
package/src/tests/widgets.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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, awaitCheck
|
|
4
|
+
import {category, test, before, expect, awaitCheck} from '@datagrok-libraries/utils/src/test';
|
|
5
5
|
import {_package} from '../package-test';
|
|
6
6
|
import {PeptidesModel, VIEWER_TYPE} from '../model';
|
|
7
|
-
import {
|
|
7
|
+
import {scaleActivity} from '../utils/misc';
|
|
8
8
|
import {startAnalysis} from '../widgets/peptides';
|
|
9
9
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
10
10
|
import * as C from '../utils/constants';
|
|
@@ -14,7 +14,6 @@ import {mutationCliffsWidget} from '../widgets/mutation-cliffs';
|
|
|
14
14
|
import {TEST_COLUMN_NAMES} from './utils';
|
|
15
15
|
import wu from 'wu';
|
|
16
16
|
import {LogoSummaryTable} from '../viewers/logo-summary';
|
|
17
|
-
import {calculateIdentity, calculateSimilarity} from '../widgets/similarity';
|
|
18
17
|
|
|
19
18
|
category('Widgets: Settings', () => {
|
|
20
19
|
let df: DG.DataFrame;
|
|
@@ -237,54 +236,3 @@ category('Widgets: Actions', () => {
|
|
|
237
236
|
'Expected to have no custom cluster in the Logo Summary Table');
|
|
238
237
|
});
|
|
239
238
|
}, {clear: false});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
category('Widgets: Identity', () => {
|
|
243
|
-
let df: DG.DataFrame;
|
|
244
|
-
let model: PeptidesModel;
|
|
245
|
-
let activityCol: DG.Column<number>;
|
|
246
|
-
let sequenceCol: DG.Column<string>;
|
|
247
|
-
let clusterCol: DG.Column<any>;
|
|
248
|
-
let scaledActivityCol: DG.Column<number>;
|
|
249
|
-
|
|
250
|
-
before(async () => {
|
|
251
|
-
df = DG.DataFrame.fromCsv(await _package.files.readAsText('tests/HELM_small.csv'));
|
|
252
|
-
activityCol = df.getCol(TEST_COLUMN_NAMES.ACTIVITY);
|
|
253
|
-
sequenceCol = df.getCol(TEST_COLUMN_NAMES.SEQUENCE);
|
|
254
|
-
sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
255
|
-
sequenceCol.setTag(DG.TAGS.UNITS, NOTATION.HELM);
|
|
256
|
-
scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
|
|
257
|
-
clusterCol = df.getCol(TEST_COLUMN_NAMES.CLUSTER);
|
|
258
|
-
const tempModel = await startAnalysis(activityCol, sequenceCol, clusterCol, df, scaledActivityCol,
|
|
259
|
-
C.SCALING_METHODS.NONE);
|
|
260
|
-
if (tempModel === null)
|
|
261
|
-
throw new Error('Model is null');
|
|
262
|
-
model = tempModel;
|
|
263
|
-
let overlayInit = false;
|
|
264
|
-
model._analysisView!.grid.onAfterDrawOverlay.subscribe(() => overlayInit = true);
|
|
265
|
-
|
|
266
|
-
// Ensure grid finished initializing to prevent Unhandled exceptions
|
|
267
|
-
let accrodionInit = false;
|
|
268
|
-
grok.events.onAccordionConstructed.subscribe((_) => accrodionInit = true);
|
|
269
|
-
await awaitCheck(() => model!.df.currentRowIdx === 0, 'Grid cell never finished initializing', 2000);
|
|
270
|
-
await awaitCheck(() => grok.shell.o instanceof DG.Column, 'Shell object never changed', 2000);
|
|
271
|
-
await awaitCheck(() => accrodionInit, 'Accordion never finished initializing', 2000);
|
|
272
|
-
await awaitCheck(() => overlayInit, 'Overlay never finished initializing', 2000);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test('Identity', async () => {
|
|
276
|
-
const seq = 'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.D-Orn.D-aThr.Phe_4Me}$$$$';
|
|
277
|
-
const template = await getTemplate(seq);
|
|
278
|
-
const identityCol = calculateIdentity(template, model.splitSeqDf);
|
|
279
|
-
expect(identityCol.get(0), 1, 'Expected 1 identity score when sequence is matching template');
|
|
280
|
-
expectFloat(identityCol.get(3)!, 0.5625, 0.01, 'Expected 0.5625 identity score agains sequence at position 3');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test('Similarity', async () => {
|
|
284
|
-
const seq = 'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.D-Orn.D-aThr.Phe_4Me}$$$$';
|
|
285
|
-
const template = await getTemplate(seq);
|
|
286
|
-
const identityCol = await calculateSimilarity(template, model.splitSeqDf);
|
|
287
|
-
expect(identityCol.get(0), 1, 'Expected 1 identity score when sequence is matching template');
|
|
288
|
-
expectFloat(identityCol.get(3)!, 0, 0.001, 'Expected 7 identity score agains sequence at position 3');
|
|
289
|
-
})
|
|
290
|
-
});
|
package/src/utils/misc.ts
CHANGED
|
@@ -3,9 +3,9 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
import * as C from './constants';
|
|
5
5
|
import * as type from './types';
|
|
6
|
-
import {getSplitterForColumn} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
6
|
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
8
|
-
import {
|
|
7
|
+
import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
|
|
8
|
+
import {TAGS as bioTags} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
9
9
|
|
|
10
10
|
export function getTypedArrayConstructor(
|
|
11
11
|
maxNum: number): Uint8ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor {
|
|
@@ -101,13 +101,3 @@ export function prepareTableForHistogram(table: DG.DataFrame): DG.DataFrame {
|
|
|
101
101
|
DG.Column.fromList(DG.TYPE.BOOL, C.COLUMNS_NAMES.SPLIT_COL, expandedMasks),
|
|
102
102
|
]);
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
export async function getTemplate(sequence: string, seqCol?: DG.Column<string>): Promise<ISeqSplitted> {
|
|
106
|
-
if (typeof seqCol === 'undefined') {
|
|
107
|
-
const tempDf = DG.DataFrame.fromCsv(`sequence\n${new Array(10).fill(sequence).join('\n')}`);
|
|
108
|
-
await grok.data.detectSemanticTypes(tempDf);
|
|
109
|
-
seqCol = tempDf.getCol('sequence');
|
|
110
|
-
}
|
|
111
|
-
const splitter = getSplitterForColumn(seqCol);
|
|
112
|
-
return splitter(sequence);
|
|
113
|
-
}
|
|
@@ -121,6 +121,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
121
121
|
mutationCliffsMode.value = true;
|
|
122
122
|
this.mode = MONOMER_POSITION_MODE.MUTATION_CLIFFS;
|
|
123
123
|
});
|
|
124
|
+
mutationCliffsMode.setTooltip('Statistically significant changes in activity');
|
|
124
125
|
mutationCliffsMode.addPostfix(MONOMER_POSITION_MODE.MUTATION_CLIFFS);
|
|
125
126
|
const invariantMapMode = ui.boolInput('', this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP);
|
|
126
127
|
invariantMapMode.root.addEventListener('click', () => {
|
|
@@ -128,6 +129,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
128
129
|
invariantMapMode.value = true;
|
|
129
130
|
this.mode = MONOMER_POSITION_MODE.INVARIANT_MAP;
|
|
130
131
|
});
|
|
132
|
+
invariantMapMode.setTooltip('Number of sequences having monomer-position');
|
|
131
133
|
invariantMapMode.addPostfix(MONOMER_POSITION_MODE.INVARIANT_MAP);
|
|
132
134
|
const setDefaultProperties = (input: DG.InputBase): void => {
|
|
133
135
|
$(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
|
|
@@ -195,10 +195,12 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
|
|
|
195
195
|
|
|
196
196
|
const splitByPosition = ui.boolInput('', defaultValuePos, updateDistributionHost);
|
|
197
197
|
splitByPosition.addPostfix('Split by position');
|
|
198
|
+
splitByPosition.setTooltip('Constructs distribution for each position separately');
|
|
198
199
|
setDefaultProperties(splitByPosition);
|
|
199
200
|
$(splitByPosition.root).css('margin-right', '10px');
|
|
200
201
|
const splitByAAR = ui.boolInput('', defaultValueAAR, updateDistributionHost);
|
|
201
202
|
splitByAAR.addPostfix('Split by monomer');
|
|
203
|
+
splitByAAR.setTooltip('Constructs distribution for each monomer separately');
|
|
202
204
|
setDefaultProperties(splitByAAR);
|
|
203
205
|
|
|
204
206
|
const controlsHost = ui.divH([splitByPosition.root, splitByAAR.root]);
|
|
@@ -8,7 +8,6 @@ import {PeptidesModel} from '../model';
|
|
|
8
8
|
import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
|
|
9
9
|
|
|
10
10
|
/** Manual sequence alignment widget.
|
|
11
|
-
*
|
|
12
11
|
* @param {DG.Column} alignedSequenceCol Aligned sequence column
|
|
13
12
|
* @param {DG.DataFrame} currentDf Working table
|
|
14
13
|
* @return {DG.Widget} Widget for manual sequence alignment */
|
|
@@ -32,13 +31,10 @@ export function manualAlignmentWidget(alignedSequenceCol: DG.Column<string>, cur
|
|
|
32
31
|
|
|
33
32
|
const peptidesController = PeptidesModel.getInstance(currentDf);
|
|
34
33
|
peptidesController.updateGrid();
|
|
35
|
-
});
|
|
34
|
+
}, 'Apply changes');
|
|
36
35
|
|
|
37
|
-
const resetBtn = ui.button(
|
|
38
|
-
|
|
39
|
-
() => sequenceInput.value = alignedSequenceCol.get(currentDf.currentRowIdx)!,
|
|
40
|
-
'Reset',
|
|
41
|
-
);
|
|
36
|
+
const resetBtn = ui.button(ui.iconFA('redo'),
|
|
37
|
+
() => sequenceInput.value = alignedSequenceCol.get(currentDf.currentRowIdx)!, 'Reset');
|
|
42
38
|
$(resetBtn).addClass('pep-snippet-editor-icon pep-reset-icon');
|
|
43
39
|
|
|
44
40
|
return new DG.Widget(ui.divV([resetBtn, sequenceInput.root, applyChangesBtn], 'pep-textarea-box'));
|
package/src/widgets/peptides.ts
CHANGED
|
@@ -16,13 +16,12 @@ import {ALIGNMENT, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/
|
|
|
16
16
|
* @param {DG.DataFrame} df Working table
|
|
17
17
|
* @param {DG.Column} col Aligned sequence column
|
|
18
18
|
* @return {Promise<DG.Widget>} Widget containing peptide analysis */
|
|
19
|
-
export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
20
|
-
{ host: HTMLElement, callback: () => Promise<boolean> } {
|
|
19
|
+
export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): {host: HTMLElement, callback: () => Promise<boolean>} {
|
|
21
20
|
const logoHost = ui.div();
|
|
22
|
-
// logoHost.style.alignContent = 'center';
|
|
23
21
|
let seqColInput: DG.InputBase | null = null;
|
|
24
22
|
if (typeof col === 'undefined') {
|
|
25
|
-
const sequenceColumns = df.columns.toList()
|
|
23
|
+
const sequenceColumns = df.columns.toList()
|
|
24
|
+
.filter((dfCol) => dfCol.semType === DG.SEMTYPE.MACROMOLECULE && dfCol.stats.missingValueCount === 0);
|
|
26
25
|
const potentialCol = DG.Utils.firstOrNull(sequenceColumns);
|
|
27
26
|
if (potentialCol === null)
|
|
28
27
|
throw new Error('Peptides Error: table doesn\'t contain sequence columns');
|
|
@@ -39,7 +38,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
39
38
|
return viewer.root;
|
|
40
39
|
}));
|
|
41
40
|
//TODO: add when new version of datagrok-api is available
|
|
42
|
-
}, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE});
|
|
41
|
+
}, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE && col.stats.missingValueCount === 0});
|
|
42
|
+
seqColInput.setTooltip('Macromolecule column in FASTA, HELM or separated format');
|
|
43
43
|
} else if (!(col.getTag(bioTAGS.aligned) === ALIGNMENT.SEQ_MSA) &&
|
|
44
44
|
col.getTag(DG.TAGS.UNITS) !== NOTATION.HELM) {
|
|
45
45
|
return {
|
|
@@ -65,10 +65,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
let scaledCol: DG.Column<number>;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
df.col('activity') || df.col('IC50') || DG.Utils.firstOrNull(df.columns.numerical);
|
|
71
|
-
;
|
|
68
|
+
const defaultActivityColumn: DG.Column<number> | null = df.col('activity') || df.col('IC50') ||
|
|
69
|
+
DG.Utils.firstOrNull(df.columns.numerical);
|
|
72
70
|
const histogramHost = ui.div([], {id: 'pep-hist-host'});
|
|
73
71
|
|
|
74
72
|
const activityScalingMethod = ui.choiceInput(
|
|
@@ -88,23 +86,26 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
88
86
|
histogramHost.lastChild?.remove();
|
|
89
87
|
histogramHost.appendChild(hist.root);
|
|
90
88
|
}) as DG.InputBase<C.SCALING_METHODS | null>;
|
|
91
|
-
activityScalingMethod.setTooltip('
|
|
89
|
+
activityScalingMethod.setTooltip('Activity column transformation method');
|
|
92
90
|
|
|
93
91
|
const activityScalingMethodState = (): void => {
|
|
94
|
-
activityScalingMethod.enabled = (activityColumnChoice.value ?? false) &&
|
|
95
|
-
|
|
96
|
-
activityScalingMethod.fireChanged();
|
|
92
|
+
activityScalingMethod.enabled = (activityColumnChoice.value ?? false) && DG.Stats.fromColumn(activityColumnChoice.value!).min > 0;
|
|
93
|
+
activityScalingMethod.value = C.SCALING_METHODS.NONE;
|
|
97
94
|
};
|
|
98
95
|
//TODO: add when new version of datagrok-api is available
|
|
99
96
|
const activityColumnChoice = ui.columnInput('Activity', df, defaultActivityColumn, activityScalingMethodState,
|
|
100
|
-
{filter: (col: DG.Column) => col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT});
|
|
101
|
-
|
|
97
|
+
{filter: (col: DG.Column) => (col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT) && col.stats.missingValueCount === 0});
|
|
98
|
+
activityColumnChoice.setTooltip('Numerical activity column');
|
|
99
|
+
const clustersColumnChoice = ui.columnInput('Clusters', df, null, null, {filter: (col: DG.Column) => col.stats.missingValueCount === 0});
|
|
100
|
+
clustersColumnChoice.setTooltip('Optional. Clusters column is used to create Logo Summary Table');
|
|
102
101
|
clustersColumnChoice.nullable = true;
|
|
103
102
|
activityColumnChoice.fireChanged();
|
|
104
103
|
activityScalingMethod.fireChanged();
|
|
105
104
|
|
|
106
105
|
const targetColumnChoice = ui.columnInput('Target', df, null, null,
|
|
107
|
-
{filter: (col: DG.Column) => col.type === DG.TYPE.STRING});
|
|
106
|
+
{filter: (col: DG.Column) => col.type === DG.TYPE.STRING && col.stats.missingValueCount === 0});
|
|
107
|
+
targetColumnChoice.setTooltip('Optional. Target represents a unique binding construct for every peptide in the data. ' +
|
|
108
|
+
'Target can be used to split mutation cliff analysis for peptides specific to a certain set of targets');
|
|
108
109
|
targetColumnChoice.nullable = true;
|
|
109
110
|
|
|
110
111
|
const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice, targetColumnChoice];
|
|
@@ -128,7 +129,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
128
129
|
const inputElements: HTMLElement[] = [ui.inputs(inputsList)];
|
|
129
130
|
$(inputElements[0]).find('label').css('width', 'unset');
|
|
130
131
|
if (typeof col !== 'undefined') {
|
|
131
|
-
const startBtn = ui.button('Launch SAR', startAnalysisCallback);
|
|
132
|
+
const startBtn = ui.button('Launch SAR', startAnalysisCallback, '');
|
|
132
133
|
startBtn.style.alignSelf = 'center';
|
|
133
134
|
inputElements.push(startBtn);
|
|
134
135
|
bottomHeight = '215px';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
|
|
4
|
+
import {PeptidesModel} from '../model';
|
|
5
|
+
|
|
6
|
+
import wu from 'wu';
|
|
7
|
+
|
|
8
|
+
export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
|
|
9
|
+
const compBitset = model.getCompoundBitset();
|
|
10
|
+
if (compBitset.trueCount === 0)
|
|
11
|
+
return new DG.Widget(ui.divText('No compounds selected'));
|
|
12
|
+
const newTable = DG.DataFrame.create(table.rowCount);
|
|
13
|
+
newTable.filter.copyFrom(compBitset);
|
|
14
|
+
const sourceGrid = model.analysisView.grid;
|
|
15
|
+
const numericalCols = wu(table.columns.numerical);
|
|
16
|
+
for (let gridColIdx = 1; gridColIdx < sourceGrid.columns.length; gridColIdx++) {
|
|
17
|
+
const gridCol = sourceGrid.columns.byIndex(gridColIdx)!;
|
|
18
|
+
if (!gridCol.visible)
|
|
19
|
+
continue;
|
|
20
|
+
const sourceCol = gridCol.column!;
|
|
21
|
+
const sourceColRawData = sourceCol.getRawData();
|
|
22
|
+
const sourceColCategories = sourceCol.categories;
|
|
23
|
+
const getValue = numericalCols.some((col) => col.name === sourceCol.name) ? (i: number): number => sourceColRawData[i] :
|
|
24
|
+
(i: number): string => sourceColCategories[sourceColRawData[i]];
|
|
25
|
+
const col = newTable.columns.addNewVirtual(gridCol.name, (i) => getValue(i), sourceCol.type as DG.TYPE);
|
|
26
|
+
for (const [tag, value] of sourceCol.tags)
|
|
27
|
+
col.setTag(tag, value);
|
|
28
|
+
}
|
|
29
|
+
const newGrid = newTable.plot.grid();
|
|
30
|
+
return new DG.Widget(ui.box(newGrid.root, {style: {width: '100%'}}));
|
|
31
|
+
}
|
package/src/widgets/settings.ts
CHANGED
|
@@ -62,12 +62,17 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
62
62
|
// General pane options
|
|
63
63
|
const activityCol = ui.columnInput(GENERAL_INPUTS.ACTIVITY, model.df,
|
|
64
64
|
model.df.getCol(model.settings.activityColumnName!), () => result.activityColumnName = activityCol.value!.name,
|
|
65
|
-
{filter: (col: DG.Column) => (col.type === DG.TYPE.FLOAT || col.type === DG.TYPE.INT) &&
|
|
65
|
+
{filter: (col: DG.Column) => (col.type === DG.TYPE.FLOAT || col.type === DG.TYPE.INT) &&
|
|
66
|
+
col.name !== C.COLUMNS_NAMES.ACTIVITY_SCALED && col.stats.missingValueCount === 0});
|
|
67
|
+
activityCol.setTooltip('Numeric activity column');
|
|
66
68
|
const activityScaling =
|
|
67
69
|
ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, currentScaling, Object.values(C.SCALING_METHODS),
|
|
68
70
|
() => result.scaling = activityScaling.value as C.SCALING_METHODS) as DG.InputBase<C.SCALING_METHODS>;
|
|
71
|
+
activityScaling.setTooltip('Activity column transformation method');
|
|
69
72
|
const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, currentBidirectional,
|
|
70
73
|
() => result.isBidirectional = bidirectionalAnalysis.value) as DG.InputBase<boolean>;
|
|
74
|
+
bidirectionalAnalysis.setTooltip('Distinguish between positive and negative mean activity difference in ' +
|
|
75
|
+
'Monomer-Position and Most Potent Residues viewers');
|
|
71
76
|
|
|
72
77
|
accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling, bidirectionalAnalysis]), true);
|
|
73
78
|
inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling, bidirectionalAnalysis];
|
|
@@ -88,6 +93,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
88
93
|
const isDendrogramEnabled = wu(model.analysisView.viewers).some((v) => v.type === VIEWER_TYPE.DENDROGRAM);
|
|
89
94
|
const dendrogram = ui.boolInput(VIEWER_TYPE.DENDROGRAM, isDendrogramEnabled ?? false,
|
|
90
95
|
() => result.showDendrogram = dendrogram.value) as DG.InputBase<boolean>;
|
|
96
|
+
dendrogram.setTooltip('Show dendrogram viewer');
|
|
91
97
|
dendrogram.enabled = getTreeHelperInstance() !== null;
|
|
92
98
|
|
|
93
99
|
accordion.addPane(SETTINGS_PANES.VIEWERS, () => ui.inputs([dendrogram]), true);
|
|
@@ -100,6 +106,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
100
106
|
result.maxMutations = val;
|
|
101
107
|
maxMutations.addPostfix(val.toString());
|
|
102
108
|
}) as DG.InputBase<number>;
|
|
109
|
+
maxMutations.setTooltip('Maximum number of mutations between reference and mutated sequences');
|
|
103
110
|
maxMutations.addPostfix((settings.maxMutations ?? 1).toString());
|
|
104
111
|
const minActivityDelta = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MIN_ACTIVITY_DELTA, currentMinActivityDelta, 0,
|
|
105
112
|
100, () => {
|
|
@@ -108,6 +115,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
108
115
|
$(minActivityDelta.root).find('label.ui-input-description').remove();
|
|
109
116
|
minActivityDelta.addPostfix(val);
|
|
110
117
|
}) as DG.InputBase<number>;
|
|
118
|
+
minActivityDelta.setTooltip('Minimum activity difference between reference and mutated sequences');
|
|
111
119
|
minActivityDelta.addPostfix((settings.minActivityDelta ?? 0).toString());
|
|
112
120
|
accordion.addPane(SETTINGS_PANES.MUTATION_CLIFFS, () => ui.inputs([maxMutations, minActivityDelta]), true);
|
|
113
121
|
inputs[SETTINGS_PANES.MUTATION_CLIFFS] = [maxMutations, minActivityDelta];
|
|
@@ -131,6 +139,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
131
139
|
delete result.columns;
|
|
132
140
|
}
|
|
133
141
|
}) as DG.InputBase<boolean>;
|
|
142
|
+
isIncludedInput.setTooltip('Include aggregated column value in tooltips, Logo Summary Table and Distribution panel');
|
|
134
143
|
|
|
135
144
|
const aggregationInput = ui.choiceInput(COLUMNS_INPUTS.AGGREGATION, (currentColumns)[colName] ?? DG.AGG.AVG,
|
|
136
145
|
Object.values(DG.STATS), () => {
|
|
@@ -143,6 +152,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
143
152
|
delete result.columns;
|
|
144
153
|
}
|
|
145
154
|
}) as DG.InputBase<DG.AggregationType>;
|
|
155
|
+
aggregationInput.setTooltip('Aggregation method');
|
|
146
156
|
$(aggregationInput.root).find('label').css('width', 'auto');
|
|
147
157
|
const inputsRow = ui.inputsRow(col.name, [isIncludedInput, aggregationInput]);
|
|
148
158
|
includedColumnsInputs.push(...[isIncludedInput, aggregationInput]);
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {sequenceChemSimilarity} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
3
|
-
import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
4
|
-
import * as DG from 'datagrok-api/dg';
|
|
5
|
-
|
|
6
|
-
export function calculateIdentity(template: ISeqSplitted, splitSeqDf: DG.DataFrame): DG.Column<number> {
|
|
7
|
-
const numPositions = splitSeqDf.columns.length;
|
|
8
|
-
const positionCols: Uint32Array[] = new Array(numPositions);
|
|
9
|
-
const positionEmptyCategories: number[] = new Array(numPositions);
|
|
10
|
-
const categoryIndexesTemplate: number[] = new Array(numPositions);
|
|
11
|
-
|
|
12
|
-
for (let posIdx = 0; posIdx < numPositions; ++posIdx) {
|
|
13
|
-
const posCol = splitSeqDf.columns.byIndex(posIdx);
|
|
14
|
-
positionCols[posIdx] = posCol.getRawData() as Uint32Array;
|
|
15
|
-
positionEmptyCategories[posIdx] = posCol.categories.indexOf('');
|
|
16
|
-
categoryIndexesTemplate[posIdx] = posCol.categories.indexOf(template[posIdx] ?? '');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const identityScoresCol = DG.Column.float('Identity', splitSeqDf.rowCount);
|
|
20
|
-
const identityScoresData = identityScoresCol.getRawData();
|
|
21
|
-
for (let rowIndex = 0; rowIndex < splitSeqDf.rowCount; ++rowIndex) {
|
|
22
|
-
identityScoresData[rowIndex] = 0;
|
|
23
|
-
for (let posIdx = 0; posIdx < template.length; ++posIdx) {
|
|
24
|
-
const categoryIndex = positionCols[posIdx][rowIndex];
|
|
25
|
-
if (categoryIndex === categoryIndexesTemplate[posIdx])
|
|
26
|
-
++identityScoresData[rowIndex];
|
|
27
|
-
}
|
|
28
|
-
identityScoresData[rowIndex] /= template.length;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return identityScoresCol;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export async function calculateSimilarity(template: ISeqSplitted, splitSeqDf: DG.DataFrame): Promise<DG.Column<number>> {
|
|
36
|
-
const columns = splitSeqDf.columns.toList() as DG.Column<string>[];
|
|
37
|
-
const scoresCol = await sequenceChemSimilarity(columns, template);
|
|
38
|
-
return scoresCol;
|
|
39
|
-
}
|