@datagrok/peptides 0.0.1 → 0.2.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/detectors.js +2 -2
- package/package.json +18 -11
- package/scripts/smiles-to-3D.py +13 -0
- package/src/{peptide-sar-viewer/describe.ts → describe.ts} +32 -64
- package/src/package.ts +66 -137
- package/src/utils/cell-renderer.ts +49 -91
- package/src/utils/chem-palette.ts +63 -41
- package/src/utils/molecular-measure.ts +175 -0
- package/src/utils/peptide-similarity-space.ts +242 -0
- package/src/utils/split-aligned.ts +65 -0
- package/src/{peptide-logo-viewer → viewers}/logo-viewer.ts +6 -4
- package/src/viewers/model.ts +63 -0
- package/src/{peptide-sar-viewer → viewers}/sar-viewer.ts +62 -24
- package/src/{stacked-barchart → viewers}/stacked-barchart-viewer.ts +29 -31
- package/src/widgets/analyze-peptides.ts +113 -0
- package/src/widgets/manual-alignment.ts +35 -0
- package/src/widgets/peptide-molecule.ts +42 -0
- package/src/workers/dimensionality-reducer.ts +29 -0
- package/tsconfig.json +12 -13
- package/webpack.config.js +4 -4
- package/.eslintrc.json +0 -29
- package/src/split-aligned.ts +0 -42
- package/src/utils/misc.ts +0 -101
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/* Do not change these import lines. Datagrok will import API library in exactly the same manner */
|
|
2
|
+
// eslint-disable-next-line no-unused-vars
|
|
3
|
+
import * as grok from 'datagrok-api/grok';
|
|
4
|
+
import * as ui from 'datagrok-api/ui';
|
|
5
|
+
import * as DG from 'datagrok-api/dg';
|
|
6
|
+
|
|
7
|
+
import {getSequenceMolecularWeight} from './molecular-measure';
|
|
8
|
+
import {AlignedSequenceEncoder} from '@datagrok-libraries/utils/src/sequence-encoder';
|
|
9
|
+
import {DimensionalityReducer} from '@datagrok-libraries/utils/src/reduce-dimensionality';
|
|
10
|
+
import {Measurer} from '@datagrok-libraries/utils/src/string-measure';
|
|
11
|
+
import {Coordinates} from '@datagrok-libraries/utils/src/type_declarations';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a worker to perform a dimensionality reduction.
|
|
15
|
+
*
|
|
16
|
+
* @param {any[]} columnData Samples to process.
|
|
17
|
+
* @param {string} method Embedding method.
|
|
18
|
+
* @param {string} measure Distance metric.
|
|
19
|
+
* @param {number} cyclesCount Number of cycles to repeat.
|
|
20
|
+
* @return {Promise<unknown>} Promise.
|
|
21
|
+
*/
|
|
22
|
+
function createDimensinalityReducingWorker(
|
|
23
|
+
columnData: any[],
|
|
24
|
+
method: string,
|
|
25
|
+
measure: string,
|
|
26
|
+
cyclesCount: number,
|
|
27
|
+
): Promise<unknown> {
|
|
28
|
+
return new Promise(function(resolve) {
|
|
29
|
+
const worker = new Worker(new URL('../workers/dimensionality-reducer.ts', import.meta.url));
|
|
30
|
+
worker.postMessage({
|
|
31
|
+
columnData: columnData,
|
|
32
|
+
method: method,
|
|
33
|
+
measure: measure,
|
|
34
|
+
cyclesCount: cyclesCount,
|
|
35
|
+
});
|
|
36
|
+
worker.onmessage = ({data: {embedding}}) => {
|
|
37
|
+
resolve(embedding);
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Finds a column with an activity.
|
|
44
|
+
*
|
|
45
|
+
* @param {DG.DataFrame} table The data frame to search for.
|
|
46
|
+
* @return {(string | null)} Column name or null if not found.
|
|
47
|
+
*/
|
|
48
|
+
function inferActivityColumnsName(table: DG.DataFrame): string | null {
|
|
49
|
+
const re = /activity|ic50/i;
|
|
50
|
+
for (const name of table.columns.names()) {
|
|
51
|
+
if (name.match(re)) {
|
|
52
|
+
console.log(`${name} found.`);
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates scatter plot with sequences embeded.
|
|
61
|
+
*
|
|
62
|
+
* @export
|
|
63
|
+
* @param {DG.DataFrame} table The table containing samples.
|
|
64
|
+
* @param {DG.Column} alignedSequencesColumn Samples column.
|
|
65
|
+
* @param {string} method Embedding method to apply.
|
|
66
|
+
* @param {string} measure Distance metric.
|
|
67
|
+
* @param {number} cyclesCount Number of cycles to repeat.
|
|
68
|
+
* @param {(string | null)} [activityColumnName] Activity containing column to assign it to points radius.
|
|
69
|
+
* @param {boolean} [zoom=false] Whether to fit view.
|
|
70
|
+
* @return {Promise<DG.ScatterPlotViewer>} A viewer.
|
|
71
|
+
*/
|
|
72
|
+
export async function createPeptideSimilaritySpaceViewer(
|
|
73
|
+
table: DG.DataFrame,
|
|
74
|
+
alignedSequencesColumn: DG.Column,
|
|
75
|
+
method: string,
|
|
76
|
+
measure: string,
|
|
77
|
+
cyclesCount: number,
|
|
78
|
+
activityColumnName?: string | null,
|
|
79
|
+
zoom: boolean = false,
|
|
80
|
+
): Promise<DG.ScatterPlotViewer> {
|
|
81
|
+
const pi = DG.TaskBarProgressIndicator.create('Creating embedding.');
|
|
82
|
+
|
|
83
|
+
activityColumnName = activityColumnName ?? inferActivityColumnsName(table);
|
|
84
|
+
|
|
85
|
+
const axesNames = ['~X', '~Y', '~MW'];
|
|
86
|
+
const columnData = alignedSequencesColumn.toList().map((v, _) => AlignedSequenceEncoder.clean(v));
|
|
87
|
+
|
|
88
|
+
const embcols = await createDimensinalityReducingWorker(columnData, method, measure, cyclesCount);
|
|
89
|
+
|
|
90
|
+
const columns = Array.from(
|
|
91
|
+
embcols as Coordinates,
|
|
92
|
+
(v: Float32Array, k) => (DG.Column.fromFloat32Array(axesNames[k], v)),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
function _getMW(sequences = columnData) {
|
|
96
|
+
const mw: Float32Array = new Float32Array(sequences.length).fill(0);
|
|
97
|
+
let currentSequence;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < sequences.length; ++i) {
|
|
100
|
+
currentSequence = sequences[i];
|
|
101
|
+
mw[i] = currentSequence == null ? 0 : getSequenceMolecularWeight(currentSequence);
|
|
102
|
+
}
|
|
103
|
+
return mw;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
columns.push(DG.Column.fromFloat32Array('~MW', _getMW()));
|
|
107
|
+
|
|
108
|
+
const edf = DG.DataFrame.fromColumns(columns);
|
|
109
|
+
|
|
110
|
+
// Add new axes.
|
|
111
|
+
for (const axis of axesNames) {
|
|
112
|
+
const col = table.col(axis);
|
|
113
|
+
|
|
114
|
+
if (col == null) {
|
|
115
|
+
table.columns.insert(edf.getCol(axis));
|
|
116
|
+
} else {
|
|
117
|
+
table.columns.replace(col, edf.getCol(axis));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const viewer = DG.Viewer.scatterPlot(table, {x: '~X', y: '~Y', color: activityColumnName ?? '~MW', size: '~MW'});
|
|
121
|
+
|
|
122
|
+
// Fit view if needed.
|
|
123
|
+
/*if (zoom) {
|
|
124
|
+
viewer.zoom(
|
|
125
|
+
table.getCol('~X').min,
|
|
126
|
+
table.getCol('~Y').min,
|
|
127
|
+
table.getCol('~X').max,
|
|
128
|
+
table.getCol('~Y').max,
|
|
129
|
+
);
|
|
130
|
+
}*/
|
|
131
|
+
pi.close();
|
|
132
|
+
return viewer;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Controls creation of the peptide similarity space viewer.
|
|
137
|
+
*
|
|
138
|
+
* @export
|
|
139
|
+
* @class PeptideSimilaritySpaceWidget
|
|
140
|
+
*/
|
|
141
|
+
export class PeptideSimilaritySpaceWidget {
|
|
142
|
+
protected method: string;
|
|
143
|
+
protected metrics: string;
|
|
144
|
+
protected cycles: number = 100;
|
|
145
|
+
protected currentDf: DG.DataFrame;
|
|
146
|
+
protected alignedSequencesColumn: DG.Column;
|
|
147
|
+
protected availableMethods: string[];
|
|
148
|
+
protected availableMetrics: string[];
|
|
149
|
+
protected viewer: HTMLElement;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates an instance of PeptideSimilaritySpaceWidget.
|
|
153
|
+
* @param {DG.Column} alignedSequencesColumn The column to get amino acid sequences from.
|
|
154
|
+
* @memberof PeptideSimilaritySpaceWidget
|
|
155
|
+
*/
|
|
156
|
+
constructor(alignedSequencesColumn: DG.Column) {
|
|
157
|
+
this.availableMethods = DimensionalityReducer.availableMethods;
|
|
158
|
+
this.availableMetrics = Measurer.availableMeasures;
|
|
159
|
+
this.method = this.availableMethods[0];
|
|
160
|
+
this.metrics = this.availableMetrics[0];
|
|
161
|
+
this.currentDf = alignedSequencesColumn.dataFrame;
|
|
162
|
+
this.alignedSequencesColumn = alignedSequencesColumn;
|
|
163
|
+
this.viewer = ui.div([]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Creates viewer itself.
|
|
168
|
+
*
|
|
169
|
+
* @return {Promise<DG.Viewer>} the viewer.
|
|
170
|
+
* @memberof PeptideSimilaritySpaceWidget
|
|
171
|
+
*/
|
|
172
|
+
public async drawViewer(): Promise<DG.Viewer> {
|
|
173
|
+
const viewer = await createPeptideSimilaritySpaceViewer(
|
|
174
|
+
this.currentDf,
|
|
175
|
+
this.alignedSequencesColumn,
|
|
176
|
+
this.method,
|
|
177
|
+
this.metrics,
|
|
178
|
+
this.cycles,
|
|
179
|
+
null,
|
|
180
|
+
true,
|
|
181
|
+
);
|
|
182
|
+
viewer.root.style.width = 'auto';
|
|
183
|
+
return viewer;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Updates the viewer on options changes.
|
|
188
|
+
*
|
|
189
|
+
* @protected
|
|
190
|
+
* @memberof PeptideSimilaritySpaceWidget
|
|
191
|
+
*/
|
|
192
|
+
protected async updateViewer() {
|
|
193
|
+
this.viewer.lastChild?.remove();
|
|
194
|
+
const viewer = await this.drawViewer();
|
|
195
|
+
this.viewer.appendChild(viewer.root);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Adds input controls to manage the viewer's parameters.
|
|
200
|
+
*
|
|
201
|
+
* @protected
|
|
202
|
+
* @return {Promise<HTMLElement>} Bunch of control elements.
|
|
203
|
+
* @memberof PeptideSimilaritySpaceWidget
|
|
204
|
+
*/
|
|
205
|
+
protected async drawInputs(): Promise<HTMLElement> {
|
|
206
|
+
const methodsList = ui.choiceInput('Embedding method', this.method, this.availableMethods,
|
|
207
|
+
async (currentMethod: string) => {
|
|
208
|
+
this.method = currentMethod;
|
|
209
|
+
await this.updateViewer();
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
methodsList.setTooltip('Embedding method to apply to the dataset.');
|
|
213
|
+
|
|
214
|
+
const metricsList = ui.choiceInput('Distance metric', this.metrics, this.availableMetrics,
|
|
215
|
+
async (currentMetrics: string) => {
|
|
216
|
+
this.metrics = currentMetrics;
|
|
217
|
+
await this.updateViewer();
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
metricsList.setTooltip('Custom distance metric to pass to the embedding procedure.');
|
|
221
|
+
|
|
222
|
+
const cyclesSlider = ui.intInput('Cycles count', this.cycles,
|
|
223
|
+
async (currentCycles: number) => {
|
|
224
|
+
this.cycles = currentCycles;
|
|
225
|
+
await this.updateViewer();
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
cyclesSlider.setTooltip('Number of cycles affects the embedding quality.');
|
|
229
|
+
|
|
230
|
+
return ui.inputs([methodsList, metricsList, cyclesSlider]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Draws a viewer on property panel.
|
|
235
|
+
*
|
|
236
|
+
* @return {Promise<DG.Widget>} The corresponding widget.
|
|
237
|
+
* @memberof PeptideSimilaritySpaceWidget
|
|
238
|
+
*/
|
|
239
|
+
public async draw(): Promise<DG.Widget> {
|
|
240
|
+
return new DG.Widget(ui.divV([(await this.drawViewer()).root, await this.drawInputs()]));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
|
|
3
|
+
export function splitAlignedPeptides(peptideColumn: DG.Column, filter: boolean = true): [DG.DataFrame, number[]] {
|
|
4
|
+
const splitPeptidesArray: string[][] = [];
|
|
5
|
+
let currentSplitPeptide: string[];
|
|
6
|
+
let modeMonomerCount = 0;
|
|
7
|
+
let currentLength;
|
|
8
|
+
const colLength = peptideColumn.length;
|
|
9
|
+
|
|
10
|
+
// splitting data
|
|
11
|
+
const monomerLengths: {[index: string]: number} = {};
|
|
12
|
+
for (let i = 0; i < colLength; i++) {
|
|
13
|
+
currentSplitPeptide = peptideColumn.get(i).split('-').map((value: string) => value ? value : '-');
|
|
14
|
+
splitPeptidesArray.push(currentSplitPeptide);
|
|
15
|
+
currentLength = currentSplitPeptide.length;
|
|
16
|
+
monomerLengths[currentLength + ''] =
|
|
17
|
+
monomerLengths[currentLength + ''] ? monomerLengths[currentLength + ''] + 1 : 1;
|
|
18
|
+
}
|
|
19
|
+
//@ts-ignore: what I do here is converting string to number the most effective way I could find. parseInt is slow
|
|
20
|
+
modeMonomerCount = 1 * Object.keys(monomerLengths).reduce((a, b) => monomerLengths[a] > monomerLengths[b] ? a : b);
|
|
21
|
+
|
|
22
|
+
// making sure all of the sequences are of the same size
|
|
23
|
+
// and marking invalid sequences
|
|
24
|
+
let nTerminal: string;
|
|
25
|
+
const invalidIndexes: number[] = [];
|
|
26
|
+
let splitColumns: string[][] = Array.from({length: modeMonomerCount}, (_) => []);
|
|
27
|
+
modeMonomerCount--; // minus N-terminal
|
|
28
|
+
for (let i = 0; i < colLength; i++) {
|
|
29
|
+
currentSplitPeptide = splitPeptidesArray[i];
|
|
30
|
+
nTerminal = currentSplitPeptide.pop()!; // it is guaranteed that there will be at least one element
|
|
31
|
+
currentLength = currentSplitPeptide.length;
|
|
32
|
+
if (currentLength !== modeMonomerCount) {
|
|
33
|
+
invalidIndexes.push(i);
|
|
34
|
+
}
|
|
35
|
+
for (let j = 0; j < modeMonomerCount; j++) {
|
|
36
|
+
splitColumns[j].push(j < currentLength ? currentSplitPeptide[j] : '-');
|
|
37
|
+
}
|
|
38
|
+
splitColumns[modeMonomerCount].push(nTerminal);
|
|
39
|
+
}
|
|
40
|
+
modeMonomerCount--; // minus C-terminal
|
|
41
|
+
|
|
42
|
+
//create column names list
|
|
43
|
+
const columnNames = Array.from({length: modeMonomerCount}, (_, index) => `${index + 1 < 10 ? 0 : ''}${index + 1 }`);
|
|
44
|
+
columnNames.splice(0, 0, 'N-terminal');
|
|
45
|
+
columnNames.push('C-terminal');
|
|
46
|
+
|
|
47
|
+
// filter out the columns with the same values
|
|
48
|
+
|
|
49
|
+
if (filter) {
|
|
50
|
+
splitColumns = splitColumns.filter((positionArray, index) => {
|
|
51
|
+
const isRetained = new Set(positionArray).size > 1;
|
|
52
|
+
if (!isRetained) {
|
|
53
|
+
columnNames.splice(index, 1);
|
|
54
|
+
}
|
|
55
|
+
return isRetained;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
DG.DataFrame.fromColumns(splitColumns.map((positionArray, index) => {
|
|
61
|
+
return DG.Column.fromList('string', columnNames[index], positionArray);
|
|
62
|
+
})),
|
|
63
|
+
invalidIndexes,
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -4,7 +4,7 @@ import * as DG from 'datagrok-api/dg';
|
|
|
4
4
|
import $ from 'cash-dom';
|
|
5
5
|
|
|
6
6
|
import * as logojs from 'logojs-react';
|
|
7
|
-
import {splitAlignedPeptides} from '../split-aligned';
|
|
7
|
+
import {splitAlignedPeptides} from '../utils/split-aligned';
|
|
8
8
|
import {ChemPalette} from '../utils/chem-palette';
|
|
9
9
|
|
|
10
10
|
export class Logo extends DG.JsViewer {
|
|
@@ -31,6 +31,7 @@ export class Logo extends DG.JsViewer {
|
|
|
31
31
|
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'K': 9, 'L': 10, 'M': 11,
|
|
32
32
|
'N': 12, 'P': 13, 'Q': 14, 'R': 15, 'S': 16, 'T': 17, 'U': 18, 'V': 19, 'W': 20, 'Y': 21, 'Z': 22,
|
|
33
33
|
};
|
|
34
|
+
//TODO: use chem palette
|
|
34
35
|
this.LET_COLORS = [
|
|
35
36
|
{color: 'rgb(44,160,44)', regex: 'A'},
|
|
36
37
|
{color: 'rgb(44,160,44)', regex: 'B'},
|
|
@@ -63,9 +64,10 @@ export class Logo extends DG.JsViewer {
|
|
|
63
64
|
// this.reactHost = ui.div([]);
|
|
64
65
|
console.log('INIT');
|
|
65
66
|
this.target = this.dataFrame;
|
|
66
|
-
this.splitted = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
|
|
67
|
+
[this.splitted] = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
|
|
67
68
|
this.root.style.width = 'auto';
|
|
68
69
|
this.root.style.height = 'auto';
|
|
70
|
+
this.root.style.maxHeight = '200px';
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
onTableAttached() {
|
|
@@ -103,8 +105,8 @@ export class Logo extends DG.JsViewer {
|
|
|
103
105
|
.aggregate();
|
|
104
106
|
}
|
|
105
107
|
if (selected) {
|
|
106
|
-
this.splitted = splitAlignedPeptides(this.target!.columns.bySemType(this.colSemType));
|
|
107
|
-
} else this.splitted = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
|
|
108
|
+
[this.splitted] = splitAlignedPeptides(this.target!.columns.bySemType(this.colSemType));
|
|
109
|
+
} else [this.splitted] = splitAlignedPeptides(this.dataFrame!.columns.bySemType(this.colSemType));
|
|
108
110
|
$(this.root).empty();
|
|
109
111
|
|
|
110
112
|
if (typeof this.dataFrame !== 'undefined') {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
|
|
3
|
+
import {describe} from '../describe';
|
|
4
|
+
import { Subject } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
class SARViewerModel {
|
|
7
|
+
private viewerGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
|
|
8
|
+
private viewerVGrid: Subject<DG.Grid> = new Subject<DG.Grid>();
|
|
9
|
+
private statsDf: Subject<DG.DataFrame> = new Subject<DG.DataFrame>();
|
|
10
|
+
public viewerGrid$ = this.viewerGrid.asObservable();
|
|
11
|
+
public viewerVGrid$ = this.viewerVGrid.asObservable();
|
|
12
|
+
public statsDf$ = this.statsDf.asObservable();
|
|
13
|
+
private dataFrame: DG.DataFrame | null;
|
|
14
|
+
private activityColumn: string | null;
|
|
15
|
+
private activityScaling: string | null;
|
|
16
|
+
private sourceGrid: DG.Grid | null;
|
|
17
|
+
private twoColorMode: boolean | null;
|
|
18
|
+
private initialBitset: DG.BitSet | null;
|
|
19
|
+
private isUpdating = false;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.dataFrame = null;
|
|
23
|
+
this.activityColumn = null;
|
|
24
|
+
this.activityScaling = null;
|
|
25
|
+
this.sourceGrid = null;
|
|
26
|
+
this.twoColorMode = null;
|
|
27
|
+
this.initialBitset = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async updateData(
|
|
31
|
+
df: DG.DataFrame,
|
|
32
|
+
activityCol: string,
|
|
33
|
+
activityScaling: string,
|
|
34
|
+
sourceGrid: DG.Grid,
|
|
35
|
+
twoColorMode: boolean,
|
|
36
|
+
initialBitset: DG.BitSet | null,
|
|
37
|
+
) {
|
|
38
|
+
this.dataFrame = df;
|
|
39
|
+
this.activityColumn = activityCol;
|
|
40
|
+
this.activityScaling = activityScaling;
|
|
41
|
+
this.sourceGrid = sourceGrid;
|
|
42
|
+
this.twoColorMode = twoColorMode;
|
|
43
|
+
this.initialBitset = initialBitset;
|
|
44
|
+
await this.updateDefault();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async updateDefault() {
|
|
48
|
+
if (this.dataFrame && this.activityColumn && this.activityScaling && this.sourceGrid && this.twoColorMode !== null && !this.isUpdating) {
|
|
49
|
+
this.isUpdating = true;
|
|
50
|
+
const [viewerGrid, viewerVGrid, statsDf] = await describe(
|
|
51
|
+
this.dataFrame, this.activityColumn, this.activityScaling,
|
|
52
|
+
this.sourceGrid, this.twoColorMode, this.initialBitset
|
|
53
|
+
);
|
|
54
|
+
this.viewerGrid.next(viewerGrid);
|
|
55
|
+
this.viewerVGrid.next(viewerVGrid);
|
|
56
|
+
this.statsDf.next(statsDf);
|
|
57
|
+
this.isUpdating = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export let model = new SARViewerModel();
|
|
63
|
+
|
|
@@ -4,12 +4,11 @@ import * as DG from 'datagrok-api/dg';
|
|
|
4
4
|
|
|
5
5
|
import $ from 'cash-dom';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { model } from './model';
|
|
8
8
|
|
|
9
|
-
export class
|
|
9
|
+
export class SARViewer extends DG.JsViewer {
|
|
10
10
|
protected viewerGrid: DG.Grid | null;
|
|
11
11
|
protected sourceGrid: DG.Grid | null;
|
|
12
|
-
protected progress: DG.TaskBarProgressIndicator;
|
|
13
12
|
protected activityColumnColumnName: string;
|
|
14
13
|
protected activityScalingMethod: string;
|
|
15
14
|
protected bidirectionalAnalysis: boolean;
|
|
@@ -21,13 +20,13 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
21
20
|
protected _initialBitset: DG.BitSet | null;
|
|
22
21
|
protected viewerVGrid: DG.Grid | null;
|
|
23
22
|
protected currentBitset: DG.BitSet | null;
|
|
23
|
+
// private df: DG.DataFrame | null;
|
|
24
24
|
// protected pValueThreshold: number;
|
|
25
25
|
// protected amountOfBestAARs: number;
|
|
26
26
|
// duplicatesHandingMethod: string;
|
|
27
27
|
constructor() {
|
|
28
28
|
super();
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
this.viewerGrid = null;
|
|
32
31
|
this.viewerVGrid = null;
|
|
33
32
|
this.statsDf = null;
|
|
@@ -52,10 +51,17 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
52
51
|
init() {
|
|
53
52
|
this._initialBitset = this.dataFrame!.filter.clone();
|
|
54
53
|
this.initialized = true;
|
|
54
|
+
this.subs.push(model.statsDf$.subscribe(data => this.statsDf = data));
|
|
55
|
+
this.subs.push(model.viewerGrid$.subscribe(data => {
|
|
56
|
+
this.viewerGrid = data;
|
|
57
|
+
this.render();
|
|
58
|
+
}));
|
|
59
|
+
this.subs.push(model.viewerVGrid$.subscribe(data => this.viewerVGrid = data));
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
onTableAttached() {
|
|
58
63
|
this.sourceGrid = this.view.grid;
|
|
64
|
+
this.sourceGrid?.dataFrame?.setTag('dataType', 'peptides');
|
|
59
65
|
this.render();
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -73,7 +79,7 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
73
79
|
|
|
74
80
|
if (property.name === 'activityScalingMethod' && typeof this.dataFrame !== 'undefined') {
|
|
75
81
|
const minActivity = DG.Stats.fromColumn(
|
|
76
|
-
this.dataFrame
|
|
82
|
+
this.dataFrame!.col(this.activityColumnColumnName)!,
|
|
77
83
|
this._initialBitset,
|
|
78
84
|
).min;
|
|
79
85
|
if (minActivity && minActivity <= 0 && this.activityScalingMethod !== 'none') {
|
|
@@ -103,12 +109,15 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
103
109
|
const otherLabel = 'Other';
|
|
104
110
|
const aarLabel = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
let splitCol = this.dataFrame.col(splitColName);
|
|
113
|
+
if (!splitCol) {
|
|
114
|
+
splitCol = this.dataFrame.columns.addNew(splitColName, 'string');
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
const isChosen = (i: number) => this.dataFrame!.get(currentPosition, i) === currentAAR;
|
|
111
|
-
|
|
118
|
+
splitCol!.init((i) => isChosen(i) ? aarLabel : otherLabel);
|
|
119
|
+
|
|
120
|
+
//TODO: use column.compact
|
|
112
121
|
|
|
113
122
|
// if (this.filterMode) {
|
|
114
123
|
// this.dataFrame.selection.setAll(false, false);
|
|
@@ -118,7 +127,8 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
118
127
|
// this.dataFrame.selection.init(isChosen).and(this._initialBitset!, false);
|
|
119
128
|
// }
|
|
120
129
|
this.currentBitset = DG.BitSet.create(this.dataFrame.rowCount, isChosen).and(this._initialBitset!);
|
|
121
|
-
// (this.filterMode ? this.dataFrame.selection.setAll(false) :
|
|
130
|
+
// (this.filterMode ? this.dataFrame.selection.setAll(false) :
|
|
131
|
+
// this.dataFrame.filter.copyFrom(this._initialBitset!)).fireChanged();
|
|
122
132
|
this.sourceFilteringFunc();
|
|
123
133
|
|
|
124
134
|
|
|
@@ -143,19 +153,21 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
143
153
|
|
|
144
154
|
private accordionFunc(accordion: DG.Accordion) {
|
|
145
155
|
if (accordion.context instanceof DG.RowGroup) {
|
|
146
|
-
const originalDf: DG.DataFrame = accordion.context.dataFrame;
|
|
156
|
+
const originalDf: DG.DataFrame = DG.toJs(accordion.context.dataFrame);
|
|
157
|
+
const viewerDf = this.viewerGrid?.dataFrame;
|
|
147
158
|
|
|
148
159
|
if (
|
|
149
160
|
originalDf.getTag('dataType') === 'peptides' &&
|
|
150
161
|
originalDf.col('~splitCol') &&
|
|
151
162
|
this.viewerGrid &&
|
|
152
|
-
|
|
163
|
+
viewerDf &&
|
|
164
|
+
viewerDf.currentCol !== null
|
|
153
165
|
) {
|
|
154
|
-
const currentAAR: string =
|
|
166
|
+
const currentAAR: string = viewerDf.get(
|
|
155
167
|
this.aminoAcidResidue,
|
|
156
|
-
|
|
168
|
+
viewerDf.currentRowIdx,
|
|
157
169
|
);
|
|
158
|
-
const currentPosition =
|
|
170
|
+
const currentPosition = viewerDf.currentCol.name;
|
|
159
171
|
|
|
160
172
|
const labelStr = `${currentAAR === '-' ? 'Empty' : currentAAR} - ${currentPosition}`;
|
|
161
173
|
const currentColor = DG.Color.toHtml(DG.Color.orange);
|
|
@@ -187,7 +199,8 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
187
199
|
for (const colName of new Set(['Count', 'pValue', 'Mean difference'])) {
|
|
188
200
|
const query = `${this.aminoAcidResidue} = ${currentAAR} and Position = ${currentPosition}`;
|
|
189
201
|
const textNum = this.statsDf?.groupBy([colName]).where(query).aggregate().get(colName, 0);
|
|
190
|
-
const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
|
|
202
|
+
// const text = textNum === 0 ? '<0.01' : `${colName === 'Count' ? textNum : textNum.toFixed(2)}`;
|
|
203
|
+
const text = colName === 'Count' ? `${textNum}` : textNum < 0.01 ? '<0.01' : textNum.toFixed(2);
|
|
191
204
|
tableMap[colName === 'pValue' ? 'p-value' : colName] = text;
|
|
192
205
|
}
|
|
193
206
|
elements.push(ui.tableFromMap(tableMap));
|
|
@@ -249,8 +262,16 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
249
262
|
//TODO: optimize. Don't calculate everything again if only view changes
|
|
250
263
|
if (computeData) {
|
|
251
264
|
if (typeof this.dataFrame !== 'undefined' && this.activityColumnColumnName && this.sourceGrid) {
|
|
252
|
-
[this.viewerGrid, this.viewerVGrid, this.statsDf] = await describe(
|
|
253
|
-
|
|
265
|
+
// [this.viewerGrid, this.viewerVGrid, this.statsDf] = await describe(
|
|
266
|
+
// this.dataFrame,
|
|
267
|
+
// this.activityColumnColumnName,
|
|
268
|
+
// this.activityScalingMethod,
|
|
269
|
+
// this.sourceGrid,
|
|
270
|
+
// this.bidirectionalAnalysis,
|
|
271
|
+
// this._initialBitset,
|
|
272
|
+
// );
|
|
273
|
+
await model?.updateData(
|
|
274
|
+
this.dataFrame!,
|
|
254
275
|
this.activityColumnColumnName,
|
|
255
276
|
this.activityScalingMethod,
|
|
256
277
|
this.sourceGrid,
|
|
@@ -260,22 +281,39 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
260
281
|
|
|
261
282
|
if (this.viewerGrid !== null && this.viewerVGrid !== null) {
|
|
262
283
|
$(this.root).empty();
|
|
263
|
-
this.root.appendChild(
|
|
284
|
+
this.root.appendChild(this.viewerGrid.root);
|
|
264
285
|
this.viewerGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => {
|
|
265
286
|
this.applyBitset();
|
|
266
287
|
this.syncGridsFunc(false);
|
|
267
288
|
});
|
|
268
289
|
this.viewerVGrid.dataFrame!.onCurrentCellChanged.subscribe((_) => this.syncGridsFunc(true));
|
|
269
|
-
this.dataFrame
|
|
270
|
-
|
|
290
|
+
this.dataFrame!.onRowsFiltering.subscribe((_) => this.sourceFilteringFunc());
|
|
271
291
|
grok.events.onAccordionConstructed.subscribe((accordion: DG.Accordion) => this.accordionFunc(accordion));
|
|
272
292
|
}
|
|
273
293
|
}
|
|
274
294
|
}
|
|
275
295
|
//fixes viewers not rendering immediately after analyze.
|
|
276
|
-
this.viewerVGrid?.invalidate();
|
|
277
296
|
this.viewerGrid?.invalidate();
|
|
278
|
-
|
|
279
|
-
this.progress.close();
|
|
280
297
|
}
|
|
281
298
|
}
|
|
299
|
+
|
|
300
|
+
export class SARViewerVertical extends DG.JsViewer {
|
|
301
|
+
viewerVGrid: DG.Grid | null;
|
|
302
|
+
constructor() {
|
|
303
|
+
super();
|
|
304
|
+
|
|
305
|
+
this.viewerVGrid = null;
|
|
306
|
+
this.subs.push(model.viewerVGrid$.subscribe(data => {
|
|
307
|
+
this.viewerVGrid = data;
|
|
308
|
+
this.render();
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
render() {
|
|
313
|
+
if (this.viewerVGrid) {
|
|
314
|
+
$(this.root).empty();
|
|
315
|
+
this.root.appendChild(this.viewerVGrid.root);
|
|
316
|
+
}
|
|
317
|
+
this.viewerVGrid?.invalidate();
|
|
318
|
+
}
|
|
319
|
+
}
|