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