@datagrok/peptides 0.5.6 → 0.6.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/package.json +1 -1
- package/src/describe.ts +1 -1
- package/src/package.ts +9 -0
- package/src/peptides.ts +6 -1
- package/src/viewers/subst-viewer.ts +244 -0
package/package.json
CHANGED
package/src/describe.ts
CHANGED
package/src/package.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {manualAlignmentWidget} from './widgets/manual-alignment';
|
|
|
16
16
|
import {SARViewer, SARViewerVertical} from './viewers/sar-viewer';
|
|
17
17
|
import {peptideMoleculeWidget} from './widgets/peptide-molecule';
|
|
18
18
|
import {SpiralPlot} from './viewers/spiral-plot';
|
|
19
|
+
import { SubstViewer } from './viewers/subst-viewer';
|
|
19
20
|
|
|
20
21
|
export const _package = new DG.Package();
|
|
21
22
|
let tableGrid: DG.Grid;
|
|
@@ -113,6 +114,14 @@ export function sarVertical(): SARViewerVertical {
|
|
|
113
114
|
return new SARViewerVertical();
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
//name: substitution-analysis-viewer
|
|
118
|
+
//description: Substitution Analysis Viewer
|
|
119
|
+
//tags: viewer
|
|
120
|
+
//output: viewer result
|
|
121
|
+
export function subst(): SubstViewer {
|
|
122
|
+
return new SubstViewer();
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
//name: StackedBarchart Widget
|
|
117
126
|
//tags: panel, widgets
|
|
118
127
|
//input: column col {semType: aminoAcids}
|
package/src/peptides.ts
CHANGED
|
@@ -42,11 +42,16 @@ export class Peptides {
|
|
|
42
42
|
|
|
43
43
|
const originalDfColumns = (currentDf.columns as DG.ColumnList).names();
|
|
44
44
|
|
|
45
|
+
const substViewer = view.addViewer(
|
|
46
|
+
'substitution-analysis-viewer', {'activityColumnName': options['activityColumnColumnName']},
|
|
47
|
+
);
|
|
48
|
+
view.dockManager.dock(substViewer, DG.DOCK_TYPE.RIGHT, null, 'Substitution Analysis');
|
|
49
|
+
|
|
45
50
|
const sarViewer = view.addViewer('peptide-sar-viewer', options);
|
|
46
51
|
const sarNode = view.dockManager.dock(sarViewer, DG.DOCK_TYPE.DOWN, null, 'SAR Viewer');
|
|
47
52
|
|
|
48
53
|
const sarViewerVertical = view.addViewer('peptide-sar-viewer-vertical');
|
|
49
|
-
view.dockManager.dock(sarViewerVertical, DG.DOCK_TYPE.RIGHT, sarNode, 'SAR Vertical Viewer');
|
|
54
|
+
const sarVNode = view.dockManager.dock(sarViewerVertical, DG.DOCK_TYPE.RIGHT, sarNode, 'SAR Vertical Viewer');
|
|
50
55
|
|
|
51
56
|
const peptideSpaceViewer = await createPeptideSimilaritySpaceViewer(
|
|
52
57
|
currentDf,
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import $ from 'cash-dom';
|
|
6
|
+
|
|
7
|
+
import { aarGroups } from '../describe';
|
|
8
|
+
import { setAARRenderer } from '../utils/cell-renderer';
|
|
9
|
+
|
|
10
|
+
export class SubstViewer extends DG.JsViewer {
|
|
11
|
+
viewerGrid: DG.Grid | null;
|
|
12
|
+
maxSubstitutions: number;
|
|
13
|
+
activityLimit: number;
|
|
14
|
+
activityColumnName: string;
|
|
15
|
+
casesGrid: DG.Grid | null;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
this.activityColumnName = this.string('activityColumnName');
|
|
21
|
+
|
|
22
|
+
this.maxSubstitutions = this.int('maxSubstitutions', 1);
|
|
23
|
+
this.activityLimit = this.float('activityLimit', 2);
|
|
24
|
+
|
|
25
|
+
this.viewerGrid = null;
|
|
26
|
+
this.casesGrid = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onPropertyChanged(property: DG.Property): void {
|
|
30
|
+
this.calcSubstitutions();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
calcSubstitutions() {
|
|
34
|
+
const aarColName = 'AAR';
|
|
35
|
+
let splitedMatrix: string[][];
|
|
36
|
+
let df: DG.DataFrame = this.dataFrame!;
|
|
37
|
+
const col: DG.Column = df.columns.bySemType('alignedSequence');
|
|
38
|
+
// let values: number[] = df.columns.byName('IC50').toList();
|
|
39
|
+
const values = df.getCol(this.activityColumnName).toList().map(x => -Math.log10(x));
|
|
40
|
+
// values = values;
|
|
41
|
+
splitedMatrix = this.split(col);
|
|
42
|
+
|
|
43
|
+
let tableValues: { [aar: string]: number[] } = {};
|
|
44
|
+
let tableTooltips: { [aar: string]: string[] } = {};
|
|
45
|
+
let tableCases: { [aar: string]: number[][][] } = {};
|
|
46
|
+
|
|
47
|
+
let nRows = splitedMatrix.length;
|
|
48
|
+
let nCols = splitedMatrix[0].length;
|
|
49
|
+
const nColsArray = Array(nCols);
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < nRows - 1; i++) {
|
|
52
|
+
for (let j = i + 1; j < nRows; j++) {
|
|
53
|
+
let substCounter = 0;
|
|
54
|
+
let subst1: { [pos: number]: [string, string] } = {};
|
|
55
|
+
let subst2: { [pos: number]: [string, string] } = {};
|
|
56
|
+
let delta = values[i] - values[j];
|
|
57
|
+
|
|
58
|
+
for (let k = 0; k < nCols; k++) {
|
|
59
|
+
const smik = splitedMatrix[i][k];
|
|
60
|
+
const smjk = splitedMatrix[j][k];
|
|
61
|
+
if (smik != smjk && Math.abs(delta) >= this.activityLimit) {
|
|
62
|
+
const vi = values[i].toFixed(2);
|
|
63
|
+
const vj = values[j].toFixed(2);
|
|
64
|
+
substCounter++;
|
|
65
|
+
subst1[k] = [smik, `${smik} -> ${smjk}\t\t${vi} -> ${vj}`];
|
|
66
|
+
subst2[k] = [smjk, `${smjk} -> ${smik}\t\t${vj} -> ${vi}`];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (substCounter <= this.maxSubstitutions && substCounter > 0) {
|
|
71
|
+
|
|
72
|
+
Object.keys(subst1).forEach((pos) => {
|
|
73
|
+
const posInt = parseInt(pos);
|
|
74
|
+
let aar = subst1[posInt][0];
|
|
75
|
+
if (!Object.keys(tableValues).includes(aar)) {
|
|
76
|
+
tableValues[aar] = Array.apply(null, nColsArray).map(function () { return DG.INT_NULL; });
|
|
77
|
+
tableTooltips[aar] = Array.apply(null, nColsArray).map(function () { return ""; });
|
|
78
|
+
tableCases[aar] = Array.apply(null, nColsArray).map(function () { return []; });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
tableValues[aar][posInt] = tableValues[aar][posInt] === DG.INT_NULL ? 1 : tableValues[aar][posInt] + 1;
|
|
82
|
+
tableTooltips[aar][posInt] = tableTooltips[aar][posInt] == "" ? "Substitution\tvalues\n" : tableTooltips[aar][posInt];
|
|
83
|
+
tableTooltips[aar][posInt] += subst1[posInt][1] + "\n";
|
|
84
|
+
tableCases[aar][posInt].push([i, j, delta]);
|
|
85
|
+
});
|
|
86
|
+
Object.keys(subst2).forEach((pos) => {
|
|
87
|
+
const posInt = parseInt(pos);
|
|
88
|
+
let aar = subst2[posInt][0];
|
|
89
|
+
if (!Object.keys(tableValues).includes(aar)) {
|
|
90
|
+
tableValues[aar] = Array.apply(null, nColsArray).map(function () { return DG.INT_NULL; });
|
|
91
|
+
tableTooltips[aar] = Array.apply(null, nColsArray).map(function () { return ""; });
|
|
92
|
+
tableCases[aar] = Array.apply(null, nColsArray).map(function () { return []; });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
tableValues[aar][posInt] = tableValues[aar][posInt] === DG.INT_NULL ? 1 : tableValues[aar][posInt] + 1;
|
|
96
|
+
// tableValues[aar][posInt]++;
|
|
97
|
+
tableTooltips[aar][posInt] = tableTooltips[aar][posInt] == "" ? "Substitution\tValues\n" : tableTooltips[aar][posInt];
|
|
98
|
+
tableTooltips[aar][posInt] += subst2[posInt][1] + "\n";
|
|
99
|
+
tableCases[aar][posInt].push([j, i, -delta]);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const cols = [...Array(nCols).keys()].map((v) => DG.Column.int(v.toString()));
|
|
106
|
+
const aarCol = DG.Column.string(aarColName);
|
|
107
|
+
cols.splice(0, 1, aarCol);
|
|
108
|
+
let table = DG.DataFrame.fromColumns(cols);
|
|
109
|
+
for (const aar of Object.keys(tableValues)) {
|
|
110
|
+
tableValues[aar].splice(0, 1);
|
|
111
|
+
table.rows.addNew([aar, ...tableValues[aar]]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// let groupMapping: { [key: string]: string } = {};
|
|
115
|
+
|
|
116
|
+
//TODO: enable grouping
|
|
117
|
+
// Object.keys(aarGroups).forEach((value) => groupMapping[value] = value);
|
|
118
|
+
|
|
119
|
+
this.viewerGrid = table.plot.grid();
|
|
120
|
+
|
|
121
|
+
setAARRenderer(aarCol, this.viewerGrid);
|
|
122
|
+
|
|
123
|
+
this.viewerGrid.onCellTooltip(
|
|
124
|
+
(gCell, x, y) => {
|
|
125
|
+
if (gCell.cell.value !== DG.INT_NULL && gCell.tableColumn !== null && gCell.tableRowIndex !== null) {
|
|
126
|
+
const colName = gCell.tableColumn.name;
|
|
127
|
+
if (colName !== aarColName) {
|
|
128
|
+
const aar = this.viewerGrid!.table.get(aarColName, gCell.tableRowIndex);
|
|
129
|
+
const pos = parseInt(colName);
|
|
130
|
+
const tooltipText = tableTooltips[aar][pos];
|
|
131
|
+
ui.tooltip.show(ui.divText(tooltipText ? tooltipText : 'No substitutions'), x, y);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
for (const col of table.columns.names()) {
|
|
139
|
+
this.viewerGrid.col(col)!.width = this.viewerGrid.props.rowHeight;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.viewerGrid.onCellRender.subscribe((args) => {
|
|
143
|
+
if (args.cell.isRowHeader && args.cell.gridColumn.visible) {
|
|
144
|
+
args.cell.gridColumn.visible = false;
|
|
145
|
+
args.preventDefault();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
table.onCurrentCellChanged.subscribe((_) => {
|
|
150
|
+
if (table.currentCol !== null && table.currentCol.name !== aarColName && table.currentCell.value !== null) {
|
|
151
|
+
const aar = table.get(aarColName, table.currentRowIdx);
|
|
152
|
+
const pos = parseInt(table.currentCol.name);
|
|
153
|
+
const currentCase = tableCases[aar][pos];
|
|
154
|
+
const initCol = DG.Column.string('Initial');
|
|
155
|
+
const subsCol = DG.Column.string('Substituted');
|
|
156
|
+
|
|
157
|
+
const tempDf = DG.DataFrame.fromColumns([
|
|
158
|
+
initCol,
|
|
159
|
+
subsCol,
|
|
160
|
+
DG.Column.float('Difference'),
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
for (const row of currentCase) {
|
|
164
|
+
tempDf.rows.addNew([col.get(row[0]), col.get(row[1]), row[2]]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
initCol.semType = 'alignedSequence';
|
|
168
|
+
subsCol.semType = 'alignedSequence';
|
|
169
|
+
|
|
170
|
+
this.casesGrid = tempDf.plot.grid();
|
|
171
|
+
} else {
|
|
172
|
+
this.casesGrid = null;
|
|
173
|
+
}
|
|
174
|
+
this.render();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.render();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
render() {
|
|
181
|
+
$(this.root).empty();
|
|
182
|
+
this.root.appendChild(this.casesGrid === null ?
|
|
183
|
+
this.viewerGrid!.root : ui.splitH([this.viewerGrid!.root, this.casesGrid.root])
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
split(peptideColumn: DG.Column, filter: boolean = true): string[][] {
|
|
188
|
+
const splitPeptidesArray: string[][] = [];
|
|
189
|
+
let currentSplitPeptide: string[];
|
|
190
|
+
let modeMonomerCount = 0;
|
|
191
|
+
let currentLength;
|
|
192
|
+
const colLength = peptideColumn.length;
|
|
193
|
+
|
|
194
|
+
// splitting data
|
|
195
|
+
const monomerLengths: { [index: string]: number } = {};
|
|
196
|
+
for (let i = 0; i < colLength; i++) {
|
|
197
|
+
currentSplitPeptide = peptideColumn.get(i).split('-').map((value: string) => value ? value : '-');
|
|
198
|
+
splitPeptidesArray.push(currentSplitPeptide);
|
|
199
|
+
currentLength = currentSplitPeptide.length;
|
|
200
|
+
monomerLengths[currentLength + ''] =
|
|
201
|
+
monomerLengths[currentLength + ''] ? monomerLengths[currentLength + ''] + 1 : 1;
|
|
202
|
+
}
|
|
203
|
+
//@ts-ignore: what I do here is converting string to number the most effective way I could find. parseInt is slow
|
|
204
|
+
modeMonomerCount = 1 * Object.keys(monomerLengths).reduce((a, b) => monomerLengths[a] > monomerLengths[b] ? a : b);
|
|
205
|
+
|
|
206
|
+
// making sure all of the sequences are of the same size
|
|
207
|
+
// and marking invalid sequences
|
|
208
|
+
let nTerminal: string;
|
|
209
|
+
const invalidIndexes: number[] = [];
|
|
210
|
+
let splitColumns: string[][] = Array.from({ length: modeMonomerCount }, (_) => []);
|
|
211
|
+
modeMonomerCount--; // minus N-terminal
|
|
212
|
+
for (let i = 0; i < colLength; i++) {
|
|
213
|
+
currentSplitPeptide = splitPeptidesArray[i];
|
|
214
|
+
nTerminal = currentSplitPeptide.pop()!; // it is guaranteed that there will be at least one element
|
|
215
|
+
currentLength = currentSplitPeptide.length;
|
|
216
|
+
if (currentLength !== modeMonomerCount) {
|
|
217
|
+
invalidIndexes.push(i);
|
|
218
|
+
}
|
|
219
|
+
for (let j = 0; j < modeMonomerCount; j++) {
|
|
220
|
+
splitColumns[j].push(j < currentLength ? currentSplitPeptide[j] : '-');
|
|
221
|
+
}
|
|
222
|
+
splitColumns[modeMonomerCount].push(nTerminal);
|
|
223
|
+
}
|
|
224
|
+
modeMonomerCount--; // minus C-terminal
|
|
225
|
+
|
|
226
|
+
//create column names list
|
|
227
|
+
const columnNames = Array.from({ length: modeMonomerCount }, (_, index) => `${index + 1 < 10 ? 0 : ''}${index + 1}`);
|
|
228
|
+
columnNames.splice(0, 0, 'N-terminal');
|
|
229
|
+
columnNames.push('C-terminal');
|
|
230
|
+
|
|
231
|
+
// filter out the columns with the same values
|
|
232
|
+
if (filter) {
|
|
233
|
+
splitColumns = splitColumns.filter((positionArray, index) => {
|
|
234
|
+
const isRetained = new Set(positionArray).size > 1;
|
|
235
|
+
if (!isRetained) {
|
|
236
|
+
columnNames.splice(index, 1);
|
|
237
|
+
}
|
|
238
|
+
return isRetained;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return splitPeptidesArray;
|
|
243
|
+
}
|
|
244
|
+
}
|