@datagrok/peptides 1.26.2 → 1.27.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 +4 -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/styles.css +11 -0
- package/src/tests/widgets.ts +1 -0
- package/src/utils/algorithms.ts +16 -4
- package/src/utils/misc.ts +53 -9
- package/src/utils/tooltips.ts +13 -9
- package/src/utils/types.ts +2 -2
- package/src/viewers/logo-summary.ts +9 -5
- package/src/viewers/mutation-cliffs-viewer.ts +3 -3
- package/src/viewers/sar-viewer.ts +28 -23
- package/src/widgets/distribution.ts +1 -1
- package/src/widgets/mutation-cliffs.ts +38 -5
- package/test-console-output-1.log +72 -100
- 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/styles.css
CHANGED
|
@@ -60,3 +60,14 @@
|
|
|
60
60
|
left: 5px;
|
|
61
61
|
top: 1px;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
.panel-base:has(.peptides-viewer-show-title:only-child) > .panel-titlebar > .panel-titlebar-text {
|
|
65
|
+
visibility: visible;
|
|
66
|
+
display: block;
|
|
67
|
+
text-align: center;
|
|
68
|
+
margin-left: 36px;
|
|
69
|
+
padding: 0;
|
|
70
|
+
width: 100%;
|
|
71
|
+
font-size: 14px !important;
|
|
72
|
+
color: #4d5261 !important;
|
|
73
|
+
}
|
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.
|
|
@@ -466,3 +500,13 @@ export function debounce<T extends Array<any>, K>(f: (...args: T) => Promise<K>,
|
|
|
466
500
|
export function isInDemo(): boolean {
|
|
467
501
|
return 'isInDemo' in grok.shell && !!grok.shell.isInDemo;
|
|
468
502
|
}
|
|
503
|
+
|
|
504
|
+
export function dartLike<T extends any>(obj: T) {
|
|
505
|
+
return {
|
|
506
|
+
set: function<K extends keyof T>(key: K, value: T[K]) {
|
|
507
|
+
(obj as any)[key] = value;
|
|
508
|
+
return this;
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
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>>>;
|
|
@@ -76,7 +76,6 @@ export interface ILogoSummaryTable {
|
|
|
76
76
|
|
|
77
77
|
/** LogoSummaryTable viewer shows per cluster information. */
|
|
78
78
|
export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
79
|
-
_titleHost = ui.divText(VIEWER_TYPE.LOGO_SUMMARY_TABLE, {id: 'pep-viewer-title'});
|
|
80
79
|
sequenceColumnName: string;
|
|
81
80
|
clustersColumnName: string;
|
|
82
81
|
webLogoMode: string;
|
|
@@ -98,6 +97,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
|
98
97
|
/** Creates LogoSummaryTable properties. */
|
|
99
98
|
constructor() {
|
|
100
99
|
super();
|
|
100
|
+
this.root.classList.add('peptides-viewer-show-title');
|
|
101
101
|
|
|
102
102
|
this.sequenceColumnName = this.column(LST_PROPERTIES.SEQUENCE,
|
|
103
103
|
{
|
|
@@ -365,7 +365,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
|
365
365
|
if (!this.logoSummaryTable.filter.anyTrue) {
|
|
366
366
|
const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
|
|
367
367
|
'Please, lower the threshold in viewer proeperties to include clusters');
|
|
368
|
-
this.root.appendChild(ui.divV([
|
|
368
|
+
this.root.appendChild(ui.divV([emptyDf]));
|
|
369
369
|
return;
|
|
370
370
|
}
|
|
371
371
|
const expand = ui.iconFA('expand-alt', () => {
|
|
@@ -380,7 +380,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
|
|
|
380
380
|
this.viewerGrid.root.style.height = 'auto';
|
|
381
381
|
this.viewerGrid.root.style.overflow = 'hidden';
|
|
382
382
|
this.root.appendChild(ui.divV([
|
|
383
|
-
ui.divH([
|
|
383
|
+
ui.divH([expand], {
|
|
384
384
|
style: {
|
|
385
385
|
alignSelf: 'center',
|
|
386
386
|
lineHeight: 'normal',
|
|
@@ -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,
|
|
@@ -30,6 +30,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
30
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'});
|
|
31
31
|
this.yAxisType = this.string('yAxisType', 'Linear', {choices: ['Linear', 'Logarithmic'], description: 'Y-Axis scale type.', nullable: false, category: 'Data'}) as 'Linear' | 'Logarithmic';
|
|
32
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'});
|
|
33
|
+
this.root.classList.add('peptides-viewer-show-title');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
onTableAttached(): void {
|
|
@@ -320,8 +321,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
|
|
|
320
321
|
this.root.appendChild(noDataDiv('No mutations cliffs found for the current peptide at the selected position.'));
|
|
321
322
|
else
|
|
322
323
|
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
|
-
}
|
|
324
|
-
else
|
|
324
|
+
} else
|
|
325
325
|
this.root.appendChild(noDataDiv('No mutation cliffs found for the selected position.'));
|
|
326
326
|
} else {
|
|
327
327
|
this._lineChart = df.plot.line({
|
|
@@ -427,4 +427,4 @@ function noDataDiv(message: string): HTMLDivElement {
|
|
|
427
427
|
noDataDiv.style.marginTop = '10px';
|
|
428
428
|
noDataDiv.style.textAlign = 'center';
|
|
429
429
|
return noDataDiv;
|
|
430
|
-
}
|
|
430
|
+
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import {showTooltip} from '../utils/tooltips';
|
|
24
24
|
import {calculateCliffsStatistics, calculateMonomerPositionStatistics, findMutations, MutationCliffsOptions} from '../utils/algorithms';
|
|
25
25
|
import {
|
|
26
|
+
dartLike,
|
|
26
27
|
debounce,
|
|
27
28
|
extractColInfo,
|
|
28
29
|
getTotalAggColumns,
|
|
@@ -41,7 +42,6 @@ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/types/monomer-lib
|
|
|
41
42
|
import {PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
42
43
|
import {PeptideUtils} from '../peptideUtils';
|
|
43
44
|
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
44
|
-
import BitArray from '@datagrok-libraries/utils/src/bit-array';
|
|
45
45
|
|
|
46
46
|
export enum SELECTION_MODE {
|
|
47
47
|
MUTATION_CLIFFS = 'Mutation Cliffs',
|
|
@@ -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;
|
|
@@ -177,6 +177,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
|
|
|
177
177
|
// this.targetCategoryInput.root.style.display = 'none';
|
|
178
178
|
// this.targetCategoryInput.root.style.maxWidth = '50%';
|
|
179
179
|
// this.targetCategoryInput.root.style.marginLeft = '8px'
|
|
180
|
+
this.root.classList.add('peptides-viewer-show-title');
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
_viewerGrid: DG.Grid | null = null;
|
|
@@ -555,10 +556,11 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
|
|
|
555
556
|
if (isApplicableDataframe(this.dataFrame)) {
|
|
556
557
|
this.getProperty(`${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`)
|
|
557
558
|
?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
|
|
559
|
+
const potentialActivityColumn = wu(this.dataFrame.columns.numerical).find((col) => col.name.toLowerCase().includes('activity'))?.name;
|
|
558
560
|
this.getProperty(`${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
|
|
559
|
-
?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
561
|
+
?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
|
|
560
562
|
this.getProperty(`${SAR_PROPERTIES.VALUE_INVARIANT_MAP}${COLUMN_NAME}`)
|
|
561
|
-
?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
563
|
+
?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
|
|
562
564
|
if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName)
|
|
563
565
|
this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
|
|
564
566
|
this.subs.push(grok.events.onContextMenu.subscribe((a: DG.EventData) => {
|
|
@@ -677,7 +679,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
677
679
|
this.customColorRange = this.bool(MONOMER_POSITION_PROPERTIES.CUSTOM_COLOR_RANGE, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
|
|
678
680
|
this.minColorValue = this.float(MONOMER_POSITION_PROPERTIES.MIN_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
|
|
679
681
|
this.maxColorValue = this.float(MONOMER_POSITION_PROPERTIES.MAX_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
|
|
680
|
-
this.showFilterControls = this.bool(MONOMER_POSITION_PROPERTIES.SHOW_FILTER_CONTROLS, true, {category: PROPERTY_CATEGORIES.GENERAL, description: 'Show monomer search and target controls'});
|
|
682
|
+
this.showFilterControls = this.bool(MONOMER_POSITION_PROPERTIES.SHOW_FILTER_CONTROLS, true, {category: PROPERTY_CATEGORIES.GENERAL, description: 'Show monomer search and target controls', userEditable: false}); // Old stuff. Not used anymore
|
|
681
683
|
this.monomerSearchInput = ui.input.string('Search', {
|
|
682
684
|
value: '', nullable: true, placeholder: 'Search monomer', tooltipText: 'Search for monomer by symbol. For multiple monomers use comma as a separator.',
|
|
683
685
|
onValueChanged: () => {
|
|
@@ -831,7 +833,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
831
833
|
let maxColorVal = -9999999;
|
|
832
834
|
const filter = (this.dataSource === 'Filtered' && this.dataFrame.filter.anyFalse) ?
|
|
833
835
|
this.dataFrame.filter : null;
|
|
834
|
-
const isTarget = filter == null ? (
|
|
836
|
+
const isTarget = filter == null ? (_index: number) => true : (index: number) => filter.get(index);
|
|
835
837
|
for (const pCol of this.positionColumns) {
|
|
836
838
|
pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = {};
|
|
837
839
|
const colorCache = pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE];
|
|
@@ -934,7 +936,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
934
936
|
if (this.valueColumnName && this.valueAggregation && this.valueAggregation !== DG.AGG.VALUE_COUNT && this.valueAggregation !== DG.AGG.TOTAL_COUNT)
|
|
935
937
|
columnEntries.unshift([this.valueColumnName, this.valueAggregation as DG.AGG]);
|
|
936
938
|
} else {
|
|
937
|
-
// in
|
|
939
|
+
// in mutation cliffs, show pairs count along with unique sequences count
|
|
938
940
|
const pairs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType);
|
|
939
941
|
if (pairs) {
|
|
940
942
|
let pairsCount = 0;
|
|
@@ -947,6 +949,7 @@ export class MonomerPosition extends SARViewer {
|
|
|
947
949
|
fromViewer: true,
|
|
948
950
|
isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
|
|
949
951
|
mpStats: this.monomerPositionStats, cliffStats: this.cliffStats?.stats ?? undefined, postfixes, additionalStats,
|
|
952
|
+
cliffIndexes: this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType),
|
|
950
953
|
});
|
|
951
954
|
});
|
|
952
955
|
grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
|
|
@@ -1102,6 +1105,8 @@ export class MonomerPosition extends SARViewer {
|
|
|
1102
1105
|
};
|
|
1103
1106
|
}
|
|
1104
1107
|
|
|
1108
|
+
private _showSearchInput = false;
|
|
1109
|
+
|
|
1105
1110
|
/** Renders the MonomerPosition viewer body. */
|
|
1106
1111
|
render(): void {
|
|
1107
1112
|
$(this.root).empty();
|
|
@@ -1148,20 +1153,20 @@ export class MonomerPosition extends SARViewer {
|
|
|
1148
1153
|
}
|
|
1149
1154
|
const viewerRoot = this.viewerGrid.root;
|
|
1150
1155
|
viewerRoot.style.width = 'auto';
|
|
1151
|
-
//
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
const
|
|
1156
|
+
// search icon
|
|
1157
|
+
const searchIcon = ui.icons.search(() => {
|
|
1158
|
+
this._showSearchInput = !this._showSearchInput;
|
|
1159
|
+
this.monomerSearchInput.input.style.display = this._showSearchInput ? 'block' : 'none';
|
|
1160
|
+
}, 'Toggle monomer search input visibility');
|
|
1161
|
+
|
|
1162
|
+
this.monomerSearchInput.input.style.display = this._showSearchInput ? 'block' : 'none';
|
|
1163
|
+
$(searchIcon).addClass('pep-help-icon');
|
|
1164
|
+
dartLike(searchIcon.style).set('top', '3px').set('fontSize', '14px');
|
|
1165
|
+
|
|
1166
|
+
const filtersHost = ui.divH([this.monomerSearchInput.input], // plural because might expand in future
|
|
1162
1167
|
{style: {alignSelf: 'center', justifyContent: 'center', width: '100%', flexWrap: 'wrap'}});
|
|
1163
|
-
targetInputsHost.style.display = this.showFilterControls ? 'flex' : 'none';
|
|
1164
|
-
const header = ui.divH([
|
|
1168
|
+
// targetInputsHost.style.display = this.showFilterControls ? 'flex' : 'none';
|
|
1169
|
+
const header = ui.divH([searchIcon, switchHost, filtersHost], {style: {alignSelf: 'center', lineHeight: 'normal', flexDirection: 'column', width: '100%'}});
|
|
1165
1170
|
this.root.appendChild(ui.divV([header, viewerRoot]));
|
|
1166
1171
|
this.viewerGrid?.invalidate();
|
|
1167
1172
|
this.monomerSearchInput.fireChanged();
|
|
@@ -1468,11 +1473,11 @@ export class MostPotentResidues extends SARViewer {
|
|
|
1468
1473
|
this.root.appendChild(ui.divText('Please, select a sequence and activity columns in the viewer properties'));
|
|
1469
1474
|
return;
|
|
1470
1475
|
}
|
|
1471
|
-
|
|
1476
|
+
|
|
1472
1477
|
const viewerRoot = this.viewerGrid.root;
|
|
1473
1478
|
viewerRoot.style.width = 'auto';
|
|
1474
|
-
|
|
1475
|
-
this.root.appendChild(ui.divV([
|
|
1479
|
+
|
|
1480
|
+
this.root.appendChild(ui.divV([viewerRoot]));
|
|
1476
1481
|
this.viewerGrid?.invalidate();
|
|
1477
1482
|
}
|
|
1478
1483
|
}
|
|
@@ -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);
|