@datagrok/peptides 1.25.1 → 1.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/216.js +1 -1
- package/dist/216.js.map +1 -1
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +1 -1
- package/src/model.ts +78 -70
- package/src/package-api.ts +7 -0
- package/src/package.g.ts +9 -0
- package/src/package.ts +12 -0
- package/src/tests/viewers.ts +22 -18
- package/src/tests/widgets.ts +1 -1
- package/src/utils/algorithms.ts +7 -3
- package/src/utils/cell-renderer.ts +4 -1
- package/src/utils/misc.ts +16 -20
- package/src/utils/statistics.ts +34 -10
- package/src/utils/tooltips.ts +2 -0
- package/src/viewers/logo-summary.ts +27 -11
- package/src/viewers/mutation-cliffs-viewer.ts +378 -0
- package/src/viewers/position-statistics-viewer.ts +2 -2
- package/src/widgets/distribution.ts +16 -11
- package/src/widgets/peptides.ts +1 -1
- package/src/workers/mutation-cliffs-worker.ts +6 -2
- package/test-console-output-1.log +132 -446
- package/test-record-1.mp4 +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
import * as ui from 'datagrok-api/ui';
|
|
4
|
+
import * as DG from 'datagrok-api/dg';
|
|
5
|
+
import {PeptideUtils} from '../peptideUtils';
|
|
6
|
+
import wu from 'wu';
|
|
7
|
+
import {MutationCliffs} from '../utils/types';
|
|
8
|
+
import {ParallelMutationCliffs} from '../utils/parallel-mutation-cliffs';
|
|
9
|
+
import $ from 'cash-dom';
|
|
10
|
+
import {PeptidesModel} from '../model';
|
|
11
|
+
import {extractColInfo} from '../utils/misc';
|
|
12
|
+
import {Subscription} from 'rxjs';
|
|
13
|
+
export type MutationCliffsWithMonomers = {
|
|
14
|
+
cliffs: MutationCliffs,
|
|
15
|
+
monomers: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MutationCliffsViewer extends DG.JsViewer {
|
|
19
|
+
public sequenceColumnName: string;
|
|
20
|
+
public seriesColumnName: string;
|
|
21
|
+
public activityColumnName: string;
|
|
22
|
+
public position = 1;
|
|
23
|
+
public yAxisType: 'Linear' | 'Logarithmic' = 'Linear';
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.sequenceColumnName = this.column('sequence', {semType: DG.SEMTYPE.MACROMOLECULE, nullable: false});
|
|
27
|
+
this.seriesColumnName = this.column('series', {columnTypeFilter: 'categorical', nullable: false});
|
|
28
|
+
this.activityColumnName = this.column('activity', {columnTypeFilter: 'numerical', nullable: false});
|
|
29
|
+
this.position = this.int('position', 1, {nullable: false, showSlider: false, min: 1, max: 100, showPlusMinus: true, description: 'Position in the sequence to analyze (1 Based).', category: 'Data'});
|
|
30
|
+
this.yAxisType = this.string('yAxisType', 'Linear', {choices: ['Linear', 'Logarithmic'], description: 'Y-Axis scale type.', nullable: false, category: 'Data'}) as 'Linear' | 'Logarithmic';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
onTableAttached(): void {
|
|
34
|
+
super.onTableAttached();
|
|
35
|
+
const seqCol = this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
36
|
+
if (seqCol)
|
|
37
|
+
this.getProperty('sequenceColumnName')!.set(this, seqCol.name);
|
|
38
|
+
this.getProperty('activityColumnName')!.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
39
|
+
const seriesCol = wu(this.dataFrame.columns.categorical)?.filter((c) => c.name !== this.sequenceColumnName && c.name?.toLowerCase().includes('series')).next()?.value;
|
|
40
|
+
if (seriesCol)
|
|
41
|
+
this.getProperty('seriesColumnName')!.set(this, seriesCol.name);
|
|
42
|
+
|
|
43
|
+
this.subs.push(DG.debounce(this.dataFrame.onFilterChanged, 200).subscribe(() => {
|
|
44
|
+
this.clearCache();
|
|
45
|
+
this.debouncedRender();
|
|
46
|
+
}));
|
|
47
|
+
this.debouncedRender();
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
this.subs.push(grok.events.onContextMenu.subscribe((e) => {
|
|
51
|
+
if (e.causedBy && e.causedBy.target && this._lineChart?.root.contains(e.causedBy.target)) {
|
|
52
|
+
e.causedBy.preventDefault();
|
|
53
|
+
e.causedBy.stopPropagation();
|
|
54
|
+
e.causedBy.stopImmediatePropagation();
|
|
55
|
+
}
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _mutationCliffsData: Promise<MutationCliffsWithMonomers> | null = null;
|
|
60
|
+
private get mutationCliffsData(): Promise<MutationCliffsWithMonomers> {
|
|
61
|
+
if (this._mutationCliffsData)
|
|
62
|
+
return this._mutationCliffsData;
|
|
63
|
+
|
|
64
|
+
if (!this.activityColumnName || !this.sequenceColumnName || !this.position)
|
|
65
|
+
throw new Error('Activity column or Sequence column is not set, or position is invalid');
|
|
66
|
+
|
|
67
|
+
this._mutationCliffsData = new Promise<MutationCliffsWithMonomers>(async (resolve, reject) => {
|
|
68
|
+
try {
|
|
69
|
+
// const seqCol = this.dataFrame.col(this.sequenceColumnName)!;
|
|
70
|
+
const activityColRawData = this.dataFrame.col(this.activityColumnName)!.getRawData();
|
|
71
|
+
|
|
72
|
+
// const seqHelper = PeptideUtils.getSeqHelper();
|
|
73
|
+
// const seqHandler = seqHelper.getSeqHandler(seqCol);
|
|
74
|
+
// get single position
|
|
75
|
+
// const monomersAtPosition = seqHandler.getMonomersAtPosition(this.position - 1, true);
|
|
76
|
+
// const monomerCol = DG.Column.fromList('string', `Monomers at pos ${this.position}`, monomersAtPosition);
|
|
77
|
+
// const monomerRawData: RawColumn = {
|
|
78
|
+
// rawData: monomerCol.getRawData(),
|
|
79
|
+
// name: monomerCol.name,
|
|
80
|
+
// cat: monomerCol.categories,
|
|
81
|
+
// };
|
|
82
|
+
|
|
83
|
+
const positionColumns = this.positionColumns;
|
|
84
|
+
const monomersAtPosition = positionColumns[this.position - 1].toList() as string[];
|
|
85
|
+
const monomerRawData = positionColumns.map(extractColInfo);
|
|
86
|
+
|
|
87
|
+
const mutationCliffs = await new ParallelMutationCliffs().calc(activityColRawData, monomerRawData,
|
|
88
|
+
{maxMutations: 1, minActivityDelta: 0, filter: this.dataFrame.filter.anyFalse ? new Uint32Array(this.dataFrame.filter.getBuffer().buffer) : undefined, singlePosition: {position: this.position - 1}},
|
|
89
|
+
);
|
|
90
|
+
resolve({cliffs: mutationCliffs, monomers: monomersAtPosition});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
reject(error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return this._mutationCliffsData;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private _dfSubs: Subscription[] = [];
|
|
99
|
+
|
|
100
|
+
private async calculateDf() {
|
|
101
|
+
if (!this.dataFrame || !this.activityColumnName || !this.sequenceColumnName || !this.position)
|
|
102
|
+
throw new Error('Activity column or Sequence column is not set, or position is invalid');
|
|
103
|
+
const mutationCliffs = await this.mutationCliffsData;
|
|
104
|
+
const uniqueIndexes = new Set<number>();
|
|
105
|
+
mutationCliffs.cliffs.forEach((positionMap) => {
|
|
106
|
+
positionMap.forEach((mcData) => { // should be only one position
|
|
107
|
+
Array.from(mcData.entries()).forEach(([from, toIndexes]) => {
|
|
108
|
+
uniqueIndexes.add(Number(from));
|
|
109
|
+
toIndexes.forEach((toIdx) => uniqueIndexes.add(Number(toIdx)));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
const indexesArray = Array.from(uniqueIndexes.values()).sort((a, b) => a - b);
|
|
114
|
+
const dataFrameIndexToViewerDfIndex = new Map<number, number>();
|
|
115
|
+
indexesArray.forEach((dataFrameIdx, viewerDfIdx) => {
|
|
116
|
+
dataFrameIndexToViewerDfIndex.set(dataFrameIdx, viewerDfIdx);
|
|
117
|
+
});
|
|
118
|
+
const monomers = indexesArray.map((i) => mutationCliffs.monomers[i]);
|
|
119
|
+
const activityColRawData = this.dataFrame.col(this.activityColumnName)!.getRawData();
|
|
120
|
+
const activityValues = indexesArray.map((i) => activityColRawData[i] as number);
|
|
121
|
+
const monomerCol = DG.Column.fromList('string', `Position ${this.position}`, monomers);
|
|
122
|
+
const activityCol = DG.Column.fromList('double', this.activityColumnName, activityValues);
|
|
123
|
+
// add original indexes to be able to trace back
|
|
124
|
+
|
|
125
|
+
const cols = [monomerCol, activityCol];
|
|
126
|
+
if (this.seriesColumnName) {
|
|
127
|
+
const seriesColRawData = this.dataFrame.col(this.seriesColumnName)!.getRawData();
|
|
128
|
+
const seriesCats = this.dataFrame.col(this.seriesColumnName)!.categories;
|
|
129
|
+
const seriesValues = indexesArray.map((i) => seriesCats[seriesColRawData[i]] as string);
|
|
130
|
+
const seriesCol = DG.Column.fromList('string', this.seriesColumnName, seriesValues);
|
|
131
|
+
cols.push(seriesCol);
|
|
132
|
+
}
|
|
133
|
+
const df = DG.DataFrame.fromColumns(cols);
|
|
134
|
+
// add dummy columns corresponding to other numerical columns
|
|
135
|
+
|
|
136
|
+
Array.from(this.dataFrame.columns.numerical)
|
|
137
|
+
.filter((c) => c.name !== this.activityColumnName && c.name !== this.seriesColumnName)
|
|
138
|
+
.forEach((c) => {
|
|
139
|
+
df.columns.addNew(c.name, c.type);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// mouse over row handling back and forth
|
|
143
|
+
this._dfSubs.push(
|
|
144
|
+
df.onMouseOverRowChanged.subscribe((_) => {
|
|
145
|
+
if (df.mouseOverRowIdx >= 0) {
|
|
146
|
+
const originalIdx = indexesArray[df.mouseOverRowIdx];
|
|
147
|
+
this.dataFrame.mouseOverRowIdx = originalIdx;
|
|
148
|
+
}
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
this._dfSubs.push(
|
|
152
|
+
this.dataFrame.onMouseOverRowChanged.subscribe((_) => {
|
|
153
|
+
if (this.dataFrame.mouseOverRowIdx >= 0) {
|
|
154
|
+
const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(this.dataFrame.mouseOverRowIdx);
|
|
155
|
+
if (viewerDfIdx != undefined)
|
|
156
|
+
df.mouseOverRowIdx = viewerDfIdx;
|
|
157
|
+
else
|
|
158
|
+
df.mouseOverRowIdx = -1;
|
|
159
|
+
}
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
this._dfSubs.push(
|
|
164
|
+
df.onCurrentRowChanged.subscribe((_) => {
|
|
165
|
+
if (df.currentRowIdx >= 0) {
|
|
166
|
+
const originalIdx = indexesArray[df.currentRowIdx];
|
|
167
|
+
this.dataFrame.currentRowIdx = originalIdx;
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
let firedFromViewer = false;
|
|
173
|
+
let firedFromTable = false;
|
|
174
|
+
// Handle selection
|
|
175
|
+
this._dfSubs.push(
|
|
176
|
+
DG.debounce(df.onSelectionChanged, 100).subscribe((_) => {
|
|
177
|
+
const selected = df.selection;
|
|
178
|
+
if (firedFromViewer) {
|
|
179
|
+
firedFromViewer = false;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
firedFromTable = true;
|
|
183
|
+
this.dataFrame.selection.setAll(false, false);
|
|
184
|
+
if (!selected.anyTrue) {
|
|
185
|
+
this.dataFrame.selection.fireChanged();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (let rowIdx = -1; (rowIdx = selected.findNext(rowIdx, true)) !== -1;) {
|
|
189
|
+
const originalIdx = indexesArray[rowIdx];
|
|
190
|
+
this.dataFrame.selection.set(originalIdx, true, false);
|
|
191
|
+
}
|
|
192
|
+
this.dataFrame.selection.fireChanged();
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
this._dfSubs.push(
|
|
197
|
+
DG.debounce(this.dataFrame.onSelectionChanged, 100).subscribe((_) => {
|
|
198
|
+
const selected = this.dataFrame.selection;
|
|
199
|
+
if (firedFromTable) {
|
|
200
|
+
firedFromTable = false;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
firedFromViewer = true;
|
|
204
|
+
df.selection.setAll(false, false);
|
|
205
|
+
if (!selected.anyTrue) {
|
|
206
|
+
df.selection.fireChanged();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (let rowIdx = -1; (rowIdx = selected.findNext(rowIdx, true)) !== -1;) {
|
|
210
|
+
const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(rowIdx);
|
|
211
|
+
if (viewerDfIdx != undefined)
|
|
212
|
+
df.selection.set(viewerDfIdx, true, false);
|
|
213
|
+
}
|
|
214
|
+
df.selection.fireChanged();
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// mouse over row GROUP handling one way
|
|
219
|
+
// legend will be responsible for this
|
|
220
|
+
// this._dfSubs.push(
|
|
221
|
+
// df.onMouseOverRowGroupChanged.subscribe((_) => {
|
|
222
|
+
// const func = df.rows.mouseOverRowFunc;
|
|
223
|
+
// if (!func || this.dataFrame.rowCount > 50_000)
|
|
224
|
+
// return;
|
|
225
|
+
// this.dataFrame.rows.highlight((i) => {
|
|
226
|
+
// const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(i);
|
|
227
|
+
// return viewerDfIdx != undefined && func(viewerDfIdx);
|
|
228
|
+
// });
|
|
229
|
+
// }),
|
|
230
|
+
// );
|
|
231
|
+
return df;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private _positionColumns: DG.Column[] | null = null;
|
|
235
|
+
public get positionColumns(): DG.Column[] {
|
|
236
|
+
if (this._positionColumns)
|
|
237
|
+
return this._positionColumns;
|
|
238
|
+
if (!this.dataFrame)
|
|
239
|
+
return [];
|
|
240
|
+
// try to find model and its position columns if the SAR was run
|
|
241
|
+
const peptidesModel = PeptidesModel.getInstance(this.dataFrame);
|
|
242
|
+
const posCols = peptidesModel?.positionColumns;
|
|
243
|
+
if (posCols && posCols.length > 0) {
|
|
244
|
+
this._positionColumns = posCols;
|
|
245
|
+
return this._positionColumns;
|
|
246
|
+
}
|
|
247
|
+
// fallback: generate columns
|
|
248
|
+
const seqCol = this.dataFrame.col(this.sequenceColumnName)!;
|
|
249
|
+
const seqHelper = PeptideUtils.getSeqHelper();
|
|
250
|
+
const seqHandler = seqHelper.getSeqHandler(seqCol);
|
|
251
|
+
const length = seqHandler.maxLength;
|
|
252
|
+
const cols: DG.Column[] = [];
|
|
253
|
+
for (let i = 0; i < length; i++) {
|
|
254
|
+
const monomersAtPosition = seqHandler.getMonomersAtPosition(i, true);
|
|
255
|
+
const monomerCol = DG.Column.fromList('string', `Position ${i + 1}`, monomersAtPosition);
|
|
256
|
+
cols.push(monomerCol);
|
|
257
|
+
}
|
|
258
|
+
this._positionColumns = cols;
|
|
259
|
+
return this._positionColumns;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private _innerDf: Promise<DG.DataFrame> | null = null;
|
|
263
|
+
public get innerDf(): Promise<DG.DataFrame> {
|
|
264
|
+
if (this._innerDf)
|
|
265
|
+
return Promise.resolve(this._innerDf);
|
|
266
|
+
this._innerDf = this.calculateDf();
|
|
267
|
+
return this._innerDf;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private _lineChart: DG.LineChartViewer | null = null;
|
|
271
|
+
|
|
272
|
+
private async render() {
|
|
273
|
+
$(this.root).empty();
|
|
274
|
+
if (!this.dataFrame || !this.activityColumnName || !this.sequenceColumnName || !this.position) {
|
|
275
|
+
this.root.appendChild(ui.divText('Please set Activity column, Sequence column and Position properties.'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this._lineChart) {
|
|
280
|
+
try {
|
|
281
|
+
this._lineChart.detach();
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.error('Error detaching previous line chart:', e);
|
|
284
|
+
}
|
|
285
|
+
this._lineChart = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
ui.setUpdateIndicator(this.root, true);
|
|
289
|
+
this.root.style.display = 'flex';
|
|
290
|
+
this.root.style.flexDirection = 'column';
|
|
291
|
+
|
|
292
|
+
const df = await this.innerDf;
|
|
293
|
+
|
|
294
|
+
this._lineChart = df.plot.line({
|
|
295
|
+
xColumnName: `Position ${this.position}`,
|
|
296
|
+
yColumnNames: [this.activityColumnName],
|
|
297
|
+
splitColumnNames: this.seriesColumnName ? [this.seriesColumnName] : [],
|
|
298
|
+
legendVisibility: this.seriesColumnName ? 'Always' : 'Never',
|
|
299
|
+
legendPosition: 'Right',
|
|
300
|
+
showXSelector: false,
|
|
301
|
+
showYSelector: true,
|
|
302
|
+
showSplitSelector: false,
|
|
303
|
+
xAxisLabelOrientation: 'Auto',
|
|
304
|
+
axisFont: 'normal normal 14px "Roboto"',
|
|
305
|
+
controlsFont: 'normal normal 14px "Roboto"',
|
|
306
|
+
} as Partial<DG.ILineChartSettings>) as DG.LineChartViewer;
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
this._lineChart.sub(this._lineChart.onPropertyValueChanged.subscribe((_e) => {
|
|
310
|
+
if (this._lineChart?.props?.yColumnNames && this._lineChart?.props?.yColumnNames?.[0] !== this.activityColumnName) {
|
|
311
|
+
const value = this._lineChart?.props?.yColumnNames?.[0];
|
|
312
|
+
setTimeout(() => this.getProperty('activityColumnName')!.set(this, value), 1);
|
|
313
|
+
}
|
|
314
|
+
}));
|
|
315
|
+
|
|
316
|
+
ui.setUpdateIndicator(this.root, false);
|
|
317
|
+
this.root.appendChild(this._lineChart.root);
|
|
318
|
+
|
|
319
|
+
const maxPosition = this.positionColumns.length + 1;
|
|
320
|
+
const positions = new Array<number>(maxPosition - 1).fill(0).map((_, i) => i + 1).map((v) => v.toString());
|
|
321
|
+
const positionInput = ui.input.choice('Position', {value: this.position.toString(), items: positions, nullable: false,
|
|
322
|
+
onValueChanged: (v) => {
|
|
323
|
+
if (!v)
|
|
324
|
+
return;
|
|
325
|
+
const intV = parseInt(v);
|
|
326
|
+
this.getProperty('position')!.set(this, intV);
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
positionInput.input.style.width = '40px';
|
|
331
|
+
this.root.appendChild(ui.divH([positionInput.root], {style: {justifyContent: 'center', marginTop: '4px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
|
|
332
|
+
|
|
333
|
+
const seriesColSelector = ui.input.column('Series', {table: this.dataFrame,
|
|
334
|
+
filter: (col: DG.Column) => {
|
|
335
|
+
return col.isCategorical && col.name !== this.sequenceColumnName;
|
|
336
|
+
}, tooltipText: 'Select column for series splitting.',
|
|
337
|
+
onValueChanged: (col: DG.Column | null) => {
|
|
338
|
+
const colName = col ? col.name : undefined;
|
|
339
|
+
this.getProperty('seriesColumnName')!.set(this, colName);
|
|
340
|
+
}, value: this.seriesColumnName ? this.dataFrame.col(this.seriesColumnName)! : undefined,
|
|
341
|
+
});
|
|
342
|
+
this.root.prepend(ui.divH([seriesColSelector.root], {style: {justifyContent: 'flex-end', paddingBottom: '4px', padding: '8px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private _debounceTimer: any = null;
|
|
346
|
+
public debouncedRender() {
|
|
347
|
+
ui.setUpdateIndicator(this.root, true);
|
|
348
|
+
if (this._debounceTimer)
|
|
349
|
+
clearTimeout(this._debounceTimer);
|
|
350
|
+
this._debounceTimer = setTimeout(() => this.render(), 300);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private clearCache() {
|
|
354
|
+
this._mutationCliffsData = null;
|
|
355
|
+
this._innerDf = null;
|
|
356
|
+
this._dfSubs.forEach((s) => s.unsubscribe());
|
|
357
|
+
this._dfSubs = [];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
detach(): void {
|
|
361
|
+
super.detach();
|
|
362
|
+
this._dfSubs.forEach((s) => s.unsubscribe());
|
|
363
|
+
this._dfSubs = [];
|
|
364
|
+
clearTimeout(this._debounceTimer);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
onPropertyChanged(property: DG.Property | null): void {
|
|
369
|
+
super.onPropertyChanged(property);
|
|
370
|
+
if (property?.name === 'activityColumnName' || property?.name === 'sequenceColumnName' || property?.name === 'position' || property?.name === 'seriesColumnName') {
|
|
371
|
+
this.clearCache();
|
|
372
|
+
this.debouncedRender();
|
|
373
|
+
} if (property?.name === 'yAxisType') {
|
|
374
|
+
if (this._lineChart)
|
|
375
|
+
this._lineChart.props.yAxisType = this.yAxisType.toLowerCase() as DG.AxisType;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -27,13 +27,13 @@ export class SequencePositionStatsViewer extends DG.JsViewer {
|
|
|
27
27
|
this.leftMotifLength = this.int('leftMotifLength', 0, {nullable: false, min: 0, max: 10});
|
|
28
28
|
this.rightMotifLength = this.int('rightMotifLength', 0, {nullable: false, min: 0, max: 10});
|
|
29
29
|
this.showPositionInfo = this.bool('showPositionInfo', true, {nullable: false, defaultValue: true, description: 'Show position and overhangs info in the viewer header'});
|
|
30
|
-
grok.events.onContextMenu.subscribe((e) => {
|
|
30
|
+
this.subs.push(grok.events.onContextMenu.subscribe((e) => {
|
|
31
31
|
if (e.causedBy && e.causedBy.target && this._boxPlotViewer?.root.contains(e.causedBy.target)) {
|
|
32
32
|
e.causedBy.preventDefault();
|
|
33
33
|
e.causedBy.stopPropagation();
|
|
34
34
|
e.causedBy.stopImmediatePropagation();
|
|
35
35
|
}
|
|
36
|
-
});
|
|
36
|
+
}));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
getPositionFromColumn(): number {
|
|
@@ -5,7 +5,8 @@ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations'
|
|
|
5
5
|
import $ from 'cash-dom';
|
|
6
6
|
|
|
7
7
|
import * as C from '../utils/constants';
|
|
8
|
-
import {AggregationColumns,
|
|
8
|
+
import {AggregationColumns, bitSetToBitArray,
|
|
9
|
+
getAggregatedColumnValues, getStats, StatsItem} from '../utils/statistics';
|
|
9
10
|
import {DistributionLabelMap, getDistributionPanel, getDistributionTable, SPLIT_CATEGORY} from '../utils/misc';
|
|
10
11
|
import {SARViewer} from '../viewers/sar-viewer';
|
|
11
12
|
import {CLUSTER_TYPE, LogoSummaryTable} from '../viewers/logo-summary';
|
|
@@ -13,7 +14,7 @@ import BitArray from '@datagrok-libraries/utils/src/bit-array';
|
|
|
13
14
|
import {Selection} from '../utils/types';
|
|
14
15
|
|
|
15
16
|
export type DistributionItemOptions = {
|
|
16
|
-
|
|
17
|
+
columns: AggregationColumns, clusterColName?: string,
|
|
17
18
|
activityCol: DG.Column<number>, monomerPositionSelection: Selection, clusterSelection: Selection,
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -143,8 +144,7 @@ export function getStatsTableMap(stats: StatsItem,
|
|
|
143
144
|
*/
|
|
144
145
|
function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: DistributionItemOptions,
|
|
145
146
|
labelMap: DistributionLabelMap = {}): HTMLDivElement {
|
|
146
|
-
const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection
|
|
147
|
-
options.peptideSelection));
|
|
147
|
+
const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection));
|
|
148
148
|
const aggregatedColMap = getAggregatedColumnValues(table, Object.entries(options.columns),
|
|
149
149
|
{filterDf: true, mask: DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, stats.mask.length)});
|
|
150
150
|
const tableMap = getStatsTableMap(stats);
|
|
@@ -165,15 +165,17 @@ function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: D
|
|
|
165
165
|
function getDistributionCategory(category: DISTRIBUTION_CATEGORIES_KEYS | typeof general, table: DG.DataFrame,
|
|
166
166
|
options: DistributionItemOptions): HTMLDivElement {
|
|
167
167
|
let body: HTMLDivElement = ui.divText('No distribution');
|
|
168
|
+
const selectionAndFilter = table.selection.clone().and(table.filter);
|
|
169
|
+
const selectionAndFilterBA = BitArray.fromUint32Array(
|
|
170
|
+
selectionAndFilter.length, new Uint32Array(selectionAndFilter.getBuffer().buffer));
|
|
168
171
|
switch (category) {
|
|
169
172
|
case general:
|
|
170
|
-
const bitArray = BitArray.fromSeq(table.selection.length, (i: number) => table.selection.get(i));
|
|
171
173
|
const stats = !table.selection.anyTrue || !table.selection.anyFalse ?
|
|
172
174
|
{
|
|
173
|
-
count: options.activityCol.length, pValue: null, meanDifference: 0, ratio: 1, mask:
|
|
175
|
+
count: options.activityCol.length, pValue: null, meanDifference: 0, ratio: 1, mask: selectionAndFilterBA,
|
|
174
176
|
mean: options.activityCol.stats.avg,
|
|
175
177
|
} :
|
|
176
|
-
getStats(options.activityCol.getRawData(),
|
|
178
|
+
getStats(options.activityCol.getRawData(), selectionAndFilterBA, bitSetToBitArray(table.filter));
|
|
177
179
|
|
|
178
180
|
body = getSingleDistribution(table, stats, options);
|
|
179
181
|
break;
|
|
@@ -207,6 +209,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
|
|
|
207
209
|
const clusterCol = table.getCol(options.clusterColName);
|
|
208
210
|
const clusterColCategories = clusterCol.categories;
|
|
209
211
|
const clusterColData = clusterCol.getRawData() as Int32Array;
|
|
212
|
+
const filterBitArray = bitSetToBitArray(table.filter);
|
|
210
213
|
|
|
211
214
|
// Build distributions for original clusters
|
|
212
215
|
const selectedClustersCategoryIndexes = selectionObject[CLUSTER_TYPE.ORIGINAL]
|
|
@@ -220,7 +223,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
|
|
|
220
223
|
}
|
|
221
224
|
for (let selectedClusterIdx = 0; selectedClusterIdx < selectedClustersCategoryIndexes.length; selectedClusterIdx++) {
|
|
222
225
|
const selectedClusterCategoryIndex = selectedClustersCategoryIndexes[selectedClusterIdx];
|
|
223
|
-
const stats = getStats(activityColData, clusterMasks[selectedClusterIdx]);
|
|
226
|
+
const stats = getStats(activityColData, clusterMasks[selectedClusterIdx], filterBitArray);
|
|
224
227
|
distributions.push(getSingleDistribution(table, stats, options,
|
|
225
228
|
{[SPLIT_CATEGORY.SELECTION]: clusterColCategories[selectedClusterCategoryIndex]}));
|
|
226
229
|
}
|
|
@@ -230,7 +233,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
|
|
|
230
233
|
for (const clusterColumnName of customClusterSelection) {
|
|
231
234
|
const customClustCol = table.getCol(clusterColumnName);
|
|
232
235
|
const bitArray = BitArray.fromUint32Array(rowCount, customClustCol.getRawData() as Uint32Array);
|
|
233
|
-
const stats = getStats(activityColData, bitArray);
|
|
236
|
+
const stats = getStats(activityColData, bitArray, filterBitArray);
|
|
234
237
|
distributions.push(getSingleDistribution(table, stats, options,
|
|
235
238
|
{[SPLIT_CATEGORY.SELECTION]: clusterColumnName}));
|
|
236
239
|
}
|
|
@@ -254,6 +257,7 @@ function getDistributionForPositions(table: DG.DataFrame, options: DistributionI
|
|
|
254
257
|
const positionColumns: (DG.Column<string> | undefined)[] = [];
|
|
255
258
|
const positionColumnsCategories: (string[] | undefined)[] = [];
|
|
256
259
|
const positionColumnsData: (Int32Array | undefined)[] = [];
|
|
260
|
+
const filterBitArray = bitSetToBitArray(table.filter);
|
|
257
261
|
|
|
258
262
|
for (let posIdx = 0; posIdx < positions.length; posIdx++) {
|
|
259
263
|
const position = positions[posIdx];
|
|
@@ -276,7 +280,7 @@ function getDistributionForPositions(table: DG.DataFrame, options: DistributionI
|
|
|
276
280
|
mask.setTrue(i);
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
|
-
const stats = getStats(activityColData, mask);
|
|
283
|
+
const stats = getStats(activityColData, mask, filterBitArray);
|
|
280
284
|
distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: position}));
|
|
281
285
|
}
|
|
282
286
|
|
|
@@ -299,6 +303,7 @@ function getDistributionForMonomers(table: DG.DataFrame, options: DistributionIt
|
|
|
299
303
|
const positionColumnsCategories: (string[] | undefined)[] = [];
|
|
300
304
|
const positionColumnsData: (Int32Array | undefined)[] = [];
|
|
301
305
|
const activityColData = options.activityCol.getRawData();
|
|
306
|
+
const filterBitArray = bitSetToBitArray(table.filter);
|
|
302
307
|
|
|
303
308
|
for (const monomer of monomers) {
|
|
304
309
|
const posList = reversedSelectionObject[monomer];
|
|
@@ -316,7 +321,7 @@ function getDistributionForMonomers(table: DG.DataFrame, options: DistributionIt
|
|
|
316
321
|
mask.setTrue(i);
|
|
317
322
|
}
|
|
318
323
|
}
|
|
319
|
-
const stats = getStats(activityColData, mask);
|
|
324
|
+
const stats = getStats(activityColData, mask, filterBitArray);
|
|
320
325
|
|
|
321
326
|
distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: monomer}));
|
|
322
327
|
}
|
package/src/widgets/peptides.ts
CHANGED
|
@@ -192,7 +192,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): Di
|
|
|
192
192
|
bitsetChanged.unsubscribe();
|
|
193
193
|
if (sequencesCol) {
|
|
194
194
|
const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
|
|
195
|
-
scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: false, addMCL:
|
|
195
|
+
scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: false, addMCL: generateClustersInput.value,
|
|
196
196
|
useEmbeddingsClusters: generateClustersInput.value ?? false, mclSettings: mclOptions});
|
|
197
197
|
return model !== null;
|
|
198
198
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import BitArray from '@datagrok-libraries/utils/src/bit-array';
|
|
2
3
|
|
|
3
4
|
onmessage = async (event): Promise<void> => {
|
|
4
5
|
const {startIdx, endIdx, activityArray, monomerInfoArray, settings} = event.data;
|
|
5
6
|
const filterArray: Uint32Array = settings.filter;
|
|
7
|
+
const singlePosition: {position: number} | undefined = settings.singlePosition;
|
|
8
|
+
const singlePositionIndex = singlePosition ? singlePosition.position : -1;
|
|
6
9
|
const filter = filterArray ? new BitArray(filterArray, filterArray.length * 32) : null;
|
|
7
10
|
|
|
8
11
|
const pos: string[] = [];
|
|
@@ -25,14 +28,15 @@ onmessage = async (event): Promise<void> => {
|
|
|
25
28
|
if (Math.abs(delta) >= settings.minActivityDelta) {
|
|
26
29
|
let substCounterFlag = false;
|
|
27
30
|
let tempDataIdx = 0;
|
|
28
|
-
for (
|
|
31
|
+
for (let positionIndex = 0; positionIndex < monomerInfoArray.length; positionIndex++) {
|
|
32
|
+
const monomerInfo = monomerInfoArray[positionIndex];
|
|
29
33
|
const seq1categoryIdx = monomerInfo.rawData[seq1Idx];
|
|
30
34
|
const seq2categoryIdx = monomerInfo.rawData[seq2Idx];
|
|
31
35
|
if (seq1categoryIdx === seq2categoryIdx)
|
|
32
36
|
continue;
|
|
33
37
|
|
|
34
38
|
substCounter++;
|
|
35
|
-
substCounterFlag = substCounter > settings.maxMutations;
|
|
39
|
+
substCounterFlag = substCounter > settings.maxMutations || (singlePositionIndex !== -1 && positionIndex !== singlePositionIndex);
|
|
36
40
|
if (substCounterFlag)
|
|
37
41
|
break;
|
|
38
42
|
|