@datagrok/peptides 1.16.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.eslintrc.json +17 -6
  2. package/CHANGELOG.md +33 -8
  3. package/README.md +12 -7
  4. package/dist/196.js +2 -3
  5. package/dist/23.js +2 -0
  6. package/dist/282.js +2 -0
  7. package/dist/361.js +2 -2
  8. package/dist/40.js +2 -0
  9. package/dist/436.js +2 -2
  10. package/dist/65.js +2 -0
  11. package/dist/704.js +2 -0
  12. package/dist/package-test.js +2 -3
  13. package/dist/package.js +2 -3
  14. package/package.json +13 -13
  15. package/setup-unlink-clean.cmd +6 -0
  16. package/setup.cmd +2 -2
  17. package/src/demo/fasta.ts +8 -2
  18. package/src/model.ts +857 -560
  19. package/src/package-test.ts +1 -3
  20. package/src/package.ts +28 -50
  21. package/src/tests/benchmarks.ts +31 -11
  22. package/src/tests/core.ts +11 -6
  23. package/src/tests/misc.ts +6 -6
  24. package/src/tests/model.ts +80 -45
  25. package/src/tests/table-view.ts +49 -39
  26. package/src/tests/utils.ts +0 -76
  27. package/src/tests/viewers.ts +30 -12
  28. package/src/tests/widgets.ts +30 -11
  29. package/src/utils/algorithms.ts +115 -38
  30. package/src/utils/cell-renderer.ts +217 -96
  31. package/src/utils/constants.ts +37 -7
  32. package/src/utils/misc.ts +285 -30
  33. package/src/utils/parallel-mutation-cliffs.ts +18 -15
  34. package/src/utils/statistics.ts +70 -14
  35. package/src/utils/tooltips.ts +46 -25
  36. package/src/utils/types.ts +29 -26
  37. package/src/utils/worker-creator.ts +5 -0
  38. package/src/viewers/logo-summary.ts +597 -135
  39. package/src/viewers/sar-viewer.ts +946 -249
  40. package/src/widgets/distribution.ts +291 -196
  41. package/src/widgets/manual-alignment.ts +18 -11
  42. package/src/widgets/mutation-cliffs.ts +45 -21
  43. package/src/widgets/peptides.ts +86 -91
  44. package/src/widgets/selection.ts +56 -22
  45. package/src/widgets/settings.ts +94 -44
  46. package/src/workers/dimensionality-reducer.ts +5 -6
  47. package/src/workers/mutation-cliffs-worker.ts +3 -16
  48. package/dist/196.js.LICENSE.txt +0 -51
  49. package/dist/209.js +0 -2
  50. package/dist/381.js +0 -2
  51. package/dist/694.js +0 -2
  52. package/dist/831.js +0 -2
  53. package/dist/868.js +0 -2
  54. package/dist/package-test.js.LICENSE.txt +0 -51
  55. package/dist/package.js.LICENSE.txt +0 -51
  56. package/src/tests/peptide-space-test.ts +0 -48
  57. package/src/tests/test-data.ts +0 -649
  58. package/src/utils/molecular-measure.ts +0 -174
  59. package/src/utils/peptide-similarity-space.ts +0 -216
  60. package/src/viewers/peptide-space-viewer.ts +0 -150
@@ -1,234 +1,121 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
2
  import * as DG from 'datagrok-api/dg';
3
-
4
3
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
4
+
5
5
  import $ from 'cash-dom';
6
6
 
7
7
  import * as C from '../utils/constants';
8
- import {getAggregatedColumnValues, getStats, Stats} from '../utils/statistics';
9
- import {PeptidesModel} from '../model';
10
- import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
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';
11
12
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
13
+ import {Selection} from '../utils/types';
12
14
 
