@datagrok/peptides 1.26.1 → 1.27.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 +8 -0
- 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 +2 -0
- package/src/tests/widgets.ts +1 -0
- package/src/utils/algorithms.ts +16 -4
- package/src/utils/misc.ts +43 -9
- package/src/utils/tooltips.ts +13 -9
- package/src/utils/types.ts +2 -2
- package/src/viewers/logo-summary.ts +6 -2
- package/src/viewers/mutation-cliffs-viewer.ts +91 -40
- package/src/viewers/sar-viewer.ts +6 -4
- package/src/widgets/distribution.ts +1 -1
- package/src/widgets/mutation-cliffs.ts +38 -5
- package/test-console-output-1.log +94 -95
- package/test-record-1.mp4 +0 -0
package/package.json
CHANGED
package/src/model.ts
CHANGED
|
@@ -65,6 +65,7 @@ export enum VIEWER_TYPE {
|
|
|
65
65
|
DENDROGRAM = 'Dendrogram',
|
|
66
66
|
CLUSTER_MAX_ACTIVITY = 'Active peptide selection',
|
|
67
67
|
MCL = 'MCL',
|
|
68
|
+
SEQUENCE_MUTATION_CLIFFS = 'Sequence Mutation Cliffs',
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
export type CachedWebLogoTooltip = { bar: string, tooltip: HTMLDivElement | null };
|
|
@@ -621,6 +622,7 @@ export class PeptidesModel {
|
|
|
621
622
|
sequenceColumnName: sarViewer.sequenceColumnName,
|
|
622
623
|
positionColumns: sarViewer.positionColumns,
|
|
623
624
|
activityCol: sarViewer.getScaledActivityColumn(),
|
|
625
|
+
mutationCliffStats: sarViewer.cliffStats,
|
|
624
626
|
}).root, true);
|
|
625
627
|
}
|
|
626
628
|
const isModelSource = requestSource === trueModel.settings;
|
package/src/tests/widgets.ts
CHANGED
package/src/utils/algorithms.ts
CHANGED
|
@@ -63,19 +63,31 @@ export function calculateCliffsStatistics(
|
|
|
63
63
|
const monomerSubMap = cliffs.get(monomer)!;
|
|
64
64
|
for (const position of monomerSubMap.keys()) {
|
|
65
65
|
const subMap = monomerSubMap.get(position)!;
|
|
66
|
-
const mask = new BitArray(activityArray.length, false);
|
|
67
66
|
if (subMap.size === 0)
|
|
68
67
|
continue;
|
|
68
|
+
// create two masks, one filtering the activities to only mutation cliffs (with given monomer at given position and its substitutions)
|
|
69
|
+
// another one corresponding to only the given monomer at given position within the mutation cliffs
|
|
70
|
+
const filterMask = new BitArray(activityArray.length, false);
|
|
71
|
+
const maskMCWithMonomerAtPosition = new BitArray(activityArray.length, false);
|
|
69
72
|
for (const index of subMap.keys()) {
|
|
70
|
-
mask
|
|
73
|
+
// set the filter mask to true for all sequences within the mutation cliff pairs
|
|
74
|
+
filterMask.setFast(index, true);
|
|
71
75
|
const toIndexes = subMap.get(index)!;
|
|
72
|
-
toIndexes.forEach((i) =>
|
|
76
|
+
toIndexes.forEach((i) => filterMask.setFast(i, true));
|
|
77
|
+
// set the mask for sequences with the given monomer at the given position within the mutation cliffs
|
|
78
|
+
maskMCWithMonomerAtPosition.setFast(index, true);
|
|
73
79
|
}
|
|
74
|
-
const stats = getStats(activityArray,
|
|
80
|
+
const stats = getStats(activityArray, maskMCWithMonomerAtPosition, filterMask);
|
|
81
|
+
stats.mask = filterMask; // store the filter mask for later use in the viewer
|
|
75
82
|
minDiff = Math.min(minDiff, stats.meanDifference);
|
|
76
83
|
maxDiff = Math.max(maxDiff, stats.meanDifference);
|
|
77
84
|
minCount = Math.min(minCount, stats.count);
|
|
78
85
|
maxCount = Math.max(maxCount, stats.count);
|
|
86
|
+
// here, stats will show the following
|
|
87
|
+
// count - number of sequences with the given monomer at the given position within the mutation cliffs
|
|
88
|
+
// mask.trueCount - number of unique sequences within the mutation cliffs (with given monomer at given position and its substitutions)
|
|
89
|
+
// meanDifference - difference between mean activity of sequences with the given monomer at the given position within the mutation cliffs
|
|
90
|
+
// and mean activity of other sequences within the mutation cliffs (with given monomer at given position substitutions)
|
|
79
91
|
monomerStatsMap.set(position, stats);
|
|
80
92
|
}
|
|
81
93
|
}
|
package/src/utils/misc.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
import * as ui from 'datagrok-api/ui';
|
|
2
3
|
import * as DG from 'datagrok-api/dg';
|
|
3
4
|
import * as grok from 'datagrok-api/grok';
|
|
@@ -111,13 +112,10 @@ export function getDistributionPanel(hist: DG.Viewer<DG.IHistogramSettings>, sta
|
|
|
111
112
|
const splitCol = hist.dataFrame.getCol(C.COLUMNS_NAMES.SPLIT_COL);
|
|
112
113
|
const labels = [];
|
|
113
114
|
const categories = splitCol.categories as SPLIT_CATEGORY[];
|
|
114
|
-
const rawData = splitCol.getRawData();
|
|
115
115
|
for (let categoryIdx = 0; categoryIdx < categories.length; ++categoryIdx) {
|
|
116
|
-
if (!
|
|
116
|
+
if (!categories[categoryIdx])
|
|
117
117
|
continue;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const color = DG.Color.toHtml(splitCol.meta.colors.getColor(rawData.indexOf(categoryIdx)));
|
|
118
|
+
const color = DG.Color.toHtml(DG.Color.categoricalPalette[categoryIdx % DG.Color.categoricalPalette.length]);
|
|
121
119
|
const label = ui.label(labelMap[categories[categoryIdx]] ?? categories[categoryIdx], {style: {color}});
|
|
122
120
|
labels.push(label);
|
|
123
121
|
}
|
|
@@ -133,10 +131,8 @@ export function getDistributionPanel(hist: DG.Viewer<DG.IHistogramSettings>, sta
|
|
|
133
131
|
* @param selection - Selection bitset.
|
|
134
132
|
* @return - Dataframe with activity distribution.
|
|
135
133
|
*/
|
|
136
|
-
export function getDistributionTable(activityCol: DG.Column<number>, selection: DG.BitSet): DG.DataFrame {
|
|
137
|
-
|
|
138
|
-
DG.DataFrame.fromColumns([activityCol]); // to make sure that activityCol has a parent dataframe
|
|
139
|
-
const filter = activityCol.dataFrame!.filter;
|
|
134
|
+
export function getDistributionTable(activityCol: DG.Column<number>, selection: DG.BitSet, dataFrame: DG.DataFrame): DG.DataFrame {
|
|
135
|
+
const filter = dataFrame.filter;
|
|
140
136
|
const selectionAndFilter = selection.clone().and(filter);
|
|
141
137
|
const rowCount = activityCol.length;
|
|
142
138
|
const activityColData = activityCol.getRawData();
|
|
@@ -166,6 +162,44 @@ export function getDistributionTable(activityCol: DG.Column<number>, selection:
|
|
|
166
162
|
return DG.DataFrame.fromColumns([DG.Column.fromFloat32Array(C.COLUMNS_NAMES.ACTIVITY, activityData), splitCol]);
|
|
167
163
|
}
|
|
168
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Creates distribution table for mutation cliffs.
|
|
167
|
+
* @param activityCol
|
|
168
|
+
* @param mpCliffs
|
|
169
|
+
*/
|
|
170
|
+
export function getMutationCliffsDistributionTable(activityCol: DG.Column<number>, mpCliffs: Map<type.INDEX, type.INDEXES>, monomerName: string) {
|
|
171
|
+
// instead of stats.selection vs everything, here we want to show mutated vs non-mutated
|
|
172
|
+
if (!activityCol.dataFrame)
|
|
173
|
+
DG.DataFrame.fromColumns([activityCol]); // to make sure that activityCol has a parent dataframe
|
|
174
|
+
const tableFilter = activityCol.dataFrame!.filter;
|
|
175
|
+
const activityData = activityCol.getRawData();
|
|
176
|
+
const uniqueMPCliffsIndexes = Array.from(new Set(mpCliffs.keys())).map((k) => Number(k)).filter((i) => tableFilter.get(i));
|
|
177
|
+
const uniqueMutatedCliffsIndexesSet = new Set<number>();
|
|
178
|
+
for (const indexes of mpCliffs.values()) {
|
|
179
|
+
for (const index of indexes as number[])
|
|
180
|
+
uniqueMutatedCliffsIndexesSet.add(index);
|
|
181
|
+
}
|
|
182
|
+
const uniqueMutatedCliffsIndexes = Array.from(uniqueMutatedCliffsIndexesSet).filter((i) => tableFilter.get(i));
|
|
183
|
+
const totalRowCount = uniqueMPCliffsIndexes.length + uniqueMutatedCliffsIndexes.length;
|
|
184
|
+
const categories: string[] = new Array(totalRowCount);
|
|
185
|
+
const activityValues = new Float32Array(totalRowCount);
|
|
186
|
+
for (let i = 0; i < uniqueMPCliffsIndexes.length; ++i) {
|
|
187
|
+
const rowIndex = uniqueMPCliffsIndexes[i];
|
|
188
|
+
activityValues[i] = activityData[rowIndex];
|
|
189
|
+
categories[i] = monomerName;
|
|
190
|
+
}
|
|
191
|
+
for (let i = 0; i < uniqueMutatedCliffsIndexes.length; ++i) {
|
|
192
|
+
const rowIndex = uniqueMutatedCliffsIndexes[i];
|
|
193
|
+
activityValues[uniqueMPCliffsIndexes.length + i] = activityData[rowIndex];
|
|
194
|
+
categories[uniqueMPCliffsIndexes.length + i] = 'Mutated';
|
|
195
|
+
}
|
|
196
|
+
const splitCol = DG.Column.fromStrings(C.COLUMNS_NAMES.SPLIT_COL, categories);
|
|
197
|
+
const categoryOrder = [monomerName, 'Mutated'];
|
|
198
|
+
splitCol.setCategoryOrder(categoryOrder);
|
|
199
|
+
splitCol.meta.colors.setCategorical();
|
|
200
|
+
return DG.DataFrame.fromColumns([DG.Column.fromFloat32Array(C.COLUMNS_NAMES.ACTIVITY, activityValues), splitCol]);
|
|
201
|
+
}
|
|
202
|
+
|
|
169
203
|
/**
|
|
170
204
|
* Adds expand in full screen icon to the grid.
|
|
171
205
|
* @param grid - Grid to add expand icon to.
|
package/src/utils/tooltips.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as type from './types';
|
|
|
6
6
|
import * as C from '../utils/constants';
|
|
7
7
|
|
|
8
8
|
import {getActivityDistribution, getStatsTableMap} from '../widgets/distribution';
|
|
9
|
-
import {getDistributionPanel, getDistributionTable} from './misc';
|
|
9
|
+
import {getDistributionPanel, getDistributionTable, getMutationCliffsDistributionTable} from './misc';
|
|
10
10
|
import {getAggregatedColumnValues, MonomerPositionStats} from './statistics';
|
|
11
11
|
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ export type TooltipOptions = {
|
|
|
14
14
|
fromViewer?: boolean, isMutationCliffs?: boolean, x: number, y: number, monomerPosition: type.SelectionItem,
|
|
15
15
|
mpStats: MonomerPositionStats, aggrColValues?: StringDictionary,
|
|
16
16
|
isMostPotentResidues?: boolean, cliffStats?: type.MutationCliffStats['stats'],
|
|
17
|
-
postfixes?: StringDictionary, additionalStats?: StringDictionary
|
|
17
|
+
postfixes?: StringDictionary, additionalStats?: StringDictionary, cliffIndexes?: Map<type.INDEX, type.INDEXES>,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -52,7 +52,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
|
|
|
52
52
|
options.isMutationCliffs ??= false;
|
|
53
53
|
options.isMostPotentResidues ??= false;
|
|
54
54
|
options.additionalStats ??= {};
|
|
55
|
-
if (!options.cliffStats || !options.isMutationCliffs) {
|
|
55
|
+
if (!options.cliffStats || !options.isMutationCliffs || !options.cliffIndexes) { // monomer position stats
|
|
56
56
|
const stats = options
|
|
57
57
|
.mpStats[options.monomerPosition.positionOrClusterType]![options.monomerPosition.monomerOrCluster];
|
|
58
58
|
if (!stats?.count)
|
|
@@ -61,7 +61,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
|
|
|
61
61
|
|
|
62
62
|
const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, activityCol.length);
|
|
63
63
|
mask.and(df.filter);
|
|
64
|
-
const hist = getActivityDistribution(getDistributionTable(activityCol, mask), true);
|
|
64
|
+
const hist = getActivityDistribution(getDistributionTable(activityCol, mask, df), true);
|
|
65
65
|
const tableMap = getStatsTableMap(stats);
|
|
66
66
|
if (options.fromViewer) {
|
|
67
67
|
tableMap['Mean difference'] = `${tableMap['Mean difference']}${options.isMostPotentResidues ? ' (size)' : ''}`;
|
|
@@ -79,7 +79,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
|
|
|
79
79
|
if (!options.fromViewer)
|
|
80
80
|
setTimeout(() => hist.props.legendVisibility = 'Never', 100); // cause rerendering
|
|
81
81
|
return distroStatsElem;
|
|
82
|
-
} else {
|
|
82
|
+
} else { // mutation cliffs
|
|
83
83
|
const stats = options.cliffStats?.get(options.monomerPosition.monomerOrCluster)
|
|
84
84
|
?.get(options.monomerPosition.positionOrClusterType)
|
|
85
85
|
;
|
|
@@ -87,12 +87,16 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
|
|
|
87
87
|
return null;
|
|
88
88
|
const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, activityCol.length);
|
|
89
89
|
mask.and(df.filter);
|
|
90
|
-
const hist = getActivityDistribution(
|
|
91
|
-
|
|
90
|
+
const hist = getActivityDistribution(
|
|
91
|
+
getMutationCliffsDistributionTable(activityCol, options.cliffIndexes, options.monomerPosition.monomerOrCluster),
|
|
92
|
+
true);
|
|
93
|
+
const countName = 'MP in cliffs count';
|
|
94
|
+
const tableMap = getStatsTableMap(stats, {countName: countName});
|
|
92
95
|
if (options.fromViewer) {
|
|
93
96
|
tableMap['Mean difference'] = `${tableMap['Mean difference']}${' (Color)'}`;
|
|
94
|
-
if (tableMap[
|
|
95
|
-
tableMap[
|
|
97
|
+
if (tableMap[countName])
|
|
98
|
+
tableMap[countName] = `${tableMap[countName]}${' (Size)'}`;
|
|
99
|
+
options.additionalStats!['Unique count'] = mask.trueCount.toString();
|
|
96
100
|
}
|
|
97
101
|
const aggregatedColMap = options.aggrColValues ?? getAggregatedColumnValues(df, columns, {mask: mask});
|
|
98
102
|
const resultMap = {...options.additionalStats, ...tableMap, ...aggregatedColMap};
|
package/src/utils/types.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {getGPUAdapterDescription} from '@datagrok-libraries/math/src/webGPU/getG
|
|
|
8
8
|
export type RawData = Int32Array | Uint32Array | Float32Array | Float64Array;
|
|
9
9
|
type MONOMER = string;
|
|
10
10
|
type POSITION = string;
|
|
11
|
-
type INDEX = number;
|
|
12
|
-
type INDEXES = number[] | UTypedArray;
|
|
11
|
+
export type INDEX = number;
|
|
12
|
+
export type INDEXES = number[] | UTypedArray;
|
|
13
13
|
export type UTypedArray = Uint8Array | Uint16Array | Uint32Array;
|
|
14
14
|
//Monomer: (Position: (index: indexList))
|
|
15
15
|
export type MutationCliffs = Map<MONOMER, Map<POSITION, Map<INDEX, INDEXES>>>;
|
|
@@ -761,7 +761,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
|
761
761
|
} else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
|
|
762
762
|
let viewer = distCache.get(currentRowIdx);
|
|
763
763
|
if (viewer === undefined) {
|
|
764
|
-
|
|
764
|
+
if (!activityCol.dataFrame)
|
|
765
|
+
DG.DataFrame.fromColumns([activityCol]);
|
|
766
|
+
const distributionDf = getDistributionTable(activityCol, clusterBitSet, activityCol.dataFrame);
|
|
765
767
|
viewer = distributionDf.plot.histogram({
|
|
766
768
|
filteringEnabled: false,
|
|
767
769
|
valueColumnName: activityCol.name,
|
|
@@ -1070,7 +1072,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
|
1070
1072
|
|
|
1071
1073
|
|
|
1072
1074
|
const mask = DG.BitSet.fromBytes(bitArray.buffer.buffer as ArrayBuffer, rowCount);
|
|
1073
|
-
|
|
1075
|
+
if (!activityCol.dataFrame)
|
|
1076
|
+
DG.DataFrame.fromColumns([activityCol]);
|
|
1077
|
+
const distributionTable = getDistributionTable(activityCol, mask, activityCol.dataFrame);
|
|
1074
1078
|
const hist = getActivityDistribution(distributionTable, true);
|
|
1075
1079
|
const tableMap = getStatsTableMap(stats);
|
|
1076
1080
|
const aggregatedColMap = getAggregatedColumnValues(this.dataFrame,
|
|
@@ -20,6 +20,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
20
20
|
public seriesColumnName: string;
|
|
21
21
|
public activityColumnName: string;
|
|
22
22
|
public position = 1;
|
|
23
|
+
public currentRowMutationsOnly: boolean = false;
|
|
23
24
|
public yAxisType: 'Linear' | 'Logarithmic' = 'Linear';
|
|
24
25
|
constructor() {
|
|
25
26
|
super();
|
|
@@ -28,6 +29,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
28
29
|
this.activityColumnName = this.column('activity', {columnTypeFilter: 'numerical', nullable: false});
|
|
29
30
|
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
31
|
this.yAxisType = this.string('yAxisType', 'Linear', {choices: ['Linear', 'Logarithmic'], description: 'Y-Axis scale type.', nullable: false, category: 'Data'}) as 'Linear' | 'Logarithmic';
|
|
32
|
+
this.currentRowMutationsOnly = this.bool('currentRowMutationsOnly', false, {nullable: false, defaultValue: false, description: 'When enabled, the viewer will show mutations related to the peptide in current row in the dataframe and selected position.', category: 'Data'});
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
onTableAttached(): void {
|
|
@@ -105,8 +107,11 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
105
107
|
mutationCliffs.cliffs.forEach((positionMap) => {
|
|
106
108
|
positionMap.forEach((mcData) => { // should be only one position
|
|
107
109
|
Array.from(mcData.entries()).forEach(([from, toIndexes]) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
const f = Number(from);
|
|
111
|
+
if (!this.currentRowMutationsOnly || this.dataFrame.currentRowIdx === f) {
|
|
112
|
+
uniqueIndexes.add(f);
|
|
113
|
+
toIndexes.forEach((toIdx) => uniqueIndexes.add(Number(toIdx)));
|
|
114
|
+
}
|
|
110
115
|
});
|
|
111
116
|
});
|
|
112
117
|
});
|
|
@@ -160,14 +165,31 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
160
165
|
}),
|
|
161
166
|
);
|
|
162
167
|
|
|
168
|
+
let currentRowFromLineChartFired = false;
|
|
169
|
+
// when clicking on point in line chart, set current row in main df
|
|
163
170
|
this._dfSubs.push(
|
|
164
171
|
df.onCurrentRowChanged.subscribe((_) => {
|
|
165
172
|
if (df.currentRowIdx >= 0) {
|
|
173
|
+
currentRowFromLineChartFired = true;
|
|
166
174
|
const originalIdx = indexesArray[df.currentRowIdx];
|
|
167
175
|
this.dataFrame.currentRowIdx = originalIdx;
|
|
168
176
|
}
|
|
169
177
|
}),
|
|
170
178
|
);
|
|
179
|
+
// if the viewer is set to follow current row, listen to it
|
|
180
|
+
if (this.currentRowMutationsOnly) {
|
|
181
|
+
this._dfSubs.push(
|
|
182
|
+
this.dataFrame.onCurrentRowChanged.subscribe((_) => {
|
|
183
|
+
if (currentRowFromLineChartFired) {
|
|
184
|
+
currentRowFromLineChartFired = false;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// recalculate viewer df
|
|
188
|
+
this.clearCache(false);
|
|
189
|
+
this.debouncedRender();
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
|
|
171
193
|
|
|
172
194
|
let firedFromViewer = false;
|
|
173
195
|
let firedFromTable = false;
|
|
@@ -272,7 +294,8 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
272
294
|
private async render() {
|
|
273
295
|
$(this.root).empty();
|
|
274
296
|
if (!this.dataFrame || !this.activityColumnName || !this.sequenceColumnName || !this.position) {
|
|
275
|
-
this.root
|
|
297
|
+
ui.setUpdateIndicator(this.root, false);
|
|
298
|
+
this.root.appendChild(noDataDiv('Please set Activity column, Sequence column and Position properties.'));
|
|
276
299
|
return;
|
|
277
300
|
}
|
|
278
301
|
|
|
@@ -290,32 +313,40 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
290
313
|
this.root.style.flexDirection = 'column';
|
|
291
314
|
|
|
292
315
|
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
316
|
ui.setUpdateIndicator(this.root, false);
|
|
317
|
-
|
|
317
|
+
if (df.rowCount === 0) {
|
|
318
|
+
if (this.currentRowMutationsOnly) {
|
|
319
|
+
if (this.dataFrame.currentRowIdx >= 0)
|
|
320
|
+
this.root.appendChild(noDataDiv('No mutations cliffs found for the current peptide at the selected position.'));
|
|
321
|
+
else
|
|
322
|
+
this.root.appendChild(noDataDiv('Please select a row in the main table to see mutation cliffs for the corresponding peptide at the selected position.'));
|
|
323
|
+
} else
|
|
324
|
+
this.root.appendChild(noDataDiv('No mutation cliffs found for the selected position.'));
|
|
325
|
+
} else {
|
|
326
|
+
this._lineChart = df.plot.line({
|
|
327
|
+
xColumnName: `Position ${this.position}`,
|
|
328
|
+
yColumnNames: [this.activityColumnName],
|
|
329
|
+
splitColumnNames: this.seriesColumnName ? [this.seriesColumnName] : [],
|
|
330
|
+
legendVisibility: this.seriesColumnName ? 'Always' : 'Never',
|
|
331
|
+
legendPosition: 'Right',
|
|
332
|
+
showXSelector: false,
|
|
333
|
+
showYSelector: true,
|
|
334
|
+
showSplitSelector: false,
|
|
335
|
+
xAxisLabelOrientation: 'Auto',
|
|
336
|
+
axisFont: 'normal normal 14px "Roboto"',
|
|
337
|
+
controlsFont: 'normal normal 14px "Roboto"',
|
|
338
|
+
} as Partial<DG.ILineChartSettings>) as DG.LineChartViewer;
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
this._lineChart.sub(this._lineChart.onPropertyValueChanged.subscribe((_e) => {
|
|
342
|
+
if (this._lineChart?.props?.yColumnNames && this._lineChart?.props?.yColumnNames?.[0] !== this.activityColumnName) {
|
|
343
|
+
const value = this._lineChart?.props?.yColumnNames?.[0];
|
|
344
|
+
setTimeout(() => this.getProperty('activityColumnName')!.set(this, value), 1);
|
|
345
|
+
}
|
|
346
|
+
}));
|
|
318
347
|
|
|
348
|
+
this.root.appendChild(this._lineChart.root);
|
|
349
|
+
}
|
|
319
350
|
const maxPosition = this.positionColumns.length + 1;
|
|
320
351
|
const positions = new Array<number>(maxPosition - 1).fill(0).map((_, i) => i + 1).map((v) => v.toString());
|
|
321
352
|
const positionInput = ui.input.choice('Position', {value: this.position.toString(), items: positions, nullable: false,
|
|
@@ -330,16 +361,18 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
330
361
|
positionInput.input.style.width = '40px';
|
|
331
362
|
this.root.appendChild(ui.divH([positionInput.root], {style: {justifyContent: 'center', marginTop: '4px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
|
|
332
363
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
364
|
+
if (df.rowCount > 0) {
|
|
365
|
+
const seriesColSelector = ui.input.column('Series', {table: this.dataFrame,
|
|
366
|
+
filter: (col: DG.Column) => {
|
|
367
|
+
return col.isCategorical && col.name !== this.sequenceColumnName;
|
|
368
|
+
}, tooltipText: 'Select column for series splitting.',
|
|
369
|
+
onValueChanged: (col: DG.Column | null) => {
|
|
370
|
+
const colName = col ? col.name : undefined;
|
|
339
371
|
this.getProperty('seriesColumnName')!.set(this, colName);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
372
|
+
}, value: this.seriesColumnName ? this.dataFrame.col(this.seriesColumnName)! : undefined,
|
|
373
|
+
});
|
|
374
|
+
this.root.prepend(ui.divH([seriesColSelector.root], {style: {justifyContent: 'flex-end', paddingBottom: '4px', padding: '8px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
|
|
375
|
+
}
|
|
343
376
|
}
|
|
344
377
|
|
|
345
378
|
private _debounceTimer: any = null;
|
|
@@ -350,8 +383,10 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
350
383
|
this._debounceTimer = setTimeout(() => this.render(), 300);
|
|
351
384
|
}
|
|
352
385
|
|
|
353
|
-
private clearCache() {
|
|
354
|
-
|
|
386
|
+
private clearCache(clearMutationCliffsData: boolean = true) {
|
|
387
|
+
// while following current row, makes no sense to clear mutation cliffs data.
|
|
388
|
+
if (clearMutationCliffsData)
|
|
389
|
+
this._mutationCliffsData = null;
|
|
355
390
|
this._innerDf = null;
|
|
356
391
|
this._dfSubs.forEach((s) => s.unsubscribe());
|
|
357
392
|
this._dfSubs = [];
|
|
@@ -368,11 +403,27 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
368
403
|
onPropertyChanged(property: DG.Property | null): void {
|
|
369
404
|
super.onPropertyChanged(property);
|
|
370
405
|
if (property?.name === 'activityColumnName' || property?.name === 'sequenceColumnName' || property?.name === 'position' || property?.name === 'seriesColumnName') {
|
|
371
|
-
|
|
406
|
+
const retainMutationCliffsData = property?.name === 'seriesColumnName' || property?.name === 'activityColumnName'; // these do not affect mutation cliffs calculation
|
|
407
|
+
this.clearCache(!retainMutationCliffsData);
|
|
408
|
+
if (property?.name === 'sequenceColumnName')
|
|
409
|
+
this._positionColumns = null;
|
|
410
|
+
|
|
372
411
|
this.debouncedRender();
|
|
373
|
-
} if (property?.name === 'yAxisType') {
|
|
412
|
+
} else if (property?.name === 'yAxisType') {
|
|
374
413
|
if (this._lineChart)
|
|
375
414
|
this._lineChart.props.yAxisType = this.yAxisType.toLowerCase() as DG.AxisType;
|
|
415
|
+
} else if (property?.name === 'currentRowMutationsOnly') {
|
|
416
|
+
// when this changes, we only clear the dataframe cache to reflect current row changes
|
|
417
|
+
this.clearCache(false);
|
|
418
|
+
this.debouncedRender();
|
|
376
419
|
}
|
|
377
420
|
}
|
|
378
421
|
}
|
|
422
|
+
|
|
423
|
+
function noDataDiv(message: string): HTMLDivElement {
|
|
424
|
+
const noDataDiv = ui.divText(message);
|
|
425
|
+
noDataDiv.style.fontSize = '14px';
|
|
426
|
+
noDataDiv.style.marginTop = '10px';
|
|
427
|
+
noDataDiv.style.textAlign = 'center';
|
|
428
|
+
return noDataDiv;
|
|
429
|
+
}
|
|
@@ -131,7 +131,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
|
|
|
131
131
|
}) as 'All' | 'Filtered';
|
|
132
132
|
|
|
133
133
|
this.activityColumnName = this.column(SAR_PROPERTIES.ACTIVITY,
|
|
134
|
-
{category: PROPERTY_CATEGORIES.GENERAL, nullable: false});
|
|
134
|
+
{category: PROPERTY_CATEGORIES.GENERAL, nullable: false, columnTypeFilter: 'numerical'});
|
|
135
135
|
this.activityScaling = this.string(SAR_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
|
|
136
136
|
{category: PROPERTY_CATEGORIES.GENERAL, choices: Object.values(C.SCALING_METHODS), nullable: false},
|
|
137
137
|
) as C.SCALING_METHODS;
|
|
@@ -555,10 +555,11 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
|
|
|
555
555
|
if (isApplicableDataframe(this.dataFrame)) {
|
|
556
556
|
this.getProperty(`${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`)
|
|
557
557
|
?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
|
|
558
|
+
const potentialActivityColumn = wu(this.dataFrame.columns.numerical).find((col) => col.name.toLowerCase().includes('activity'))?.name;
|
|
558
559
|
this.getProperty(`${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
|
|
559
|
-
?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
560
|
+
?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
|
|
560
561
|
this.getProperty(`${SAR_PROPERTIES.VALUE_INVARIANT_MAP}${COLUMN_NAME}`)
|
|
561
|
-
?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
562
|
+
?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
|
|
562
563
|
if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName)
|
|
563
564
|
this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
|
|
564
565
|
this.subs.push(grok.events.onContextMenu.subscribe((a: DG.EventData) => {
|
|
@@ -934,7 +935,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
934
935
|
if (this.valueColumnName && this.valueAggregation && this.valueAggregation !== DG.AGG.VALUE_COUNT && this.valueAggregation !== DG.AGG.TOTAL_COUNT)
|
|
935
936
|
columnEntries.unshift([this.valueColumnName, this.valueAggregation as DG.AGG]);
|
|
936
937
|
} else {
|
|
937
|
-
// in
|
|
938
|
+
// in mutation cliffs, show pairs count along with unique sequences count
|
|
938
939
|
const pairs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType);
|
|
939
940
|
if (pairs) {
|
|
940
941
|
let pairsCount = 0;
|
|
@@ -947,6 +948,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
947
948
|
fromViewer: true,
|
|
948
949
|
isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
|
|
949
950
|
mpStats: this.monomerPositionStats, cliffStats: this.cliffStats?.stats ?? undefined, postfixes, additionalStats,
|
|
951
|
+
cliffIndexes: this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType),
|
|
950
952
|
});
|
|
951
953
|
});
|
|
952
954
|
grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
|
|
@@ -144,7 +144,7 @@ export function getStatsTableMap(stats: StatsItem,
|
|
|
144
144
|
*/
|
|
145
145
|
function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: DistributionItemOptions,
|
|
146
146
|
labelMap: DistributionLabelMap = {}): HTMLDivElement {
|
|
147
|
-
const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection));
|
|
147
|
+
const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection, table));
|
|
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);
|
|
@@ -3,12 +3,14 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
import * as C from '../utils/constants';
|
|
5
5
|
import * as type from '../utils/types';
|
|
6
|
-
import {addExpandIconGen, getSeparator, setGridProps} from '../utils/misc';
|
|
6
|
+
import {addExpandIconGen, getDistributionPanel, getMutationCliffsDistributionTable, getSeparator, setGridProps} from '../utils/misc';
|
|
7
7
|
import {renderCellSelection} from '../utils/cell-renderer';
|
|
8
8
|
import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
|
|
9
|
+
import {getActivityDistribution, getStatsTableMap} from './distribution';
|
|
10
|
+
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
9
11
|
|
|
10
|
-
export type
|
|
11
|
-
mutationCliffs: type.MutationCliffs, mutationCliffsSelection: type.Selection, sequenceColumnName: string,
|
|
12
|
+
export type MutationCliffsWidgetOptions = {
|
|
13
|
+
mutationCliffs: type.MutationCliffs, mutationCliffStats: type.MutationCliffStats | null, mutationCliffsSelection: type.Selection, sequenceColumnName: string,
|
|
12
14
|
positionColumns: DG.Column<string>[], gridColumns: DG.GridColumnList, activityCol: DG.Column<number>,
|
|
13
15
|
};
|
|
14
16
|
|
|
@@ -21,7 +23,7 @@ export type MutationCliffsOptions = {
|
|
|
21
23
|
* @return - mutation cliffs widget.
|
|
22
24
|
*/
|
|
23
25
|
export function mutationCliffsWidget(
|
|
24
|
-
table: DG.DataFrame, options:
|
|
26
|
+
table: DG.DataFrame, options: MutationCliffsWidgetOptions, allowExpand = true,
|
|
25
27
|
): DG.Widget {
|
|
26
28
|
//addExpandIcon(pairsGrid);
|
|
27
29
|
//addExpandIcon(uniqueSequencesGrid);
|
|
@@ -32,6 +34,37 @@ export function mutationCliffsWidget(
|
|
|
32
34
|
const comboGrids = [pairsGrid, uniqueSequencesGrid];
|
|
33
35
|
const widgetRoot = ui.divV([aminoToInput.root, ...comboGrids.map((grid) => grid.root)],
|
|
34
36
|
{style: {width: '100%'}});
|
|
37
|
+
// add mutation cliffs distribution histogram
|
|
38
|
+
const isSingleMutationPosition = Object.values(options.mutationCliffsSelection).filter((monomers) => monomers.length > 0).length === 1;
|
|
39
|
+
if (isSingleMutationPosition) {
|
|
40
|
+
const position = Object.keys(options.mutationCliffsSelection).find((pos) => options.mutationCliffsSelection[pos].length > 0)!;
|
|
41
|
+
const monomers = options.mutationCliffsSelection[position];
|
|
42
|
+
if (monomers.length === 1) {
|
|
43
|
+
const monomerName = monomers[0];
|
|
44
|
+
const cliffs = options.mutationCliffs?.get(monomerName)?.get(position);
|
|
45
|
+
if (cliffs && cliffs.size > 0) {
|
|
46
|
+
const distributionTable = getMutationCliffsDistributionTable(options.activityCol, cliffs, monomerName);
|
|
47
|
+
const hist = getActivityDistribution(distributionTable, false);
|
|
48
|
+
// quick stats
|
|
49
|
+
const stats = options.mutationCliffStats?.stats?.get(monomerName)?.get(position);
|
|
50
|
+
const tableMap = {} as StringDictionary;
|
|
51
|
+
let pairsCount = 0;
|
|
52
|
+
for (const indexArray of cliffs.values())
|
|
53
|
+
pairsCount += indexArray.length;
|
|
54
|
+
tableMap['Pairs count'] = pairsCount.toString();
|
|
55
|
+
if (stats) {
|
|
56
|
+
tableMap['Unique Count'] = stats.mask.trueCount().toString();
|
|
57
|
+
Object.assign(tableMap, getStatsTableMap(stats, {countName: 'MP in cliffs count'}));
|
|
58
|
+
}
|
|
59
|
+
const distributionPanel = getDistributionPanel(hist, tableMap); // no need to show stats here
|
|
60
|
+
hist.root.style.maxHeight = '100px';
|
|
61
|
+
distributionPanel.style.marginTop = '10px';
|
|
62
|
+
widgetRoot.appendChild(distributionPanel);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
35
68
|
if (allowExpand) {
|
|
36
69
|
addExpandIconGen('Mutation Cliffs pairs', aminoToInput.root, widgetRoot,
|
|
37
70
|
() => {
|
|
@@ -74,7 +107,7 @@ export function mutationCliffsWidget(
|
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
|
|
77
|
-
function cliffsPairsWidgetParts(table: DG.DataFrame, options:
|
|
110
|
+
function cliffsPairsWidgetParts(table: DG.DataFrame, options: MutationCliffsWidgetOptions):
|
|
78
111
|
{pairsGrid: DG.Grid, uniqueSequencesGrid: DG.Grid, aminoToInput: DG.InputBase<string>} | null {
|
|
79
112
|
const filteredIndexes = table.filter.getSelectedIndexes();
|
|
80
113
|
const positions = Object.keys(options.mutationCliffsSelection);
|