@datagrok/peptides 0.8.9 → 0.8.13

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.
Files changed (39) hide show
  1. package/.eslintrc.json +2 -1
  2. package/dist/package-test.js +22626 -0
  3. package/dist/package.js +21429 -0
  4. package/dist/vendors-node_modules_datagrok-libraries_ml_src_workers_dimensionality-reducer_js.js +8840 -0
  5. package/jest.config.js +33 -0
  6. package/package.json +75 -62
  7. package/src/__jest__/remote.test.ts +50 -0
  8. package/src/__jest__/test-node.ts +96 -0
  9. package/src/model.ts +950 -86
  10. package/src/monomer-library.ts +8 -0
  11. package/src/package-test.ts +3 -2
  12. package/src/package.ts +57 -22
  13. package/src/peptides.ts +165 -119
  14. package/src/styles.css +8 -0
  15. package/src/tests/peptides-tests.ts +17 -78
  16. package/src/tests/utils.ts +1 -7
  17. package/src/utils/SAR-multiple-filter.ts +439 -0
  18. package/src/utils/SAR-multiple-selection.ts +177 -0
  19. package/src/utils/cell-renderer.ts +49 -50
  20. package/src/utils/chem-palette.ts +61 -163
  21. package/src/utils/constants.ts +56 -0
  22. package/src/utils/filtering-statistics.ts +62 -0
  23. package/src/utils/multiple-sequence-alignment.ts +33 -2
  24. package/src/utils/multivariate-analysis.ts +79 -0
  25. package/src/utils/peptide-similarity-space.ts +12 -31
  26. package/src/utils/types.ts +10 -0
  27. package/src/viewers/logo-viewer.ts +2 -1
  28. package/src/viewers/peptide-space-viewer.ts +121 -0
  29. package/src/viewers/sar-viewer.ts +111 -313
  30. package/src/viewers/stacked-barchart-viewer.ts +126 -173
  31. package/src/widgets/analyze-peptides.ts +39 -18
  32. package/src/widgets/distribution.ts +61 -0
  33. package/src/widgets/manual-alignment.ts +3 -3
  34. package/src/widgets/peptide-molecule.ts +4 -4
  35. package/src/widgets/subst-table.ts +30 -22
  36. package/test-Peptides-f8114def7953-4bf59d70.html +256 -0
  37. package/src/describe.ts +0 -534
  38. package/src/utils/split-aligned.ts +0 -72
  39. package/src/viewers/subst-viewer.ts +0 -320
@@ -1,77 +1,48 @@
1
1
  import {after, before, category, test} from '@datagrok-libraries/utils/src/test';
2
2
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
3
- // import {splitAlignedPeptides} from '../utils/split-aligned';
4
3
  import * as DG from 'datagrok-api/dg';
5
4
  import * as grok from 'datagrok-api/grok';
6
5
  import {PeptidesController} from '../peptides';
7
- import {describe} from '../describe';
8
6
  import {analyzePeptidesWidget} from '../widgets/analyze-peptides';
9
7
  import {manualAlignmentWidget} from '../widgets/manual-alignment';
10
8
  import {peptideMoleculeWidget} from '../widgets/peptide-molecule';
11
- import * as P from '../package';
12
-
13
- // let _package = new DG.Package();
9
+ import {_packageTest} from '../package-test';
14
10
 
