@datagrok/peptides 1.17.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.eslintrc.json +17 -6
  2. package/dist/196.js +2 -0
  3. package/dist/23.js +2 -0
  4. package/dist/282.js +2 -0
  5. package/dist/361.js +2 -2
  6. package/dist/40.js +2 -0
  7. package/dist/436.js +2 -2
  8. package/dist/65.js +2 -0
  9. package/dist/704.js +2 -0
  10. package/dist/package-test.js +2 -3
  11. package/dist/package.js +2 -3
  12. package/package.json +14 -14
  13. package/src/demo/fasta.ts +8 -2
  14. package/src/model.ts +780 -531
  15. package/src/package-test.ts +1 -3
  16. package/src/package.ts +15 -28
  17. package/src/tests/benchmarks.ts +31 -11
  18. package/src/tests/core.ts +11 -6
  19. package/src/tests/misc.ts +6 -6
  20. package/src/tests/model.ts +79 -44
  21. package/src/tests/table-view.ts +48 -38
  22. package/src/tests/utils.ts +0 -76
  23. package/src/tests/viewers.ts +30 -12
  24. package/src/tests/widgets.ts +30 -11
  25. package/src/utils/algorithms.ts +115 -38
  26. package/src/utils/cell-renderer.ts +181 -72
  27. package/src/utils/constants.ts +33 -7
  28. package/src/utils/misc.ts +244 -10
  29. package/src/utils/parallel-mutation-cliffs.ts +18 -15
  30. package/src/utils/statistics.ts +70 -15
  31. package/src/utils/tooltips.ts +42 -17
  32. package/src/utils/types.ts +29 -26
  33. package/src/utils/worker-creator.ts +5 -0
  34. package/src/viewers/logo-summary.ts +591 -130
  35. package/src/viewers/sar-viewer.ts +893 -239
  36. package/src/widgets/distribution.ts +305 -64
  37. package/src/widgets/manual-alignment.ts +18 -11
  38. package/src/widgets/mutation-cliffs.ts +44 -18
  39. package/src/widgets/peptides.ts +86 -91
  40. package/src/widgets/selection.ts +56 -22
  41. package/src/widgets/settings.ts +94 -44
  42. package/src/workers/dimensionality-reducer.ts +5 -6
  43. package/src/workers/mutation-cliffs-worker.ts +3 -16
  44. package/dist/209.js +0 -2
  45. package/dist/381.js +0 -2
  46. package/dist/770.js +0 -2
  47. package/dist/831.js +0 -2
  48. package/dist/868.js +0 -2
  49. package/dist/931.js +0 -3
  50. package/dist/931.js.LICENSE.txt +0 -51
  51. package/dist/932.js +0 -2
  52. package/dist/package-test.js.LICENSE.txt +0 -51
  53. package/dist/package.js.LICENSE.txt +0 -51
  54. package/src/tests/peptide-space-test.ts +0 -48
  55. package/src/tests/test-data.ts +0 -649
  56. package/src/utils/molecular-measure.ts +0 -174
  57. package/src/utils/peptide-similarity-space.ts +0 -216
  58. package/src/viewers/peptide-space-viewer.ts +0 -150
@@ -1,78 +1,98 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
2
  import * as DG from 'datagrok-api/dg';
3
-
4
- import BitArray from '@datagrok-libraries/utils/src/bit-array';
5
3
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
6
4
 
7
5
  import $ from 'cash-dom';
8
6
 
9
7
  import * as C from '../utils/constants';
