@datagrok/peptides 0.8.13 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +5 -2
- package/dist/package-test.js +1268 -1766
- package/dist/package.js +1097 -1622
- package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +120 -62
- package/package.json +13 -17
- package/package.png +0 -0
- package/src/model.ts +504 -448
- package/src/monomer-library.ts +31 -30
- package/src/package-test.ts +5 -6
- package/src/package.ts +52 -70
- package/src/tests/core.ts +67 -0
- package/src/tests/msa-tests.ts +3 -3
- package/src/tests/peptide-space-test.ts +65 -45
- package/src/tests/utils.ts +20 -50
- package/src/utils/cell-renderer.ts +25 -151
- package/src/utils/chem-palette.ts +3 -14
- package/src/utils/constants.ts +5 -0
- package/src/utils/filtering-statistics.ts +2 -2
- package/src/utils/misc.ts +29 -0
- package/src/utils/multiple-sequence-alignment.ts +5 -18
- package/src/utils/multivariate-analysis.ts +5 -8
- package/src/utils/peptide-similarity-space.ts +12 -9
- package/src/utils/types.ts +5 -2
- package/src/viewers/peptide-space-viewer.ts +67 -39
- package/src/viewers/sar-viewer.ts +34 -37
- package/src/viewers/stacked-barchart-viewer.ts +38 -61
- package/src/widgets/analyze-peptides.ts +53 -75
- package/src/widgets/distribution.ts +34 -18
- package/src/widgets/manual-alignment.ts +8 -12
- package/src/widgets/peptide-molecule.ts +48 -25
- package/src/widgets/subst-table.ts +53 -52
- package/src/workers/dimensionality-reducer.ts +8 -13
- package/{test-Peptides-f8114def7953-4bf59d70.html → test-Peptides-69a4761f6044-40ac3a0c.html} +2 -2
- package/src/peptides.ts +0 -327
- package/src/semantics.ts +0 -5
- package/src/tests/peptides-tests.ts +0 -60
- package/src/utils/SAR-multiple-filter.ts +0 -439
- package/src/utils/SAR-multiple-selection.ts +0 -177
- package/src/viewers/logo-viewer.ts +0 -195
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
|
|
3
|
-
import * as rxjs from 'rxjs';
|
|
4
|
-
|
|
5
|
-
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
6
|
-
import {MultipleSelection} from './SAR-multiple-selection';
|
|
7
|
-
import {FilteringStatistics, Stats as FilterStats} from './filtering-statistics';
|
|
8
|
-
|
|
9
|
-
import * as C from './constants';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Implements multiple filtering callbacks to be used in events subscription.
|
|
13
|
-
*/
|
|
14
|
-
export class SARMultipleFilter {
|
|
15
|
-
protected selection: MultipleSelection;
|
|
16
|
-
protected resources: Resources;
|
|
17
|
-
protected filterMode: boolean;
|
|
18
|
-
protected stats: FilteringStatistics;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Creates an instance of SARMultipleFilter.
|
|
22
|
-
* @param {boolean} filterMode Whether to filter or to select.
|
|
23
|
-
*/
|
|
24
|
-
constructor(filterMode: boolean) {
|
|
25
|
-
this.filterMode = filterMode;
|
|
26
|
-
this.resources = new Resources();
|
|
27
|
-
this.selection = new MultipleSelection();
|
|
28
|
-
this.stats = new FilteringStatistics();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Makes label to display filtered residue-positions on accordion. */
|
|
32
|
-
get filterLabel() {
|
|
33
|
-
return this.selection.toString();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Makes query to display data frame statistics on accordion. */
|
|
37
|
-
get query(): string {
|
|
38
|
-
this.resources.assert(['residueColumnName']);
|
|
39
|
-
return this.selection.toQuery(this.resources.residueColumnName);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Updates filtering mode.
|
|
44
|
-
* @param {boolean} value Filtering if true or selection else.
|
|
45
|
-
*/
|
|
46
|
-
set filteringMode(value: boolean) {
|
|
47
|
-
this.filterMode = value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Returns bit set obtained from selection mask. */
|
|
51
|
-
get mask() {
|
|
52
|
-
this.resources.assert(['dataFrame', 'groupMapping']);
|
|
53
|
-
|
|
54
|
-
const df = this.resources.dataFrame!;
|
|
55
|
-
const mask = this.selection.eval(df, this.resources.groupMapping!);
|
|
56
|
-
return DG.BitSet.create(mask.length, (i) => mask[i]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Resets selection mask. */
|
|
60
|
-
resetSelection() {
|
|
61
|
-
this.selection = new MultipleSelection();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Adds/updates one of objects required.
|
|
66
|
-
* @param {ResourceKey} key Resource key.
|
|
67
|
-
* @param {any} value Resource value.
|
|
68
|
-
*/
|
|
69
|
-
addResource(key: ResourceKey, value: any) {
|
|
70
|
-
this.resources.add(key, value);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Evaluates selection into mask.
|
|
75
|
-
* @return {boolean[]} List of booleans where a true corresponds to the row selected.
|
|
76
|
-
*/
|
|
77
|
-
eval(): boolean[] {
|
|
78
|
-
this.resources.assert(['dataFrame', 'groupMapping']);
|
|
79
|
-
return this.selection.eval(this.resources.dataFrame!, this.resources.groupMapping!);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Reacts on mouse event.
|
|
84
|
-
* @param {MouseEventType} eventType Mouse event type.
|
|
85
|
-
* @param {CellType} cellType Type of the grid cell.
|
|
86
|
-
* @param {(string | null)} colName Name of column the cell from.
|
|
87
|
-
* @param {(number | null)} rowIdx Index of the row the cell at.
|
|
88
|
-
* @param {boolean} ctrlPressed Whether CTRL-button was pressed while catching event.
|
|
89
|
-
*/
|
|
90
|
-
onSARGridMouseEvent(
|
|
91
|
-
eventType: MouseEventType,
|
|
92
|
-
cellType: CellType,
|
|
93
|
-
colName: string | null,
|
|
94
|
-
rowIdx: number | null,
|
|
95
|
-
ctrlPressed: boolean,
|
|
96
|
-
) {
|
|
97
|
-
switch (eventType) {
|
|
98
|
-
case 'mouseout':
|
|
99
|
-
this.onSARCellMouseOut();
|
|
100
|
-
break;
|
|
101
|
-
case 'click':
|
|
102
|
-
if (colName)
|
|
103
|
-
this.onSARCellClicked(colName, rowIdx, ctrlPressed);
|
|
104
|
-
break;
|
|
105
|
-
case 'mousemove':
|
|
106
|
-
this.onSARCellHover(colName, rowIdx);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Reacts on click to the SAR viewer cell.
|
|
113
|
-
* @param {string} colName Name of the column which the cell is from.
|
|
114
|
-
* @param {(number | null)} rowIdx Index of the row at which the cell is.
|
|
115
|
-
* @param {boolean} ctrlPressed Whether the CTRL button was pressed.
|
|
116
|
-
*/
|
|
117
|
-
onSARCellClicked(colName: string, rowIdx: number | null, ctrlPressed: boolean) {
|
|
118
|
-
this.resources.assert(['dataFrame', 'residueColumnName', 'grid']);
|
|
119
|
-
|
|
120
|
-
const residueColumnName = this.resources.residueColumnName!;
|
|
121
|
-
const grid = this.resources.grid!;
|
|
122
|
-
const isMultiposSel = colName == residueColumnName;
|
|
123
|
-
const isMultiresSel = rowIdx === null;
|
|
124
|
-
|
|
125
|
-
if (isMultiposSel && isMultiresSel)
|
|
126
|
-
return;
|
|
127
|
-
|
|
128
|
-
const changeFilter = ctrlPressed ? this.selection.input : this.selection.set;
|
|
129
|
-
const df = grid.dataFrame!;
|
|
130
|
-
const columns = df.columns as DG.ColumnList;
|
|
131
|
-
let pos: string | undefined;
|
|
132
|
-
let res: string | undefined;
|
|
133
|
-
|
|
134
|
-
if (!isMultiposSel)
|
|
135
|
-
pos = colName;
|
|
136
|
-
if (!isMultiresSel)
|
|
137
|
-
res = df.get(residueColumnName, rowIdx);
|
|
138
|
-
|
|
139
|
-
if (isMultiposSel)
|
|
140
|
-
this.selection.setRes(res!, columns.names().slice(1)); // All except the first residues column.
|
|
141
|
-
else if (isMultiresSel)
|
|
142
|
-
this.selection.setPos(pos!, df.col(residueColumnName)!.toList());
|
|
143
|
-
else
|
|
144
|
-
changeFilter.bind(this.selection)(pos!, res!);
|
|
145
|
-
|
|
146
|
-
// console.warn([ctrlPressed ? 'ctrl+click' : 'click', this.selection.filter]);
|
|
147
|
-
|
|
148
|
-
if (this.filterMode)
|
|
149
|
-
this.resources.dataFrame!.rows.requestFilter();
|
|
150
|
-
else
|
|
151
|
-
this.maskRows();
|
|
152
|
-
|
|
153
|
-
this.createSplitCol();
|
|
154
|
-
this.resources.dataFrame!.temp[C.STATS] = this.getStatistics();
|
|
155
|
-
|
|
156
|
-
grid.invalidate();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
createSplitCol() {
|
|
160
|
-
this.resources.assert(['grid', 'dataFrame']);
|
|
161
|
-
|
|
162
|
-
const viewerDf = this.resources.grid!.dataFrame!;
|
|
163
|
-
const df = this.resources.dataFrame!;
|
|
164
|
-
let aarLabel = this.filterLabel;
|
|
165
|
-
if (aarLabel === '') {
|
|
166
|
-
const currentAAR: string = viewerDf.get(C.COLUMNS_NAMES.AMINO_ACID_RESIDUE, viewerDf.currentRowIdx);
|
|
167
|
-
const currentPosition = viewerDf.currentCol?.name;
|
|
168
|
-
|
|
169
|
-
aarLabel = (currentAAR && currentPosition) ?? true ? 'All' :
|
|
170
|
-
`${currentAAR === '-' ? 'Gap' : currentAAR} - ${currentPosition}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const splitCol = df.col(C.COLUMNS_NAMES.SPLIT_COL) ??
|
|
174
|
-
df.columns.addNew(C.COLUMNS_NAMES.SPLIT_COL, 'string') as DG.Column;
|
|
175
|
-
|
|
176
|
-
const bitset = this.filterMode ? df.filter : df.selection;
|
|
177
|
-
splitCol.init((i) => bitset.get(i) ? aarLabel : C.CATEGORIES.OTHER);
|
|
178
|
-
splitCol.setCategoryOrder([aarLabel, C.CATEGORIES.OTHER]);
|
|
179
|
-
splitCol.compact();
|
|
180
|
-
|
|
181
|
-
const colorMap: {[index: string]: string | number} = {};
|
|
182
|
-
|
|
183
|
-
colorMap[C.CATEGORIES.OTHER] = DG.Color.blue;
|
|
184
|
-
colorMap[aarLabel] = DG.Color.orange;
|
|
185
|
-
// colorMap[currentAAR] = cp.getColor(currentAAR);
|
|
186
|
-
df.getCol(C.COLUMNS_NAMES.SPLIT_COL).colors.setCategorical(colorMap);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Reacts on mouse hover over SAR viewer cell.
|
|
191
|
-
* @param {(string | null)} colName Name of the column which the cell is from.
|
|
192
|
-
* @param {(number | null)} rowIdx Index of the row at which the cell is.
|
|
193
|
-
*/
|
|
194
|
-
onSARCellHover(colName: string | null, rowIdx: number | null) {
|
|
195
|
-
this.resources.assert(['residueColumnName', 'grid', 'dataFrame']);
|
|
196
|
-
|
|
197
|
-
const residueColumnName = this.resources.residueColumnName!;
|
|
198
|
-
|
|
199
|
-
if (!colName || !rowIdx || colName == residueColumnName)
|
|
200
|
-
return;
|
|
201
|
-
|
|
202
|
-
const df = this.resources.grid?.dataFrame!;
|
|
203
|
-
|
|
204
|
-
if (!(df.columns as DG.ColumnList).names().includes(colName))
|
|
205
|
-
return;
|
|
206
|
-
|
|
207
|
-
const res = df.get(residueColumnName, rowIdx);
|
|
208
|
-
|
|
209
|
-
this.resources.dataFrame?.rows.match({[colName]: res}).highlight();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** Reacts on mouse out from the SAR viewer */
|
|
213
|
-
onSARCellMouseOut() {
|
|
214
|
-
this.resources.assert(['dataFrame']);
|
|
215
|
-
this.resources.dataFrame?.rows.match({}).highlight();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Sets up SAR viewer grid visualization. */
|
|
219
|
-
setupGridVizualization() {
|
|
220
|
-
this.resources.assert(['grid']);
|
|
221
|
-
this.resources.grid?.setOptions({
|
|
222
|
-
'showCurrentRowIndicator': false,
|
|
223
|
-
'allowBlockSelection': false,
|
|
224
|
-
'allowColSelection': false,
|
|
225
|
-
'allowRowSelection': false,
|
|
226
|
-
'showMouseOverRowIndicator': false,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Custom renderrer to be applied to SAR viewer cell rendering.
|
|
232
|
-
* @param {DG.GridCellRenderArgs} args Cell rendering arguments.
|
|
233
|
-
*/
|
|
234
|
-
selectionCellRenderrer(args: DG.GridCellRenderArgs) {
|
|
235
|
-
if (args.cell.isTableCell) {
|
|
236
|
-
this.resources.assert(['residueColumnName', 'groupMapping']);
|
|
237
|
-
|
|
238
|
-
const residueColumnName = this.resources.residueColumnName!;
|
|
239
|
-
const cell = args.cell;
|
|
240
|
-
const pos = cell.gridColumn.name;
|
|
241
|
-
|
|
242
|
-
if (pos !== residueColumnName) {
|
|
243
|
-
const rowIdx = cell.tableRowIndex!;
|
|
244
|
-
const res = cell.cell.dataFrame.get(residueColumnName, rowIdx);
|
|
245
|
-
|
|
246
|
-
if (this.selection.test(pos, res, this.resources.groupMapping!)) {
|
|
247
|
-
const ctx = args.g;
|
|
248
|
-
ctx.lineWidth = 1;
|
|
249
|
-
ctx.strokeStyle = 'black';
|
|
250
|
-
ctx.strokeRect(args.bounds.x, args.bounds.y, args.bounds.width, args.bounds.height);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Reacts on SAR grid update/reloading. */
|
|
257
|
-
onSARGridChanged() {
|
|
258
|
-
this.resources.assert(['grid']);
|
|
259
|
-
|
|
260
|
-
const grid = this.resources.grid!;
|
|
261
|
-
|
|
262
|
-
addGridMouseHandler(grid, this.onSARGridMouseEvent.bind(this));
|
|
263
|
-
this.setupGridVizualization();
|
|
264
|
-
grid.onCellRender.subscribe(this.selectionCellRenderrer.bind(this));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Selects or filters rows depending from the filtering mode set. */
|
|
268
|
-
maskRows() {
|
|
269
|
-
this.resources.assert(['dataFrame']);
|
|
270
|
-
|
|
271
|
-
const df = this.resources.dataFrame!;
|
|
272
|
-
|
|
273
|
-
if (this.filterMode) {
|
|
274
|
-
df.filter.and(this.mask);
|
|
275
|
-
df.selection.setAll(false, false);
|
|
276
|
-
|
|
277
|
-
// console.warn(['onRowsFiltering', this.selection.filter, df.filter.trueCount]);
|
|
278
|
-
} else {
|
|
279
|
-
df.selection.copyFrom(this.mask);
|
|
280
|
-
df.filter.fireChanged();
|
|
281
|
-
|
|
282
|
-
// console.warn(['onSelectionChanged', this.selection.filter, df.selection.trueCount]);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Calculates statistics on the activity column.
|
|
288
|
-
* @return {FilterStats} Statistics.
|
|
289
|
-
*/
|
|
290
|
-
getStatistics(): FilterStats {
|
|
291
|
-
this.resources.assert(['activityColumnName', 'dataFrame']);
|
|
292
|
-
|
|
293
|
-
const df = this.resources.dataFrame!;
|
|
294
|
-
|
|
295
|
-
this.stats.setData(df.col(this.resources.activityColumnName!)?.getRawData() as Float32Array);
|
|
296
|
-
this.stats.setMask(this.mask);
|
|
297
|
-
return this.stats.result;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/** Declares resources needed by the filtering. */
|
|
302
|
-
interface FilterResources {
|
|
303
|
-
residueColumnName?: string;
|
|
304
|
-
activityColumnName?: string;
|
|
305
|
-
grid?: DG.Grid;
|
|
306
|
-
dataFrame?: DG.DataFrame;
|
|
307
|
-
groupMapping?: StringDictionary;
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
type ResourceKey = keyof FilterResources;
|
|
311
|
-
|
|
312
|
-
/** Resources controlling helper */
|
|
313
|
-
class Resources {
|
|
314
|
-
protected data: FilterResources;
|
|
315
|
-
|
|
316
|
-
/** Creates an instance of Resources. */
|
|
317
|
-
constructor() {
|
|
318
|
-
this.data = {};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Adds/updates the resource given.
|
|
323
|
-
* @param {ResourceKey} resource Resource key.
|
|
324
|
-
* @param {any} value Resource value.
|
|
325
|
-
*/
|
|
326
|
-
add(resource: ResourceKey, value: any) {
|
|
327
|
-
this.data[resource] = value;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Checks if all requested resources were added previously.
|
|
332
|
-
* @param {ResourceKey[]} resources Requested resources.
|
|
333
|
-
* @return {boolean} True if all those resources are initialized.
|
|
334
|
-
*/
|
|
335
|
-
enough(resources: ResourceKey[]): boolean {
|
|
336
|
-
return resources.every((k) => this.data[k] !== undefined);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Throws an error if one of the resources given was not set.
|
|
341
|
-
* @param {ResourceKey[]} [resources] Optional set of resources to check.
|
|
342
|
-
*/
|
|
343
|
-
assert(resources?: ResourceKey[]) {
|
|
344
|
-
if (resources) {
|
|
345
|
-
if (!this.enough(resources))
|
|
346
|
-
throw new Error(`Not enough one of ${resources} or more.`);
|
|
347
|
-
} else {
|
|
348
|
-
if (!this.ready())
|
|
349
|
-
throw new Error(`Resources are not ready.`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Checks if all resources were initialized.
|
|
355
|
-
* @return {boolean} True if the test was successful.
|
|
356
|
-
*/
|
|
357
|
-
ready(): boolean {
|
|
358
|
-
return Object.values(this.data).every((v) => v !== undefined);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Data frame resource. */
|
|
362
|
-
get dataFrame() {
|
|
363
|
-
return this.data.dataFrame;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/** Grid resource. */
|
|
367
|
-
get grid() {
|
|
368
|
-
return this.data.grid;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** Group mapping. */
|
|
372
|
-
get groupMapping() {
|
|
373
|
-
return this.data.groupMapping;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/** Residue column name. */
|
|
377
|
-
get residueColumnName() {
|
|
378
|
-
return this.data.residueColumnName;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** Activity column name. */
|
|
382
|
-
get activityColumnName() {
|
|
383
|
-
return this.data.activityColumnName;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const MouseEventsSource = {
|
|
388
|
-
click: 0,
|
|
389
|
-
mousemove: 1,
|
|
390
|
-
mouseout: 2,
|
|
391
|
-
};
|
|
392
|
-
const MouseEvents = Object.keys(MouseEventsSource);
|
|
393
|
-
export type MouseEventType = keyof typeof MouseEventsSource;
|
|
394
|
-
export type CellType = 'isTableCell' | 'isColHeader' | 'unknown';
|
|
395
|
-
|
|
396
|
-
type MouseEventHandler = (
|
|
397
|
-
eventType: MouseEventType,
|
|
398
|
-
cellType: CellType,
|
|
399
|
-
colName: string | null,
|
|
400
|
-
rowIdx: number | null,
|
|
401
|
-
ctrlPressed: boolean
|
|
402
|
-
) => void;
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Adds mouse event handler to the click event bus.
|
|
406
|
-
* @param {DG.Grid} grid Grid to add to.
|
|
407
|
-
* @param {MouseEventHandler} handler Event handler.
|
|
408
|
-
*/
|
|
409
|
-
export function addGridMouseHandler(grid: DG.Grid, handler: MouseEventHandler) {
|
|
410
|
-
const onMouseEvent = (mouseEvent: MouseEvent) => {
|
|
411
|
-
if (!MouseEvents.includes(mouseEvent.type))
|
|
412
|
-
return;
|
|
413
|
-
|
|
414
|
-
const mouseEventType: MouseEventType = mouseEvent.type as MouseEventType;
|
|
415
|
-
const keyPressed = mouseEvent.ctrlKey || mouseEvent.metaKey;
|
|
416
|
-
const cell = grid.hitTest(mouseEvent.offsetX, mouseEvent.offsetY);
|
|
417
|
-
let pos: string | null = null;
|
|
418
|
-
let rowIdx: number | null = null;
|
|
419
|
-
let cellType: CellType = 'unknown';
|
|
420
|
-
|
|
421
|
-
if (cell) {
|
|
422
|
-
pos = cell.gridColumn.name;
|
|
423
|
-
|
|
424
|
-
if (pos.length == 0)
|
|
425
|
-
pos = null;
|
|
426
|
-
|
|
427
|
-
if (cell.isTableCell) {
|
|
428
|
-
rowIdx = cell.tableRowIndex!;
|
|
429
|
-
cellType = 'isTableCell';
|
|
430
|
-
} else if (cell.isColHeader)
|
|
431
|
-
cellType = 'isColHeader';
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
handler(mouseEventType, cellType, pos, rowIdx, keyPressed);
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
for (const e of MouseEvents)
|
|
438
|
-
rxjs.fromEvent<MouseEvent>(grid.overlay, e).subscribe(onMouseEvent);
|
|
439
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
|
|
3
|
-
import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
|
|
4
|
-
|
|
5
|
-
type Operation = (op1: boolean, op2: boolean) => boolean;
|
|
6
|
-
|
|
7
|
-
/** Logical operations awailable between selection items. */
|
|
8
|
-
const Operations: {[op: string]: Operation} = {
|
|
9
|
-
and: (op1: boolean, op2: boolean) => op1 && op2,
|
|
10
|
-
or: (op1: boolean, op2: boolean) => op1 || op2,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type FilterOperation = 'and' | 'or';
|
|
14
|
-
type PositionFilter = {[pos: string]: Set<string>};
|
|
15
|
-
|
|
16
|
-
/** Implements multiple selection in position-residue space. */
|
|
17
|
-
export class MultipleSelection {
|
|
18
|
-
conjunction: boolean;
|
|
19
|
-
filter: PositionFilter;
|
|
20
|
-
protected operation: Operation;
|
|
21
|
-
protected complete: (v: boolean) => boolean;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Creates an instance of MultipleSelection.
|
|
25
|
-
* @param {FilterOperation} [operation='and'] Operation to apply to items.
|
|
26
|
-
*/
|
|
27
|
-
constructor(operation: FilterOperation = 'and') {
|
|
28
|
-
this.conjunction = operation == 'and';
|
|
29
|
-
this.filter = {};
|
|
30
|
-
this.operation = Operations[operation];
|
|
31
|
-
this.complete = (v: boolean) => (this.conjunction && !v) || (!this.conjunction && v);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Adds position-residue entity into selection.
|
|
36
|
-
* @param {string} pos Position in a sequence.
|
|
37
|
-
* @param {string} res Residue at the position.
|
|
38
|
-
*/
|
|
39
|
-
input(pos: string, res: string) {
|
|
40
|
-
if (!this.filter[pos])
|
|
41
|
-
this.filter[pos] = new Set([]);
|
|
42
|
-
|
|
43
|
-
if (this.filter[pos].has(res))
|
|
44
|
-
this.remove(pos, res);
|
|
45
|
-
else
|
|
46
|
-
this.filter[pos].add(res);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Removes position-residue entity from selection.
|
|
51
|
-
* @param {string} pos Position in a sequence.
|
|
52
|
-
* @param {string} res Residue at the position.
|
|
53
|
-
*/
|
|
54
|
-
remove(pos: string, res: string) {
|
|
55
|
-
if (this.filter[pos]) {
|
|
56
|
-
this.filter[pos].delete(res);
|
|
57
|
-
|
|
58
|
-
if (this.filter[pos].size == 0)
|
|
59
|
-
delete this.filter[pos];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Sets the particular residue at position into selection.
|
|
65
|
-
* @param {string} pos Position in a sequence.
|
|
66
|
-
* @param {string} res Residue at the position.
|
|
67
|
-
*/
|
|
68
|
-
set(pos: string, res: string) {
|
|
69
|
-
for (const p of Object.keys(this.filter))
|
|
70
|
-
delete this.filter[p];
|
|
71
|
-
|
|
72
|
-
this.filter[pos] = new Set([res]);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Sets the particular position with the given residues into selection.
|
|
77
|
-
* @param {string} pos Position in a sequence.
|
|
78
|
-
* @param {string[]} values Residues list at the position.
|
|
79
|
-
*/
|
|
80
|
-
setPos(pos: string, values: string[]) {
|
|
81
|
-
this.filter[pos] = new Set(values);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Sets the particular residue to be at given positions into selection.
|
|
86
|
-
* @param {string} res Residue to set.
|
|
87
|
-
* @param {string[]} values Positions list to assign the residue to.
|
|
88
|
-
*/
|
|
89
|
-
setRes(res: string, values: string[]) {
|
|
90
|
-
for (const pos of values) {
|
|
91
|
-
if (this.filter[pos])
|
|
92
|
-
this.filter[pos].add(res);
|
|
93
|
-
else
|
|
94
|
-
this.filter[pos] = new Set([res]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Evaluates selection into a list of booleans of the same length as the given data frame.
|
|
100
|
-
* @param {DG.DataFrame} df Data frame to consider.
|
|
101
|
-
* @param {StringDictionary} [mapper={}] Optional residues mapper.
|
|
102
|
-
* @return {boolean[]} List of trues/falses corresponding selection constructed.
|
|
103
|
-
*/
|
|
104
|
-
eval(df: DG.DataFrame, mapper: StringDictionary = {}): boolean[] {
|
|
105
|
-
const itemsCount = df.rowCount;
|
|
106
|
-
const cond = new Array<boolean>(itemsCount).fill(this.conjunction);
|
|
107
|
-
|
|
108
|
-
for (let i = 0; i < itemsCount; ++i)
|
|
109
|
-
cond[i] = this.match(i, df, mapper);
|
|
110
|
-
|
|
111
|
-
return cond;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Tests if i-th element matches filter.
|
|
116
|
-
* @param {number} i Element's index
|
|
117
|
-
* @param {DG.DataFrame} df Data frame to consider.
|
|
118
|
-
* @param {StringDictionary} [mapper={}] Optional residues mapper.
|
|
119
|
-
* @return {boolean} Result of the test.
|
|
120
|
-
*/
|
|
121
|
-
match(i: number, df: DG.DataFrame, mapper: StringDictionary = {}): boolean {
|
|
122
|
-
let cond: boolean = this.conjunction;
|
|
123
|
-
|
|
124
|
-
for (const [posColumnName, resFilter] of Object.entries(this.filter)) {
|
|
125
|
-
const residue = df.get(posColumnName, i);
|
|
126
|
-
const isMatched = resFilter.has(mapper[residue] ?? residue);
|
|
127
|
-
cond = this.operation(cond, isMatched);
|
|
128
|
-
|
|
129
|
-
if (this.complete(cond))
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
return cond;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Tests if position-residue is selected.
|
|
137
|
-
* @param {string} pos Position in a sequence.
|
|
138
|
-
* @param {string} res Residue at the position.
|
|
139
|
-
* @param {StringDictionary} [mapper={}] Optional residues mapper.
|
|
140
|
-
* @return {boolean} True if this entity is selected else false.
|
|
141
|
-
*/
|
|
142
|
-
test(pos: string, res: string, mapper: StringDictionary = {}): boolean {
|
|
143
|
-
if (this.filter[pos])
|
|
144
|
-
return this.filter[pos].has(mapper[res] ?? res);
|
|
145
|
-
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Make string representation of the selected items.
|
|
151
|
-
* @return {string} String representation.
|
|
152
|
-
*/
|
|
153
|
-
toString(): string {
|
|
154
|
-
let repr = '';
|
|
155
|
-
|
|
156
|
-
for (const [pos, res] of Object.entries(this.filter))
|
|
157
|
-
repr += `${pos}: ${Array.from(res).join(',')}\n`;
|
|
158
|
-
|
|
159
|
-
return repr;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Turns filter into grouping query string.
|
|
164
|
-
* @param {string} [residueColumnName='Res'] Optional residues column name.
|
|
165
|
-
* @return {string} Query-formatted string.
|
|
166
|
-
*/
|
|
167
|
-
toQuery(residueColumnName: string = 'Res'): string {
|
|
168
|
-
const alt: string[] = [];
|
|
169
|
-
|
|
170
|
-
for (const [pos, residues] of Object.entries(this.filter)) {
|
|
171
|
-
for (const res of residues)
|
|
172
|
-
alt.push(`${residueColumnName} = ${res} and Pos = ${pos}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return alt.join(' or ');
|
|
176
|
-
}
|
|
177
|
-
}
|