15
11
  category('peptides', async () => {
16
12
  let peptidesDf: DG.DataFrame;
17
13
  let options: StringDictionary;
18
- let peptidesGrid: DG.Grid;
14
+ // let peptidesGrid: DG.Grid;
19
15
  let asCol: DG.Column;
20
- let pepView: DG.TableView;
16
+ // let pepView: DG.TableView;
21
17
 
22
18
  before(async () => {
23
- // peptidesDf = DG.DataFrame.fromCsv(await P._package.files.readAsText('aligned.csv'));
24
- const csv = `ID,AlignedSequence,IC50
25
- 1,NH2--A-Q-T-T-Y-K-N-Y-R-R-N-L-L--COOH,4.6411368455908086e-4
26
- 2,NH2-M-A-N-T-T-Y-K-N-Y-R-N-N-L-L--COOH,0.003327324930165897
27
- 3,NH2--A-N-T-T-Y-K-C-Y-R-R-N-L-L--COOH,3.0748148478921324e-4
28
- 4,NH2--A-N-T-T-Y-K-F-Y-R-R-N-L-L--COOH,0.0015532837750281958
29
- 5,NH2--A-V-T-T-Y-K-N-Y-R-R-N-L-L--COOH,6.549885174778741e-4
30
- 6,NH2--A-N-T-T-Y-K-N-Y-R-R-N-L-L--COOH,0.00213298315038382
31
- 7,NH2--A-N-T-T-Y-K-N-Y-R-F-N-L-L--COOH,0.002171297321903189
32
- 8,NH2--A-N-T-T-Y-K-N-Y-R-R-N-H-L--COOH,0.002060711496394637
33
- 9,NH2-M-A-N-T-T-Y-K-N-Y-R-R-N-L-L--COOH,0.0016058870359321516
34
- 10,NH2--A-N-T-T-Y-K-N-Y-R-N-N-L-L--COOH,0.00212911675087592
35
- 11,NH2--A-N-T-T-Y-K-N-Y-R-R-N-L-L--COOH,0.002736311013579287
36
- 12,NH2--A-N-T-T-Y-K-N-Y-R-R-N-L-L--COOH,5.673074652436946e-5
37
- 13,NH2-C-A-N-T-T-Y-K-N-Y-R-R-N-L-L--COOH,0.0032881139376902814
38
- 14,NH2--A-N-T-T-Y-K-N-Y-R-H-N-L-L--COOH,0.0012828163841736553
39
- 15,NH2-Y-A-N-T-T-Y-K-N-Y-R-D-N-L-L--COOH,7.186983807098166e-4
40
- 16,NH2-M-A-N-T-T-Y-K-N-Y-R-N-N-L-L--COOH,0.00659708587488309
41
- 17,NH2-P-A-N-T-T-Y-K-N-Y-R-G-N-L-L--COOH,3.7620528849324097e-4
42
- 18,NH2-Y-A-N-T--Y-K-N-Y-R-S-N-L-L--COOH,6.812868474160967e-4
43
- 19,NH2--A-N-T-T-Y-K-N-Y-R-S-N-L-L--COOH,0.0010148578953195436`;
44
- peptidesDf = DG.DataFrame.fromCsv(csv);
19
+ peptidesDf = DG.DataFrame.fromCsv(await _packageTest.files.readAsText('aligned.csv'));
45
20
  options = {
46
- 'activityColumnName': 'IC50',
47
- 'scaling': '-lg',
21
+ activityColumnName: 'IC50',
22
+ scaling: '-lg',
48
23
  };
49
24
  asCol = peptidesDf.getCol('AlignedSequence');
50
- pepView = grok.shell.addTableView(peptidesDf);
51
- peptidesGrid = pepView.grid;
25
+ // pepView = grok.shell.addTableView(peptidesDf);
26
+ // peptidesGrid = pepView.grid;
52
27
  });
53
28
 
54
29
  test('utils.split-sequence', async () => {
55
30
  PeptidesController.splitAlignedPeptides(peptidesDf.getCol('AlignedSequence'));
56
31
  });
57
32
 
58
- test('describe', async () => {
59
- await describe(
60
- peptidesDf, options['activityColumnName'], options['scaling'], peptidesGrid, true,
61
- DG.BitSet.create(peptidesDf.rowCount, (i) => i % 2 === 0), true);
62
- });
33
+ // test('describe', async () => {
34
+ // await describe(
35
+ // peptidesDf, options['activityColumnName'], options['scaling'], peptidesGrid, true,
36
+ // DG.BitSet.create(peptidesDf.rowCount, (i) => i % 2 === 0), true);
37
+ // });
63
38
 
64
39
  test('Peptides-controller', async () => {
65
- const peptides = PeptidesController.getInstance(peptidesDf);
66
- peptides.init(peptidesGrid, pepView, options, asCol, peptidesDf.columns.names());
67
- });
68
-
69
- test('panel.peptides', async () => {
70
- await P.peptidesPanel(asCol);
40
+ const peptides = await PeptidesController.getInstance(peptidesDf);
41
+ peptides.init(peptidesDf); //, peptidesDf.columns.names());
71
42
  });
72
43
 
73
44
  test('widgets.analyze-peptides', async () => {
74
- await analyzePeptidesWidget(asCol, pepView, peptidesGrid, peptidesDf);
45
+ await analyzePeptidesWidget(peptidesDf, asCol);
75
46
  });
76
47
 
77
48
  test('widgets.manual-alignment', async () => {
@@ -82,40 +53,8 @@ category('peptides', async () => {
82
53
  await peptideMoleculeWidget('NH2--A-N-T-T-Y-K-N-Y-R-S-N-L-L--COOH');
83
54
  });
84
55
 
85
- test('widgets.molfile', async () => {
86
- await P.peptideMolfile2('');
87
- });
88
-
89
- test('renderers.aligned-sequence-cell', async () => {
90
- P.alignedSequenceCellRenderer();
91
- });
92
-
93
- test('renderers.amino-acids-cell', async () => {
94
- P.aminoAcidsCellRenderer();
95
- });
96
-
97
- test('viewers.logo-viewer', async () => {
98
- P.logov();
99
- });
100
-
101
- test('viewers.sar-viewer', async () => {
102
- P.sar();
103
- });
104
-
105
- test('viewers.sar-vertical-viewer', async () => {
106
- P.sarVertical();
107
- });
108
-
109
- test('viewers.stacked-barchart-viewer', async () => {
110
- P.stackedBarChart();
111
- });
112
-
113
- test('viewers.subst-viewer', async () => {
114
- P.subst();
115
- });
116
-
117
56
  after(async () => {
118
- pepView.close();
57
+ // pepView.close();
119
58
  grok.shell.closeTable(peptidesDf);
120
59
  });
121
60
  });
@@ -86,13 +86,7 @@ export async function _testPeptideSimilaritySpaceViewer(
86
86
 
87
87
  try {
88
88
  viewer = await createPeptideSimilaritySpaceViewer(
89
- table,
90
- alignedSequencesColumn,
91
- method,
92
- measure,
93
- cyclesCount,
94
- null,
95
- );
89
+ table, method, measure, cyclesCount, undefined, alignedSequencesColumn);
96
90
  df = viewer.dataFrame!;
97
91
  } catch (error) {
98
92
  noException = false;
@@ -0,0 +1,439 @@
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
+ }