10
- import {getAggregatedColumnValues, getStats, Stats} from '../utils/statistics';
11
- import {PeptidesModel} from '../model';
12
- import {getDistributionPanel, getDistributionTable} from '../utils/misc';
13
-
14
- export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
15
- if (!table.selection.anyTrue)
16
- return new DG.Widget(ui.divText('No distribution'));
17
-
18
- const activityCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY);
19
- const rowCount = activityCol.length;
20
-
21
- // const setDefaultProperties = (input: DG.InputBase): void => {
22
- // input.enabled = !model.isMutationCliffsSelectionEmpty;
23
- // $(input.root).find('.ui-input-editor').css('margin', '0px');
24
- // $(input.root).find('.ui-input-description').css('padding', '0px').css('padding-left', '5px');
25
- // $(input.captionLabel).addClass('ui-label-right');
26
- // };
27
- //
28
- // let defaultValuePos = model.splitByPos;
29
- // let defaultValueMonomer = model.splitByMonomer;
30
- // if (!model.isClusterSelectionEmpty && model.isMutationCliffsSelectionEmpty) {
31
- // defaultValuePos = false;
32
- // defaultValueMonomer = false;
33
- // }
34
-
35
- const distributionHost = ui.div([], 'd4-flex-wrap');
36
-
37
- const updateDistributionHost = (): void => {
38
- const res: HTMLDivElement[] = [];
39
- if (!table.selection.anyTrue)
40
- res.push(ui.divText('No distribution'));
41
- else {
42
- const hist = getActivityDistribution(getDistributionTable(activityCol, model.df.selection, model.getCombinedSelection()));
43
- const bitArray = BitArray.fromString(table.selection.toBinaryString());
44
- const mask = DG.BitSet.create(rowCount,
45
- bitArray.allFalse || bitArray.allTrue ? (_): boolean => true : (i): boolean => bitArray.getBit(i));
46
- const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
47
- const stats = bitArray.allFalse || bitArray.allTrue ?
48
- {count: rowCount, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray, mean: activityCol.stats.avg} :
49
- getStats(activityCol.getRawData(), bitArray);
50
- const tableMap = getStatsTableMap(stats);
51
- const resultMap: { [key: string]: any } = {...tableMap, ...aggregatedColMap};
52
- const distributionRoot = getDistributionPanel(hist, resultMap);
53
- $(distributionRoot).addClass('d4-flex-col');
54
-
55
- res.push(distributionRoot);
56
- }
8
+ import {AggregationColumns, getAggregatedColumnValues, getStats, StatsItem} from '../utils/statistics';
9
+ import {DistributionLabelMap, getDistributionPanel, getDistributionTable, SPLIT_CATEGORY} from '../utils/misc';
10
+ import {SARViewer} from '../viewers/sar-viewer';
11
+ import {CLUSTER_TYPE, LogoSummaryTable} from '../viewers/logo-summary';
12
+ import BitArray from '@datagrok-libraries/utils/src/bit-array';
13
+ import {Selection} from '../utils/types';
14
+
15
+ export type DistributionItemOptions = {
16
+ peptideSelection: DG.BitSet, columns: AggregationColumns, clusterColName?: string,
17
+ activityCol: DG.Column<number>, monomerPositionSelection: Selection, clusterSelection: Selection,
18
+ };
19
+
20
+ export enum DISTRIBUTION_CATEGORIES_KEYS {
21
+ SEPARATE_MONOMERS = 'separateMonomers',
22
+ SEPARATE_POSITIONS = 'separatePositions',
23
+ SEPARATE_CLUSTERS = 'separateClusters',
24
+ }
25
+
26
+ const general = 'general';
27
+ const key2category = (key: DISTRIBUTION_CATEGORIES_KEYS | typeof general): string => {
28
+ if (key === general)
29
+ return 'General';
30
+
31
+
32
+ return key.substring(8);
33
+ };
34
+ export type PeptideViewer = SARViewer | LogoSummaryTable;
35
+
36
+
37
+ /**
38
+ * Builds Distribution panel
39
+ * @param table - Dataframe with peptides
40
+ * @param options - Distribution options
41
+ * @return - Distribution panel
42
+ */
43
+ export function getDistributionWidget(table: DG.DataFrame, options: DistributionItemOptions): HTMLDivElement {
44
+ const mask = table.selection;
45
+ if (!mask.anyTrue)
46
+ return ui.divText('No distribution');
47
+
57
48
 
58
- $(distributionHost).empty().append(res);
49
+ const getDistributionCategoreisHost = (): HTMLDivElement => {
50
+ const distributionCategories: HTMLDivElement[] = [getDistributionCategory(general, table, options)];
51
+ for (const tag of Object.values(DISTRIBUTION_CATEGORIES_KEYS)) {
52
+ if (table.getTag(tag) !== `${true}` ||
53
+ (tag === DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_CLUSTERS && !options.clusterColName))
54
+ continue;
55
+
56
+
57
+ distributionCategories.push(getDistributionCategory(tag, table, options));
58
+ }
59
+ return (distributionCategories.length === 1) ? distributionCategories[0] : ui.div(distributionCategories);
59
60
  };
61
+ const distributionCategoriesHost = ui.div(getDistributionCategoreisHost());
62
+ const inputsNames = Object.values(DISTRIBUTION_CATEGORIES_KEYS);
63
+ const inputsArray: DG.InputBase[] = new Array(inputsNames.length);
64
+ for (let inputIdx = 0; inputIdx < inputsNames.length; inputIdx++) {
65
+ const inputName = inputsNames[inputIdx].substring(8);
66
+ inputsArray[inputIdx] = ui.boolInput(inputName,
67
+ table.getTag(inputsNames[inputIdx]) === `${true}`, () => {
68
+ table.setTag(inputsNames[inputIdx], `${inputsArray[inputIdx].value}`);
69
+ $(distributionCategoriesHost).empty();
70
+ distributionCategoriesHost.append(getDistributionCategoreisHost());
71
+ }) as DG.InputBase<boolean>;
72
+ $(inputsArray[inputIdx].captionLabel).addClass('ui-label-right').css('text-align', 'left');
73
+ $(inputsArray[inputIdx].root).find('.ui-input-editor').css('margin', '0px');
74
+ $(inputsArray[inputIdx].root).find('.ui-input-description').css('margin', '0px');
75
+ inputsArray[inputIdx].setTooltip(`Show distribution for each ${inputName}`);
76
+ if (inputName === DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_CLUSTERS)
77
+ inputsArray[inputIdx].enabled = !!(options.clusterColName && options.clusterSelection[CLUSTER_TYPE.ORIGINAL]);
78
+ else if (inputName === DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_MONOMERS ||
79
+ inputName === DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_POSITIONS)
80
+ inputsArray[inputIdx].enabled = Object.entries(options.monomerPositionSelection).length !== 0;
81
+ }
60
82
 
61
- // const splitByPosition = ui.boolInput('Split by position', defaultValuePos, updateDistributionHost);
62
- // splitByPosition.setTooltip('Constructs distribution for each position separately');
63
- // setDefaultProperties(splitByPosition);
64
- // $(splitByPosition.root).css('margin-right', '10px');
65
- // const splitByMonomer = ui.boolInput('Split by monomer', defaultValueMonomer, updateDistributionHost);
66
- // splitByMonomer.setTooltip('Constructs distribution for each monomer separately');
67
- // setDefaultProperties(splitByMonomer);
68
-
69
- // const controlsHost = ui.divH([splitByPosition.root, splitByMonomer.root]);
70
- // splitByMonomer.fireChanged();
71
- updateDistributionHost();
72
- return new DG.Widget(ui.divV([/*controlsHost,*/ distributionHost]));
83
+ const inputsHost = ui.form(inputsArray);
84
+ $(inputsHost).css('display', 'inline-flex');
85
+ return ui.divV([inputsHost, distributionCategoriesHost]);
73
86
  }
74
87
 
75
- export function getActivityDistribution(table: DG.DataFrame, isTooltip: boolean = false): DG.Viewer<DG.IHistogramLookSettings> {
88
+ /**
89
+ * Builds activity distribution histogram
90
+ * @param table - Dataframe with peptides
91
+ * @param isTooltip - Is histogram for tooltip
92
+ * @return - Histogram viewer
93
+ */
94
+ export function getActivityDistribution(table: DG.DataFrame, isTooltip: boolean = false,
95
+ ): DG.Viewer<DG.IHistogramLookSettings> {
76
96
  const hist = table.plot.histogram({
77
97
  filteringEnabled: false,
78
98
  valueColumnName: C.COLUMNS_NAMES.ACTIVITY,
@@ -88,7 +108,14 @@ export function getActivityDistribution(table: DG.DataFrame, isTooltip: boolean
88
108
  return hist;
89
109
  }
90
110
 
91
- export function getStatsTableMap(stats: Stats, options: {fractionDigits?: number} = {}): StringDictionary {
111
+ /**
112
+ * Builds stats table map
113
+ * @param stats - Stats item
114
+ * @param options - Stats table map options
115
+ * @param options.fractionDigits - Number of fraction digits for stats values
116
+ * @return - Stats table map
117
+ */
118
+ export function getStatsTableMap(stats: StatsItem, options: { fractionDigits?: number } = {}): StringDictionary {
92
119
  options.fractionDigits ??= 3;
93
120
  const tableMap: StringDictionary = {
94
121
  'Count': `${stats.count} (${stats.ratio.toFixed(options.fractionDigits)}%)`,
@@ -97,5 +124,219 @@ export function getStatsTableMap(stats: Stats, options: {fractionDigits?: number
97
124
  };
98
125
  if (stats.pValue !== null)
99
126
  tableMap['p-value'] = stats.pValue < 0.01 ? '<0.01' : stats.pValue.toFixed(options.fractionDigits);
127
+
128
+
100
129
  return tableMap;
101
130
  }
131
+
132
+ /**
133
+ * Builds distribution for signle item
134
+ * @param table - Dataframe with peptides
135
+ * @param stats - Stats item
136
+ * @param options - Distribution options
137
+ * @param [labelMap] - Map for histogram legend labels
138
+ * @return - Distribution panel
139
+ */
140
+ function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: DistributionItemOptions,
141
+ labelMap: DistributionLabelMap = {}): HTMLDivElement {
142
+ const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection,
143
+ options.peptideSelection));
144
+ const aggregatedColMap = getAggregatedColumnValues(table, Object.entries(options.columns),
145
+ {filterDf: true, mask: DG.BitSet.fromBytes(stats.mask.buffer.buffer, stats.mask.length)});
146
+ const tableMap = getStatsTableMap(stats);
147
+ const resultMap: { [key: string]: any } = {...tableMap, ...aggregatedColMap};
148
+ const distributionRoot = getDistributionPanel(hist, resultMap, labelMap);
149
+ $(distributionRoot).addClass('d4-flex-col');
150
+
151
+ return distributionRoot;
152
+ }
153
+
154
+ /**
155
+ * Builds distribution item for specified category
156
+ * @param category - Distribution category
157
+ * @param table - Dataframe with peptides
158
+ * @param options - Distribution options
159
+ * @return - Distribution item
160
+ */
161
+ function getDistributionCategory(category: DISTRIBUTION_CATEGORIES_KEYS | typeof general, table: DG.DataFrame,
162
+ options: DistributionItemOptions): HTMLDivElement {
163
+ let body: HTMLDivElement = ui.divText('No distribution');
164
+ switch (category) {
165
+ case general:
166
+ const bitArray = BitArray.fromSeq(table.selection.length, (i: number) => table.selection.get(i));
167
+ const stats = !table.selection.anyTrue || !table.selection.anyFalse ?
168
+ {
169
+ count: options.activityCol.length, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray,
170
+ mean: options.activityCol.stats.avg,
171
+ } :
172
+ getStats(options.activityCol.getRawData(), bitArray);
173
+
174
+ body = getSingleDistribution(table, stats, options);
175
+ break;
176
+ case DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_CLUSTERS:
177
+ body = getDistributionForClusters(table, options as Required<DistributionItemOptions>, options.clusterSelection);
178
+ break;
179
+ case DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_MONOMERS:
180
+ const reversedSelectionObject = getReversedObject(options.monomerPositionSelection);
181
+ body = getDistributionForMonomers(table, options, reversedSelectionObject);
182
+ break;
183
+ case DISTRIBUTION_CATEGORIES_KEYS.SEPARATE_POSITIONS:
184
+ body = getDistributionForPositions(table, options, options.monomerPositionSelection);
185
+ break;
186
+ }
187
+
188
+ return ui.divV([ui.h1(key2category(category)), body]);
189
+ }
190
+
191
+ /**
192
+ * Builds distribution group for clusters
193
+ * @param table - Dataframe with peptides
194
+ * @param options - Distribution options
195
+ * @param selectionObject - Selection object
196
+ * @return - Distribution group host
197
+ */
198
+ function getDistributionForClusters(table: DG.DataFrame, options: Required<DistributionItemOptions>,
199
+ selectionObject: Selection): HTMLDivElement {
200
+ const rowCount = table.rowCount;
201
+ const distributions: HTMLDivElement[] = [];
202
+ const activityColData = options.activityCol.getRawData();
203
+ const clusterCol = table.getCol(options.clusterColName);
204
+ const clusterColCategories = clusterCol.categories;
205
+ const clusterColData = clusterCol.getRawData() as Int32Array;
206
+
207
+ // Build distributions for original clusters
208
+ const selectedClustersCategoryIndexes = selectionObject[CLUSTER_TYPE.ORIGINAL]
209
+ .map((cluster: string) => clusterColCategories.indexOf(cluster));
210
+ const clusterMasks: BitArray[] = new Array(selectedClustersCategoryIndexes.length).fill(new BitArray(rowCount));
211
+ for (let i = 0; i < rowCount; i++) {
212
+ const cluster = clusterColData[i];
213
+ const selectedIndex = selectedClustersCategoryIndexes.indexOf(cluster);
214
+ if (selectedIndex !== -1)
215
+ clusterMasks[selectedIndex].setTrue(i);
216
+ }
217
+ for (let selectedClusterIdx = 0; selectedClusterIdx < selectedClustersCategoryIndexes.length; selectedClusterIdx++) {
218
+ const selectedClusterCategoryIndex = selectedClustersCategoryIndexes[selectedClusterIdx];
219
+ const stats = getStats(activityColData, clusterMasks[selectedClusterIdx]);
220
+ distributions.push(getSingleDistribution(table, stats, options,
221
+ {[SPLIT_CATEGORY.SELECTION]: clusterColCategories[selectedClusterCategoryIndex]}));
222
+ }
223
+
224
+ // Build distributions for custom clusters
225
+ const customClusterSelection = selectionObject[CLUSTER_TYPE.CUSTOM];
226
+ for (const clusterColumnName of customClusterSelection) {
227
+ const customClustCol = table.getCol(clusterColumnName);
228
+ const bitArray = BitArray.fromUint32Array(rowCount, customClustCol.getRawData() as Uint32Array);
229
+ const stats = getStats(activityColData, bitArray);
230
+ distributions.push(getSingleDistribution(table, stats, options,
231
+ {[SPLIT_CATEGORY.SELECTION]: clusterColumnName}));
232
+ }
233
+
234
+ return ui.div(distributions, 'd4-flex-wrap');
235
+ }
236
+
237
+ /**
238
+ * Builds distribution group for positions category
239
+ * @param table - Dataframe with peptides
240
+ * @param options - Distribution options
241
+ * @param selectionObject - Selection object
242
+ * @return - Distribution group host
243
+ */
244
+ function getDistributionForPositions(table: DG.DataFrame, options: DistributionItemOptions,
245
+ selectionObject: Selection): HTMLDivElement {
246
+ const positions = Object.keys(selectionObject);
247
+ const rowCount = table.rowCount;
248
+ const distributions: HTMLDivElement[] = [];
249
+ const activityColData = options.activityCol.getRawData();
250
+ const positionColumns: (DG.Column<string> | undefined)[] = [];
251
+ const positionColumnsCategories: (string[] | undefined)[] = [];
252
+ const positionColumnsData: (Int32Array | undefined)[] = [];
253
+
254
+ for (let posIdx = 0; posIdx < positions.length; posIdx++) {
255
+ const position = positions[posIdx];
256
+ const monomerList = selectionObject[position];
257
+ if (monomerList.length === 0)
258
+ continue;
259
+
260
+
261
+ positionColumns[posIdx] ??= table.getCol(position);
262
+ positionColumnsCategories[posIdx] ??= positionColumns[posIdx]!.categories;
263
+ positionColumnsData[posIdx] ??= positionColumns[posIdx]!.getRawData() as Int32Array;
264
+
265
+ const mask = new BitArray(table.rowCount);
266
+ for (let monomerIdx = 0; monomerIdx < monomerList.length; monomerIdx++) {
267
+ const monomer = monomerList[monomerIdx];
268
+ const monomerCategoryIndex = positionColumnsCategories[posIdx]!.indexOf(monomer);
269
+
270
+ for (let i = 0; i < rowCount; i++) {
271
+ if (positionColumnsData[posIdx]![i] === monomerCategoryIndex)
272
+ mask.setTrue(i);
273
+ }
274
+ }
275
+ const stats = getStats(activityColData, mask);
276
+ distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: position}));
277
+ }
278
+
279
+ return ui.div(distributions, 'd4-flex-wrap');
280
+ }
281
+
282
+ /**
283
+ * Builds distribution group for monomers category
284
+ * @param table - Dataframe with peptides
285
+ * @param options - Distribution options
286
+ * @param reversedSelectionObject - Selection object with monomers as keys and list of positions as values
287
+ * @return - Distribution group host
288
+ */
289
+ function getDistributionForMonomers(table: DG.DataFrame, options: DistributionItemOptions,
290
+ reversedSelectionObject: Selection): HTMLDivElement {
291
+ const monomers = Object.keys(reversedSelectionObject);
292
+ const rowCount = table.rowCount;
293
+ const distributions: HTMLDivElement[] = [];
294
+ const positionColumns: (DG.Column<string> | undefined)[] = [];
295
+ const positionColumnsCategories: (string[] | undefined)[] = [];
296
+ const positionColumnsData: (Int32Array | undefined)[] = [];
297
+ const activityColData = options.activityCol.getRawData();
298
+
299
+ for (const monomer of monomers) {
300
+ const posList = reversedSelectionObject[monomer];
301
+ const mask = new BitArray(rowCount);
302
+
303
+ for (let posIdx = 0; posIdx < posList.length; posIdx++) {
304
+ const position = posList[posIdx];
305
+ positionColumns[posIdx] ??= table.getCol(position);
306
+ positionColumnsCategories[posIdx] ??= positionColumns[posIdx]!.categories;
307
+ positionColumnsData[posIdx] ??= positionColumns[posIdx]!.getRawData() as Int32Array;
308
+
309
+ const monomerCategoryIndex = positionColumnsCategories[posIdx]!.indexOf(monomer);
310
+ for (let i = 0; i < rowCount; i++) {
311
+ if (positionColumnsData[posIdx]![i] === monomerCategoryIndex)
312
+ mask.setTrue(i);
313
+ }
314
+ }
315
+ const stats = getStats(activityColData, mask);
316
+
317
+ distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: monomer}));
318
+ }
319
+
320
+ return ui.div(distributions, 'd4-flex-wrap');
321
+ }
322
+
323
+ /**
324
+ * Converts monomer-position selection object to have monomers as keys and list of positions as values
325
+ * @param selectionObject - Selection object
326
+ * @return - Reversed selection object
327
+ */
328
+ function getReversedObject(selectionObject: Selection): Selection {
329
+ const reversedSelectionObject: Selection = {};
330
+ const positions = Object.keys(selectionObject);
331
+ for (const position of positions) {
332
+ for (const monomer of selectionObject[position]) {
333
+ if (!reversedSelectionObject.hasOwnProperty(monomer)) {
334
+ reversedSelectionObject[monomer] = [position];
335
+ continue;
336
+ }
337
+ if (!reversedSelectionObject[monomer].includes(position))
338
+ reversedSelectionObject[monomer].push(position);
339
+ }
340
+ }
341
+ return reversedSelectionObject;
342
+ }
@@ -5,32 +5,39 @@ import * as DG from 'datagrok-api/dg';
5
5
  import $ from 'cash-dom';
