@datagrok/peptides 1.11.3 → 1.13.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 +22 -9
- package/dist/535.js +2 -2
- package/dist/package-test.js +2 -2
- package/dist/package.js +2 -2
- package/package.json +7 -7
- package/src/demo/fasta.ts +6 -25
- package/src/model.ts +332 -382
- package/src/package.ts +1 -2
- package/src/tests/core.ts +2 -10
- package/src/tests/table-view.ts +48 -48
- package/src/tests/viewers.ts +15 -13
- package/src/tests/widgets.ts +5 -58
- package/src/utils/cell-renderer.ts +33 -39
- package/src/utils/constants.ts +1 -0
- package/src/utils/misc.ts +2 -15
- package/src/utils/statistics.ts +22 -3
- package/src/utils/types.ts +6 -5
- package/src/viewers/logo-summary.ts +55 -42
- package/src/viewers/sar-viewer.ts +163 -107
- package/src/widgets/distribution.ts +61 -58
- package/src/widgets/manual-alignment.ts +3 -7
- package/src/widgets/mutation-cliffs.ts +2 -2
- package/src/widgets/peptides.ts +26 -18
- package/src/widgets/selection.ts +31 -0
- package/src/widgets/settings.ts +11 -1
- package/src/widgets/similarity.ts +0 -39
|
@@ -33,8 +33,8 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
|
|
|
33
33
|
const posColCategories = posCol.categories;
|
|
34
34
|
const posColData = posCol.getRawData();
|
|
35
35
|
|
|
36
|
-
for (const
|
|
37
|
-
const substitutionsMap = substInfo.get(
|
|
36
|
+
for (const monomer of currentCell[pos]) {
|
|
37
|
+
const substitutionsMap = substInfo.get(monomer)?.get(pos) as Map<number, type.UTypedArray> | undefined;
|
|
38
38
|
if (typeof substitutionsMap === 'undefined')
|
|
39
39
|
continue;
|
|
40
40
|
|
package/src/widgets/peptides.ts
CHANGED
|
@@ -16,16 +16,16 @@ 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
23
|
const sequenceColumns = df.columns.toList().filter((dfCol) => dfCol.semType === DG.SEMTYPE.MACROMOLECULE);
|
|
26
24
|
const potentialCol = DG.Utils.firstOrNull(sequenceColumns);
|
|
27
25
|
if (potentialCol === null)
|
|
28
26
|
throw new Error('Peptides Error: table doesn\'t contain sequence columns');
|
|
27
|
+
else if (potentialCol.stats.missingValueCount !== 0)
|
|
28
|
+
grok.shell.info('Sequences column contains missing values. They will be ignored during analysis');
|
|
29
29
|
|
|
30
30
|
seqColInput = ui.columnInput('Sequence', df, potentialCol, () => {
|
|
31
31
|
const seqCol = seqColInput!.value;
|
|
@@ -38,8 +38,10 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
38
38
|
viewer.root.style.setProperty('height', '130px');
|
|
39
39
|
return viewer.root;
|
|
40
40
|
}));
|
|
41
|
-
|
|
41
|
+
if (seqCol.stats.missingValueCount !== 0)
|
|
42
|
+
grok.shell.info('Sequences column contains missing values. They will be ignored during analysis');
|
|
42
43
|
}, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE});
|
|
44
|
+
seqColInput.setTooltip('Macromolecule column in FASTA, HELM or separated format');
|
|
43
45
|
} else if (!(col.getTag(bioTAGS.aligned) === ALIGNMENT.SEQ_MSA) &&
|
|
44
46
|
col.getTag(DG.TAGS.UNITS) !== NOTATION.HELM) {
|
|
45
47
|
return {
|
|
@@ -65,10 +67,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
let scaledCol: DG.Column<number>;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
df.col('activity') || df.col('IC50') || DG.Utils.firstOrNull(df.columns.numerical);
|
|
71
|
-
;
|
|
70
|
+
const defaultActivityColumn: DG.Column<number> | null = df.col('activity') || df.col('IC50') ||
|
|
71
|
+
DG.Utils.firstOrNull(df.columns.numerical);
|
|
72
72
|
const histogramHost = ui.div([], {id: 'pep-hist-host'});
|
|
73
73
|
|
|
74
74
|
const activityScalingMethod = ui.choiceInput(
|
|
@@ -88,23 +88,27 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
88
88
|
histogramHost.lastChild?.remove();
|
|
89
89
|
histogramHost.appendChild(hist.root);
|
|
90
90
|
}) as DG.InputBase<C.SCALING_METHODS | null>;
|
|
91
|
-
activityScalingMethod.setTooltip('
|
|
91
|
+
activityScalingMethod.setTooltip('Activity column transformation method');
|
|
92
92
|
|
|
93
93
|
const activityScalingMethodState = (): void => {
|
|
94
|
-
activityScalingMethod.enabled = (activityColumnChoice.value ?? false) &&
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
activityScalingMethod.enabled = (activityColumnChoice.value ?? false) && activityColumnChoice.value!.stats.min > 0;
|
|
95
|
+
activityScalingMethod.value = C.SCALING_METHODS.NONE;
|
|
96
|
+
if (activityColumnChoice.value!.stats.missingValueCount !== 0)
|
|
97
|
+
grok.shell.info('Activity column contains missing values. They will be ignored during analysis');
|
|
97
98
|
};
|
|
98
99
|
//TODO: add when new version of datagrok-api is available
|
|
99
100
|
const activityColumnChoice = ui.columnInput('Activity', df, defaultActivityColumn, activityScalingMethodState,
|
|
100
101
|
{filter: (col: DG.Column) => col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT});
|
|
101
|
-
|
|
102
|
+
activityColumnChoice.setTooltip('Numerical activity column');
|
|
103
|
+
const clustersColumnChoice = ui.columnInput('Clusters', df, null, null);
|
|
104
|
+
clustersColumnChoice.setTooltip('Optional. Clusters column is used to create Logo Summary Table');
|
|
102
105
|
clustersColumnChoice.nullable = true;
|
|
103
106
|
activityColumnChoice.fireChanged();
|
|
104
107
|
activityScalingMethod.fireChanged();
|
|
105
108
|
|
|
106
|
-
const targetColumnChoice = ui.columnInput('Target', df, null, null,
|
|
107
|
-
|
|
109
|
+
const targetColumnChoice = ui.columnInput('Target', df, null, null, {filter: (col: DG.Column) => col.type === DG.TYPE.STRING});
|
|
110
|
+
targetColumnChoice.setTooltip('Optional. Target represents a unique binding construct for every peptide in the data. ' +
|
|
111
|
+
'Target can be used to split mutation cliff analysis for peptides specific to a certain set of targets');
|
|
108
112
|
targetColumnChoice.nullable = true;
|
|
109
113
|
|
|
110
114
|
const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice, targetColumnChoice];
|
|
@@ -128,7 +132,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
|
|
|
128
132
|
const inputElements: HTMLElement[] = [ui.inputs(inputsList)];
|
|
129
133
|
$(inputElements[0]).find('label').css('width', 'unset');
|
|
130
134
|
if (typeof col !== 'undefined') {
|
|
131
|
-
const startBtn = ui.button('Launch SAR', startAnalysisCallback);
|
|
135
|
+
const startBtn = ui.button('Launch SAR', startAnalysisCallback, '');
|
|
132
136
|
startBtn.style.alignSelf = 'center';
|
|
133
137
|
inputElements.push(startBtn);
|
|
134
138
|
bottomHeight = '215px';
|
|
@@ -198,10 +202,14 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
|
|
|
198
202
|
newDf.setTag(C.TAGS.UUID, dfUuid);
|
|
199
203
|
newDf.setTag('monomerType', monomerType);
|
|
200
204
|
|
|
205
|
+
const bitset = DG.BitSet.create(currentDf.rowCount,
|
|
206
|
+
(i) => !activityColumn.isNone(i) && !peptidesCol.isNone(i) && currentDf.filter.get(i));
|
|
207
|
+
|
|
201
208
|
// Cloning dataframe with applied filter. If filter is not applied, cloning is
|
|
202
209
|
// needed anyway to allow filtering on the original dataframe
|
|
203
|
-
model = PeptidesModel.getInstance(newDf.clone(
|
|
204
|
-
if (clustersColumn)
|
|
210
|
+
model = PeptidesModel.getInstance(newDf.clone(bitset));
|
|
211
|
+
if (clustersColumn)
|
|
212
|
+
await model.addLogoSummaryTable();
|
|
205
213
|
await model.addMonomerPosition();
|
|
206
214
|
await model.addMostPotentResidues();
|
|
207
215
|
} else
|
|
@@ -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
|
-
}
|