@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
package/src/model.ts
CHANGED
|
@@ -1,311 +1,188 @@
|
|
|
1
1
|
import * as ui from 'datagrok-api/ui';
|
|
2
2
|
import * as grok from 'datagrok-api/grok';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
monomerToShort,
|
|
6
|
+
NOTATION,
|
|
7
|
+
pickUpPalette,
|
|
8
|
+
TAGS as bioTAGS,
|
|
9
|
+
} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
10
|
import {calculateScores, SCORE} from '@datagrok-libraries/bio/src/utils/macromolecule/scoring';
|
|
9
11
|
import {Options} from '@datagrok-libraries/utils/src/type-declarations';
|
|
10
12
|
import {DistanceMatrix} from '@datagrok-libraries/ml/src/distance-matrix';
|
|
11
|
-
import {StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
|
|
13
|
+
import {BitArrayMetrics, StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
|
|
12
14
|
import {ITreeHelper} from '@datagrok-libraries/bio/src/trees/tree-helper';
|
|
13
15
|
import {TAGS as treeTAGS} from '@datagrok-libraries/bio/src/trees';
|
|
14
16
|
import BitArray from '@datagrok-libraries/utils/src/bit-array';
|
|
15
|
-
|
|
17
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
16
18
|
import wu from 'wu';
|
|
17
19
|
import * as rxjs from 'rxjs';
|
|
18
|
-
import * as uuid from 'uuid';
|
|
19
20
|
import $ from 'cash-dom';
|
|
20
21
|
|
|
21
22
|
import * as C from './utils/constants';
|
|
23
|
+
import {COLUMN_NAME, COLUMNS_NAMES} from './utils/constants';
|
|
22
24
|
import * as type from './utils/types';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
+
import {PeptidesSettings} from './utils/types';
|
|
26
|
+
import {
|
|
27
|
+
areParametersEqual,
|
|
28
|
+
calculateSelected,
|
|
29
|
+
getSelectionBitset,
|
|
30
|
+
highlightMonomerPosition,
|
|
31
|
+
initSelection,
|
|
32
|
+
modifySelection,
|
|
33
|
+
mutationCliffsToMaskInfo,
|
|
34
|
+
scaleActivity,
|
|
35
|
+
} from './utils/misc';
|
|
36
|
+
import {ISARViewer, MonomerPosition, MostPotentResidues, SARViewer} from './viewers/sar-viewer';
|
|
25
37
|
import * as CR from './utils/cell-renderer';
|
|
26
38
|
import {mutationCliffsWidget} from './widgets/mutation-cliffs';
|
|
27
|
-
import {getDistributionWidget} from './widgets/distribution';
|
|
28
|
-
import {
|
|
29
|
-
import {LogoSummaryTable} from './viewers/logo-summary';
|
|
39
|
+
import {getDistributionWidget, PeptideViewer} from './widgets/distribution';
|
|
40
|
+
import {CLUSTER_TYPE, ILogoSummaryTable, LogoSummaryTable, LST_PROPERTIES} from './viewers/logo-summary';
|
|
30
41
|
import {getSettingsDialog} from './widgets/settings';
|
|
31
42
|
import {_package, getTreeHelperInstance} from './package';
|
|
32
|
-
import {
|
|
43
|
+
import {calculateMonomerPositionStatistics} from './utils/algorithms';
|
|
33
44
|
import {createDistanceMatrixWorker} from './utils/worker-creator';
|
|
34
45
|
import {getSelectionWidget} from './widgets/selection';
|
|
35
46
|
|
|
36
|
-
import {MmDistanceFunctionsNames} from '@datagrok-libraries/ml/src/macromolecule-distance-functions';
|
|
37
|
-
import {BitArrayMetrics} from '@datagrok-libraries/ml/src/typed-metrics';
|
|
47
|
+
import {mmDistanceFunctions, MmDistanceFunctionsNames} from '@datagrok-libraries/ml/src/macromolecule-distance-functions';
|
|
38
48
|
import {DimReductionMethods, ITSNEOptions, IUMAPOptions} from '@datagrok-libraries/ml/src/reduce-dimensionality';
|
|
39
49
|
import {showMonomerTooltip} from './utils/tooltips';
|
|
50
|
+
import {AggregationColumns, MonomerPositionStats} from './utils/statistics';
|
|
51
|
+
import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
|
|
40
52
|
|
|
41
|
-
export enum CLUSTER_TYPE {
|
|
42
|
-
ORIGINAL = 'original',
|
|
43
|
-
CUSTOM = 'custom',
|
|
44
|
-
};
|
|
45
|
-
export type ClusterType = `${CLUSTER_TYPE}`;
|
|
46
53
|
export enum VIEWER_TYPE {
|
|
47
54
|
MONOMER_POSITION = 'Monomer-Position',
|
|
48
55
|
MOST_POTENT_RESIDUES = 'Most Potent Residues',
|
|
49
56
|
LOGO_SUMMARY_TABLE = 'Logo Summary Table',
|
|
50
57
|
DENDROGRAM = 'Dendrogram',
|
|
51
|
-
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type CachedWebLogoTooltip = { bar: string, tooltip: HTMLDivElement | null };
|
|
52
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Peptides model class
|
|
64
|
+
* Controls analysis settings and initialization, collaborative filtering and property panel.
|
|
65
|
+
*/
|
|
53
66
|
export class PeptidesModel {
|
|
67
|
+
// Tag for storing model instance in DataFrame temp
|
|
54
68
|
static modelName = 'peptidesModel';
|
|
55
69
|
|
|
70
|
+
// Prevents firing bitset changed event if not initialized
|
|
56
71
|
isBitsetChangedInitialized = false;
|
|
72
|
+
// Prevents overriding user selection
|
|
57
73
|
isUserChangedSelection = true;
|
|
58
74
|
|
|
59
75
|
df: DG.DataFrame;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
_mutationCliffsSelection!: type.Selection;
|
|
63
|
-
_invariantMapSelection!: type.Selection;
|
|
64
|
-
_clusterSelection!: type.Selection;
|
|
65
|
-
_mutationCliffs: type.MutationCliffs | null = null;
|
|
76
|
+
_dm: DistanceMatrix | null = null;
|
|
77
|
+
// Prevents redundant intialization
|
|
66
78
|
isInitialized = false;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
_settings!: type.PeptidesSettings;
|
|
79
|
+
// Prevents duplicating ribbon
|
|
71
80
|
isRibbonSet = false;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
// Cached stats for WebLogo selection for efficient rendering
|
|
82
|
+
webLogoSelectedMonomers: type.SelectionStats = {};
|
|
83
|
+
// WebLogo bounds used for interactivity (e.g. tooltips, selection)
|
|
75
84
|
webLogoBounds: CR.WebLogoBounds = {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
// Cached WebLogo tooltip. Because tooltip is requested for each mouse movement, it is cached unless mouse entered
|
|
86
|
+
// bounds of other monomer in WebLogo
|
|
87
|
+
cachedWebLogoTooltip: CachedWebLogoTooltip = {bar: '', tooltip: null};
|
|
88
|
+
// Prevents from redundant grid processing
|
|
79
89
|
_layoutEventInitialized = false;
|
|
80
|
-
|
|
90
|
+
// Stores subscriptions to remove after analysis is closed
|
|
81
91
|
subs: rxjs.Subscription[] = [];
|
|
92
|
+
// Prevents from redundant unhilight operation
|
|
82
93
|
isHighlighting: boolean = false;
|
|
83
|
-
|
|
94
|
+
// Fires bitset changed to properly render selection in scroller bar
|
|
84
95
|
controlFire: boolean = false;
|
|
85
|
-
|
|
96
|
+
// Indicates the source of accordion construction
|
|
97
|
+
accordionSource: VIEWER_TYPE | null = null;
|
|
98
|
+
// sequence space viewer
|
|
99
|
+
_sequenceSpaceViewer: DG.ScatterPlotViewer | null = null;
|
|
100
|
+
/**
|
|
101
|
+
* @param dataFrame - DataFrame to use for analysis
|
|
102
|
+
*/
|
|
86
103
|
private constructor(dataFrame: DG.DataFrame) {
|
|
87
104
|
this.df = dataFrame;
|
|
88
105
|
}
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(dataFrame.temp[PeptidesModel.modelName] as PeptidesModel).init();
|
|
93
|
-
return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
get id(): string {
|
|
97
|
-
const id = this.df.getTag(C.TAGS.UUID);
|
|
98
|
-
if (id === null || id === '')
|
|
99
|
-
throw new Error('PeptidesError: UUID is not defined');
|
|
100
|
-
|
|
101
|
-
return id;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
get monomerPositionStats(): MonomerPositionStats {
|
|
105
|
-
this._monomerPositionStats ??= calculateMonomerPositionStatistics(this.df, this.positionColumns.toArray());
|
|
106
|
-
return this._monomerPositionStats!;
|
|
107
|
-
}
|
|
107
|
+
// Monomer-Position statistics cache
|
|
108
|
+
_monomerPositionStats: MonomerPositionStats | null = null;
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
get alphabet(): string {
|
|
118
|
-
const col = this.df.getCol(this.settings.sequenceColumnName!);
|
|
119
|
-
return col.getTag(bioTAGS.alphabet);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
get mutationCliffs(): type.MutationCliffs | null {
|
|
123
|
-
return this._mutationCliffs!;
|
|
124
|
-
}
|
|
110
|
+
/**
|
|
111
|
+
* @return - Monomer-Position statistics
|
|
112
|
+
*/
|
|
113
|
+
get monomerPositionStats(): MonomerPositionStats | null {
|
|
114
|
+
if (this._monomerPositionStats !== null) {
|
|
115
|
+
return this._monomerPositionStats;
|
|
116
|
+
}
|
|
125
117
|
|
|
126
|
-
set mutationCliffs(si: type.MutationCliffs | null) {
|
|
127
|
-
this._mutationCliffs = si;
|
|
128
|
-
}
|
|
129
118
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
119
|
+
const scaledActivityColumn = this.getScaledActivityColumn();
|
|
120
|
+
if (this.positionColumns === null || scaledActivityColumn === null) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
135
123
|
|
|
136
|
-
set clusterStats(clusterStats: ClusterTypeStats) {
|
|
137
|
-
this._clusterStats = clusterStats;
|
|
138
|
-
}
|
|
139
124
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return this.
|
|
125
|
+
this._monomerPositionStats ??= calculateMonomerPositionStatistics(scaledActivityColumn,
|
|
126
|
+
this.df.filter, this.positionColumns);
|
|
127
|
+
return this._monomerPositionStats;
|
|
143
128
|
}
|
|
144
129
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
130
|
+
// Analysis Table View
|
|
131
|
+
_analysisView?: DG.TableView;
|
|
148
132
|
|
|
133
|
+
/**
|
|
134
|
+
* @return - Analysis table view
|
|
135
|
+
*/
|
|
149
136
|
get analysisView(): DG.TableView {
|
|
150
137
|
if (this._analysisView === undefined) {
|
|
151
|
-
this._analysisView = wu(grok.shell.tableViews).find(({dataFrame}) => dataFrame?.getTag(
|
|
152
|
-
if (this._analysisView === undefined) {
|
|
138
|
+
this._analysisView = wu(grok.shell.tableViews).find(({dataFrame}) => dataFrame?.getTag(DG.TAGS.ID) === this.id);
|
|
139
|
+
if (typeof this._analysisView === 'undefined') {
|
|
153
140
|
this._analysisView = grok.shell.addTableView(this.df);
|
|
154
|
-
const posCols = this.positionColumns.toArray().map((col) => col.name);
|
|
155
|
-
|
|
156
|
-
for (let colIdx = 1; colIdx < this._analysisView.grid.columns.length; ++colIdx) {
|
|
157
|
-
const gridCol = this._analysisView.grid.columns.byIndex(colIdx)!;
|
|
158
|
-
gridCol.visible =
|
|
159
|
-
posCols.includes(gridCol.column!.name) || (gridCol.column!.name === C.COLUMNS_NAMES.ACTIVITY_SCALED);
|
|
160
|
-
}
|
|
161
141
|
}
|
|
162
142
|
}
|
|
163
143
|
|
|
164
|
-
if (this.df.getTag(C.TAGS.MULTIPLE_VIEWS) !== '1' && !this._layoutEventInitialized)
|
|
144
|
+
if (this.df.getTag(C.TAGS.MULTIPLE_VIEWS) !== '1' && !this._layoutEventInitialized) {
|
|
165
145
|
grok.shell.v = this._analysisView;
|
|
166
|
-
|
|
167
|
-
this._analysisView.grid.invalidate();
|
|
168
|
-
return this._analysisView;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
get mutationCliffsSelection(): type.Selection {
|
|
172
|
-
const tagSelection = this.df.getTag(C.TAGS.MUTATION_CLIFFS_SELECTION) ?? this.df.getTag(C.TAGS.SELECTION);
|
|
173
|
-
if (tagSelection === null)
|
|
174
|
-
this.initMutationCliffsSelection({notify: false});
|
|
175
|
-
this._mutationCliffsSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.MUTATION_CLIFFS_SELECTION) ?? this.df.getTag(C.TAGS.SELECTION)!);
|
|
176
|
-
return this._mutationCliffsSelection;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
set mutationCliffsSelection(selection: type.Selection) {
|
|
180
|
-
this._mutationCliffsSelection = selection;
|
|
181
|
-
// TODO: Remove in 1.14.0
|
|
182
|
-
this.df.setTag(C.TAGS.SELECTION, JSON.stringify(selection));
|
|
183
|
-
this.df.setTag(C.TAGS.MUTATION_CLIFFS_SELECTION, JSON.stringify(selection));
|
|
184
|
-
this.fireBitsetChanged();
|
|
185
|
-
|
|
186
|
-
const mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
187
|
-
mpViewer?.viewerGrid.invalidate();
|
|
188
|
-
const mprViewer = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
|
|
189
|
-
mprViewer?.viewerGrid.invalidate();
|
|
190
|
-
|
|
191
|
-
this.analysisView.grid.invalidate();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
get invariantMapSelection(): type.Selection {
|
|
195
|
-
const tagSelection = this.df.getTag(C.TAGS.INVARIANT_MAP_SELECTION) ?? this.df.getTag(C.TAGS.FILTER);
|
|
196
|
-
if (tagSelection === null)
|
|
197
|
-
this.initInvariantMapSelection({notify: false});
|
|
198
|
-
this._invariantMapSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.INVARIANT_MAP_SELECTION) ?? this.df.getTag(C.TAGS.FILTER)!);
|
|
199
|
-
return this._invariantMapSelection;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
set invariantMapSelection(selection: type.Selection) {
|
|
203
|
-
this._invariantMapSelection = selection;
|
|
204
|
-
this.df.setTag(C.TAGS.INVARIANT_MAP_SELECTION, JSON.stringify(selection));
|
|
205
|
-
// TODO: Remove in 1.14.0
|
|
206
|
-
this.df.setTag(C.TAGS.FILTER, JSON.stringify(selection));
|
|
207
|
-
this.fireBitsetChanged();
|
|
208
|
-
this.analysisView.grid.invalidate();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
get clusterSelection(): type.Selection {
|
|
212
|
-
const tagSelection = this.df.getTag(C.TAGS.CLUSTER_SELECTION);
|
|
213
|
-
if (tagSelection === null)
|
|
214
|
-
this.initClusterSelection({notify: false});
|
|
215
|
-
this._clusterSelection ??= JSON.parse(tagSelection ?? this.df.getTag(C.TAGS.CLUSTER_SELECTION)!);
|
|
216
|
-
// TODO: Remove in 1.14.0
|
|
217
|
-
if (Array.isArray(this._clusterSelection)) {
|
|
218
|
-
const newSelection: type.Selection = {};
|
|
219
|
-
newSelection[CLUSTER_TYPE.ORIGINAL] = [];
|
|
220
|
-
newSelection[CLUSTER_TYPE.CUSTOM] = [];
|
|
221
|
-
for (const cluster of this._clusterSelection) {
|
|
222
|
-
if (wu(this.customClusters).some((col) => col.name === cluster))
|
|
223
|
-
newSelection[CLUSTER_TYPE.CUSTOM].push(cluster);
|
|
224
|
-
else
|
|
225
|
-
newSelection[CLUSTER_TYPE.ORIGINAL].push(cluster);
|
|
226
|
-
}
|
|
227
|
-
this._clusterSelection = newSelection;
|
|
228
146
|
}
|
|
229
|
-
return this._clusterSelection;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
set clusterSelection(selection: type.Selection) {
|
|
233
|
-
this._clusterSelection = selection;
|
|
234
|
-
this.df.tags[C.TAGS.CLUSTER_SELECTION] = JSON.stringify(selection);
|
|
235
|
-
this.fireBitsetChanged();
|
|
236
|
-
this.analysisView.grid.invalidate();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
get splitByPos(): boolean {
|
|
240
|
-
const splitByPosFlag = (this.df.tags['distributionSplit'] || '00')[0];
|
|
241
|
-
return splitByPosFlag === '1' ? true : false;
|
|
242
|
-
}
|
|
243
147
|
|
|
244
|
-
set splitByPos(flag: boolean) {
|
|
245
|
-
const splitByMonomerFlag = (this.df.tags['distributionSplit'] || '00')[1];
|
|
246
|
-
this.df.tags['distributionSplit'] = `${flag ? 1 : 0}${splitByMonomerFlag}`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
get splitByMonomer(): boolean {
|
|
250
|
-
const splitByPosFlag = (this.df.tags['distributionSplit'] || '00')[1];
|
|
251
|
-
return splitByPosFlag === '1' ? true : false;
|
|
252
|
-
}
|
|
253
148
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.df.tags['distributionSplit'] = `${splitByMonomerFlag}${flag ? 1 : 0}`;
|
|
149
|
+
this._analysisView.grid.invalidate();
|
|
150
|
+
return this._analysisView;
|
|
257
151
|
}
|
|
258
152
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
153
|
+
// Peptides analysis settings
|
|
154
|
+
_settings: type.PeptidesSettings | null = null;
|
|
155
|
+
_sequenceSpaceCols: string[] = [];
|
|
266
156
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
157
|
+
/**
|
|
158
|
+
* @return - Peptides analysis settings
|
|
159
|
+
*/
|
|
160
|
+
get settings(): type.PeptidesSettings | null {
|
|
161
|
+
const settingsStr = this.df.getTag(C.TAGS.SETTINGS);
|
|
162
|
+
if (settingsStr == null) {
|
|
163
|
+
return null;
|
|
271
164
|
}
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
get isClusterSelectionEmpty(): boolean {
|
|
276
|
-
return (this.clusterSelection[CLUSTER_TYPE.ORIGINAL].length + this.clusterSelection[CLUSTER_TYPE.CUSTOM].length) === 0;
|
|
277
|
-
}
|
|
278
165
|
|
|
279
|
-
get customClusters(): wu.WuIterable<DG.Column<boolean>> {
|
|
280
|
-
const query: { [key: string]: string } = {};
|
|
281
|
-
query[C.TAGS.CUSTOM_CLUSTER] = '1';
|
|
282
|
-
return wu(this.df.columns.byTags(query));
|
|
283
|
-
}
|
|
284
166
|
|
|
285
|
-
|
|
286
|
-
this._settings
|
|
287
|
-
return this._settings;
|
|
167
|
+
this._settings ??= JSON.parse(settingsStr);
|
|
168
|
+
return this._settings!;
|
|
288
169
|
}
|
|
289
170
|
|
|
290
|
-
|
|
291
|
-
|
|
171
|
+
/**
|
|
172
|
+
* @param s - Peptides analysis settings
|
|
173
|
+
*/
|
|
174
|
+
set settings(s: type.PartialPeptidesSettings) {
|
|
175
|
+
const newSettingsEntries = Object.entries(s) as ([keyof type.PeptidesSettings, never])[];
|
|
176
|
+
// Holds updated settings categories
|
|
292
177
|
const updateVars: Set<string> = new Set();
|
|
293
178
|
for (const [key, value] of newSettingsEntries) {
|
|
294
|
-
this.
|
|
179
|
+
this.settings![key] = value;
|
|
295
180
|
switch (key) {
|
|
296
181
|
case 'activityColumnName':
|
|
297
|
-
case '
|
|
182
|
+
case 'activityScaling':
|
|
298
183
|
updateVars.add('activity');
|
|
299
|
-
updateVars.add('mutationCliffs');
|
|
300
184
|
updateVars.add('stats');
|
|
301
185
|
break;
|
|
302
|
-
case 'columns':
|
|
303
|
-
updateVars.add('grid');
|
|
304
|
-
break;
|
|
305
|
-
case 'maxMutations':
|
|
306
|
-
case 'minActivityDelta':
|
|
307
|
-
updateVars.add('mutationCliffs');
|
|
308
|
-
break;
|
|
309
186
|
case 'showDendrogram':
|
|
310
187
|
updateVars.add('dendrogram');
|
|
311
188
|
break;
|
|
@@ -318,164 +195,441 @@ export class PeptidesModel {
|
|
|
318
195
|
case 'showMostPotentResidues':
|
|
319
196
|
updateVars.add('mostPotentResidues');
|
|
320
197
|
break;
|
|
198
|
+
case 'columns':
|
|
199
|
+
updateVars.add('columns');
|
|
200
|
+
break;
|
|
201
|
+
case 'sequenceSpaceParams':
|
|
202
|
+
updateVars.add('sequenceSpaceParams');
|
|
321
203
|
}
|
|
322
204
|
}
|
|
205
|
+
// Write updated settings
|
|
323
206
|
this.df.setTag('settings', JSON.stringify(this._settings));
|
|
324
|
-
|
|
207
|
+
if (!this.isInitialized) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
// Apply new settings
|
|
325
213
|
for (const variable of updateVars) {
|
|
326
214
|
switch (variable) {
|
|
327
215
|
case 'activity':
|
|
328
216
|
this.createScaledCol();
|
|
329
|
-
updateViewersData = true;
|
|
330
|
-
break;
|
|
331
|
-
case 'mutationCliffs':
|
|
332
|
-
this.updateMutationCliffs();
|
|
333
217
|
break;
|
|
334
218
|
case 'stats':
|
|
335
|
-
this.
|
|
336
|
-
this.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
lstViewer?.createLogoSummaryTableGrid();
|
|
344
|
-
lstViewer?.render();
|
|
219
|
+
this.webLogoSelection = initSelection(this.positionColumns!);
|
|
220
|
+
this.webLogoBounds = {};
|
|
221
|
+
this.cachedWebLogoTooltip = {
|
|
222
|
+
bar: '',
|
|
223
|
+
tooltip: null,
|
|
224
|
+
};
|
|
225
|
+
// Invalidate monomer-position statistics. The next time it gets accessed with getter, it will be recalculated
|
|
226
|
+
this._monomerPositionStats = null;
|
|
345
227
|
break;
|
|
346
228
|
case 'dendrogram':
|
|
347
|
-
this.settings
|
|
229
|
+
this.settings!.showDendrogram ? this.addDendrogram() : this.closeViewer(VIEWER_TYPE.DENDROGRAM);
|
|
348
230
|
break;
|
|
349
231
|
case 'logoSummaryTable':
|
|
350
|
-
this.settings
|
|
232
|
+
this.settings!.showLogoSummaryTable ? this.addLogoSummaryTable() :
|
|
351
233
|
this.closeViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE);
|
|
352
234
|
break;
|
|
353
235
|
case 'monomerPosition':
|
|
354
|
-
this.settings
|
|
236
|
+
this.settings!.showMonomerPosition ? this.addMonomerPosition() :
|
|
355
237
|
this.closeViewer(VIEWER_TYPE.MONOMER_POSITION);
|
|
356
238
|
break;
|
|
357
239
|
case 'mostPotentResidues':
|
|
358
|
-
this.settings
|
|
240
|
+
this.settings!.showMostPotentResidues ? this.addMostPotentResidues() :
|
|
359
241
|
this.closeViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES);
|
|
360
242
|
break;
|
|
243
|
+
case 'columns':
|
|
244
|
+
const lst = this.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable;
|
|
245
|
+
lst._viewerGrid = null;
|
|
246
|
+
lst._logoSummaryTable = null;
|
|
247
|
+
lst.render();
|
|
248
|
+
const mpr = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as LogoSummaryTable;
|
|
249
|
+
mpr._viewerGrid = null;
|
|
250
|
+
mpr.render();
|
|
251
|
+
break;
|
|
252
|
+
case 'sequenceSpaceParams':
|
|
253
|
+
this.addSequenceSpace();
|
|
254
|
+
break;
|
|
361
255
|
}
|
|
362
256
|
}
|
|
257
|
+
}
|
|
363
258
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
lstViewer?.createLogoSummaryTableGrid();
|
|
376
|
-
lstViewer?.render();
|
|
259
|
+
// Current Monomer-Position selection that came from WebLogo in header
|
|
260
|
+
_webLogoSelection: type.Selection | null = null;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @return - Current Monomer-Position selection that came from WebLogo in header
|
|
264
|
+
*/
|
|
265
|
+
get webLogoSelection(): type.Selection {
|
|
266
|
+
const tagSelection = this.df.getTag(`${C.SUFFIXES.WL}${C.TAGS.INVARIANT_MAP_SELECTION}`);
|
|
267
|
+
this._webLogoSelection ??= tagSelection === null && this.positionColumns !== null ?
|
|
268
|
+
initSelection(this.positionColumns) : JSON.parse(tagSelection ?? `{}`);
|
|
269
|
+
return this._webLogoSelection!;
|
|
377
270
|
}
|
|
378
271
|
|
|
379
|
-
|
|
380
|
-
|
|
272
|
+
/**
|
|
273
|
+
* @param selection - Current Monomer-Position selection that came from WebLogo in header
|
|
274
|
+
*/
|
|
275
|
+
set webLogoSelection(selection: type.Selection) {
|
|
276
|
+
this._webLogoSelection = selection;
|
|
277
|
+
this.df.setTag(`${C.SUFFIXES.WL}${C.TAGS.INVARIANT_MAP_SELECTION}`, JSON.stringify(selection));
|
|
278
|
+
this.fireBitsetChanged(null);
|
|
279
|
+
this.analysisView.grid.invalidate();
|
|
381
280
|
}
|
|
382
281
|
|
|
383
|
-
|
|
384
|
-
|
|
282
|
+
/**
|
|
283
|
+
* @return - Array of columns that represent monomers at specific position in sequences
|
|
284
|
+
*/
|
|
285
|
+
get positionColumns(): DG.Column<string>[] | null {
|
|
286
|
+
const positionColumns = wu(this.df.columns.byTags({[C.TAGS.POSITION_COL]: `${true}`})).toArray();
|
|
287
|
+
if (positionColumns.length === 0) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
return positionColumns;
|
|
385
293
|
}
|
|
386
294
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
let mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
394
|
-
const currentTarget = mpViewer?.getProperty(MONOMER_POSITION_PROPERTIES.TARGET)?.get(mpViewer) as string | undefined;
|
|
395
|
-
const targetOptions = {targetCol: targetCol, currentTarget: currentTarget};
|
|
396
|
-
const mutationCliffs = await findMutations(scaledActivityCol.getRawData(), monomerCols, this.settings, targetOptions);
|
|
397
|
-
if (notify)
|
|
398
|
-
this.mutationCliffs = mutationCliffs;
|
|
399
|
-
else
|
|
400
|
-
this._mutationCliffs = mutationCliffs;
|
|
295
|
+
/**
|
|
296
|
+
* @return - DataFrame ID
|
|
297
|
+
*/
|
|
298
|
+
get id(): string {
|
|
299
|
+
return this.df.getTag(DG.TAGS.ID)!;
|
|
300
|
+
}
|
|
401
301
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
302
|
+
/**
|
|
303
|
+
* @return - Sequence alphabet
|
|
304
|
+
*/
|
|
305
|
+
get alphabet(): string {
|
|
306
|
+
const col = this.df.getCol(this.settings!.sequenceColumnName);
|
|
307
|
+
return col.getTag(bioTAGS.alphabet);
|
|
406
308
|
}
|
|
407
309
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Creates an instance of PeptidesModel or returns existing if present
|
|
312
|
+
* @param dataFrame - DataFrame to use for analysis
|
|
313
|
+
* @return - PeptidesModel instance
|
|
314
|
+
*/
|
|
315
|
+
static getInstance(dataFrame: DG.DataFrame): PeptidesModel {
|
|
316
|
+
if (dataFrame.columns.contains(C.COLUMNS_NAMES.ACTIVITY_SCALED) &&
|
|
317
|
+
!dataFrame.columns.contains(C.COLUMNS_NAMES.ACTIVITY)) {
|
|
318
|
+
dataFrame.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).name = C.COLUMNS_NAMES.ACTIVITY;
|
|
319
|
+
}
|
|
411
320
|
|
|
412
|
-
|
|
321
|
+
|
|
322
|
+
dataFrame.temp[PeptidesModel.modelName] ??= new PeptidesModel(dataFrame);
|
|
323
|
+
return dataFrame.temp[PeptidesModel.modelName] as PeptidesModel;
|
|
413
324
|
}
|
|
414
325
|
|
|
415
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Modifies WebLogo selection. If shift and ctrl keys are both pressed, it removes WebLogo from
|
|
328
|
+
* selection. If only shift key is pressed, it adds WebLogo to selection. If only ctrl key is pressed, it
|
|
329
|
+
* changes WebLogo presence in selection. If none of the keys is pressed, it sets the WebLogo as the
|
|
330
|
+
* only selected one.
|
|
331
|
+
* @param monomerPosition - cluster to modify selection with.
|
|
332
|
+
* @param options - selection options.
|
|
333
|
+
* @param notify - flag indicating if bitset changed event should fire.
|
|
334
|
+
*/
|
|
335
|
+
modifyWebLogoSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {
|
|
336
|
+
shiftPressed: false,
|
|
337
|
+
ctrlPressed: false,
|
|
338
|
+
}, notify: boolean = true): void {
|
|
339
|
+
if (notify) {
|
|
340
|
+
this.webLogoSelection = modifySelection(this.webLogoSelection, monomerPosition, options);
|
|
341
|
+
} else {
|
|
342
|
+
this._webLogoSelection = modifySelection(this.webLogoSelection, monomerPosition, options);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @return - Bitset of visible selection
|
|
348
|
+
*/
|
|
349
|
+
getVisibleSelection(): DG.BitSet {
|
|
416
350
|
return this.df.selection.clone().and(this.df.filter);
|
|
417
351
|
}
|
|
418
352
|
|
|
353
|
+
/**
|
|
354
|
+
* @return - Accordion with analysis info based on current selection
|
|
355
|
+
*/
|
|
419
356
|
createAccordion(): DG.Accordion | null {
|
|
420
357
|
const trueModel: PeptidesModel | undefined = grok.shell.t?.temp[PeptidesModel.modelName];
|
|
421
|
-
if (!trueModel)
|
|
358
|
+
if (!trueModel) {
|
|
422
359
|
return null;
|
|
360
|
+
}
|
|
423
361
|
|
|
424
|
-
|
|
362
|
+
|
|
363
|
+
const acc = ui.accordion('Peptides analysis panel');
|
|
425
364
|
acc.root.style.width = '100%';
|
|
426
|
-
const filterAndSelectionBs = trueModel.
|
|
427
|
-
const filteredTitlePart = trueModel.df.filter.anyFalse ? ` among ${trueModel.df.filter.trueCount} filtered` :
|
|
428
|
-
|
|
365
|
+
const filterAndSelectionBs = trueModel.getVisibleSelection();
|
|
366
|
+
const filteredTitlePart = trueModel.df.filter.anyFalse ? ` among ${trueModel.df.filter.trueCount} filtered` :
|
|
367
|
+
'';
|
|
368
|
+
const getSelectionString = (selection: type.Selection): string => {
|
|
369
|
+
const selectedMonomerPositions: string[] = [];
|
|
370
|
+
for (const [pos, monomerList] of Object.entries(selection)) {
|
|
371
|
+
for (const monomer of monomerList) {
|
|
372
|
+
selectedMonomerPositions.push(`${pos}:${monomer}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return selectedMonomerPositions.join(', ');
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Logo Summary Table viewer selection overview
|
|
379
|
+
const trueLSTViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
380
|
+
const selectionDescription: HTMLElement[] = [];
|
|
381
|
+
const selectedClusters: string = (trueLSTViewer === null ? [] :
|
|
382
|
+
trueLSTViewer.clusterSelection[CLUSTER_TYPE.ORIGINAL].concat(trueLSTViewer.clusterSelection[CLUSTER_TYPE.CUSTOM]))
|
|
383
|
+
.join(', ');
|
|
384
|
+
if (selectedClusters.length !== 0) {
|
|
385
|
+
selectionDescription.push(ui.h1('Logo summary table selection'));
|
|
386
|
+
selectionDescription.push(ui.divText(`Selected clusters: ${selectedClusters}`));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Monomer-Position viewer selection overview
|
|
390
|
+
const trueMPViewer = trueModel.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
391
|
+
const selectedMonomerPositions = getSelectionString(trueMPViewer?.invariantMapSelection ?? {});
|
|
392
|
+
const selectedMutationCliffs = getSelectionString(trueMPViewer?.mutationCliffsSelection ?? {});
|
|
393
|
+
if (selectedMonomerPositions.length !== 0 || selectedMutationCliffs.length !== 0) {
|
|
394
|
+
selectionDescription.push(ui.h1('Monomer-Position viewer selection'));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
if (selectedMonomerPositions.length !== 0) {
|
|
399
|
+
selectionDescription.push(ui.divText(`Selected monomer-positions: ${selectedMonomerPositions}`));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if (selectedMutationCliffs.length !== 0) {
|
|
404
|
+
selectionDescription.push(ui.divText(`Selected mutation cliffs pairs: ${selectedMutationCliffs}`));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
// Most Potent Residues viewer selection overview
|
|
409
|
+
const trueMPRViewer = trueModel.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
|
|
410
|
+
const selectedMPRMonomerPositions = getSelectionString(trueMPRViewer?.mutationCliffsSelection ?? {});
|
|
411
|
+
if (selectedMPRMonomerPositions.length !== 0) {
|
|
412
|
+
selectionDescription.push(ui.h1('Most Potent Residues viewer selection'));
|
|
413
|
+
selectionDescription.push(ui.divText(`Selected monomer-positions: ${selectedMPRMonomerPositions}`));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// WebLogo selection overview
|
|
417
|
+
const selectedMonomers = getSelectionString(trueModel.webLogoSelection);
|
|
418
|
+
if (selectedMonomers.length !== 0) {
|
|
419
|
+
selectionDescription.push(ui.h1('WebLogo selection'));
|
|
420
|
+
selectionDescription.push(ui.divText(`Selected monomers: ${selectedMonomers}`));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const descritionsHost = ui.div(ui.divV(selectionDescription));
|
|
424
|
+
acc.addTitle(ui.divV([
|
|
425
|
+
ui.h1(`${filterAndSelectionBs.trueCount} selected rows${filteredTitlePart}`),
|
|
426
|
+
descritionsHost,
|
|
427
|
+
], 'css-gap-small'));
|
|
428
|
+
|
|
429
429
|
if (filterAndSelectionBs.anyTrue) {
|
|
430
430
|
acc.addPane('Actions', () => {
|
|
431
431
|
const newView = ui.label('New view');
|
|
432
432
|
$(newView).addClass('d4-link-action');
|
|
433
433
|
newView.onclick = (): string => trueModel.createNewView();
|
|
434
|
-
newView.onmouseover =
|
|
435
|
-
|
|
434
|
+
newView.onmouseover = (ev): void =>
|
|
435
|
+
ui.tooltip.show('Creates a new view from current selection', ev.clientX + 5, ev.clientY + 5);
|
|
436
|
+
if (trueLSTViewer === null) {
|
|
437
|
+
return ui.divV([newView]);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
436
441
|
const newCluster = ui.label('New cluster');
|
|
437
442
|
$(newCluster).addClass('d4-link-action');
|
|
438
443
|
newCluster.onclick = (): void => {
|
|
439
|
-
|
|
440
|
-
if (lstViewer === null)
|
|
444
|
+
if (trueLSTViewer === null) {
|
|
441
445
|
throw new Error('Logo summary table viewer is not found');
|
|
442
|
-
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
trueLSTViewer.clusterFromSelection();
|
|
443
450
|
};
|
|
444
|
-
newCluster.onmouseover =
|
|
445
|
-
|
|
451
|
+
newCluster.onmouseover = (ev): void =>
|
|
452
|
+
ui.tooltip.show('Creates a new cluster from selection', ev.clientX + 5, ev.clientY + 5);
|
|
453
|
+
|
|
446
454
|
const removeCluster = ui.label('Remove cluster');
|
|
447
455
|
$(removeCluster).addClass('d4-link-action');
|
|
448
456
|
removeCluster.onclick = (): void => {
|
|
449
457
|
const lstViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
450
|
-
if (lstViewer === null)
|
|
458
|
+
if (lstViewer === null) {
|
|
451
459
|
throw new Error('Logo summary table viewer is not found');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
452
463
|
lstViewer.removeCluster();
|
|
453
464
|
};
|
|
454
|
-
removeCluster.onmouseover =
|
|
455
|
-
|
|
456
|
-
removeCluster.style.visibility =
|
|
465
|
+
removeCluster.onmouseover = (ev): void =>
|
|
466
|
+
ui.tooltip.show('Removes currently selected custom cluster', ev.clientX + 5, ev.clientY + 5);
|
|
467
|
+
removeCluster.style.visibility = trueLSTViewer.clusterSelection[CLUSTER_TYPE.CUSTOM].length === 0 ? 'hidden' :
|
|
468
|
+
'visible';
|
|
469
|
+
|
|
457
470
|
return ui.divV([newView, newCluster, removeCluster]);
|
|
471
|
+
}, true);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Get the source of the bitset change and find viewers that share the same parameters as source
|
|
475
|
+
let requestSource: SARViewer | LogoSummaryTable | PeptidesSettings | null = trueModel.settings;
|
|
476
|
+
const viewers: (PeptideViewer | PeptidesSettings | null)[] = [trueMPViewer, trueMPRViewer, trueLSTViewer]
|
|
477
|
+
.filter((v) => {
|
|
478
|
+
if (v === null) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
if (v.type !== this.accordionSource) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
requestSource = v;
|
|
489
|
+
return false;
|
|
458
490
|
});
|
|
491
|
+
|
|
492
|
+
if (requestSource === null) {
|
|
493
|
+
throw new Error('PeptidesError: Model is the source of accordion but is not initialized');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
if (requestSource !== trueModel.settings) {
|
|
498
|
+
viewers.push(trueModel.settings);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
const notEmpty = (v: PeptideViewer | PeptidesSettings | null): v is PeptideViewer | PeptidesSettings =>
|
|
503
|
+
v !== null && areParametersEqual(requestSource!, v) && (v !== trueModel.settings || trueModel.isInitialized);
|
|
504
|
+
const panelDataSources = viewers.filter(notEmpty);
|
|
505
|
+
panelDataSources.push(requestSource);
|
|
506
|
+
const combinedBitset: DG.BitSet | null = DG.BitSet.create(trueModel.df.rowCount);
|
|
507
|
+
for (const panelDataSource of panelDataSources) {
|
|
508
|
+
const bitset =
|
|
509
|
+
(panelDataSource === this.settings) ? getSelectionBitset(this.webLogoSelection, this.monomerPositionStats!) :
|
|
510
|
+
(panelDataSource instanceof LogoSummaryTable) ?
|
|
511
|
+
getSelectionBitset(panelDataSource.clusterSelection, panelDataSource.clusterStats) :
|
|
512
|
+
(panelDataSource instanceof SARViewer) ?
|
|
513
|
+
getSelectionBitset(panelDataSource.mutationCliffsSelection,
|
|
514
|
+
mutationCliffsToMaskInfo(panelDataSource.mutationCliffs ?? new Map(), trueModel.df.rowCount)) :
|
|
515
|
+
null;
|
|
516
|
+
if (bitset !== null) {
|
|
517
|
+
combinedBitset.or(bitset);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if (panelDataSource instanceof MonomerPosition) {
|
|
522
|
+
const invariantMapSelectionBitset = getSelectionBitset(panelDataSource.invariantMapSelection,
|
|
523
|
+
panelDataSource.monomerPositionStats);
|
|
524
|
+
if (invariantMapSelectionBitset !== null) {
|
|
525
|
+
combinedBitset.or(invariantMapSelectionBitset);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
459
528
|
}
|
|
460
|
-
const table = trueModel.df.filter.anyFalse ? trueModel.df.clone(trueModel.df.filter, null, true) : trueModel.df;
|
|
461
|
-
acc.addPane('Mutation Cliffs pairs', () => mutationCliffsWidget(trueModel.df, trueModel).root);
|
|
462
|
-
acc.addPane('Distribution', () => getDistributionWidget(table, trueModel).root);
|
|
463
|
-
acc.addPane('Selection', () => getSelectionWidget(trueModel.df, trueModel));
|
|
464
529
|
|
|
530
|
+
const sarViewer = requestSource as any as SARViewer | LogoSummaryTable;
|
|
531
|
+
if (requestSource !== trueModel.settings && !(sarViewer instanceof LogoSummaryTable) &&
|
|
532
|
+
sarViewer.mutationCliffs != null) {
|
|
533
|
+
// MC and Selection are left
|
|
534
|
+
acc.addPane('Mutation Cliffs pairs', () => mutationCliffsWidget(trueModel.df, {
|
|
535
|
+
mutationCliffs: sarViewer.mutationCliffs!,
|
|
536
|
+
mutationCliffsSelection: sarViewer.mutationCliffsSelection,
|
|
537
|
+
gridColumns: trueModel.analysisView.grid.columns,
|
|
538
|
+
sequenceColumnName: sarViewer.sequenceColumnName,
|
|
539
|
+
positionColumns: sarViewer.positionColumns,
|
|
540
|
+
activityCol: sarViewer.getScaledActivityColumn(),
|
|
541
|
+
}).root, true);
|
|
542
|
+
}
|
|
543
|
+
const isModelSource = requestSource === trueModel.settings;
|
|
544
|
+
const totalMonomerPositionSelection = isModelSource ? this.webLogoSelection :
|
|
545
|
+
(requestSource instanceof MonomerPosition) ? requestSource.invariantMapSelection : {};
|
|
546
|
+
const clusterSelection = (requestSource instanceof LogoSummaryTable) ? requestSource.clusterSelection :
|
|
547
|
+
trueLSTViewer?.clusterSelection ?? {};
|
|
548
|
+
acc.addPane('Distribution', () => getDistributionWidget(trueModel.df, {
|
|
549
|
+
peptideSelection: combinedBitset,
|
|
550
|
+
columns: isModelSource ? trueModel.settings!.columns ?? {} :
|
|
551
|
+
(requestSource as PeptideViewer).getAggregationColumns(),
|
|
552
|
+
activityCol: isModelSource ? trueModel.getScaledActivityColumn()! :
|
|
553
|
+
(requestSource as PeptideViewer).getScaledActivityColumn(),
|
|
554
|
+
monomerPositionSelection: totalMonomerPositionSelection,
|
|
555
|
+
clusterSelection: clusterSelection,
|
|
556
|
+
clusterColName: trueLSTViewer?.clustersColumnName,
|
|
557
|
+
}), true);
|
|
558
|
+
const areObjectsEqual = (o1?: AggregationColumns | null, o2?: AggregationColumns | null): boolean => {
|
|
559
|
+
if (o1 == null || o2 == null) {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
for (const [key, value] of Object.entries(o1)) {
|
|
565
|
+
if (value !== o2[key]) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
};
|
|
571
|
+
acc.addPane('Selection', () => getSelectionWidget(trueModel.df, {
|
|
572
|
+
positionColumns: isModelSource ? trueModel.positionColumns! :
|
|
573
|
+
(requestSource as SARViewer | LogoSummaryTable).positionColumns,
|
|
574
|
+
columns: isModelSource ? trueModel.settings!.columns ?? {} :
|
|
575
|
+
(requestSource as SARViewer | LogoSummaryTable).getAggregationColumns(),
|
|
576
|
+
activityColumn: isModelSource ? trueModel.getScaledActivityColumn()! :
|
|
577
|
+
(requestSource as SARViewer | LogoSummaryTable).getScaledActivityColumn(),
|
|
578
|
+
gridColumns: trueModel.analysisView.grid.columns,
|
|
579
|
+
colorPalette: pickUpPalette(trueModel.df.getCol(isModelSource ? trueModel.settings!.sequenceColumnName :
|
|
580
|
+
(requestSource as SARViewer | LogoSummaryTable).sequenceColumnName)),
|
|
581
|
+
tableSelection: trueModel.getCombinedSelection(),
|
|
582
|
+
isAnalysis: trueModel.settings !== null && (isModelSource ||
|
|
583
|
+
areObjectsEqual(trueModel.settings.columns, (requestSource as PeptideViewer).getAggregationColumns())),
|
|
584
|
+
}), true);
|
|
465
585
|
return acc;
|
|
466
586
|
}
|
|
467
587
|
|
|
588
|
+
/**
|
|
589
|
+
* @param [isFiltered] - Whether to return filtered activity column
|
|
590
|
+
* @return - Scaled activity column
|
|
591
|
+
*/
|
|
592
|
+
getScaledActivityColumn(isFiltered: boolean = false): DG.Column<number> | null {
|
|
593
|
+
const scaledActivityColumn = this.df.col(C.COLUMNS_NAMES.ACTIVITY);
|
|
594
|
+
if (isFiltered && scaledActivityColumn !== null) {
|
|
595
|
+
return DG.DataFrame.fromColumns([scaledActivityColumn]).clone(this.df.filter)
|
|
596
|
+
.getCol(scaledActivityColumn.name) as DG.Column<number>;
|
|
597
|
+
}
|
|
598
|
+
return scaledActivityColumn as DG.Column<number> | null;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Sets grid properties such as column semtypes, visibility, order, width, renderers and tooltips
|
|
603
|
+
*/
|
|
468
604
|
updateGrid(): void {
|
|
469
605
|
this.joinDataFrames();
|
|
470
606
|
this.createScaledCol();
|
|
471
|
-
// this.setWebLogoInteraction();
|
|
472
607
|
this.webLogoBounds = {};
|
|
473
608
|
|
|
474
|
-
CR.
|
|
609
|
+
const cellRendererOptions: CR.WebLogoCellRendererOptions = {
|
|
610
|
+
selectionCallback: (monomerPosition: type.SelectionItem, options: type.SelectionOptions): void =>
|
|
611
|
+
this.modifyWebLogoSelection(monomerPosition, options),
|
|
612
|
+
unhighlightCallback: (): void => this.unhighlight(),
|
|
613
|
+
colorPalette: () => pickUpPalette(this.df.getCol(this.settings!.sequenceColumnName)),
|
|
614
|
+
webLogoBounds: () => this.webLogoBounds,
|
|
615
|
+
cachedWebLogoTooltip: () => this.cachedWebLogoTooltip,
|
|
616
|
+
highlightCallback: (mp: type.SelectionItem, df: DG.DataFrame, mpStats: MonomerPositionStats): void =>
|
|
617
|
+
highlightMonomerPosition(mp, df, mpStats),
|
|
618
|
+
isSelectionTable: false,
|
|
619
|
+
headerSelectedMonomers: () => this.webLogoSelectedMonomers,
|
|
620
|
+
};
|
|
621
|
+
if (this.monomerPositionStats === null || this.positionColumns === null) {
|
|
622
|
+
throw new Error('PeptidesError: Could not updage grid: monomerPositionStats or positionColumns are null');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
CR.setWebLogoRenderer(this.analysisView.grid, this.monomerPositionStats, this.positionColumns,
|
|
627
|
+
this.getScaledActivityColumn()!, cellRendererOptions);
|
|
475
628
|
if (!this._layoutEventInitialized) {
|
|
476
629
|
grok.events.onViewLayoutApplied.subscribe((layout) => {
|
|
477
|
-
if (layout.view.id === this.analysisView.id)
|
|
630
|
+
if (layout.view.id === this.analysisView.id) {
|
|
478
631
|
this.updateGrid();
|
|
632
|
+
}
|
|
479
633
|
});
|
|
480
634
|
this._layoutEventInitialized = true;
|
|
481
635
|
}
|
|
@@ -485,45 +639,22 @@ export class PeptidesModel {
|
|
|
485
639
|
this.setGridProperties();
|
|
486
640
|
}
|
|
487
641
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const tempSelection: type.Selection = {};
|
|
492
|
-
const positionColumns = this.positionColumns.toArray().map((col) => col.name);
|
|
493
|
-
for (const pos of positionColumns)
|
|
494
|
-
tempSelection[pos] = [];
|
|
495
|
-
|
|
496
|
-
if (options.notify)
|
|
497
|
-
this.invariantMapSelection = tempSelection;
|
|
498
|
-
else
|
|
499
|
-
this._invariantMapSelection = tempSelection;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
initMutationCliffsSelection(options: {notify?: boolean} = {}): void {
|
|
503
|
-
options.notify ??= true;
|
|
504
|
-
|
|
505
|
-
const tempSelection: type.Selection = {};
|
|
506
|
-
const positionColumns = this.positionColumns.toArray().map((col) => col.name);
|
|
507
|
-
for (const pos of positionColumns)
|
|
508
|
-
tempSelection[pos] = [];
|
|
509
|
-
|
|
510
|
-
if (options.notify)
|
|
511
|
-
this.mutationCliffsSelection = tempSelection;
|
|
512
|
-
else
|
|
513
|
-
this._mutationCliffsSelection = tempSelection;
|
|
514
|
-
}
|
|
515
|
-
|
|
642
|
+
/**
|
|
643
|
+
* Splits sequences and adds position columns to this.df.
|
|
644
|
+
*/
|
|
516
645
|
joinDataFrames(): void {
|
|
517
646
|
// append splitSeqDf columns to source table and make sure columns are not added more than once
|
|
518
647
|
const name = this.df.name;
|
|
519
648
|
const cols = this.df.columns;
|
|
520
|
-
const splitSeqDf = this.
|
|
649
|
+
const splitSeqDf = splitAlignedSequences(this.df.getCol(this.settings!.sequenceColumnName));
|
|
521
650
|
const positionColumns = splitSeqDf.columns.names();
|
|
522
651
|
for (const colName of positionColumns) {
|
|
523
652
|
let col = this.df.col(colName);
|
|
524
653
|
const newCol = splitSeqDf.getCol(colName);
|
|
525
|
-
if (col !== null)
|
|
654
|
+
if (col !== null) {
|
|
526
655
|
cols.remove(colName);
|
|
656
|
+
}
|
|
657
|
+
|
|
527
658
|
|
|
528
659
|
const newColCat = newCol.categories;
|
|
529
660
|
const newColData = newCol.getRawData();
|
|
@@ -535,143 +666,140 @@ export class PeptidesModel {
|
|
|
535
666
|
this.df.name = name;
|
|
536
667
|
}
|
|
537
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Creates scaled activity column
|
|
671
|
+
*/
|
|
538
672
|
createScaledCol(): void {
|
|
539
673
|
const sourceGrid = this.analysisView.grid;
|
|
540
|
-
const scaledCol = scaleActivity(this.df.getCol(this.settings
|
|
674
|
+
const scaledCol = scaleActivity(this.df.getCol(this.settings!.activityColumnName),
|
|
675
|
+
this.settings!.activityScaling);
|
|
541
676
|
//TODO: make another func
|
|
542
|
-
this.df.columns.replace(
|
|
677
|
+
this.df.columns.replace(COLUMNS_NAMES.ACTIVITY, scaledCol);
|
|
543
678
|
|
|
544
679
|
sourceGrid.columns.setOrder([scaledCol.name]);
|
|
545
680
|
}
|
|
546
681
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (options.notify)
|
|
554
|
-
this.clusterSelection = newClusterSelection;
|
|
555
|
-
else
|
|
556
|
-
this._clusterSelection = newClusterSelection;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
highlightMonomerPosition(monomerPosition: type.SelectionItem): void {
|
|
560
|
-
const bitArray = new BitArray(this.df.rowCount);
|
|
561
|
-
if (monomerPosition.positionOrClusterType === C.COLUMNS_NAMES.MONOMER) {
|
|
562
|
-
const positionStats = Object.values(this.monomerPositionStats);
|
|
563
|
-
for (const posStat of positionStats) {
|
|
564
|
-
const monomerPositionStats = (posStat as PositionStats)[monomerPosition.monomerOrCluster];
|
|
565
|
-
if (monomerPositionStats ?? false)
|
|
566
|
-
bitArray.or(monomerPositionStats!.mask);
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
const monomerPositionStats = this.monomerPositionStats[monomerPosition.positionOrClusterType]![monomerPosition.monomerOrCluster];
|
|
570
|
-
if (monomerPositionStats ?? false)
|
|
571
|
-
bitArray.or(monomerPositionStats!.mask);
|
|
682
|
+
/**
|
|
683
|
+
* Resets rows highlighting
|
|
684
|
+
*/
|
|
685
|
+
unhighlight(): void {
|
|
686
|
+
if (!this.isHighlighting) {
|
|
687
|
+
return;
|
|
572
688
|
}
|
|
573
689
|
|
|
574
|
-
this.df.rows.highlight((i) => bitArray.getBit(i));
|
|
575
|
-
this.isHighlighting = true;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
highlightCluster(cluster: type.SelectionItem): void {
|
|
579
|
-
const bitArray = this.clusterStats[cluster.positionOrClusterType as ClusterType][cluster.monomerOrCluster].mask;
|
|
580
|
-
this.df.rows.highlight((i) => bitArray.getBit(i));
|
|
581
|
-
this.isHighlighting = true;
|
|
582
|
-
}
|
|
583
690
|
|
|
584
|
-
unhighlight(): void {
|
|
585
|
-
if (!this.isHighlighting)
|
|
586
|
-
return;
|
|
587
691
|
this.df.rows.highlight(null);
|
|
588
692
|
this.isHighlighting = false;
|
|
589
693
|
}
|
|
590
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Sets tooltips to analysis grid
|
|
697
|
+
*/
|
|
591
698
|
setTooltips(): void {
|
|
592
699
|
this.analysisView.grid.onCellTooltip((cell, x, y) => {
|
|
593
|
-
if (cell.isColHeader && cell.tableColumn
|
|
700
|
+
if (cell.isColHeader && cell.tableColumn?.semType === C.SEM_TYPES.MONOMER) {
|
|
594
701
|
return true;
|
|
595
|
-
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
if (!(cell.isTableCell && cell.tableColumn?.semType === C.SEM_TYPES.MONOMER)) {
|
|
596
706
|
return false;
|
|
707
|
+
}
|
|
708
|
+
|
|
597
709
|
|
|
598
710
|
showMonomerTooltip(cell.cell.value, x, y);
|
|
599
711
|
return true;
|
|
600
712
|
});
|
|
601
713
|
}
|
|
602
714
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
for (const [position, monomerList] of Object.entries(this.invariantMapSelection)) {
|
|
715
|
+
/**
|
|
716
|
+
* Builds total analysis selection that comes from viewers and components
|
|
717
|
+
* @return - Total analysis selection
|
|
718
|
+
*/
|
|
719
|
+
getCombinedSelection(): DG.BitSet {
|
|
720
|
+
const combinedSelection = new BitArray(this.df.rowCount, false);
|
|
721
|
+
// Invariant map selection
|
|
722
|
+
const addInvariantMapSelection = (selection: type.Selection, stats: MonomerPositionStats | null): void => {
|
|
723
|
+
for (const [position, monomerList] of Object.entries(selection)) {
|
|
613
724
|
for (const monomer of monomerList) {
|
|
614
|
-
const
|
|
725
|
+
const positionStats = stats?.[position];
|
|
726
|
+
if (typeof positionStats === 'undefined') {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
const monomerPositionStats = positionStats[monomer];
|
|
732
|
+
if (typeof monomerPositionStats === 'undefined') {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
|
|
615
737
|
combinedSelection.or(monomerPositionStats.mask);
|
|
616
738
|
}
|
|
617
739
|
}
|
|
740
|
+
};
|
|
618
741
|
|
|
619
|
-
|
|
620
|
-
|
|
742
|
+
addInvariantMapSelection(this.webLogoSelection, this.monomerPositionStats);
|
|
743
|
+
const mpViewer = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
744
|
+
addInvariantMapSelection(mpViewer?.invariantMapSelection ?? {}, mpViewer?.monomerPositionStats ?? null);
|
|
745
|
+
|
|
746
|
+
// Mutation cliffs selection
|
|
747
|
+
const addMutationCliffsSelection = (selection: type.Selection, mc: type.MutationCliffs | null): void => {
|
|
748
|
+
for (const [position, monomerList] of Object.entries(selection)) {
|
|
621
749
|
for (const monomer of monomerList) {
|
|
622
|
-
const substitutions =
|
|
623
|
-
if (substitutions === null)
|
|
750
|
+
const substitutions = mc?.get(monomer)?.get(position) ?? null;
|
|
751
|
+
if (substitutions === null) {
|
|
624
752
|
continue;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
|
|
625
756
|
for (const [key, value] of substitutions.entries()) {
|
|
626
757
|
combinedSelection.setTrue(key);
|
|
627
|
-
for (const v of value)
|
|
758
|
+
for (const v of value) {
|
|
628
759
|
combinedSelection.setTrue(v);
|
|
760
|
+
}
|
|
629
761
|
}
|
|
630
762
|
}
|
|
631
763
|
}
|
|
764
|
+
};
|
|
765
|
+
addMutationCliffsSelection(mpViewer?.mutationCliffsSelection ?? {}, mpViewer?.mutationCliffs ?? null);
|
|
632
766
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
767
|
+
const mprViewer = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
|
|
768
|
+
addMutationCliffsSelection(mprViewer?.mutationCliffsSelection ?? {}, mprViewer?.mutationCliffs ?? null);
|
|
769
|
+
|
|
770
|
+
// Cluster selection
|
|
771
|
+
const lstViewer = this.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
772
|
+
for (const clustType of Object.keys(lstViewer?.clusterSelection ?? {})) {
|
|
773
|
+
for (const clust of lstViewer!.clusterSelection[clustType] ?? []) {
|
|
774
|
+
const clusterStats = lstViewer!.clusterStats[clustType as CLUSTER_TYPE][clust];
|
|
775
|
+
combinedSelection.or(clusterStats.mask);
|
|
639
776
|
}
|
|
777
|
+
}
|
|
640
778
|
|
|
641
|
-
|
|
642
|
-
|
|
779
|
+
return DG.BitSet.fromBytes(combinedSelection.buffer.buffer, combinedSelection.length);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Sets selection and filter changed callbacks
|
|
785
|
+
*/
|
|
786
|
+
setBitsetCallback(): void {
|
|
787
|
+
if (this.isBitsetChangedInitialized) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
643
790
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (this.latestSelectionItem.kind === SELECTION_MODE.INVARIANT_MAP) {
|
|
648
|
-
const monomerPositionStats = this.monomerPositionStats[this.latestSelectionItem.positionOrClusterType]![this.latestSelectionItem.monomerOrCluster]!;
|
|
649
|
-
return DG.BitSet.fromBytes(monomerPositionStats.mask.buffer.buffer, monomerPositionStats.mask.length);
|
|
650
|
-
} else if (this.latestSelectionItem.kind === SELECTION_MODE.MUTATION_CLIFFS) {
|
|
651
|
-
const substitutions = this.mutationCliffs?.get(this.latestSelectionItem.monomerOrCluster)?.get(this.latestSelectionItem.positionOrClusterType) ?? null;
|
|
652
|
-
if (substitutions === null)
|
|
653
|
-
throw new Error(`Couldn't find substitutions for ${this.latestSelectionItem.monomerOrCluster} at ${this.latestSelectionItem.positionOrClusterType}`);
|
|
654
|
-
const latestSelection = new BitArray(this.df.rowCount, false);
|
|
655
|
-
for (const [key, value] of substitutions.entries()) {
|
|
656
|
-
latestSelection.setTrue(key);
|
|
657
|
-
for (const v of value)
|
|
658
|
-
latestSelection.setTrue(v);
|
|
659
|
-
}
|
|
660
|
-
return DG.BitSet.fromBytes(latestSelection.buffer.buffer, latestSelection.length);
|
|
661
|
-
} else if (this.latestSelectionItem.kind === 'Cluster') {
|
|
662
|
-
const clusterStats = this.clusterStats[this.latestSelectionItem.positionOrClusterType as CLUSTER_TYPE][this.latestSelectionItem.monomerOrCluster]!;
|
|
663
|
-
return DG.BitSet.fromBytes(clusterStats.mask.buffer.buffer, clusterStats.mask.length);
|
|
664
|
-
}
|
|
665
|
-
throw new Error(`Unknown selection kind: ${this.latestSelectionItem.kind}`);
|
|
666
|
-
};
|
|
791
|
+
|
|
792
|
+
const selection = this.df.selection;
|
|
793
|
+
const filter = this.df.filter;
|
|
667
794
|
|
|
668
795
|
const showAccordion = (): void => {
|
|
669
796
|
const acc = this.createAccordion();
|
|
670
|
-
if (acc === null)
|
|
797
|
+
if (acc === null) {
|
|
671
798
|
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
|
|
672
802
|
grok.shell.o = acc.root;
|
|
673
|
-
for (const pane of acc.panes)
|
|
674
|
-
pane.expanded = true;
|
|
675
803
|
};
|
|
676
804
|
|
|
677
805
|
selection.onChanged.subscribe(() => {
|
|
@@ -679,87 +807,163 @@ export class PeptidesModel {
|
|
|
679
807
|
this.controlFire = false;
|
|
680
808
|
return;
|
|
681
809
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
810
|
+
try {
|
|
811
|
+
if (!this.isUserChangedSelection) {
|
|
812
|
+
selection.copyFrom(this.getCombinedSelection(), false);
|
|
813
|
+
}
|
|
814
|
+
} catch (e) {
|
|
815
|
+
_package.logger.debug('Peptides: Error on selection changed');
|
|
816
|
+
_package.logger.debug(e as string);
|
|
817
|
+
} finally {
|
|
818
|
+
showAccordion();
|
|
819
|
+
}
|
|
686
820
|
});
|
|
687
821
|
|
|
688
822
|
filter.onChanged.subscribe(() => {
|
|
689
|
-
|
|
690
|
-
this.controlFire
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
lstViewer.
|
|
696
|
-
|
|
823
|
+
try {
|
|
824
|
+
if (this.controlFire) {
|
|
825
|
+
this.controlFire = false;
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const lstViewer = this.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
829
|
+
if (lstViewer !== null && typeof lstViewer.model !== 'undefined') {
|
|
830
|
+
lstViewer.createLogoSummaryTableGrid();
|
|
831
|
+
lstViewer.render();
|
|
832
|
+
}
|
|
833
|
+
} catch (e) {
|
|
834
|
+
_package.logger.debug('Peptides: Error on filter changed');
|
|
835
|
+
_package.logger.debug(e as string);
|
|
836
|
+
} finally {
|
|
837
|
+
showAccordion();
|
|
697
838
|
}
|
|
698
|
-
showAccordion();
|
|
699
839
|
});
|
|
700
840
|
|
|
701
841
|
this.isBitsetChangedInitialized = true;
|
|
702
842
|
}
|
|
703
843
|
|
|
704
|
-
|
|
844
|
+
/**
|
|
845
|
+
* Fires bitset changed event and rebuilds accordion in context panel
|
|
846
|
+
* @param source - Source of bitset changed event
|
|
847
|
+
* @param fireFilterChanged - Whether to fire filter changed event
|
|
848
|
+
*/
|
|
849
|
+
fireBitsetChanged(source: VIEWER_TYPE | null, fireFilterChanged: boolean = false): void {
|
|
850
|
+
this.accordionSource = source;
|
|
851
|
+
if (!this.isBitsetChangedInitialized) {
|
|
852
|
+
this.setBitsetCallback();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
|
|
705
856
|
this.isUserChangedSelection = false;
|
|
706
857
|
this.df.selection.fireChanged();
|
|
707
|
-
if (fireFilterChanged)
|
|
858
|
+
if (fireFilterChanged) {
|
|
708
859
|
this.df.filter.fireChanged();
|
|
860
|
+
}
|
|
861
|
+
|
|
709
862
|
|
|
710
863
|
// Fire bitset changed event again to update UI
|
|
711
864
|
this.controlFire = true;
|
|
712
865
|
this.df.selection.fireChanged();
|
|
713
|
-
if (fireFilterChanged)
|
|
866
|
+
if (fireFilterChanged) {
|
|
714
867
|
this.df.filter.fireChanged();
|
|
868
|
+
}
|
|
869
|
+
|
|
715
870
|
|
|
716
|
-
this.
|
|
871
|
+
this.isUserChangedSelection = true;
|
|
872
|
+
this.webLogoSelectedMonomers = calculateSelected(this.df);
|
|
717
873
|
}
|
|
718
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Sets grid properties such
|
|
877
|
+
* @param props - Grid properties
|
|
878
|
+
*/
|
|
719
879
|
setGridProperties(props?: DG.IGridLookSettings): void {
|
|
720
880
|
const sourceGrid = this.analysisView.grid;
|
|
721
881
|
const sourceGridProps = sourceGrid.props;
|
|
722
882
|
sourceGridProps.allowColSelection = props?.allowColSelection ?? false;
|
|
723
883
|
sourceGridProps.allowEdit = props?.allowEdit ?? false;
|
|
884
|
+
sourceGridProps.showReadOnlyNotifications = props?.showReadOnlyNotifications ?? false;
|
|
724
885
|
sourceGridProps.showCurrentRowIndicator = props?.showCurrentRowIndicator ?? false;
|
|
725
|
-
|
|
726
|
-
|
|
886
|
+
const positionCols = this.positionColumns;
|
|
887
|
+
if (positionCols === null) {
|
|
888
|
+
throw new Error('PeptidesError: Could not set grid properties: positionColumns are null');
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
|
|
727
892
|
let maxWidth = 10;
|
|
728
893
|
const canvasContext = sourceGrid.canvas.getContext('2d');
|
|
894
|
+
if (canvasContext === null) {
|
|
895
|
+
throw new Error('PeptidesError: Could not set grid properties: canvas context is null');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
|
|
729
899
|
for (const positionCol of positionCols) {
|
|
730
900
|
// Longest category
|
|
731
901
|
const maxCategory = monomerToShort(positionCol.categories.reduce((a, b) => a.length > b.length ? a : b), 6);
|
|
732
902
|
// Measure text width of longest category
|
|
733
|
-
const width = Math.ceil(canvasContext
|
|
903
|
+
const width = Math.ceil(canvasContext.measureText(maxCategory).width);
|
|
734
904
|
maxWidth = Math.max(maxWidth, width);
|
|
735
905
|
}
|
|
906
|
+
|
|
907
|
+
const posCols = positionCols.map((col) => col.name);
|
|
908
|
+
for (let colIdx = 1; colIdx < this.analysisView.grid.columns.length; ++colIdx) {
|
|
909
|
+
const gridCol = this.analysisView.grid.columns.byIndex(colIdx);
|
|
910
|
+
if (gridCol === null) {
|
|
911
|
+
throw new Error(`PeptidesError: Could not get analysis view: grid column with index '${colIdx}' is null`);
|
|
912
|
+
} else if (gridCol.column === null) {
|
|
913
|
+
throw new Error(`PeptidesError: Could not get analysis view: grid column with index '${colIdx}' has null ` +
|
|
914
|
+
`column`);
|
|
915
|
+
}
|
|
916
|
+
gridCol.visible = posCols.includes(gridCol.column.name) || (gridCol.column.name === C.COLUMNS_NAMES.ACTIVITY);
|
|
917
|
+
}
|
|
918
|
+
|
|
736
919
|
setTimeout(() => {
|
|
737
|
-
for (const positionCol of positionCols)
|
|
738
|
-
sourceGrid.col(positionCol.name)
|
|
920
|
+
for (const positionCol of positionCols) {
|
|
921
|
+
const gridCol = sourceGrid.col(positionCol.name);
|
|
922
|
+
if (gridCol === null) {
|
|
923
|
+
throw new Error(`PeptidesError: Could not set column width: grid column '${positionCol.name}' is null`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
gridCol.width = maxWidth + 15;
|
|
928
|
+
}
|
|
739
929
|
}, 100);
|
|
740
930
|
}
|
|
741
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Closes peptides viewer
|
|
934
|
+
* @param viewerType - Viewer type to close
|
|
935
|
+
*/
|
|
742
936
|
closeViewer(viewerType: VIEWER_TYPE): void {
|
|
743
937
|
const viewer = this.findViewer(viewerType);
|
|
744
938
|
viewer?.detach();
|
|
745
939
|
viewer?.close();
|
|
746
940
|
}
|
|
747
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Finds viewer node in the analysis view
|
|
944
|
+
* @param viewerType - Viewer type to find
|
|
945
|
+
* @return - Viewer node or null if not found
|
|
946
|
+
*/
|
|
748
947
|
findViewerNode(viewerType: VIEWER_TYPE): DG.DockNode | null {
|
|
749
948
|
for (const node of this.analysisView.dockManager.rootNode.children) {
|
|
750
|
-
if (node.container.containerElement.innerHTML.includes(viewerType))
|
|
949
|
+
if (node.container.containerElement.innerHTML.includes(viewerType)) {
|
|
751
950
|
return node;
|
|
951
|
+
}
|
|
752
952
|
}
|
|
753
953
|
return null;
|
|
754
954
|
}
|
|
755
955
|
|
|
956
|
+
/**
|
|
957
|
+
* Adds Dendrogram viewer to the analysis view
|
|
958
|
+
* @returns
|
|
959
|
+
*/
|
|
756
960
|
async addDendrogram(): Promise<void> {
|
|
757
961
|
const pi = DG.TaskBarProgressIndicator.create('Calculating distance matrix...');
|
|
758
962
|
try {
|
|
759
|
-
const pepColValues: string[] = this.df.getCol(this.settings
|
|
963
|
+
const pepColValues: string[] = this.df.getCol(this.settings!.sequenceColumnName).toList();
|
|
760
964
|
this._dm ??= new DistanceMatrix(await createDistanceMatrixWorker(pepColValues, StringMetricsNames.Levenshtein));
|
|
761
965
|
const leafCol = this.df.col('~leaf-id') ?? this.df.columns.addNewString('~leaf-id').init((i) => i.toString());
|
|
762
|
-
const treeHelper: ITreeHelper = getTreeHelperInstance()
|
|
966
|
+
const treeHelper: ITreeHelper = getTreeHelperInstance();
|
|
763
967
|
const treeNode = await treeHelper.hierarchicalClusteringByDistance(this._dm, 'ward');
|
|
764
968
|
|
|
765
969
|
this.df.setTag(treeTAGS.NEWICK, treeHelper.toNewick(treeNode));
|
|
@@ -775,10 +979,17 @@ export class PeptidesModel {
|
|
|
775
979
|
}
|
|
776
980
|
}
|
|
777
981
|
|
|
778
|
-
/**
|
|
779
|
-
|
|
780
|
-
|
|
982
|
+
/**
|
|
983
|
+
* Analysis initializer
|
|
984
|
+
* @param settings - Analysis settings
|
|
985
|
+
*/
|
|
986
|
+
init(settings: type.PeptidesSettings): void {
|
|
987
|
+
if (this.isInitialized) {
|
|
781
988
|
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
this.settings = settings;
|
|
782
993
|
this.isInitialized = true;
|
|
783
994
|
|
|
784
995
|
if (!this.isRibbonSet && this.df.getTag(C.TAGS.MULTIPLE_VIEWS) !== '1') {
|
|
@@ -790,88 +1001,131 @@ export class PeptidesModel {
|
|
|
790
1001
|
}
|
|
791
1002
|
|
|
792
1003
|
this.subs.push(grok.events.onAccordionConstructed.subscribe((acc) => {
|
|
793
|
-
if (!(grok.shell.o instanceof DG.SemanticValue || (grok.shell.o instanceof DG.Column &&
|
|
1004
|
+
if (!(grok.shell.o instanceof DG.SemanticValue || (grok.shell.o instanceof DG.Column &&
|
|
1005
|
+
this.df.columns.toList().includes(grok.shell.o)))) {
|
|
794
1006
|
return;
|
|
1007
|
+
}
|
|
795
1008
|
|
|
796
|
-
const actionsPane = acc.getPane('Actions');
|
|
797
1009
|
|
|
1010
|
+
const actionsPane = acc.getPane('Actions');
|
|
798
1011
|
const actionsHost = $(actionsPane.root).find('.d4-flex-col');
|
|
799
1012
|
const calculateIdentity = ui.label('Calculate identity');
|
|
800
1013
|
calculateIdentity.classList.add('d4-link-action');
|
|
801
|
-
ui.tooltip.bind(calculateIdentity,
|
|
1014
|
+
ui.tooltip.bind(calculateIdentity,
|
|
1015
|
+
'Adds a column with fractions of matching monomers against sequence in the current row');
|
|
802
1016
|
calculateIdentity.onclick = (): void => {
|
|
803
|
-
const seqCol = this.df.getCol(this.settings
|
|
804
|
-
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.IDENTITY)
|
|
1017
|
+
const seqCol = this.df.getCol(this.settings!.sequenceColumnName);
|
|
1018
|
+
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.IDENTITY)
|
|
1019
|
+
.then((col: DG.Column<number>) => col.setTag(C.TAGS.IDENTITY_TEMPLATE, seqCol.get(this.df.currentRowIdx)))
|
|
1020
|
+
.catch((e) => _package.logger.debug(e));
|
|
805
1021
|
};
|
|
806
1022
|
actionsHost.append(ui.span([calculateIdentity], 'd4-markdown-row'));
|
|
807
1023
|
|
|
808
1024
|
const calculateSimilarity = ui.label('Calculate similarity');
|
|
809
1025
|
calculateSimilarity.classList.add('d4-link-action');
|
|
810
|
-
ui.tooltip.bind(calculateSimilarity,
|
|
1026
|
+
ui.tooltip.bind(calculateSimilarity,
|
|
1027
|
+
'Adds a column with sequence similarity scores against sequence in the current row');
|
|
811
1028
|
calculateSimilarity.onclick = (): void => {
|
|
812
|
-
const seqCol = this.df.getCol(this.settings
|
|
813
|
-
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.SIMILARITY)
|
|
1029
|
+
const seqCol = this.df.getCol(this.settings!.sequenceColumnName);
|
|
1030
|
+
calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.SIMILARITY)
|
|
1031
|
+
.then((col: DG.Column<number>) => col.setTag(C.TAGS.SIMILARITY_TEMPLATE, seqCol.get(this.df.currentRowIdx)))
|
|
1032
|
+
.catch((e) => _package.logger.debug(e));
|
|
814
1033
|
};
|
|
815
1034
|
actionsHost.append(ui.span([calculateSimilarity], 'd4-markdown-row'));
|
|
816
1035
|
}));
|
|
817
1036
|
|
|
818
1037
|
this.subs.push(grok.events.onViewRemoved.subscribe((view) => {
|
|
819
|
-
if (view.id === this.analysisView.id)
|
|
1038
|
+
if (view.id === this.analysisView.id) {
|
|
820
1039
|
this.subs.forEach((v) => v.unsubscribe());
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
|
|
821
1043
|
grok.log.debug(`Peptides: view ${view.name} removed`);
|
|
822
1044
|
}));
|
|
823
1045
|
this.subs.push(grok.events.onTableRemoved.subscribe((table: DG.DataFrame) => {
|
|
824
|
-
if (table.id === this.df.id)
|
|
1046
|
+
if (table.id === this.df.id) {
|
|
825
1047
|
this.subs.forEach((v) => v.unsubscribe());
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
|
|
826
1051
|
grok.log.debug(`Peptides: table ${table.name} removed`);
|
|
827
1052
|
}));
|
|
828
1053
|
this.subs.push(grok.events.onProjectClosed.subscribe((project: DG.Project) => {
|
|
829
|
-
if (project.id === grok.shell.project.id)
|
|
1054
|
+
if (project.id === grok.shell.project.id) {
|
|
830
1055
|
this.subs.forEach((v) => v.unsubscribe());
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
|
|
831
1059
|
grok.log.debug(`Peptides: project ${project.name} closed`);
|
|
832
1060
|
}));
|
|
833
1061
|
|
|
834
|
-
this.fireBitsetChanged(true);
|
|
835
|
-
if (typeof this.settings.targetColumnName === 'undefined')
|
|
836
|
-
this.updateMutationCliffs();
|
|
1062
|
+
this.fireBitsetChanged(null, true);
|
|
837
1063
|
|
|
838
1064
|
this.analysisView.grid.invalidate();
|
|
839
1065
|
}
|
|
840
1066
|
|
|
1067
|
+
/**
|
|
1068
|
+
* Finds viewer by type
|
|
1069
|
+
* @param viewerType - Viewer type to find
|
|
1070
|
+
* @return - Viewer or null if not found
|
|
1071
|
+
*/
|
|
841
1072
|
findViewer(viewerType: VIEWER_TYPE): DG.Viewer | null {
|
|
842
1073
|
return wu(this.analysisView.viewers).find((v) => v.type === viewerType) || null;
|
|
843
1074
|
}
|
|
844
1075
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1076
|
+
/**
|
|
1077
|
+
* Adds Logo Summary Table viewer to the analysis view
|
|
1078
|
+
* @param [viewerProperties] - Viewer properties
|
|
1079
|
+
*/
|
|
1080
|
+
async addLogoSummaryTable(viewerProperties?: ILogoSummaryTable): Promise<void> {
|
|
1081
|
+
viewerProperties ??= {
|
|
1082
|
+
sequenceColumnName: this.settings!.sequenceColumnName,
|
|
1083
|
+
clustersColumnName: wu(this.df.columns.categorical).next().value,
|
|
1084
|
+
activityColumnName: this.settings!.activityColumnName,
|
|
1085
|
+
activityScaling: this.settings!.activityScaling,
|
|
1086
|
+
};
|
|
1087
|
+
const logoSummaryTable = await this.df.plot
|
|
1088
|
+
.fromType(VIEWER_TYPE.LOGO_SUMMARY_TABLE, viewerProperties) as LogoSummaryTable;
|
|
849
1089
|
this.analysisView.dockManager.dock(logoSummaryTable, DG.DOCK_TYPE.RIGHT, null, VIEWER_TYPE.LOGO_SUMMARY_TABLE);
|
|
850
|
-
|
|
851
|
-
await this.addMonomerPosition();
|
|
852
|
-
if (this.settings.showMostPotentResidues)
|
|
853
|
-
await this.addMostPotentResidues();
|
|
1090
|
+
|
|
854
1091
|
logoSummaryTable.viewerGrid.invalidate();
|
|
855
1092
|
}
|
|
856
1093
|
|
|
857
|
-
|
|
858
|
-
|
|
1094
|
+
/**
|
|
1095
|
+
* Adds Monomer-Position viewer to the analysis view
|
|
1096
|
+
* @param [viewerProperties] - Viewer properties
|
|
1097
|
+
*/
|
|
1098
|
+
async addMonomerPosition(viewerProperties?: ISARViewer): Promise<void> {
|
|
1099
|
+
viewerProperties ??= {
|
|
1100
|
+
maxMutations: 1,
|
|
1101
|
+
activityScaling: this.settings!.activityScaling,
|
|
1102
|
+
activityColumnName: this.settings!.activityColumnName,
|
|
1103
|
+
sequenceColumnName: this.settings!.sequenceColumnName,
|
|
1104
|
+
minActivityDelta: 0,
|
|
1105
|
+
};
|
|
1106
|
+
const monomerPosition = await this.df.plot
|
|
1107
|
+
.fromType(VIEWER_TYPE.MONOMER_POSITION, viewerProperties) as MonomerPosition;
|
|
859
1108
|
const mostPotentResidues = this.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
|
|
860
1109
|
const dm = this.analysisView.dockManager;
|
|
861
1110
|
const [dockType, refNode, ratio] = mostPotentResidues === null ? [DG.DOCK_TYPE.DOWN, null, undefined] :
|
|
862
1111
|
[DG.DOCK_TYPE.LEFT, this.findViewerNode(VIEWER_TYPE.MOST_POTENT_RESIDUES), 0.7];
|
|
863
1112
|
dm.dock(monomerPosition, dockType, refNode, VIEWER_TYPE.MONOMER_POSITION, ratio);
|
|
864
|
-
if (typeof this.settings.targetColumnName !== 'undefined') {
|
|
865
|
-
const target = monomerPosition.getProperty(MONOMER_POSITION_PROPERTIES.TARGET)!;
|
|
866
|
-
const choices = this.df.getCol(this.settings.targetColumnName!).categories;
|
|
867
|
-
target.choices = choices;
|
|
868
|
-
target.set(monomerPosition, choices[0]);
|
|
869
|
-
}
|
|
870
1113
|
}
|
|
871
1114
|
|
|
872
|
-
|
|
1115
|
+
/**
|
|
1116
|
+
* Adds Most Potent Residues viewer to the analysis view
|
|
1117
|
+
* @param [viewerProperties] - Viewer properties
|
|
1118
|
+
*/
|
|
1119
|
+
async addMostPotentResidues(viewerProperties?: ISARViewer): Promise<void> {
|
|
1120
|
+
viewerProperties ??= {
|
|
1121
|
+
activityScaling: this.settings!.activityScaling,
|
|
1122
|
+
activityColumnName: this.settings!.activityColumnName,
|
|
1123
|
+
sequenceColumnName: this.settings!.sequenceColumnName,
|
|
1124
|
+
minActivityDelta: 0,
|
|
1125
|
+
maxMutations: 1,
|
|
1126
|
+
};
|
|
873
1127
|
const mostPotentResidues =
|
|
874
|
-
await this.df.plot.fromType(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues;
|
|
1128
|
+
await this.df.plot.fromType(VIEWER_TYPE.MOST_POTENT_RESIDUES, viewerProperties) as MostPotentResidues;
|
|
875
1129
|
const monomerPosition = this.findViewer(VIEWER_TYPE.MONOMER_POSITION) as MonomerPosition | null;
|
|
876
1130
|
const dm = this.analysisView.dockManager;
|
|
877
1131
|
const [dockType, refNode, ratio] = monomerPosition === null ? [DG.DOCK_TYPE.DOWN, null, undefined] :
|
|
@@ -879,89 +1133,132 @@ export class PeptidesModel {
|
|
|
879
1133
|
dm.dock(mostPotentResidues, dockType, refNode, VIEWER_TYPE.MOST_POTENT_RESIDUES, ratio);
|
|
880
1134
|
}
|
|
881
1135
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
this.df.columns.add(newClusterCol);
|
|
887
|
-
this.analysisView.grid.col(newClusterCol.name)!.visible = false;
|
|
888
|
-
}
|
|
889
|
-
|
|
1136
|
+
/**
|
|
1137
|
+
* Creates new view from analysis dataframe selection, and adds LogoSummaryTable to it
|
|
1138
|
+
* @return - New view id
|
|
1139
|
+
*/
|
|
890
1140
|
createNewView(): string {
|
|
891
|
-
const rowMask = this.
|
|
892
|
-
const newDfId = uuid.v4();
|
|
893
|
-
|
|
1141
|
+
const rowMask = this.getVisibleSelection();
|
|
894
1142
|
const newDf = this.df.clone(rowMask);
|
|
895
|
-
for (const [tag, value] of newDf.tags)
|
|
1143
|
+
for (const [tag, value] of newDf.tags) {
|
|
896
1144
|
newDf.setTag(tag, tag === C.TAGS.SETTINGS ? value : '');
|
|
1145
|
+
}
|
|
1146
|
+
|
|
897
1147
|
|
|
898
1148
|
newDf.name = 'Peptides Multiple Views';
|
|
899
1149
|
newDf.setTag(C.TAGS.MULTIPLE_VIEWS, '1');
|
|
900
|
-
newDf.setTag(C.TAGS.UUID, newDfId);
|
|
901
1150
|
|
|
902
1151
|
const view = grok.shell.addTableView(newDf);
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
modifyMutationCliffsSelection(monomerPosition: type.SelectionItem, options: type.SelectionOptions = {shiftPressed: false, ctrlPressed: false}, notify: boolean = true): void {
|
|
916
|
-
if (notify)
|
|
917
|
-
this.mutationCliffsSelection = this.modifySelection(this.mutationCliffsSelection, monomerPosition, options);
|
|
918
|
-
else
|
|
919
|
-
this._mutationCliffsSelection = this.modifySelection(this._mutationCliffsSelection, monomerPosition, options);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
modifyClusterSelection(cluster: type.SelectionItem, options: type.SelectionOptions = {shiftPressed: false, ctrlPressed: false}, notify: boolean = true): void {
|
|
923
|
-
if (notify)
|
|
924
|
-
this.clusterSelection = this.modifySelection(this.clusterSelection, cluster, options);
|
|
925
|
-
else
|
|
926
|
-
this._clusterSelection = this.modifySelection(this._clusterSelection, cluster, options);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
modifySelection(selection: type.Selection, clusterOrMonomerPosition: type.SelectionItem, options: type.SelectionOptions): type.Selection {
|
|
930
|
-
const monomerList = selection[clusterOrMonomerPosition.positionOrClusterType];
|
|
931
|
-
const monomerIndex = monomerList.indexOf(clusterOrMonomerPosition.monomerOrCluster);
|
|
932
|
-
if (options.shiftPressed && options.ctrlPressed) {
|
|
933
|
-
if (monomerIndex !== -1)
|
|
934
|
-
monomerList.splice(monomerIndex, 1);
|
|
935
|
-
} else if (options.ctrlPressed) {
|
|
936
|
-
if (monomerIndex === -1)
|
|
937
|
-
monomerList.push(clusterOrMonomerPosition.monomerOrCluster);
|
|
938
|
-
else
|
|
939
|
-
monomerList.splice(monomerIndex, 1);
|
|
940
|
-
} else if (options.shiftPressed) {
|
|
941
|
-
if (monomerIndex === -1)
|
|
942
|
-
monomerList.push(clusterOrMonomerPosition.monomerOrCluster);
|
|
943
|
-
} else {
|
|
944
|
-
const selectionKeys = Object.keys(selection);
|
|
945
|
-
selection = {};
|
|
946
|
-
for (const posOrClustType of selectionKeys) {
|
|
947
|
-
selection[posOrClustType] = [];
|
|
948
|
-
if (posOrClustType === clusterOrMonomerPosition.positionOrClusterType)
|
|
949
|
-
selection[posOrClustType].push(clusterOrMonomerPosition.monomerOrCluster);
|
|
950
|
-
}
|
|
1152
|
+
const lstViewer = this.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
|
|
1153
|
+
if (lstViewer != null) {
|
|
1154
|
+
view.addViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE, {
|
|
1155
|
+
[`${LST_PROPERTIES.SEQUENCE}${COLUMN_NAME}`]: lstViewer.sequenceColumnName,
|
|
1156
|
+
[`${LST_PROPERTIES.ACTIVITY}${COLUMN_NAME}`]: lstViewer.activityColumnName,
|
|
1157
|
+
[LST_PROPERTIES.ACTIVITY_SCALING]: lstViewer.activityScaling,
|
|
1158
|
+
[LST_PROPERTIES.WEB_LOGO_MODE]: lstViewer.webLogoMode,
|
|
1159
|
+
[LST_PROPERTIES.MEMBERS_RATIO_THRESHOLD]: lstViewer.membersRatioThreshold,
|
|
1160
|
+
[`${LST_PROPERTIES.CLUSTERS}${COLUMN_NAME}`]: lstViewer.clustersColumnName,
|
|
1161
|
+
});
|
|
951
1162
|
}
|
|
952
|
-
|
|
1163
|
+
|
|
1164
|
+
return newDf.getTag(DG.TAGS.ID)!;
|
|
953
1165
|
}
|
|
954
1166
|
|
|
1167
|
+
/**
|
|
1168
|
+
* Adds Sequence Space viewer to the analysis view
|
|
1169
|
+
*/
|
|
955
1170
|
async addSequenceSpace(): Promise<void> {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1171
|
+
if (this._sequenceSpaceViewer !== null) {
|
|
1172
|
+
try {
|
|
1173
|
+
this._sequenceSpaceViewer?.detach();
|
|
1174
|
+
this._sequenceSpaceViewer?.close();
|
|
1175
|
+
} catch (_) {}
|
|
1176
|
+
}
|
|
1177
|
+
if (this._sequenceSpaceCols.length !== 0)
|
|
1178
|
+
this._sequenceSpaceCols.forEach((col) => this.df.columns.remove(col));
|
|
1179
|
+
|
|
1180
|
+
this._sequenceSpaceCols = [];
|
|
1181
|
+
let seqCol = this.df.getCol(this.settings!.sequenceColumnName!);
|
|
1182
|
+
const uh = UnitsHandler.getOrCreate(seqCol);
|
|
1183
|
+
const isHelm = uh.isHelm();
|
|
1184
|
+
if (isHelm) {
|
|
1185
|
+
try {
|
|
1186
|
+
grok.shell.warning('Column is in HELM notation. Sequences space will linearize sequences from position 0 ' +
|
|
1187
|
+
'prior to analysis');
|
|
1188
|
+
const linearCol = uh.convert(NOTATION.SEPARATOR, '/');
|
|
1189
|
+
const newName = this.df.columns.getUnusedName(`Separator(${seqCol.name})`);
|
|
1190
|
+
linearCol.name = newName;
|
|
1191
|
+
this.df.columns.add(linearCol, true);
|
|
1192
|
+
this.analysisView.grid.col(newName)!.visible = false;
|
|
1193
|
+
seqCol = linearCol;
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
grok.shell.error('Error on converting HELM notation to linear notation');
|
|
1196
|
+
grok.shell.error(e as string);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const seqSpaceSettings = this.settings?.sequenceSpaceParams ?? new type.SequenceSpaceParams();
|
|
1201
|
+
const seqSpaceParams: {
|
|
1202
|
+
table: DG.DataFrame,
|
|
1203
|
+
molecules: DG.Column,
|
|
1204
|
+
methodName: DimReductionMethods,
|
|
1205
|
+
similarityMetric: BitArrayMetrics | MmDistanceFunctionsNames,
|
|
1206
|
+
plotEmbeddings: boolean,
|
|
1207
|
+
sparseMatrixThreshold?: number,
|
|
1208
|
+
clusterEmbeddings?: boolean,
|
|
1209
|
+
options?: (IUMAPOptions | ITSNEOptions) & Options
|
|
1210
|
+
} =
|
|
1211
|
+
{
|
|
1212
|
+
table: this.df,
|
|
1213
|
+
molecules: seqCol,
|
|
1214
|
+
methodName: DimReductionMethods.UMAP,
|
|
1215
|
+
similarityMetric: seqSpaceSettings.distanceF,
|
|
1216
|
+
plotEmbeddings: true,
|
|
1217
|
+
sparseMatrixThreshold: 0.3,
|
|
1218
|
+
options: {'bypassLargeDataWarning': true, dbScanEpsilon: seqSpaceSettings.epsilon, dbScanMinPts: seqSpaceSettings.minPts,
|
|
1219
|
+
preprocessingFuncArgs: {gapOpen: seqSpaceSettings.gapOpen, gapExtend: seqSpaceSettings.gapExtend, fingerprintType: seqSpaceSettings.fingerprintType}},
|
|
1220
|
+
clusterEmbeddings: seqSpaceSettings.clusterEmbeddings,
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// Use counter to unsubscribe when 2 columns are hidden
|
|
1224
|
+
let counter = 0;
|
|
1225
|
+
const addedColCount = seqSpaceSettings.clusterEmbeddings ? 3 : 2;
|
|
1226
|
+
const columnAddedSub = this.df.onColumnsAdded.subscribe((colArgs: DG.ColumnsArgs) => {
|
|
1227
|
+
for (const col of colArgs.columns) {
|
|
1228
|
+
if (col.name.startsWith('Embed_') || ( seqSpaceSettings.clusterEmbeddings && col.name.toLowerCase().startsWith('cluster'))) {
|
|
1229
|
+
const gridCol = this.analysisView.grid.col(col.name);
|
|
1230
|
+
if (gridCol == null) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
gridCol.visible = false;
|
|
1234
|
+
this._sequenceSpaceCols.push(col.name);
|
|
1235
|
+
counter++;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (counter === addedColCount) {
|
|
1239
|
+
columnAddedSub.unsubscribe();
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
const seqSpaceViewer: DG.ScatterPlotViewer | undefined =
|
|
1244
|
+
await grok.functions.call('Bio:sequenceSpaceTopMenu', seqSpaceParams);
|
|
1245
|
+
if (!(seqSpaceViewer instanceof DG.ScatterPlotViewer)) {
|
|
964
1246
|
return;
|
|
965
|
-
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (!seqSpaceSettings.clusterEmbeddings) { // color by activity if clusters are not automatically generated.
|
|
1250
|
+
seqSpaceViewer.props.colorColumnName = this.getScaledActivityColumn()!.name;
|
|
1251
|
+
}
|
|
1252
|
+
seqSpaceViewer.props.showXSelector = false;
|
|
1253
|
+
seqSpaceViewer.props.showYSelector = false;
|
|
1254
|
+
this._sequenceSpaceViewer = seqSpaceViewer;
|
|
1255
|
+
seqSpaceViewer.onContextMenu.subscribe((menu) => {
|
|
1256
|
+
try {
|
|
1257
|
+
menu.item('Modify Sequence space parameters', () => {
|
|
1258
|
+
getSettingsDialog(this);
|
|
1259
|
+
});
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
966
1263
|
}
|
|
967
1264
|
}
|