6
6
  import '../styles.css';
7
7
  import {PeptidesModel} from '../model';
8
- import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
8
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
9
9
 
10
- /** Manual sequence alignment widget.
11
- * @param {DG.Column} alignedSequenceCol Aligned sequence column
12
- * @param {DG.DataFrame} currentDf Working table
13
- * @return {DG.Widget} Widget for manual sequence alignment */
10
+ /**
11
+ * Allows to edit sequence and apply changes to the table and analysis.
12
+ * @param alignedSequenceCol Aligned sequence column
13
+ * @param currentDf Working table
14
+ * @return Widget for manual sequence alignment
15
+ */
14
16
  export function manualAlignmentWidget(alignedSequenceCol: DG.Column<string>, currentDf: DG.DataFrame): DG.Widget {
15
17
  const sequenceInput = ui.textInput('', alignedSequenceCol.get(currentDf.currentRowIdx)!);
16
18
  $(sequenceInput.root).addClass('pep-textinput');
17
19
 
18
20
  const applyChangesBtn = ui.button('Apply', async () => {
21
+ const uh = UnitsHandler.getOrCreate(alignedSequenceCol);
19
22
  const newSequence = sequenceInput.value;
23
+ const splitter = uh.getSplitter();
24
+ const splitSequence = splitter(newSequence);
20
25
  const affectedRowIndex = currentDf.currentRowIdx;
21
- const splitSequence = splitAlignedSequences(DG.Column.fromStrings('splitSequence', [newSequence]));
22
-
23
26
  alignedSequenceCol.set(affectedRowIndex, newSequence);
24
- for (const part of splitSequence.columns) {
25
- if (currentDf.col(part.name) !== null)
26
- currentDf.set(part.name, affectedRowIndex, part.get(0));
27
+ for (let i = 0; i < splitSequence.length; i++) {
28
+ const part = splitSequence[i];
29
+ if (currentDf.col(i.toString()) !== null)
30
+ currentDf.set(i.toString(), affectedRowIndex, part);
27
31
  }
28
32
  const temp = grok.shell.o;
29
33
  grok.shell.o = null;
30
- grok.shell.o = temp;
31
34
 
32
35
  const peptidesController = PeptidesModel.getInstance(currentDf);
33
36
  peptidesController.updateGrid();
37
+
38
+ setTimeout(() => {
39
+ grok.shell.o = temp;
40
+ }, 100);
34
41
  }, 'Apply changes');
35
42
 
36
43
  const resetBtn = ui.button(ui.iconFA('redo'),
@@ -2,46 +2,61 @@ import * as ui from 'datagrok-api/ui';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import * as C from '../utils/constants';
4
4
  import * as type from '../utils/types';
5
- import {PeptidesModel} from '../model';
6
5
  import {addExpandIcon, getSeparator, setGridProps} from '../utils/misc';
7
6
  import {renderCellSelection} from '../utils/cell-renderer';
8
7
 
9
- export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
8
+ export type MutationCliffsOptions = {
9
+ mutationCliffs: type.MutationCliffs, mutationCliffsSelection: type.Selection, sequenceColumnName: string,
10
+ positionColumns: DG.Column<string>[], gridColumns: DG.GridColumnList, activityCol: DG.Column<number>,
11
+ };
12
+
13
+ /**
14
+ * Creates mutation cliffs widget that shows grid of sequences that form mutation cliffs pairs as well as
15
+ * grid of mutation cliffs pairs themselves.
16
+ * @param table - table with sequences.
17
+ * @param options - options for mutation cliffs widget.
18
+ * @return - mutation cliffs widget.
19
+ */
20
+ export function mutationCliffsWidget(table: DG.DataFrame, options: MutationCliffsOptions): DG.Widget {
10
21
  const filteredIndexes = table.filter.getSelectedIndexes();
11
- const substInfo = model.mutationCliffs;
12
- const currentCell = model.mutationCliffsSelection;
13
- const positions = Object.keys(currentCell);
22
+ const positions = Object.keys(options.mutationCliffsSelection);
14
23
 
15
- if (!positions.length || substInfo === null)
24
+ if (!positions.length || options.mutationCliffs === null)
16
25
  return new DG.Widget(ui.label('No mutations table generated'));
17
26
 
27
+
18
28
  const substitutionsArray: string[] = [];
19
29
  const deltaArray: number[] = [];
20
30
  const substitutedToArray: string[] = [];
21
31
  const fromIdxArray: number[] = [];
22
32
  const toIdxArray: number[] = [];
23
- const alignedSeqCol = table.getCol(model.settings.sequenceColumnName!);
33
+ const alignedSeqCol = table.getCol(options.sequenceColumnName!);
24
34
  const alignedSeqColCategories = alignedSeqCol.categories;
25
35
  const alignedSeqColData = alignedSeqCol.getRawData();
26
- const activityScaledCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY);
27
- const activityScaledColData = activityScaledCol.getRawData();
36
+ // const activityScaledCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY);
37
+ const activityScaledColData = options.activityCol.getRawData();
28
38
  const seenIndexes = new Map<number, number[]>();
29
39
  const uniqueSequencesBitSet = DG.BitSet.create(table.rowCount);
30
40
 
41
+ const positionColumns: { [colName: string]: DG.Column<string> } =
42
+ Object.fromEntries(options.positionColumns.map((col) => [col.name, col]));
31
43
  for (const pos of positions) {
32
- const posCol = table.getCol(pos);
44
+ const posCol = positionColumns[pos];
33
45
  const posColCategories = posCol.categories;
34
46
  const posColData = posCol.getRawData();
35
47
 
36
- for (const monomer of currentCell[pos]) {
37
- const substitutionsMap = substInfo.get(monomer)?.get(pos) as Map<number, type.UTypedArray> | undefined;
48
+ for (const monomer of options.mutationCliffsSelection[pos]) {
49
+ const substitutionsMap = options.mutationCliffs.get(monomer)
50
+ ?.get(pos) as Map<number, type.UTypedArray> | undefined;
38
51
  if (typeof substitutionsMap === 'undefined')
39
52
  continue;
40
53
 
54
+
41
55
  for (const [referenceIdx, indexArray] of substitutionsMap.entries()) {
42
56
  if (!filteredIndexes.includes(referenceIdx))
43
57
  continue;
44
58
 
59
+
45
60
  const forbiddentIndexes = seenIndexes.get(referenceIdx) ?? [];
46
61
  const baseSequence = alignedSeqColCategories[alignedSeqColData[referenceIdx]];
47
62
  const baseActivity = activityScaledColData[referenceIdx];
@@ -50,8 +65,11 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
50
65
  if (forbiddentIndexes.includes(subIdx) || !filteredIndexes.includes(subIdx))
51
66
  continue;
52
67
 
68
+
53
69
  if (!seenIndexes.has(subIdx))
54
70
  seenIndexes.set(subIdx, []);
71
+
72
+
55
73
  const subSeq = alignedSeqColCategories[alignedSeqColData[subIdx]];
56
74
 
57
75
  seenIndexes.get(subIdx)!.push(referenceIdx);
@@ -70,6 +88,7 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
70
88
  if (substitutionsArray.length === 0)
71
89
  return new DG.Widget(ui.label('No mutations table generated'));
72
90
 
91
+
73
92
  const substCol = DG.Column.fromStrings('Mutation', substitutionsArray);
74
93
  const activityDeltaCol = DG.Column.fromList('double', 'Delta', deltaArray);
75
94
  const hiddenSubstToAarCol = DG.Column.fromStrings('~to', substitutedToArray);
@@ -102,11 +121,16 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
102
121
  const rowIdx = gridCell.tableRowIndex;
103
122
  if (!keyPress)
104
123
  return;
124
+
125
+
105
126
  if (rowIdx === null)
106
127
  return;
128
+
129
+
107
130
  if (lastSelectedIndex !== null)
108
131
  pairsSelectedIndexes.splice(pairsSelectedIndexes.indexOf(lastSelectedIndex), 1);
109
132
 
133
+
110
134
  if (!pairsSelectedIndexes.includes(rowIdx)) {
111
135
  pairsSelectedIndexes.push(rowIdx);
112
136
  pairsGrid.invalidate();
@@ -123,6 +147,7 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
123
147
  if (!gridCell || gridCell.tableRowIndex === null)
124
148
  return;
125
149
 
150
+
126
151
  const rowIdx = gridCell.tableRowIndex;
127
152
  if (!event.shiftKey) {
128
153
  pairsSelectedIndexes.length = 0;
@@ -141,16 +166,16 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
141
166
  if (gcArgs.cell.tableColumn?.name !== substCol.name || !pairsSelectedIndexes.includes(gcArgs.cell.tableRowIndex!))
142
167
  return;
143
168
 
169
+
144
170
  renderCellSelection(gcArgs.g, gcArgs.bounds);
145
171
  });
146
172
 
147
- const gridCols = model.analysisView.grid.columns;
148
- const originalGridColCount = gridCols.length;
149
- const positionColumns = model.positionColumns.toArray().map((col) => col.name);
173
+ const originalGridColCount = options.gridColumns.length;
150
174
  const columnNames: string[] = [];
151
175
  for (let colIdx = 1; colIdx < originalGridColCount; colIdx++) {
152
- const gridCol = gridCols.byIndex(colIdx);
153
- if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.includes(gridCol.name)))
176
+ const gridCol = options.gridColumns.byIndex(colIdx);
177
+ if (gridCol?.name === options.sequenceColumnName ||
178
+ (gridCol?.visible === true && !Object.hasOwn(positionColumns, gridCol.name)))
154
179
  columnNames.push(gridCol!.name);
155
180
  }
156
181
 
@@ -176,5 +201,6 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
176
201
  addExpandIcon(pairsGrid);
177
202
  addExpandIcon(uniqueSequencesGrid);
178
203
 
179
- return new DG.Widget(ui.divV([aminoToInput.root, pairsGrid.root, uniqueSequencesGrid.root], {style: {width: '100%'}}));
204
+ return new DG.Widget(ui.divV([aminoToInput.root, pairsGrid.root, uniqueSequencesGrid.root],
205
+ {style: {width: '100%'}}));
180
206
  }