13
- const allConst = 'All';
14
- const otherConst = 'Other';
15
-
16
- export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
17
- if (!table.selection.anyTrue)
18
- return new DG.Widget(ui.divText('No distribution'));
19
-
20
- const activityCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
21
- const activityColData = activityCol.getRawData();
22
- const rowCount = activityCol.length;
23
- const selectionObject = model.invariantMapSelection;
24
- const clustersColName = model.settings.clustersColumnName;
25
- let clustersProcessedObject: string[] = [];
26
- if (clustersColName)
27
- clustersProcessedObject = Object.values(model.clusterSelection).flat();
28
-
29
- const positions = Object.keys(selectionObject);
30
- let monomerStr = allConst;
31
- let otherStr = '';
32
-
33
- const updateDistributionHost = (): void => {
34
- model.splitByPos = splitByPosition.value!;
35
- model.splitByMonomer = splitByMonomer.value!;
36
- const res: HTMLDivElement[] = [];
37
- if (splitByPosition.value && splitByMonomer.value) {
38
- otherStr = otherConst;
39
- for (const position of positions) {
40
- const monomerList = selectionObject[position];
41
- if (monomerList.length === 0)
42
- continue;
43
-
44
- const posCol = table.getCol(position);
45
- const posColCategories = posCol.categories;
46
- const posColData = posCol.getRawData();
47
-
48
- for (const monomer of monomerList) {
49
- const labels = getDistributionLegend(`${position} : ${monomer}`, otherStr);
50
-
51
- const monomerCategoryIndex = posColCategories.indexOf(monomer);
52
- const mask = DG.BitSet.create(rowCount, (i) => posColData[i] === monomerCategoryIndex);
53
- const distributionTable = DG.DataFrame.fromColumns(
54
- [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
55
- const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
56
-
57
- const stats = model.monomerPositionStats[position]![monomer]!;
58
- const tableMap = getStatsTableMap(stats);
59
-
60
- const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
61
-
62
- const resultMap = {...tableMap, ...aggregatedColMap};
63
- const distributionRoot = getStatsSummary(labels, hist, resultMap);
64
- $(distributionRoot).addClass('d4-flex-col');
65
-
66
- res.push(distributionRoot);
67
- }
68
- }
69
- } else if (splitByPosition.value) {
70
- otherStr = otherConst;
71
- for (const position of positions) {
72
- const monomerList = selectionObject[position];
73
- if (monomerList.length === 0)
74
- continue;
75
-
76
- monomerStr = `${position}: {${monomerList.join(', ')}}`;
77
- const labels = getDistributionLegend(monomerStr, otherStr);
78
-
79
- const posCol = table.getCol(position);
80
- const posColCategories = posCol.categories;
81
- const posColData = posCol.getRawData();
82
- const monomerIndexesList = monomerList.map((monomer) => posColCategories.indexOf(monomer));
83
- const mask = DG.BitSet.create(rowCount, (i) => monomerIndexesList.includes(posColData[i]));
84
- const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
85
-
86
- const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
87
-
88
- const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
89
- const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
15
+ export type DistributionItemOptions = {
16
+ peptideSelection: DG.BitSet, columns: AggregationColumns, clusterColName?: string,
17
+ activityCol: DG.Column<number>, monomerPositionSelection: Selection, clusterSelection: Selection,
18
+ };
90
19
 
91
- const bitArray = BitArray.fromUint32Array(rowCount, splitCol.getRawData() as Uint32Array);
92
- const stats = getStats(activityColData, bitArray);
93
- const tableMap = getStatsTableMap(stats);
20
+ export enum DISTRIBUTION_CATEGORIES_KEYS {
21
+ SEPARATE_MONOMERS = 'separateMonomers',
22
+ SEPARATE_POSITIONS = 'separatePositions',
23
+ SEPARATE_CLUSTERS = 'separateClusters',
24
+ }
94
25
 
95
- const resultMap = {...tableMap, ...aggregatedColMap};
96
- const distributionRoot = getStatsSummary(labels, hist, resultMap);
97
- $(distributionRoot).addClass('d4-flex-col');
26
+ const general = 'general';
27
+ const key2category = (key: DISTRIBUTION_CATEGORIES_KEYS | typeof general): string => {
28
+ if (key === general)
29
+ return 'General';
98
30
 
99
- res.push(distributionRoot);
100
- }
101
- } else if (splitByMonomer.value) {
102
- const reversedSelectionObject: {[monomer: string]: string[]} = {};
103
- const monomers = [];
104
- for (const position of positions) {
105
- for (const monomer of selectionObject[position]) {
106
- if (!reversedSelectionObject.hasOwnProperty(monomer)) {
107
- reversedSelectionObject[monomer] = [position];
108
- monomers.push(monomer);
109
- continue;
110
- }
111
- if (!reversedSelectionObject[monomer].includes(position))
112
- reversedSelectionObject[monomer].push(position);
113
- }
114
- }
115
31
 
116
- otherStr = otherConst;
117
- for (const monomer of monomers) {
118
- const posList = reversedSelectionObject[monomer];
119
- const posColList = posList.map((pos) => table.getCol(pos));
120
- const posColCategoriesList = posColList.map((posCol) => posCol.categories);
121
- const posColDataList = posColList.map((posCol) => posCol.getRawData());
122
- const monomerCategoryIndexList = posColCategoriesList.map((posColCategories) => posColCategories.indexOf(monomer));
32
+ return key.substring(8);
33
+ };
34
+ export type PeptideViewer = SARViewer | LogoSummaryTable;
123
35
 
124
- monomerStr = `${monomer}: {${posList.join(', ')}}`;
125
- const labels = getDistributionLegend(monomerStr, otherStr);
126
36
 
127
- const mask = DG.BitSet.create(rowCount,
128
- (i) => posColDataList.some((posColData, j) => posColData[i] === monomerCategoryIndexList[j]));
129
- const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
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');
130
47
 
131
- const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
132
- const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
133
- const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
134
48
 
135
- const bitArray = BitArray.fromUint32Array(rowCount, splitCol.getRawData() as Uint32Array);
136
- const stats = getStats(activityColData, bitArray);
137
- const tableMap = getStatsTableMap(stats);
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;
138
55
 
139
- const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
140
- const distributionRoot = getStatsSummary(labels, hist, resultMap);
141
- $(distributionRoot).addClass('d4-flex-col');
142
56
 
143
- res.push(distributionRoot);
144
- }
145
- } else {
146
- if (!table.selection.anyTrue)
147
- res.push(ui.divText('No distribution'));
148
- else {
149
- otherStr = '';
150
- if (Object.values(selectionObject).some((selectedAar) => selectedAar.length !== 0) ||
151
- clustersProcessedObject.length !== 0) {
152
- monomerStr = '';
153
- for (const position of positions) {
154
- const monomerList = selectionObject[position];
155
- if (monomerList.length !== 0)
156
- monomerStr += `${position}: {${monomerList.join(', ')}}; `;
157
- }
158
- if (clustersProcessedObject.length !== 0)
159
- monomerStr += `Clusters: ${clustersProcessedObject.join(', ')}`;
160
- otherStr = otherConst;
161
- }
162
- const labels = getDistributionLegend(monomerStr, otherStr);
163
-
164
- const distributionTable = DG.DataFrame.fromColumns([activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, table.selection)]);
165
- const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
166
- const bitArray = BitArray.fromString(table.selection.toBinaryString());
167
- const mask = DG.BitSet.create(rowCount,
168
- bitArray.allFalse || bitArray.allTrue ? (_): boolean => true : (i): boolean => bitArray.getBit(i));
169
- const aggregatedColMap = getAggregatedColumnValues(model.df, model.settings.columns!, {filterDf: true, mask});
170
- const stats = bitArray.allFalse || bitArray.allTrue ?
171
- {count: rowCount, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray,
172
- mean: activityCol.stats.avg} :
173
- getStats(activityColData, bitArray);
174
- const tableMap = getStatsTableMap(stats);
175
- const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
176
- const distributionRoot = getStatsSummary(labels, hist, resultMap);
177
- $(distributionRoot).addClass('d4-flex-col');
178
-
179
- res.push(distributionRoot);
180
- }
57
+ distributionCategories.push(getDistributionCategory(tag, table, options));
181
58
  }
182
- $(distributionHost).empty().append(res);
183
- };
184
-
185
- const setDefaultProperties = (input: DG.InputBase): void => {
186
- input.enabled = !model.isMutationCliffsSelectionEmpty;
187
- $(input.root).find('.ui-input-editor').css('margin', '0px');
188
- $(input.root).find('.ui-input-description').css('padding', '0px').css('padding-left', '5px');
189
- $(input.captionLabel).addClass('ui-label-right');
59
+ return (distributionCategories.length === 1) ? distributionCategories[0] : ui.div(distributionCategories);
190
60
  };
191
-
192
- let defaultValuePos = model.splitByPos;
193
- let defaultValueMonomer = model.splitByMonomer;
194
- if (!model.isClusterSelectionEmpty && model.isMutationCliffsSelectionEmpty) {
195
- defaultValuePos = false;
196
- defaultValueMonomer = false;
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;
197
81
  }
198
82
 
199
- const splitByPosition = ui.boolInput('Split by position', defaultValuePos, updateDistributionHost);
200
- splitByPosition.setTooltip('Constructs distribution for each position separately');
201
- setDefaultProperties(splitByPosition);
202
- $(splitByPosition.root).css('margin-right', '10px');
203
- const splitByMonomer = ui.boolInput('Split by monomer', defaultValueMonomer, updateDistributionHost);
204
- splitByMonomer.setTooltip('Constructs distribution for each monomer separately');
205
- setDefaultProperties(splitByMonomer);
206
-
207
- const controlsHost = ui.divH([splitByPosition.root, splitByMonomer.root]);
208
- const distributionHost = ui.div([], 'd4-flex-wrap');
209
- splitByMonomer.fireChanged();
210
-
211
- 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]);
212
86
  }
