@datagrok/peptides 1.16.0 → 1.17.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/.eslintrc.json +17 -6
- package/CHANGELOG.md +33 -8
- package/README.md +12 -7
- package/dist/196.js +2 -3
- package/dist/23.js +2 -0
- package/dist/282.js +2 -0
- package/dist/361.js +2 -2
- package/dist/40.js +2 -0
- package/dist/436.js +2 -2
- package/dist/65.js +2 -0
- package/dist/704.js +2 -0
- package/dist/package-test.js +2 -3
- package/dist/package.js +2 -3
- package/package.json +13 -13
- package/setup-unlink-clean.cmd +6 -0
- package/setup.cmd +2 -2
- package/src/demo/fasta.ts +8 -2
- package/src/model.ts +857 -560
- package/src/package-test.ts +1 -3
- package/src/package.ts +28 -50
- package/src/tests/benchmarks.ts +31 -11
- package/src/tests/core.ts +11 -6
- package/src/tests/misc.ts +6 -6
- package/src/tests/model.ts +80 -45
- package/src/tests/table-view.ts +49 -39
- package/src/tests/utils.ts +0 -76
- package/src/tests/viewers.ts +30 -12
- package/src/tests/widgets.ts +30 -11
- package/src/utils/algorithms.ts +115 -38
- package/src/utils/cell-renderer.ts +217 -96
- package/src/utils/constants.ts +37 -7
- package/src/utils/misc.ts +285 -30
- package/src/utils/parallel-mutation-cliffs.ts +18 -15
- package/src/utils/statistics.ts +70 -14
- package/src/utils/tooltips.ts +46 -25
- package/src/utils/types.ts +29 -26
- package/src/utils/worker-creator.ts +5 -0
- package/src/viewers/logo-summary.ts +597 -135
- package/src/viewers/sar-viewer.ts +946 -249
- package/src/widgets/distribution.ts +291 -196
- package/src/widgets/manual-alignment.ts +18 -11
- package/src/widgets/mutation-cliffs.ts +45 -21
- package/src/widgets/peptides.ts +86 -91
- package/src/widgets/selection.ts +56 -22
- package/src/widgets/settings.ts +94 -44
- package/src/workers/dimensionality-reducer.ts +5 -6
- package/src/workers/mutation-cliffs-worker.ts +3 -16
- package/dist/196.js.LICENSE.txt +0 -51
- package/dist/209.js +0 -2
- package/dist/381.js +0 -2
- package/dist/694.js +0 -2
- package/dist/831.js +0 -2
- package/dist/868.js +0 -2
- package/dist/package-test.js.LICENSE.txt +0 -51
- package/dist/package.js.LICENSE.txt +0 -51
- package/src/tests/peptide-space-test.ts +0 -48
- package/src/tests/test-data.ts +0 -649
- package/src/utils/molecular-measure.ts +0 -174
- package/src/utils/peptide-similarity-space.ts +0 -216
- package/src/viewers/peptide-space-viewer.ts +0 -150
|
@@ -4,297 +4,888 @@ import * as DG from 'datagrok-api/dg';
|
|
|
4
4
|
|
|
5
5
|
import $ from 'cash-dom';
|
|
6
6
|
import * as C from '../utils/constants';
|
|
7
|
+
import {COLUMN_NAME} from '../utils/constants';
|
|
7
8
|
import * as CR from '../utils/cell-renderer';
|
|
8
9
|
import {PeptidesModel, VIEWER_TYPE} from '../model';
|
|
9
10
|
import wu from 'wu';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
11
|
+
import * as type from '../utils/types';
|
|
12
|
+
import {MutationCliffs, PeptidesSettings, SelectionItem} from '../utils/types';
|
|
13
|
+
import {
|
|
14
|
+
AggregationColumns,
|
|
15
|
+
getAggregatedColName,
|
|
16
|
+
getAggregatedColumnValuesFromDf,
|
|
17
|
+
getAggregatedValue,
|
|
18
|
+
MonomerPositionStats,
|
|
19
|
+
PositionStats,
|
|
20
|
+
StatsItem,
|
|
21
|
+
} from '../utils/statistics';
|
|
12
22
|
import {_package} from '../package';
|
|
13
23
|
import {showTooltip} from '../utils/tooltips';
|
|
24
|
+
import {calculateMonomerPositionStatistics, findMutations, MutationCliffsOptions} from '../utils/algorithms';
|
|
25
|
+
import {
|
|
26
|
+
extractColInfo,
|
|
27
|
+
getTotalAggColumns,
|
|
28
|
+
highlightMonomerPosition,
|
|
29
|
+
initSelection,
|
|
30
|
+
isApplicableDataframe,
|
|
31
|
+
isSelectionEmpty,
|
|
32
|
+
modifySelection,
|
|
33
|
+
scaleActivity,
|
|
34
|
+
} from '../utils/misc';
|
|
35
|
+
import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
|
|
36
|
+
import {LogoSummaryTable} from './logo-summary';
|
|
37
|
+
import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
38
|
+
import {ALPHABET} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
14
39
|
|
|
15
40
|
export enum SELECTION_MODE {
|
|
16
41
|
MUTATION_CLIFFS = 'Mutation Cliffs',
|
|
17
42
|
INVARIANT_MAP = 'Invariant Map',
|
|
18
43
|
}
|
|
19
44
|
|
|
20
|
-
export enum
|
|
21
|
-
|
|
22
|
-
|
|
45
|
+
export enum SAR_PROPERTIES {
|
|
46
|
+
SEQUENCE = 'sequence',
|
|
47
|
+
ACTIVITY = 'activity',
|
|
48
|
+
ACTIVITY_SCALING = 'activityScaling',
|
|
23
49
|
TARGET = 'target',
|
|
24
|
-
|
|
50
|
+
TARGET_CATEGORY = 'targetCategory',
|
|
51
|
+
MIN_ACTIVITY_DELTA = 'minActivityDelta',
|
|
52
|
+
MAX_MUTATIONS = 'maxMutations',
|
|
53
|
+
COLUMNS = 'columns',
|
|
54
|
+
AGGREGATION = 'aggregation',
|
|
55
|
+
}
|
|
25
56
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
_model!: PeptidesModel;
|
|
31
|
-
color: string;
|
|
32
|
-
aggregation: string;
|
|
33
|
-
target: string;
|
|
34
|
-
keyPressed: boolean = false;
|
|
35
|
-
currentGridCell: DG.GridCell | null = null;
|
|
57
|
+
export enum MONOMER_POSITION_PROPERTIES {
|
|
58
|
+
COLOR = 'color',
|
|
59
|
+
COLOR_AGGREGATION = 'colorAggregation',
|
|
60
|
+
}
|
|
36
61
|
|
|
37
|
-
|
|
62
|
+
export enum PROPERTY_CATEGORIES {
|
|
63
|
+
GENERAL = 'General',
|
|
64
|
+
INVARIANT_MAP = 'Invariant Map',
|
|
65
|
+
MUTATION_CLIFFS = 'Mutation Cliffs',
|
|
66
|
+
AGGREGATION = 'Aggregation',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const MUTATION_CLIFFS_CELL_WIDTH = 40;
|
|
70
|
+
const AAR_CELL_WIDTH = 30;
|
|
71
|
+
|
|
72
|
+
export interface ISARViewer {
|
|
73
|
+
sequenceColumnName: string;
|
|
74
|
+
activityColumnName: string;
|
|
75
|
+
activityScaling: C.SCALING_METHODS;
|
|
76
|
+
minActivityDelta: number;
|
|
77
|
+
maxMutations: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Abstract class for MonomerPosition and MostPotentResidues viewers. */
|
|
81
|
+
export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
|
|
82
|
+
keyPressed: boolean = false;
|
|
83
|
+
sequenceColumnName: string;
|
|
84
|
+
activityColumnName: string;
|
|
85
|
+
activityScaling: C.SCALING_METHODS;
|
|
86
|
+
columns: string[];
|
|
87
|
+
aggregation: string;
|
|
88
|
+
targetColumnName: string;
|
|
89
|
+
targetCategory: string;
|
|
90
|
+
minActivityDelta: number;
|
|
91
|
+
maxMutations: number;
|
|
92
|
+
_scaledActivityColumn: DG.Column | null = null;
|
|
93
|
+
doRender: boolean = true;
|
|
94
|
+
|
|
95
|
+
/** Sets common properties for inheritor viewers. */
|
|
96
|
+
protected constructor() {
|
|
38
97
|
super();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
{category:
|
|
46
|
-
|
|
47
|
-
|
|
98
|
+
// General properties
|
|
99
|
+
this.sequenceColumnName = this.column(SAR_PROPERTIES.SEQUENCE,
|
|
100
|
+
{category: PROPERTY_CATEGORIES.GENERAL, semType: DG.SEMTYPE.MACROMOLECULE, nullable: false});
|
|
101
|
+
this.activityColumnName = this.column(SAR_PROPERTIES.ACTIVITY,
|
|
102
|
+
{category: PROPERTY_CATEGORIES.GENERAL, nullable: false});
|
|
103
|
+
this.activityScaling = this.string(SAR_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
|
|
104
|
+
{category: PROPERTY_CATEGORIES.GENERAL, choices: Object.values(C.SCALING_METHODS), nullable: false},
|
|
105
|
+
) as C.SCALING_METHODS;
|
|
106
|
+
|
|
107
|
+
// Mutation Cliffs properties
|
|
108
|
+
this.targetColumnName = this.column(SAR_PROPERTIES.TARGET, {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS});
|
|
109
|
+
this.targetCategory = this.string(SAR_PROPERTIES.TARGET_CATEGORY, null,
|
|
110
|
+
{category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, choices: []});
|
|
111
|
+
this.minActivityDelta = this.float(SAR_PROPERTIES.MIN_ACTIVITY_DELTA, 0,
|
|
112
|
+
{category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 0, max: 100});
|
|
113
|
+
this.maxMutations = this.int(SAR_PROPERTIES.MAX_MUTATIONS, 1,
|
|
114
|
+
{category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 1, max: 50});
|
|
115
|
+
|
|
116
|
+
this.columns = this.columnList(SAR_PROPERTIES.COLUMNS, [], {category: PROPERTY_CATEGORIES.AGGREGATION});
|
|
117
|
+
this.aggregation = this.string(SAR_PROPERTIES.AGGREGATION, DG.AGG.AVG,
|
|
118
|
+
{category: PROPERTY_CATEGORIES.AGGREGATION, choices: C.AGGREGATION_TYPES});
|
|
48
119
|
}
|
|
49
120
|
|
|
50
|
-
|
|
121
|
+
_viewerGrid: DG.Grid | null = null;
|
|
51
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Returns SARViewer grid. Creates a new one if it is null.
|
|
125
|
+
* @return - SARViewer grid.
|
|
126
|
+
*/
|
|
52
127
|
get viewerGrid(): DG.Grid {
|
|
53
|
-
|
|
54
|
-
this.createMonomerPositionGrid();
|
|
128
|
+
this._viewerGrid ??= this.createViewerGrid();
|
|
55
129
|
return this._viewerGrid;
|
|
56
130
|
}
|
|
57
|
-
|
|
58
|
-
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns sequence column alphabet.
|
|
134
|
+
* @return - sequence column alphabet.
|
|
135
|
+
*/
|
|
136
|
+
get alphabet(): string {
|
|
137
|
+
const col = this.dataFrame.getCol(this.sequenceColumnName);
|
|
138
|
+
return col.getTag(bioTAGS.alphabet) ?? ALPHABET.UN;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Returns PeptidesModel instance that belongs to the attached dataframe.
|
|
143
|
+
* @return - PeptidesModel instance.
|
|
144
|
+
*/
|
|
145
|
+
get model(): PeptidesModel {
|
|
146
|
+
return PeptidesModel.getInstance(this.dataFrame);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_positionColumns: DG.Column<string>[] | null = null;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns position columns. If position columns are not attached to the viewer, it tries to get them from
|
|
153
|
+
* other viewers or analysis (given that relevant parameters are the same), or creates own position columns.
|
|
154
|
+
* @return - position columns.
|
|
155
|
+
*/
|
|
156
|
+
get positionColumns(): DG.Column<string>[] {
|
|
157
|
+
if (this._positionColumns != null) {
|
|
158
|
+
return this._positionColumns;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
const getSharedPositionColumns = (viewerType: VIEWER_TYPE): DG.Column<string>[] | null => {
|
|
163
|
+
const viewer = this.model.findViewer(viewerType) as SARViewer | LogoSummaryTable | null;
|
|
164
|
+
if (this.sequenceColumnName === viewer?.sequenceColumnName) {
|
|
165
|
+
return viewer._positionColumns;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (this.model.positionColumns != null && this.sequenceColumnName === this.model.settings?.sequenceColumnName) {
|
|
173
|
+
this._positionColumns = this.model.positionColumns;
|
|
174
|
+
} else if (this instanceof MonomerPosition) {
|
|
175
|
+
this._positionColumns = getSharedPositionColumns(VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
176
|
+
} else if (this instanceof MostPotentResidues) {
|
|
177
|
+
this._positionColumns = getSharedPositionColumns(VIEWER_TYPE.MONOMER_POSITION);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
this._positionColumns ??= getSharedPositionColumns(VIEWER_TYPE.LOGO_SUMMARY_TABLE) ??
|
|
182
|
+
splitAlignedSequences(this.dataFrame.getCol(this.sequenceColumnName)).columns.toList();
|
|
183
|
+
return this._positionColumns!;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_monomerPositionStats: MonomerPositionStats | null = null;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Gets monomer-position statistics. If monomer-position statistics are not attached to the viewer, it tries to get
|
|
190
|
+
* them from other viewers or analysis (given that relevant parameters are the same), or calculates own statistics.
|
|
191
|
+
* @return - monomer-position statistics.
|
|
192
|
+
*/
|
|
193
|
+
get monomerPositionStats(): MonomerPositionStats {
|
|
194
|
+
if (this._monomerPositionStats != null) {
|
|
195
|
+
return this._monomerPositionStats;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
const isMonomerPositionStatsEqual = (other: SARViewer | PeptidesSettings | null): boolean =>
|
|
200
|
+
this.sequenceColumnName === other?.sequenceColumnName &&
|
|
201
|
+
this.activityColumnName === other?.activityColumnName &&
|
|
202
|
+
this.activityScaling === other?.activityScaling;
|
|
203
|
+
|
|
204
|
+
const getSharedStats = (viewerType: VIEWER_TYPE): MonomerPositionStats | null => {
|
|
205
|
+
const viewer = this.model.findViewer(viewerType) as SARViewer | null;
|
|
206
|
+
if (isMonomerPositionStatsEqual(viewer)) {
|
|
207
|
+
return viewer!._monomerPositionStats;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (this.model.monomerPositionStats !== null && isMonomerPositionStatsEqual(this.model.settings)) {
|
|
215
|
+
this._monomerPositionStats = this.model.monomerPositionStats;
|
|
216
|
+
} else if (this instanceof MonomerPosition) {
|
|
217
|
+
this._monomerPositionStats = getSharedStats(VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
218
|
+
} else if (this instanceof MostPotentResidues) {
|
|
219
|
+
this._monomerPositionStats = getSharedStats(VIEWER_TYPE.MONOMER_POSITION);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
this._monomerPositionStats ??= calculateMonomerPositionStatistics(this.getScaledActivityColumn(),
|
|
224
|
+
this.dataFrame.filter, this.positionColumns);
|
|
225
|
+
return this._monomerPositionStats;
|
|
59
226
|
}
|
|
60
227
|
|
|
228
|
+
_mutationCliffs: type.MutationCliffs | null = null;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Gets mutation cliffs. If mutation cliffs are not attached to the viewer, it tries to get them from other viewers,
|
|
232
|
+
* or calculates its own.
|
|
233
|
+
* @return - mutation cliffs.
|
|
234
|
+
*/
|
|
235
|
+
get mutationCliffs(): type.MutationCliffs | null {
|
|
236
|
+
if (this._mutationCliffs != null) {
|
|
237
|
+
return this._mutationCliffs;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
const isMutationCliffsEqual = (v1: SARViewer, v2: SARViewer | null): boolean =>
|
|
242
|
+
v1.sequenceColumnName === v2?.sequenceColumnName &&
|
|
243
|
+
v1.activityColumnName === v2.activityColumnName &&
|
|
244
|
+
v1.activityScaling === v2.activityScaling &&
|
|
245
|
+
v1.targetColumnName === v2?.targetColumnName &&
|
|
246
|
+
v1.targetCategory === v2?.targetCategory &&
|
|
247
|
+
v1.minActivityDelta === v2?.minActivityDelta &&
|
|
248
|
+
v1.maxMutations === v2?.maxMutations;
|
|
249
|
+
|
|
250
|
+
const getSharedMutationCliffs = (viewerType: VIEWER_TYPE): type.MutationCliffs | null => {
|
|
251
|
+
const viewer = this.model.findViewer(viewerType) as SARViewer | null;
|
|
252
|
+
if (isMutationCliffsEqual(this, viewer)) {
|
|
253
|
+
return viewer!._mutationCliffs;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (this instanceof MonomerPosition) {
|
|
261
|
+
this._mutationCliffs = getSharedMutationCliffs(VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
262
|
+
} else if (this instanceof MostPotentResidues) {
|
|
263
|
+
this._mutationCliffs = getSharedMutationCliffs(VIEWER_TYPE.MONOMER_POSITION);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
return this._mutationCliffs;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Sets mutation cliffs.
|
|
272
|
+
* @param mc - mutation cliffs to set.
|
|
273
|
+
*/
|
|
274
|
+
set mutationCliffs(mc: type.MutationCliffs) {
|
|
275
|
+
this._mutationCliffs = mc;
|
|
276
|
+
this.viewerGrid.invalidate();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
_mutationCliffsSelection: type.Selection | null = null;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Gets mutation cliffs selection.
|
|
283
|
+
* @return - mutation cliffs selection.
|
|
284
|
+
*/
|
|
285
|
+
get mutationCliffsSelection(): type.Selection {
|
|
286
|
+
const tagSuffix = this instanceof MonomerPosition ? C.SUFFIXES.MP : C.SUFFIXES.MPR;
|
|
287
|
+
const tagSelection = this.dataFrame.getTag(`${tagSuffix}${C.TAGS.MUTATION_CLIFFS_SELECTION}`);
|
|
288
|
+
this._mutationCliffsSelection ??= tagSelection === null ? initSelection(this.positionColumns) :
|
|
289
|
+
JSON.parse(tagSelection);
|
|
290
|
+
return this._mutationCliffsSelection!;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Sets mutation cliffs selection.
|
|
295
|
+
* @param selection - selection to set.
|
|
296
|
+
*/
|
|
297
|
+
set mutationCliffsSelection(selection: type.Selection) {
|
|
298
|
+
this._mutationCliffsSelection = selection;
|
|
299
|
+
const tagSuffix = this instanceof MonomerPosition ? C.SUFFIXES.MP : C.SUFFIXES.MPR;
|
|
300
|
+
this.dataFrame.setTag(`${tagSuffix}${C.TAGS.MUTATION_CLIFFS_SELECTION}`, JSON.stringify(selection));
|
|
301
|
+
this.model.fireBitsetChanged(this instanceof MonomerPosition ? VIEWER_TYPE.MONOMER_POSITION :
|
|
302
|
+
VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
303
|
+
|
|
304
|
+
const mpViewer = this.model.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
305
|
+
mpViewer?.viewerGrid.invalidate();
|
|
306
|
+
const mprViewer = this.model.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
|
|
307
|
+
mprViewer?.viewerGrid.invalidate();
|
|
308
|
+
|
|
309
|
+
this.model.analysisView.grid.invalidate();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Gets scaled activity column.
|
|
314
|
+
* @param isFiltered - flag indicating if only filtered rows should be taken into account.
|
|
315
|
+
* @return - scaled activity column.
|
|
316
|
+
*/
|
|
317
|
+
getScaledActivityColumn(isFiltered: boolean = false): DG.Column<number> {
|
|
318
|
+
if (this.model.settings?.activityColumnName === this.activityColumnName &&
|
|
319
|
+
this.model.settings?.activityScaling === this.activityScaling) {
|
|
320
|
+
this._scaledActivityColumn = this.model.getScaledActivityColumn(isFiltered);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
this._scaledActivityColumn ??= scaleActivity(this.dataFrame.getCol(this.activityColumnName),
|
|
325
|
+
this.activityScaling);
|
|
326
|
+
if (isFiltered) {
|
|
327
|
+
return DG.DataFrame.fromColumns([this._scaledActivityColumn]).clone(this.dataFrame.filter)
|
|
328
|
+
.getCol(this._scaledActivityColumn.name) as DG.Column<number>;
|
|
329
|
+
}
|
|
330
|
+
return this._scaledActivityColumn as DG.Column<number>;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Modifies mutation cliffs selection. If shift and ctrl keys are both pressed, it removes mutation cliffs from
|
|
335
|
+
* selection. If only shift key is pressed, it adds mutation cliffs to selection. If only ctrl key is pressed, it
|
|
336
|
+
* changes mutation cliffs presence in selection. If none of the keys is pressed, it sets the mutation cliffs as the
|
|
337
|
+
* only selected one.
|
|
338
|
+
* @param monomerPosition - monomer-position to modify selection with.
|
|
339
|
+
* @param options - selection options.
|
|
340
|
+
* @param notify - flag indicating if bitset changed event should fire.
|
|
341
|
+
*/
|
|
342
|
+
modifyMutationCliffsSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {
|
|
343
|
+
shiftPressed: false,
|
|
344
|
+
ctrlPressed: false,
|
|
345
|
+
}, notify: boolean = true): void {
|
|
346
|
+
if (notify) {
|
|
347
|
+
this.mutationCliffsSelection = modifySelection(this.mutationCliffsSelection, monomerPosition, options);
|
|
348
|
+
} else {
|
|
349
|
+
this._mutationCliffsSelection = modifySelection(this.mutationCliffsSelection, monomerPosition, options);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Processes property changes.
|
|
355
|
+
* @param property - changed property.
|
|
356
|
+
*/
|
|
357
|
+
onPropertyChanged(property: DG.Property): void {
|
|
358
|
+
super.onPropertyChanged(property);
|
|
359
|
+
this.doRender = true;
|
|
360
|
+
switch (property.name) {
|
|
361
|
+
case `${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`:
|
|
362
|
+
this._positionColumns = null;
|
|
363
|
+
this._monomerPositionStats = null;
|
|
364
|
+
this._mutationCliffs = null;
|
|
365
|
+
this._mutationCliffsSelection = null;
|
|
366
|
+
this._viewerGrid = null;
|
|
367
|
+
break;
|
|
368
|
+
case `${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`:
|
|
369
|
+
case SAR_PROPERTIES.ACTIVITY_SCALING:
|
|
370
|
+
this._monomerPositionStats = null;
|
|
371
|
+
this._mutationCliffs = null;
|
|
372
|
+
this._mutationCliffsSelection = null;
|
|
373
|
+
this._viewerGrid = null;
|
|
374
|
+
this._scaledActivityColumn = null;
|
|
375
|
+
break;
|
|
376
|
+
case `${SAR_PROPERTIES.TARGET}${COLUMN_NAME}`:
|
|
377
|
+
case SAR_PROPERTIES.TARGET_CATEGORY:
|
|
378
|
+
case SAR_PROPERTIES.MIN_ACTIVITY_DELTA:
|
|
379
|
+
case SAR_PROPERTIES.MAX_MUTATIONS:
|
|
380
|
+
this._mutationCliffs = null;
|
|
381
|
+
this._mutationCliffsSelection = null;
|
|
382
|
+
this.doRender = false;
|
|
383
|
+
break;
|
|
384
|
+
case SAR_PROPERTIES.COLUMNS:
|
|
385
|
+
case SAR_PROPERTIES.AGGREGATION:
|
|
386
|
+
if (this instanceof MostPotentResidues) {
|
|
387
|
+
this._viewerGrid = null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName) {
|
|
394
|
+
this.calculateMutationCliffs().then((mc) => this.mutationCliffs = mc);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Gets a map of columns and aggregations.
|
|
400
|
+
* @return - map of columns and aggregations.
|
|
401
|
+
*/
|
|
402
|
+
getAggregationColumns(): AggregationColumns {
|
|
403
|
+
return Object.fromEntries(
|
|
404
|
+
this.columns.map((colName) => [colName, this.aggregation] as [string, DG.AGG]));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Gets total viewer aggregated columns from viewer properties and analysis settings if applicable.
|
|
409
|
+
* @return - total viewer aggregated columns.
|
|
410
|
+
*/
|
|
411
|
+
getTotalViewerAggColumns(): [string, DG.AggregationType][] {
|
|
412
|
+
const aggrCols = this.getAggregationColumns();
|
|
413
|
+
return getTotalAggColumns(this.columns, aggrCols, this.model?.settings?.columns);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Creates viewer grid. */
|
|
417
|
+
createViewerGrid(): DG.Grid {
|
|
418
|
+
throw new Error('Not implemented');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Removes all the active subscriptions. */
|
|
422
|
+
detach(): void {
|
|
423
|
+
this.subs.forEach((sub) => sub.unsubscribe());
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Processes attached table and sets viewer properties. */
|
|
427
|
+
onTableAttached(): void {
|
|
428
|
+
super.onTableAttached();
|
|
429
|
+
this.helpUrl = 'https://datagrok.ai/help/datagrok/solutions/domains/bio/peptides-sar';
|
|
430
|
+
if (isApplicableDataframe(this.dataFrame)) {
|
|
431
|
+
this.getProperty(`${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`)
|
|
432
|
+
?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
|
|
433
|
+
this.getProperty(`${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
|
|
434
|
+
?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
|
|
435
|
+
if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName) {
|
|
436
|
+
this.calculateMutationCliffs().then((mc) => this.mutationCliffs = mc);
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
|
|
440
|
+
grok.log.error(msg);
|
|
441
|
+
grok.shell.warning(msg);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Calculates Mutation Cliffs.
|
|
447
|
+
* @return - mutation cliffs.
|
|
448
|
+
*/
|
|
449
|
+
async calculateMutationCliffs(): Promise<MutationCliffs> {
|
|
450
|
+
const scaledActivityCol: DG.Column<number> = this.dataFrame.getCol(this.sequenceColumnName);
|
|
451
|
+
//TODO: set categories ordering the same to share compare indexes instead of strings
|
|
452
|
+
const monomerCols: type.RawColumn[] = this.positionColumns.map(extractColInfo);
|
|
453
|
+
const targetCol = this.targetColumnName ? extractColInfo(this.dataFrame.getCol(this.targetColumnName)) : null;
|
|
454
|
+
|
|
455
|
+
const options: MutationCliffsOptions = {
|
|
456
|
+
maxMutations: this.maxMutations, minActivityDelta: this.minActivityDelta,
|
|
457
|
+
targetCol, currentTarget: this.targetCategory,
|
|
458
|
+
};
|
|
459
|
+
return await findMutations(scaledActivityCol.getRawData(), monomerCols, options);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Structure-activity relationship viewer */
|
|
464
|
+
export class MonomerPosition extends SARViewer {
|
|
465
|
+
colorColumnName: string;
|
|
466
|
+
colorAggregation: string;
|
|
467
|
+
currentGridCell: DG.GridCell | null = null;
|
|
468
|
+
|
|
469
|
+
/** Sets MonomerPosition properties. */
|
|
470
|
+
constructor() {
|
|
471
|
+
super();
|
|
472
|
+
|
|
473
|
+
const colorChoices = wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name);
|
|
474
|
+
this.colorColumnName = this.column(MONOMER_POSITION_PROPERTIES.COLOR,
|
|
475
|
+
{category: PROPERTY_CATEGORIES.INVARIANT_MAP, choices: colorChoices, nullable: false});
|
|
476
|
+
this.colorAggregation = this.string(MONOMER_POSITION_PROPERTIES.COLOR_AGGREGATION, DG.AGG.AVG,
|
|
477
|
+
{category: PROPERTY_CATEGORIES.INVARIANT_MAP, choices: C.AGGREGATION_TYPES});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Returns viewer name.
|
|
482
|
+
* @return - viewer name.
|
|
483
|
+
*/
|
|
484
|
+
get name(): string {
|
|
485
|
+
return VIEWER_TYPE.MONOMER_POSITION;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Returns current viewer operation mode.
|
|
490
|
+
* @return - current viewer operation mode.
|
|
491
|
+
*/
|
|
61
492
|
get mode(): SELECTION_MODE {
|
|
62
493
|
return this.dataFrame.getTag(C.TAGS.MONOMER_POSITION_MODE) as SELECTION_MODE ??
|
|
63
494
|
SELECTION_MODE.MUTATION_CLIFFS;
|
|
64
495
|
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Sets viewer operation mode.
|
|
499
|
+
* @param mode - viewer operation mode to set.
|
|
500
|
+
*/
|
|
65
501
|
set mode(mode: SELECTION_MODE) {
|
|
66
502
|
this.dataFrame.setTag(C.TAGS.MONOMER_POSITION_MODE, mode);
|
|
67
503
|
this.viewerGrid.invalidate();
|
|
68
504
|
}
|
|
69
505
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
506
|
+
_invariantMapSelection: type.Selection | null = null;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Gets invariant map selection. Initializes it if it is null.
|
|
510
|
+
* @return - invariant map selection.
|
|
511
|
+
*/
|
|
512
|
+
get invariantMapSelection(): type.Selection {
|
|
513
|
+
const tagSelection = this.dataFrame.getTag(`${C.SUFFIXES.MP}${C.TAGS.INVARIANT_MAP_SELECTION}`);
|
|
514
|
+
this._invariantMapSelection ??= tagSelection === null ? initSelection(this.positionColumns) :
|
|
515
|
+
JSON.parse(tagSelection);
|
|
516
|
+
return this._invariantMapSelection!;
|
|
73
517
|
}
|
|
74
518
|
|
|
75
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Sets invariant map selection and notifies the model.
|
|
521
|
+
* @param selection - selection to set.
|
|
522
|
+
*/
|
|
523
|
+
set invariantMapSelection(selection: type.Selection) {
|
|
524
|
+
this._invariantMapSelection = selection;
|
|
525
|
+
this.dataFrame.setTag(`${C.SUFFIXES.MP}${C.TAGS.INVARIANT_MAP_SELECTION}`, JSON.stringify(selection));
|
|
526
|
+
this.model.fireBitsetChanged(VIEWER_TYPE.MONOMER_POSITION);
|
|
527
|
+
this.model.analysisView.grid.invalidate();
|
|
528
|
+
}
|
|
76
529
|
|
|
530
|
+
/** Processes attached table and sets viewer properties. */
|
|
77
531
|
onTableAttached(): void {
|
|
78
532
|
super.onTableAttached();
|
|
79
|
-
this.
|
|
533
|
+
if (isApplicableDataframe(this.dataFrame)) {
|
|
534
|
+
this.getProperty(`${MONOMER_POSITION_PROPERTIES.COLOR}${COLUMN_NAME}`)
|
|
535
|
+
?.set(this, this.activityColumnName);
|
|
536
|
+
} else {
|
|
537
|
+
const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
|
|
538
|
+
grok.log.error(msg);
|
|
539
|
+
grok.shell.warning(msg);
|
|
540
|
+
}
|
|
80
541
|
this.render();
|
|
81
542
|
}
|
|
82
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Modifies invariant map selection. If shift and ctrl keys are both pressed, it removes invariant map from
|
|
546
|
+
* selection. If only shift key is pressed, it adds invariant map to selection. If only ctrl key is pressed, it
|
|
547
|
+
* changes invariant map presence in selection. If none of the keys is pressed, it sets the invariant map as the
|
|
548
|
+
* only selected one.
|
|
549
|
+
* @param monomerPosition - monomer-position to modify selection with.
|
|
550
|
+
* @param options - selection options.
|
|
551
|
+
* @param notify - flag indicating if bitset changed event should fire.
|
|
552
|
+
*/
|
|
553
|
+
modifyInvariantMapSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {
|
|
554
|
+
shiftPressed: false,
|
|
555
|
+
ctrlPressed: false,
|
|
556
|
+
}, notify: boolean = true): void {
|
|
557
|
+
if (notify) {
|
|
558
|
+
this.invariantMapSelection = modifySelection(this.invariantMapSelection, monomerPosition, options);
|
|
559
|
+
} else {
|
|
560
|
+
this._invariantMapSelection = modifySelection(this.invariantMapSelection, monomerPosition, options);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Processes property changes.
|
|
566
|
+
* @param property - changed property.
|
|
567
|
+
*/
|
|
83
568
|
onPropertyChanged(property: DG.Property): void {
|
|
84
569
|
super.onPropertyChanged(property);
|
|
85
|
-
|
|
86
|
-
|
|
570
|
+
switch (property.name) {
|
|
571
|
+
case MONOMER_POSITION_PROPERTIES.COLOR:
|
|
572
|
+
case MONOMER_POSITION_PROPERTIES.COLOR_AGGREGATION:
|
|
573
|
+
this.viewerGrid.invalidate();
|
|
574
|
+
break;
|
|
575
|
+
case SAR_PROPERTIES.SEQUENCE:
|
|
576
|
+
this._invariantMapSelection = null;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
87
579
|
|
|
88
|
-
this
|
|
580
|
+
// this will cause colors to recalculate
|
|
581
|
+
this.model.df.columns.toList().forEach((col) => {
|
|
582
|
+
col.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = null;
|
|
583
|
+
});
|
|
584
|
+
if (this.doRender) {
|
|
585
|
+
this.render();
|
|
586
|
+
}
|
|
89
587
|
}
|
|
90
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Creates monomer-position dataframe to be used in MonomerPosition grid.
|
|
591
|
+
* @return - monomer-position dataframe.
|
|
592
|
+
*/
|
|
91
593
|
createMonomerPositionDf(): DG.DataFrame {
|
|
92
594
|
const uniqueMonomers = new Set<string>();
|
|
93
|
-
const splitSeqCols = this.
|
|
595
|
+
const splitSeqCols = this.positionColumns;
|
|
94
596
|
for (const col of splitSeqCols) {
|
|
95
597
|
const colCat = col.categories;
|
|
96
598
|
for (const cat of colCat) {
|
|
97
|
-
if (cat !== '')
|
|
599
|
+
if (cat !== '') {
|
|
98
600
|
uniqueMonomers.add(cat);
|
|
601
|
+
}
|
|
99
602
|
}
|
|
100
603
|
}
|
|
101
604
|
|
|
102
605
|
const monomerCol = DG.Column.fromStrings(C.COLUMNS_NAMES.MONOMER, Array.from(uniqueMonomers));
|
|
103
606
|
const monomerPositionDf = DG.DataFrame.fromColumns([monomerCol]);
|
|
104
607
|
monomerPositionDf.name = 'SAR';
|
|
105
|
-
for (const col of splitSeqCols)
|
|
608
|
+
for (const col of splitSeqCols) {
|
|
106
609
|
monomerPositionDf.columns.addNewBool(col.name);
|
|
610
|
+
}
|
|
611
|
+
|
|
107
612
|
|
|
108
613
|
return monomerPositionDf;
|
|
109
614
|
}
|
|
110
615
|
|
|
111
|
-
|
|
112
|
-
|
|
616
|
+
/**
|
|
617
|
+
* Builds MonomerPosition interactive grid.
|
|
618
|
+
* @return - MonomerPosition grid.
|
|
619
|
+
*/
|
|
620
|
+
createViewerGrid(): DG.Grid {
|
|
113
621
|
const monomerPositionDf = this.createMonomerPositionDf();
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
622
|
+
const grid = monomerPositionDf.plot.grid();
|
|
623
|
+
grid.sort([C.COLUMNS_NAMES.MONOMER]);
|
|
624
|
+
const positionColumns = this.positionColumns.map((col) => col.name);
|
|
625
|
+
grid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...positionColumns]);
|
|
117
626
|
const monomerCol = monomerPositionDf.getCol(C.COLUMNS_NAMES.MONOMER);
|
|
118
|
-
CR.setMonomerRenderer(monomerCol, this.
|
|
119
|
-
|
|
120
|
-
this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.
|
|
627
|
+
CR.setMonomerRenderer(monomerCol, this.alphabet);
|
|
628
|
+
grid.onCellRender.subscribe((args: DG.GridCellRenderArgs) => renderCell(args, this,
|
|
629
|
+
this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.colorColumnName),
|
|
630
|
+
this.colorAggregation as DG.AGG));
|
|
121
631
|
|
|
122
|
-
|
|
632
|
+
grid.onCellTooltip((gridCell: DG.GridCell, x: number, y: number) => {
|
|
123
633
|
if (!gridCell.isTableCell) {
|
|
124
634
|
this.model.unhighlight();
|
|
125
635
|
return true;
|
|
126
636
|
}
|
|
127
637
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
128
|
-
this.
|
|
129
|
-
|
|
638
|
+
highlightMonomerPosition(monomerPosition, this.dataFrame, this.monomerPositionStats);
|
|
639
|
+
this.model.isHighlighting = true;
|
|
640
|
+
const columnEntries = this.getTotalViewerAggColumns();
|
|
641
|
+
return showTooltip(this.model.df, this.getScaledActivityColumn(), columnEntries, {
|
|
642
|
+
fromViewer: true,
|
|
130
643
|
isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
|
|
131
|
-
mpStats: this.
|
|
644
|
+
mpStats: this.monomerPositionStats,
|
|
645
|
+
});
|
|
132
646
|
});
|
|
133
|
-
|
|
134
|
-
DG.debounce(
|
|
647
|
+
grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
|
|
648
|
+
DG.debounce(grid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
|
|
135
649
|
try {
|
|
136
|
-
if (!this.keyPressed)
|
|
650
|
+
if (!this.keyPressed) {
|
|
137
651
|
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
|
|
138
655
|
if (this.currentGridCell !== null) {
|
|
139
656
|
const previousMonomerPosition = this.getMonomerPosition(this.currentGridCell);
|
|
140
|
-
if (this.mode === SELECTION_MODE.INVARIANT_MAP)
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
657
|
+
if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
|
|
658
|
+
this.modifyInvariantMapSelection(previousMonomerPosition, {
|
|
659
|
+
shiftPressed: true,
|
|
660
|
+
ctrlPressed: true,
|
|
661
|
+
}, false);
|
|
662
|
+
} else {
|
|
663
|
+
const hasMutationCliffs = this.mutationCliffs
|
|
664
|
+
?.get(previousMonomerPosition.monomerOrCluster)?.get(previousMonomerPosition.positionOrClusterType)?.size;
|
|
665
|
+
if (hasMutationCliffs) {
|
|
666
|
+
this.modifyMutationCliffsSelection(previousMonomerPosition, {
|
|
667
|
+
shiftPressed: true,
|
|
668
|
+
ctrlPressed: true,
|
|
669
|
+
}, false);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
144
672
|
}
|
|
145
673
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
146
|
-
if (this.mode === SELECTION_MODE.INVARIANT_MAP)
|
|
147
|
-
this.
|
|
148
|
-
else
|
|
149
|
-
|
|
674
|
+
if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
|
|
675
|
+
this.modifyInvariantMapSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, true);
|
|
676
|
+
} else {
|
|
677
|
+
const hasMutationCliffs = this.mutationCliffs
|
|
678
|
+
?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType)?.size;
|
|
679
|
+
if (hasMutationCliffs) {
|
|
680
|
+
this.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false},
|
|
681
|
+
true);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
150
684
|
|
|
151
|
-
|
|
685
|
+
grid.invalidate();
|
|
152
686
|
} finally {
|
|
153
687
|
this.keyPressed = false;
|
|
154
688
|
this.currentGridCell = gridCell;
|
|
155
689
|
}
|
|
156
690
|
});
|
|
157
|
-
|
|
691
|
+
grid.root.addEventListener('keydown', (ev) => {
|
|
158
692
|
this.keyPressed = ev.key.startsWith('Arrow');
|
|
159
|
-
if (this.keyPressed)
|
|
693
|
+
if (this.keyPressed) {
|
|
160
694
|
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
|
|
161
698
|
if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.ctrlKey && ev.shiftKey)) {
|
|
162
|
-
if (this.mode === SELECTION_MODE.INVARIANT_MAP)
|
|
163
|
-
this.
|
|
164
|
-
else
|
|
165
|
-
this.
|
|
699
|
+
if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
|
|
700
|
+
this._invariantMapSelection = initSelection(this.positionColumns);
|
|
701
|
+
} else {
|
|
702
|
+
this._mutationCliffsSelection = initSelection(this.positionColumns);
|
|
703
|
+
}
|
|
166
704
|
} else if (ev.code === 'KeyA' && ev.ctrlKey) {
|
|
167
|
-
const positions = Object.keys(this.
|
|
705
|
+
const positions = Object.keys(this.monomerPositionStats).filter((pos) => pos !== 'general');
|
|
168
706
|
for (const position of positions) {
|
|
169
|
-
const monomers = Object.keys(this.
|
|
707
|
+
const monomers = Object.keys(this.monomerPositionStats[position]!)
|
|
708
|
+
.filter((monomer) => monomer !== 'general');
|
|
170
709
|
for (const monomer of monomers) {
|
|
171
710
|
const monomerPosition = {monomerOrCluster: monomer, positionOrClusterType: position};
|
|
172
|
-
if (this.mode === SELECTION_MODE.INVARIANT_MAP)
|
|
173
|
-
this.
|
|
174
|
-
else
|
|
175
|
-
this.
|
|
711
|
+
if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
|
|
712
|
+
this.modifyInvariantMapSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, false);
|
|
713
|
+
} else {
|
|
714
|
+
this.modifyMutationCliffsSelection(monomerPosition, {
|
|
715
|
+
shiftPressed: true,
|
|
716
|
+
ctrlPressed: false,
|
|
717
|
+
}, false);
|
|
718
|
+
}
|
|
176
719
|
}
|
|
177
720
|
}
|
|
721
|
+
} else {
|
|
722
|
+
return;
|
|
178
723
|
}
|
|
179
|
-
|
|
180
|
-
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
this.model.fireBitsetChanged(VIEWER_TYPE.MONOMER_POSITION);
|
|
727
|
+
grid.invalidate();
|
|
181
728
|
});
|
|
182
|
-
|
|
183
|
-
const gridCell =
|
|
184
|
-
if (!gridCell?.isTableCell || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.MONOMER)
|
|
729
|
+
grid.root.addEventListener('click', (ev) => {
|
|
730
|
+
const gridCell = grid.hitTest(ev.offsetX, ev.offsetY);
|
|
731
|
+
if (!gridCell?.isTableCell || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.MONOMER) {
|
|
185
732
|
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
186
735
|
|
|
187
736
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
188
737
|
if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
|
|
189
|
-
this.
|
|
190
|
-
if (this.
|
|
738
|
+
this.modifyInvariantMapSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
|
|
739
|
+
if (isSelectionEmpty(this.invariantMapSelection)) {
|
|
191
740
|
monomerPositionDf.currentRowIdx = -1;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
const hasMutationCliffs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)
|
|
744
|
+
?.get(monomerPosition.positionOrClusterType)?.size;
|
|
745
|
+
if (hasMutationCliffs) {
|
|
746
|
+
this.modifyMutationCliffsSelection(monomerPosition,
|
|
747
|
+
{shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
grid.invalidate();
|
|
196
751
|
|
|
197
752
|
this.showHelp();
|
|
198
753
|
});
|
|
199
754
|
|
|
200
|
-
setViewerGridProps(
|
|
755
|
+
setViewerGridProps(grid);
|
|
756
|
+
|
|
757
|
+
// Monomer cell renderer overrides width settings. This way I ensure is "initially" set.
|
|
758
|
+
const afterDraw = grid.onAfterDrawContent.subscribe(() => {
|
|
759
|
+
const monomerGCol = grid.col(C.COLUMNS_NAMES.MONOMER)!;
|
|
760
|
+
if (monomerGCol.width === AAR_CELL_WIDTH) {
|
|
761
|
+
afterDraw.unsubscribe();
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
monomerGCol.width = AAR_CELL_WIDTH;
|
|
765
|
+
for (const posCol of positionColumns) {
|
|
766
|
+
const gcCol = grid.col(posCol)!;
|
|
767
|
+
gcCol.width = MUTATION_CLIFFS_CELL_WIDTH;
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
return grid;
|
|
201
772
|
}
|
|
202
773
|
|
|
774
|
+
/** Shows viewer context help. */
|
|
203
775
|
showHelp(): void {
|
|
204
776
|
_package.files.readAsText('help/monomer-position.md').then((text) => {
|
|
205
777
|
grok.shell.windows.help.showHelp(ui.markdown(text));
|
|
206
778
|
}).catch((e) => grok.log.error(e));
|
|
207
779
|
}
|
|
208
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Gets monomer-position from MonomerPosition grid cell.
|
|
783
|
+
* @param gridCell - MonomerPosition grid cell.
|
|
784
|
+
* @return - monomer-position object.
|
|
785
|
+
*/
|
|
209
786
|
getMonomerPosition(gridCell: DG.GridCell): SelectionItem {
|
|
210
|
-
return {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
mutationCliffsMode.value = false;
|
|
230
|
-
invariantMapMode.value = true;
|
|
231
|
-
this.mode = SELECTION_MODE.INVARIANT_MAP;
|
|
232
|
-
this.showHelp();
|
|
233
|
-
});
|
|
234
|
-
invariantMapMode.setTooltip('Number of sequences having monomer-position');
|
|
235
|
-
const setDefaultProperties = (input: DG.InputBase): void => {
|
|
236
|
-
$(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
|
|
237
|
-
$(input.root).find('.ui-input-description').css('padding', '0px').css('padding-right', '16px');
|
|
238
|
-
$(input.captionLabel).addClass('ui-label-right');
|
|
239
|
-
};
|
|
240
|
-
setDefaultProperties(mutationCliffsMode);
|
|
241
|
-
setDefaultProperties(invariantMapMode);
|
|
242
|
-
|
|
243
|
-
switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root], {id: 'pep-viewer-title'});
|
|
244
|
-
$(switchHost).css('width', 'auto').css('align-self', 'center');
|
|
787
|
+
return {
|
|
788
|
+
monomerOrCluster: gridCell.cell.dataFrame.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!) as string,
|
|
789
|
+
positionOrClusterType: gridCell!.tableColumn!.name,
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** Renders the MonomerPosition viewer body. */
|
|
794
|
+
render(): void {
|
|
795
|
+
$(this.root).empty();
|
|
796
|
+
if (!this.activityColumnName || !this.sequenceColumnName) {
|
|
797
|
+
this.root.appendChild(ui.divText('Please, select a sequence and activity columns in the viewer properties'));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
// Backward compatability with 1.16.0
|
|
801
|
+
const columnProperty = this.getProperty(MONOMER_POSITION_PROPERTIES.COLOR);
|
|
802
|
+
if (columnProperty) {
|
|
803
|
+
columnProperty.choices = wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name);
|
|
804
|
+
if (columnProperty.get(this) === C.COLUMNS_NAMES.ACTIVITY_SCALED) {
|
|
805
|
+
columnProperty.set(this, C.COLUMNS_NAMES.ACTIVITY);
|
|
245
806
|
}
|
|
246
|
-
const viewerRoot = this.viewerGrid.root;
|
|
247
|
-
viewerRoot.style.width = 'auto';
|
|
248
|
-
const header = ui.divH([switchHost], {style: {alignSelf: 'center', lineHeight: 'normal'}});
|
|
249
|
-
this.root.appendChild(ui.divV([header, viewerRoot]));
|
|
250
807
|
}
|
|
808
|
+
|
|
809
|
+
$(this.root).empty();
|
|
810
|
+
let switchHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
|
|
811
|
+
if (this.name === VIEWER_TYPE.MONOMER_POSITION) {
|
|
812
|
+
const mutationCliffsMode = ui.boolInput(SELECTION_MODE.MUTATION_CLIFFS,
|
|
813
|
+
this.mode === SELECTION_MODE.MUTATION_CLIFFS);
|
|
814
|
+
mutationCliffsMode.root.addEventListener('click', () => {
|
|
815
|
+
invariantMapMode.value = false;
|
|
816
|
+
mutationCliffsMode.value = true;
|
|
817
|
+
this.mode = SELECTION_MODE.MUTATION_CLIFFS;
|
|
818
|
+
this.showHelp();
|
|
819
|
+
});
|
|
820
|
+
mutationCliffsMode.setTooltip('Statistically significant changes in activity');
|
|
821
|
+
const invariantMapMode = ui.boolInput(SELECTION_MODE.INVARIANT_MAP, this.mode === SELECTION_MODE.INVARIANT_MAP);
|
|
822
|
+
invariantMapMode.root.addEventListener('click', () => {
|
|
823
|
+
mutationCliffsMode.value = false;
|
|
824
|
+
invariantMapMode.value = true;
|
|
825
|
+
this.mode = SELECTION_MODE.INVARIANT_MAP;
|
|
826
|
+
this.showHelp();
|
|
827
|
+
});
|
|
828
|
+
invariantMapMode.setTooltip('Number of sequences having monomer-position');
|
|
829
|
+
const setDefaultProperties = (input: DG.InputBase): void => {
|
|
830
|
+
$(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
|
|
831
|
+
$(input.root).find('.ui-input-description').css('padding', '0px').css('padding-right', '16px');
|
|
832
|
+
$(input.captionLabel).addClass('ui-label-right');
|
|
833
|
+
};
|
|
834
|
+
setDefaultProperties(mutationCliffsMode);
|
|
835
|
+
setDefaultProperties(invariantMapMode);
|
|
836
|
+
|
|
837
|
+
switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root], {id: 'pep-viewer-title'});
|
|
838
|
+
$(switchHost).css('width', 'auto').css('align-self', 'center');
|
|
839
|
+
}
|
|
840
|
+
const viewerRoot = this.viewerGrid.root;
|
|
841
|
+
viewerRoot.style.width = 'auto';
|
|
842
|
+
const header = ui.divH([switchHost], {style: {alignSelf: 'center', lineHeight: 'normal'}});
|
|
843
|
+
this.root.appendChild(ui.divV([header, viewerRoot]));
|
|
251
844
|
this.viewerGrid?.invalidate();
|
|
252
845
|
}
|
|
253
846
|
}
|
|
254
847
|
|
|
255
848
|
/** Vertical structure activity relationship viewer */
|
|
256
|
-
export class MostPotentResidues extends
|
|
257
|
-
_titleHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
|
|
258
|
-
_viewerGrid!: DG.Grid;
|
|
259
|
-
_model!: PeptidesModel;
|
|
260
|
-
keyPressed: boolean = false;
|
|
849
|
+
export class MostPotentResidues extends SARViewer {
|
|
261
850
|
currentGridRowIdx: number | null = null;
|
|
262
851
|
|
|
852
|
+
/** Sets MostPotentResidues properties. */
|
|
263
853
|
constructor() {
|
|
264
854
|
super();
|
|
265
855
|
}
|
|
266
856
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return
|
|
273
|
-
}
|
|
274
|
-
set viewerGrid(grid: DG.Grid) {
|
|
275
|
-
this._viewerGrid = grid;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
get model(): PeptidesModel {
|
|
279
|
-
this._model ??= PeptidesModel.getInstance(this.dataFrame);
|
|
280
|
-
return this._model;
|
|
857
|
+
/**
|
|
858
|
+
* Returns viewer name.
|
|
859
|
+
* @return - viewer name.
|
|
860
|
+
*/
|
|
861
|
+
get name(): string {
|
|
862
|
+
return VIEWER_TYPE.MOST_POTENT_RESIDUES;
|
|
281
863
|
}
|
|
282
864
|
|
|
283
|
-
|
|
284
|
-
|
|
865
|
+
/** Processes attached table and sets viewer properties. */
|
|
285
866
|
onTableAttached(): void {
|
|
286
867
|
super.onTableAttached();
|
|
287
868
|
this.helpUrl = 'https://datagrok.ai/help/datagrok/solutions/domains/bio/peptides-sar';
|
|
288
869
|
this.render();
|
|
289
870
|
}
|
|
290
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Processes property changes.
|
|
874
|
+
* @param property - changed property.
|
|
875
|
+
*/
|
|
291
876
|
onPropertyChanged(property: DG.Property): void {
|
|
292
877
|
super.onPropertyChanged(property);
|
|
293
|
-
this.
|
|
878
|
+
if (this.doRender) {
|
|
879
|
+
this.render();
|
|
880
|
+
}
|
|
294
881
|
}
|
|
295
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Creates most potent residues dataframe to be used in MostPotentResidues grid.
|
|
885
|
+
* @return - most potent residues dataframe.
|
|
886
|
+
*/
|
|
296
887
|
createMostPotentResiduesDf(): DG.DataFrame {
|
|
297
|
-
const monomerPositionStatsEntries = Object.entries(this.
|
|
888
|
+
const monomerPositionStatsEntries = Object.entries(this.monomerPositionStats) as [string, PositionStats][];
|
|
298
889
|
const posData: number[] = new Array(monomerPositionStatsEntries.length - 1);
|
|
299
890
|
const monomerData: string[] = new Array(posData.length);
|
|
300
891
|
const mdData: number[] = new Array(posData.length);
|
|
@@ -302,37 +893,56 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
302
893
|
const countData: number[] = new Array(posData.length);
|
|
303
894
|
const ratioData: number[] = new Array(posData.length);
|
|
304
895
|
const meanData: number[] = new Array(posData.length);
|
|
305
|
-
|
|
896
|
+
const aggrColumnEntries = this.getTotalViewerAggColumns();
|
|
897
|
+
const aggColNames = aggrColumnEntries.map(([colName, aggFn]) => getAggregatedColName(aggFn, colName));
|
|
898
|
+
const aggrColsData = new Array<Array<number>>(aggColNames.length);
|
|
899
|
+
aggColNames.forEach(((_, idx) => aggrColsData[idx] = new Array<number>(posData.length)));
|
|
306
900
|
let i = 0;
|
|
307
901
|
for (const [position, positionStats] of monomerPositionStatsEntries) {
|
|
308
902
|
const generalPositionStats = positionStats.general;
|
|
309
|
-
if (!generalPositionStats)
|
|
903
|
+
if (!generalPositionStats) {
|
|
310
904
|
continue;
|
|
311
|
-
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
if (Object.entries(positionStats).length === 1) {
|
|
312
909
|
continue;
|
|
910
|
+
}
|
|
911
|
+
|
|
313
912
|
|
|
314
|
-
const filteredMonomerStats: [string,
|
|
913
|
+
const filteredMonomerStats: [string, StatsItem][] = [];
|
|
315
914
|
for (const [monomer, monomerStats] of Object.entries(positionStats)) {
|
|
316
|
-
if (monomer === 'general')
|
|
915
|
+
if (monomer === 'general') {
|
|
317
916
|
continue;
|
|
318
|
-
|
|
319
|
-
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
if ((monomerStats as StatsItem).count > 1 && (monomerStats as StatsItem).pValue === null) {
|
|
921
|
+
filteredMonomerStats.push([monomer, monomerStats as StatsItem]);
|
|
922
|
+
}
|
|
320
923
|
|
|
321
|
-
|
|
322
|
-
|
|
924
|
+
|
|
925
|
+
if ((monomerStats as StatsItem).pValue === generalPositionStats.minPValue) {
|
|
926
|
+
filteredMonomerStats.push([monomer, monomerStats as StatsItem]);
|
|
927
|
+
}
|
|
323
928
|
}
|
|
324
929
|
|
|
325
|
-
if (filteredMonomerStats.length === 0)
|
|
930
|
+
if (filteredMonomerStats.length === 0) {
|
|
326
931
|
continue;
|
|
932
|
+
}
|
|
327
933
|
|
|
328
|
-
|
|
934
|
+
|
|
935
|
+
let maxEntry: [string, StatsItem] | null = null;
|
|
329
936
|
for (const [monomer, monomerStats] of filteredMonomerStats) {
|
|
330
|
-
if (maxEntry === null || maxEntry[1].meanDifference < monomerStats.meanDifference)
|
|
937
|
+
if (maxEntry === null || maxEntry[1].meanDifference < monomerStats.meanDifference) {
|
|
331
938
|
maxEntry = [monomer, monomerStats];
|
|
939
|
+
}
|
|
332
940
|
}
|
|
333
941
|
|
|
334
|
-
if (maxEntry === null)
|
|
942
|
+
if (maxEntry === null) {
|
|
335
943
|
continue;
|
|
944
|
+
}
|
|
945
|
+
|
|
336
946
|
|
|
337
947
|
posData[i] = parseInt(position);
|
|
338
948
|
monomerData[i] = maxEntry![0];
|
|
@@ -341,6 +951,13 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
341
951
|
countData[i] = maxEntry![1].count;
|
|
342
952
|
ratioData[i] = maxEntry![1].ratio;
|
|
343
953
|
meanData[i] = maxEntry![1].mean;
|
|
954
|
+
|
|
955
|
+
const stats = this.monomerPositionStats[position][maxEntry![0]];
|
|
956
|
+
const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer, this.model.df.col(this.activityColumnName)!.length);
|
|
957
|
+
for (let j = 0; j < aggColNames.length; j++) {
|
|
958
|
+
const [colName, aggFn] = aggrColumnEntries[j];
|
|
959
|
+
aggrColsData[j][i] = getAggregatedValue(this.model.df.getCol(colName), aggFn, mask);
|
|
960
|
+
}
|
|
344
961
|
++i;
|
|
345
962
|
}
|
|
346
963
|
|
|
@@ -350,8 +967,9 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
350
967
|
pValData.length = i;
|
|
351
968
|
countData.length = i;
|
|
352
969
|
ratioData.length = i;
|
|
970
|
+
meanData.length = i;
|
|
353
971
|
|
|
354
|
-
const mprDf = DG.DataFrame.create(i);
|
|
972
|
+
const mprDf = DG.DataFrame.create(i);
|
|
355
973
|
const mprDfCols = mprDf.columns;
|
|
356
974
|
mprDfCols.add(DG.Column.fromList(DG.TYPE.INT, C.COLUMNS_NAMES.POSITION, posData));
|
|
357
975
|
mprDfCols.add(DG.Column.fromList(DG.TYPE.STRING, C.COLUMNS_NAMES.MONOMER, monomerData));
|
|
@@ -360,112 +978,184 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
360
978
|
mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.P_VALUE, pValData));
|
|
361
979
|
mprDfCols.add(DG.Column.fromList(DG.TYPE.INT, C.COLUMNS_NAMES.COUNT, countData));
|
|
362
980
|
mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, C.COLUMNS_NAMES.RATIO, ratioData));
|
|
981
|
+
aggColNames.forEach((it, idx) => mprDfCols.add(DG.Column.fromList(DG.TYPE.FLOAT, it, aggrColsData[idx])));
|
|
363
982
|
|
|
364
983
|
return mprDf;
|
|
365
984
|
}
|
|
366
985
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Builds MostPotentResidues interactive grid.
|
|
988
|
+
* @return - MostPotentResidues grid.
|
|
989
|
+
*/
|
|
990
|
+
createViewerGrid(): DG.Grid {
|
|
991
|
+
const mprDf = this.createMostPotentResiduesDf();
|
|
992
|
+
const grid = mprDf.plot.grid();
|
|
993
|
+
grid.sort([C.COLUMNS_NAMES.POSITION]);
|
|
994
|
+
const pValGridCol = grid.col(C.COLUMNS_NAMES.P_VALUE)!;
|
|
372
995
|
pValGridCol.format = '#.000';
|
|
373
996
|
pValGridCol.name = 'P-value';
|
|
374
|
-
const monomerCol =
|
|
997
|
+
const monomerCol = mprDf.getCol(C.COLUMNS_NAMES.MONOMER);
|
|
375
998
|
|
|
376
999
|
// Setting Monomer column renderer
|
|
377
|
-
CR.setMonomerRenderer(monomerCol, this.
|
|
378
|
-
|
|
379
|
-
(args: DG.GridCellRenderArgs) => renderCell(args, this
|
|
1000
|
+
CR.setMonomerRenderer(monomerCol, this.alphabet);
|
|
1001
|
+
grid.onCellRender.subscribe(
|
|
1002
|
+
(args: DG.GridCellRenderArgs) => renderCell(args, this, false, undefined, undefined));
|
|
380
1003
|
|
|
381
|
-
|
|
1004
|
+
grid.onCellTooltip((gridCell: DG.GridCell, x: number, y: number) => {
|
|
382
1005
|
if (!gridCell.isTableCell) {
|
|
383
1006
|
this.model.unhighlight();
|
|
384
1007
|
return true;
|
|
385
1008
|
}
|
|
386
1009
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
387
|
-
this.
|
|
388
|
-
|
|
1010
|
+
highlightMonomerPosition(monomerPosition, this.dataFrame, this.monomerPositionStats);
|
|
1011
|
+
this.model.isHighlighting = true;
|
|
1012
|
+
|
|
1013
|
+
if (gridCell.tableColumn?.name === C.COLUMNS_NAMES.MONOMER) {
|
|
389
1014
|
monomerPosition.positionOrClusterType = C.COLUMNS_NAMES.MONOMER;
|
|
390
|
-
else if (gridCell.tableColumn?.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE)
|
|
1015
|
+
} else if (gridCell.tableColumn?.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE) {
|
|
391
1016
|
return false;
|
|
392
|
-
|
|
393
|
-
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
const columnEntries = this.getTotalViewerAggColumns();
|
|
1021
|
+
const aggrValues = gridCell.tableRowIndex == null ? undefined :
|
|
1022
|
+
getAggregatedColumnValuesFromDf(gridCell.grid.dataFrame!, gridCell.tableRowIndex, columnEntries, {});
|
|
1023
|
+
return showTooltip(this.model.df, this.getScaledActivityColumn(), columnEntries,
|
|
1024
|
+
{
|
|
1025
|
+
fromViewer: true, isMutationCliffs: true, monomerPosition, x, y, mpStats: this.monomerPositionStats,
|
|
1026
|
+
aggrColValues: aggrValues,
|
|
1027
|
+
});
|
|
394
1028
|
});
|
|
395
|
-
DG.debounce(
|
|
1029
|
+
DG.debounce(grid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
|
|
396
1030
|
try {
|
|
397
|
-
if ((this.keyPressed &&
|
|
1031
|
+
if ((this.keyPressed && mprDf.currentCol.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE) || !this.keyPressed) {
|
|
398
1032
|
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
|
|
399
1036
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
400
1037
|
if (this.currentGridRowIdx !== null) {
|
|
401
|
-
const previousMonomerPosition = this.getMonomerPosition(
|
|
402
|
-
this.
|
|
1038
|
+
const previousMonomerPosition = this.getMonomerPosition(grid.cell('Diff', this.currentGridRowIdx));
|
|
1039
|
+
this.modifyMutationCliffsSelection(previousMonomerPosition, {
|
|
1040
|
+
shiftPressed: true,
|
|
1041
|
+
ctrlPressed: true,
|
|
1042
|
+
}, false);
|
|
1043
|
+
}
|
|
1044
|
+
const hasMutationCliffs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)
|
|
1045
|
+
?.get(monomerPosition.positionOrClusterType)?.size;
|
|
1046
|
+
if (hasMutationCliffs) {
|
|
1047
|
+
this.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false});
|
|
403
1048
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
grid.invalidate();
|
|
407
1052
|
} finally {
|
|
408
1053
|
this.keyPressed = false;
|
|
409
1054
|
this.currentGridRowIdx = gridCell.gridRow;
|
|
410
1055
|
}
|
|
411
1056
|
});
|
|
412
|
-
|
|
1057
|
+
grid.root.addEventListener('keydown', (ev) => {
|
|
413
1058
|
this.keyPressed = ev.key.startsWith('Arrow');
|
|
414
|
-
if (this.keyPressed)
|
|
1059
|
+
if (this.keyPressed) {
|
|
415
1060
|
return;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
if (ev.key === 'Escape' || (ev.code === 'KeyA' && ev.ctrlKey && ev.shiftKey)) {
|
|
1065
|
+
this._mutationCliffsSelection = initSelection(this.positionColumns);
|
|
1066
|
+
} else if (ev.code === 'KeyA' && ev.ctrlKey) {
|
|
1067
|
+
for (let rowIdx = 0; rowIdx < mprDf.rowCount; ++rowIdx) {
|
|
1068
|
+
const monomerPosition = this.getMonomerPosition(grid.cell('Diff', rowIdx));
|
|
1069
|
+
this.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: true, ctrlPressed: false}, false);
|
|
422
1070
|
}
|
|
1071
|
+
} else {
|
|
1072
|
+
return;
|
|
423
1073
|
}
|
|
424
|
-
|
|
425
|
-
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
this.model.fireBitsetChanged(VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
1077
|
+
grid.invalidate();
|
|
426
1078
|
});
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const gridCell =
|
|
430
|
-
if (!gridCell?.isTableCell || gridCell!.tableColumn!.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE)
|
|
1079
|
+
grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
|
|
1080
|
+
grid.root.addEventListener('click', (ev) => {
|
|
1081
|
+
const gridCell = grid.hitTest(ev.offsetX, ev.offsetY);
|
|
1082
|
+
if (!gridCell?.isTableCell || gridCell!.tableColumn!.name !== C.COLUMNS_NAMES.MEAN_DIFFERENCE) {
|
|
431
1083
|
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
432
1086
|
|
|
433
1087
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
434
|
-
|
|
1088
|
+
const hasMutationCliffs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)
|
|
1089
|
+
?.get(monomerPosition.positionOrClusterType)?.size;
|
|
1090
|
+
if (!hasMutationCliffs) {
|
|
435
1091
|
return;
|
|
436
|
-
|
|
437
|
-
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
this.modifyMutationCliffsSelection(monomerPosition, {shiftPressed: ev.shiftKey, ctrlPressed: ev.ctrlKey});
|
|
1096
|
+
grid.invalidate();
|
|
438
1097
|
|
|
439
1098
|
_package.files.readAsText('help/most-potent-residues.md').then((text) => {
|
|
440
1099
|
grok.shell.windows.help.showHelp(ui.markdown(text));
|
|
441
1100
|
}).catch((e) => grok.log.error(e));
|
|
442
1101
|
});
|
|
443
|
-
|
|
1102
|
+
|
|
1103
|
+
setViewerGridProps(grid);
|
|
1104
|
+
const mdCol: DG.GridColumn = grid.col(C.COLUMNS_NAMES.MEAN_DIFFERENCE)!;
|
|
444
1105
|
mdCol.name = 'Diff';
|
|
445
|
-
|
|
1106
|
+
// Monomer cell renderer overrides width settings. This way I ensure is "initially" set.
|
|
1107
|
+
const afterDraw = grid.onAfterDrawContent.subscribe(() => {
|
|
1108
|
+
const monomerGCol = grid.col(C.COLUMNS_NAMES.MONOMER)!;
|
|
1109
|
+
if (monomerGCol.width === AAR_CELL_WIDTH) {
|
|
1110
|
+
afterDraw.unsubscribe();
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
monomerGCol.width = AAR_CELL_WIDTH;
|
|
1114
|
+
mdCol.width = MUTATION_CLIFFS_CELL_WIDTH;
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
return grid;
|
|
446
1118
|
}
|
|
447
1119
|
|
|
1120
|
+
/**
|
|
1121
|
+
* Gets monomer-position from MostPotentResidues grid cell.
|
|
1122
|
+
* @param gridCell - MostPotentResidues grid cell.
|
|
1123
|
+
* @return - monomer-position object.
|
|
1124
|
+
*/
|
|
448
1125
|
getMonomerPosition(gridCell: DG.GridCell): SelectionItem {
|
|
449
|
-
return {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1126
|
+
return {
|
|
1127
|
+
monomerOrCluster: gridCell.cell.dataFrame.get(C.COLUMNS_NAMES.MONOMER, gridCell!.tableRowIndex!),
|
|
1128
|
+
positionOrClusterType: `${gridCell.cell.dataFrame.get(C.COLUMNS_NAMES.POSITION, gridCell!.tableRowIndex!)}`,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/** Renders the MostPotentResidues viewer body. */
|
|
1133
|
+
render(): void {
|
|
1134
|
+
$(this.root).empty();
|
|
1135
|
+
if (!this.activityColumnName || !this.sequenceColumnName) {
|
|
1136
|
+
this.root.appendChild(ui.divText('Please, select a sequence and activity columns in the viewer properties'));
|
|
1137
|
+
return;
|
|
461
1138
|
}
|
|
1139
|
+
const switchHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
|
|
1140
|
+
const viewerRoot = this.viewerGrid.root;
|
|
1141
|
+
viewerRoot.style.width = 'auto';
|
|
1142
|
+
const header = ui.divH([switchHost], {style: {alignSelf: 'center', lineHeight: 'normal'}});
|
|
1143
|
+
this.root.appendChild(ui.divV([header, viewerRoot]));
|
|
462
1144
|
this.viewerGrid?.invalidate();
|
|
463
1145
|
}
|
|
464
1146
|
}
|
|
465
1147
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1148
|
+
/**
|
|
1149
|
+
* Renders SARViewer grid cells.
|
|
1150
|
+
* @param args - grid cell render arguments.
|
|
1151
|
+
* @param viewer - viewer instance.
|
|
1152
|
+
* @param isInvariantMap - flag indicating if viewr is operating in invariant map mode.
|
|
1153
|
+
* @param colorCol - column to use for color coding in invariant map mode.
|
|
1154
|
+
* @param colorAgg - aggregation function to use for color coding in invariant map mode.
|
|
1155
|
+
*/
|
|
1156
|
+
function renderCell(args: DG.GridCellRenderArgs, viewer: SARViewer, isInvariantMap?: boolean,
|
|
1157
|
+
colorCol?: DG.Column<number>, colorAgg?: DG.AGG): void {
|
|
1158
|
+
const renderColNames = [...viewer.positionColumns.map((col) => col.name), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
|
|
469
1159
|
const canvasContext = args.g;
|
|
470
1160
|
const bound = args.bounds;
|
|
471
1161
|
|
|
@@ -494,7 +1184,7 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
|
|
|
494
1184
|
const currentMonomer: string = gridTable.get(C.COLUMNS_NAMES.MONOMER, tableRowIndex);
|
|
495
1185
|
const currentPosition: string = tableColName !== C.COLUMNS_NAMES.MEAN_DIFFERENCE ? tableColName :
|
|
496
1186
|
gridTable.get(C.COLUMNS_NAMES.POSITION, tableRowIndex).toFixed();
|
|
497
|
-
const currentPosStats =
|
|
1187
|
+
const currentPosStats = viewer.monomerPositionStats[currentPosition];
|
|
498
1188
|
|
|
499
1189
|
if (!currentPosStats![currentMonomer]) {
|
|
500
1190
|
args.preventDefault();
|
|
@@ -504,32 +1194,46 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
|
|
|
504
1194
|
|
|
505
1195
|
if (isInvariantMap) {
|
|
506
1196
|
const value = currentPosStats![currentMonomer]!.count;
|
|
507
|
-
const positionCol =
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1197
|
+
const positionCol = viewer.positionColumns.find((col) => col.name === currentPosition)!;
|
|
1198
|
+
const colorCache: { [_: string]: number } = positionCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] ?? {};
|
|
1199
|
+
let color: number;
|
|
1200
|
+
if (colorCache[currentMonomer]) {
|
|
1201
|
+
color = colorCache[currentMonomer];
|
|
1202
|
+
} else {
|
|
1203
|
+
const positionColData = positionCol.getRawData();
|
|
1204
|
+
const positionColCategories = positionCol.categories;
|
|
1205
|
+
|
|
1206
|
+
const colorColData = colorCol!.getRawData();
|
|
1207
|
+
const colorValuesIndexes: number[] = [];
|
|
1208
|
+
for (let i = 0; i < positionCol.length; ++i) {
|
|
1209
|
+
if (positionColCategories[positionColData[i]] === currentMonomer) {
|
|
1210
|
+
colorValuesIndexes.push(i);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
const cellColorDataCol = DG.Column.float('color', colorValuesIndexes.length)
|
|
1214
|
+
.init((i) => colorColData[colorValuesIndexes[i]]);
|
|
1215
|
+
const colorColStats = colorCol!.stats;
|
|
1216
|
+
color = DG.Color.scaleColor(cellColorDataCol.aggregate(colorAgg!), colorColStats.min, colorColStats.max);
|
|
1217
|
+
colorCache[currentMonomer] = color;
|
|
1218
|
+
positionCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = colorCache;
|
|
516
1219
|
}
|
|
517
|
-
const cellColorDataCol = DG.Column.float('color', colorValuesIndexes.length)
|
|
518
|
-
.init((i) => colorColData[colorValuesIndexes[i]]);
|
|
519
|
-
const colorColStats = colorCol!.stats;
|
|
520
1220
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
canvasContext, currentMonomer, currentPosition, model.invariantMapSelection, value, bound, color);
|
|
1221
|
+
CR.renderInvariantMapCell(canvasContext, currentMonomer, currentPosition,
|
|
1222
|
+
(viewer as MonomerPosition).invariantMapSelection, value, bound, color);
|
|
524
1223
|
} else {
|
|
525
|
-
CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition,
|
|
526
|
-
model.mutationCliffsSelection, model.mutationCliffs, renderNums);
|
|
1224
|
+
CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition, viewer, bound);
|
|
527
1225
|
}
|
|
1226
|
+
|
|
1227
|
+
|
|
528
1228
|
args.preventDefault();
|
|
529
1229
|
canvasContext.restore();
|
|
530
1230
|
}
|
|
531
1231
|
|
|
532
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* Sets common grid properties.
|
|
1234
|
+
* @param grid - viewer grid.
|
|
1235
|
+
*/
|
|
1236
|
+
function setViewerGridProps(grid: DG.Grid): void {
|
|
533
1237
|
const gridProps = grid.props;
|
|
534
1238
|
gridProps.allowEdit = false;
|
|
535
1239
|
gridProps.allowRowSelection = false;
|
|
@@ -537,14 +1241,7 @@ function setViewerGridProps(grid: DG.Grid, isMostPotentResiduesGrid: boolean): v
|
|
|
537
1241
|
gridProps.allowColSelection = false;
|
|
538
1242
|
gridProps.showRowHeader = false;
|
|
539
1243
|
gridProps.showCurrentRowIndicator = false;
|
|
1244
|
+
gridProps.showReadOnlyNotifications = false;
|
|
540
1245
|
|
|
541
1246
|
gridProps.rowHeight = 20;
|
|
542
|
-
const girdCols = grid.columns;
|
|
543
|
-
const colNum = girdCols.length;
|
|
544
|
-
for (let i = 0; i < colNum; ++i) {
|
|
545
|
-
const col = girdCols.byIndex(i)!;
|
|
546
|
-
const colName = col.name;
|
|
547
|
-
col.width = isMostPotentResiduesGrid && colName !== 'Diff' && colName !== C.COLUMNS_NAMES.MONOMER ? 50 :
|
|
548
|
-
gridProps.rowHeight + 10;
|
|
549
|
-
}
|
|
550
1247
|
}
|