213
87
 
88
+ /**
89
+ * Builds activity distribution histogram
90
+ * @param table - Dataframe with peptides
91
+ * @param isTooltip - Is histogram for tooltip
92
+ * @return - Histogram viewer
93
+ */
214
94
  export function getActivityDistribution(table: DG.DataFrame, isTooltip: boolean = false,
215
95
  ): DG.Viewer<DG.IHistogramLookSettings> {
216
96
  const hist = table.plot.histogram({
217
97
  filteringEnabled: false,
218
- valueColumnName: C.COLUMNS_NAMES.ACTIVITY_SCALED,
98
+ valueColumnName: C.COLUMNS_NAMES.ACTIVITY,
219
99
  splitColumnName: C.COLUMNS_NAMES.SPLIT_COL,
220
100
  legendVisibility: 'Never',
221
101
  showXAxis: true,
222
102
  showColumnSelector: false,
223
103
  showRangeSlider: false,
224
- showBinSelector: !isTooltip,
104
+ showBinSelector: false,
225
105
  backColor: isTooltip ? '#fdffe5' : '#fffff',
226
106
  }) as DG.Viewer<DG.IHistogramLookSettings>;
227
107
  hist.root.style.width = 'auto';
228
108
  return hist;
229
109
  }
230
110
 
231
- 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 {
232
119
  options.fractionDigits ??= 3;
233
120
  const tableMap: StringDictionary = {
234
121
  'Count': `${stats.count} (${stats.ratio.toFixed(options.fractionDigits)}%)`,
@@ -237,11 +124,219 @@ export function getStatsTableMap(stats: Stats, options: {fractionDigits?: number
237
124
  };
238
125
  if (stats.pValue !== null)
239
126
  tableMap['p-value'] = stats.pValue < 0.01 ? '<0.01' : stats.pValue.toFixed(options.fractionDigits);
127
+
128
+
240
129
  return tableMap;
241
130
  }
242
131
 
243
- export function getDistributionLegend(thisLabel: string, otherLabel: string = ''): HTMLDivElement {
244
- return ui.divV([
245
- ui.label(thisLabel, {style: {color: DG.Color.toHtml(otherLabel.length === 0 ? DG.Color.blue : DG.Color.orange)}}),
246
- ui.label(otherLabel, {style: {color: DG.Color.toHtml(DG.Color.blue)}})]);
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;
247
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'),