@datagrok/peptides 1.25.1 → 1.26.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.
@@ -0,0 +1,378 @@
1
+ /* eslint-disable max-len */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+ import {PeptideUtils} from '../peptideUtils';
6
+ import wu from 'wu';
7
+ import {MutationCliffs} from '../utils/types';
8
+ import {ParallelMutationCliffs} from '../utils/parallel-mutation-cliffs';
9
+ import $ from 'cash-dom';
10
+ import {PeptidesModel} from '../model';
11
+ import {extractColInfo} from '../utils/misc';
12
+ import {Subscription} from 'rxjs';
13
+ export type MutationCliffsWithMonomers = {
14
+ cliffs: MutationCliffs,
15
+ monomers: string[]
16
+ }
17
+
18
+ export class MutationCliffsViewer extends DG.JsViewer {
19
+ public sequenceColumnName: string;
20
+ public seriesColumnName: string;
21
+ public activityColumnName: string;
22
+ public position = 1;
23
+ public yAxisType: 'Linear' | 'Logarithmic' = 'Linear';
24
+ constructor() {
25
+ super();
26
+ this.sequenceColumnName = this.column('sequence', {semType: DG.SEMTYPE.MACROMOLECULE, nullable: false});
27
+ this.seriesColumnName = this.column('series', {columnTypeFilter: 'categorical', nullable: false});
28
+ this.activityColumnName = this.column('activity', {columnTypeFilter: 'numerical', nullable: false});
29
+ this.position = this.int('position', 1, {nullable: false, showSlider: false, min: 1, max: 100, showPlusMinus: true, description: 'Position in the sequence to analyze (1 Based).', category: 'Data'});
30
+ this.yAxisType = this.string('yAxisType', 'Linear', {choices: ['Linear', 'Logarithmic'], description: 'Y-Axis scale type.', nullable: false, category: 'Data'}) as 'Linear' | 'Logarithmic';
31
+ }
32
+
33
+ onTableAttached(): void {
34
+ super.onTableAttached();
35
+ const seqCol = this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
36
+ if (seqCol)
37
+ this.getProperty('sequenceColumnName')!.set(this, seqCol.name);
38
+ this.getProperty('activityColumnName')!.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
39
+ const seriesCol = wu(this.dataFrame.columns.categorical)?.filter((c) => c.name !== this.sequenceColumnName && c.name?.toLowerCase().includes('series')).next()?.value;
40
+ if (seriesCol)
41
+ this.getProperty('seriesColumnName')!.set(this, seriesCol.name);
42
+
43
+ this.subs.push(DG.debounce(this.dataFrame.onFilterChanged, 200).subscribe(() => {
44
+ this.clearCache();
45
+ this.debouncedRender();
46
+ }));
47
+ this.debouncedRender();
48
+
49
+
50
+ this.subs.push(grok.events.onContextMenu.subscribe((e) => {
51
+ if (e.causedBy && e.causedBy.target && this._lineChart?.root.contains(e.causedBy.target)) {
52
+ e.causedBy.preventDefault();
53
+ e.causedBy.stopPropagation();
54
+ e.causedBy.stopImmediatePropagation();
55
+ }
56
+ }));
57
+ }
58
+
59
+ private _mutationCliffsData: Promise<MutationCliffsWithMonomers> | null = null;
60
+ private get mutationCliffsData(): Promise<MutationCliffsWithMonomers> {
61
+ if (this._mutationCliffsData)
62
+ return this._mutationCliffsData;
63
+
64
+ if (!this.activityColumnName || !this.sequenceColumnName || !this.position)
65
+ throw new Error('Activity column or Sequence column is not set, or position is invalid');
66
+
67
+ this._mutationCliffsData = new Promise<MutationCliffsWithMonomers>(async (resolve, reject) => {
68
+ try {
69
+ // const seqCol = this.dataFrame.col(this.sequenceColumnName)!;
70
+ const activityColRawData = this.dataFrame.col(this.activityColumnName)!.getRawData();
71
+
72
+ // const seqHelper = PeptideUtils.getSeqHelper();
73
+ // const seqHandler = seqHelper.getSeqHandler(seqCol);
74
+ // get single position
75
+ // const monomersAtPosition = seqHandler.getMonomersAtPosition(this.position - 1, true);
76
+ // const monomerCol = DG.Column.fromList('string', `Monomers at pos ${this.position}`, monomersAtPosition);
77
+ // const monomerRawData: RawColumn = {
78
+ // rawData: monomerCol.getRawData(),
79
+ // name: monomerCol.name,
80
+ // cat: monomerCol.categories,
81
+ // };
82
+
83
+ const positionColumns = this.positionColumns;
84
+ const monomersAtPosition = positionColumns[this.position - 1].toList() as string[];
85
+ const monomerRawData = positionColumns.map(extractColInfo);
86
+
87
+ const mutationCliffs = await new ParallelMutationCliffs().calc(activityColRawData, monomerRawData,
88
+ {maxMutations: 1, minActivityDelta: 0, filter: this.dataFrame.filter.anyFalse ? new Uint32Array(this.dataFrame.filter.getBuffer().buffer) : undefined, singlePosition: {position: this.position - 1}},
89
+ );
90
+ resolve({cliffs: mutationCliffs, monomers: monomersAtPosition});
91
+ } catch (error) {
92
+ reject(error);
93
+ }
94
+ });
95
+ return this._mutationCliffsData;
96
+ }
97
+
98
+ private _dfSubs: Subscription[] = [];
99
+
100
+ private async calculateDf() {
101
+ if (!this.dataFrame || !this.activityColumnName || !this.sequenceColumnName || !this.position)
102
+ throw new Error('Activity column or Sequence column is not set, or position is invalid');
103
+ const mutationCliffs = await this.mutationCliffsData;
104
+ const uniqueIndexes = new Set<number>();
105
+ mutationCliffs.cliffs.forEach((positionMap) => {
106
+ positionMap.forEach((mcData) => { // should be only one position
107
+ Array.from(mcData.entries()).forEach(([from, toIndexes]) => {
108
+ uniqueIndexes.add(Number(from));
109
+ toIndexes.forEach((toIdx) => uniqueIndexes.add(Number(toIdx)));
110
+ });
111
+ });
112
+ });
113
+ const indexesArray = Array.from(uniqueIndexes.values()).sort((a, b) => a - b);
114
+ const dataFrameIndexToViewerDfIndex = new Map<number, number>();
115
+ indexesArray.forEach((dataFrameIdx, viewerDfIdx) => {
116
+ dataFrameIndexToViewerDfIndex.set(dataFrameIdx, viewerDfIdx);
117
+ });
118
+ const monomers = indexesArray.map((i) => mutationCliffs.monomers[i]);
119
+ const activityColRawData = this.dataFrame.col(this.activityColumnName)!.getRawData();
120
+ const activityValues = indexesArray.map((i) => activityColRawData[i] as number);
121
+ const monomerCol = DG.Column.fromList('string', `Position ${this.position}`, monomers);
122
+ const activityCol = DG.Column.fromList('double', this.activityColumnName, activityValues);
123
+ // add original indexes to be able to trace back
124
+
125
+ const cols = [monomerCol, activityCol];
126
+ if (this.seriesColumnName) {
127
+ const seriesColRawData = this.dataFrame.col(this.seriesColumnName)!.getRawData();
128
+ const seriesCats = this.dataFrame.col(this.seriesColumnName)!.categories;
129
+ const seriesValues = indexesArray.map((i) => seriesCats[seriesColRawData[i]] as string);
130
+ const seriesCol = DG.Column.fromList('string', this.seriesColumnName, seriesValues);
131
+ cols.push(seriesCol);
132
+ }
133
+ const df = DG.DataFrame.fromColumns(cols);
134
+ // add dummy columns corresponding to other numerical columns
135
+
136
+ Array.from(this.dataFrame.columns.numerical)
137
+ .filter((c) => c.name !== this.activityColumnName && c.name !== this.seriesColumnName)
138
+ .forEach((c) => {
139
+ df.columns.addNew(c.name, c.type);
140
+ });
141
+
142
+ // mouse over row handling back and forth
143
+ this._dfSubs.push(
144
+ df.onMouseOverRowChanged.subscribe((_) => {
145
+ if (df.mouseOverRowIdx >= 0) {
146
+ const originalIdx = indexesArray[df.mouseOverRowIdx];
147
+ this.dataFrame.mouseOverRowIdx = originalIdx;
148
+ }
149
+ }),
150
+ );
151
+ this._dfSubs.push(
152
+ this.dataFrame.onMouseOverRowChanged.subscribe((_) => {
153
+ if (this.dataFrame.mouseOverRowIdx >= 0) {
154
+ const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(this.dataFrame.mouseOverRowIdx);
155
+ if (viewerDfIdx != undefined)
156
+ df.mouseOverRowIdx = viewerDfIdx;
157
+ else
158
+ df.mouseOverRowIdx = -1;
159
+ }
160
+ }),
161
+ );
162
+
163
+ this._dfSubs.push(
164
+ df.onCurrentRowChanged.subscribe((_) => {
165
+ if (df.currentRowIdx >= 0) {
166
+ const originalIdx = indexesArray[df.currentRowIdx];
167
+ this.dataFrame.currentRowIdx = originalIdx;
168
+ }
169
+ }),
170
+ );
171
+
172
+ let firedFromViewer = false;
173
+ let firedFromTable = false;
174
+ // Handle selection
175
+ this._dfSubs.push(
176
+ DG.debounce(df.onSelectionChanged, 100).subscribe((_) => {
177
+ const selected = df.selection;
178
+ if (firedFromViewer) {
179
+ firedFromViewer = false;
180
+ return;
181
+ }
182
+ firedFromTable = true;
183
+ this.dataFrame.selection.setAll(false, false);
184
+ if (!selected.anyTrue) {
185
+ this.dataFrame.selection.fireChanged();
186
+ return;
187
+ }
188
+ for (let rowIdx = -1; (rowIdx = selected.findNext(rowIdx, true)) !== -1;) {
189
+ const originalIdx = indexesArray[rowIdx];
190
+ this.dataFrame.selection.set(originalIdx, true, false);
191
+ }
192
+ this.dataFrame.selection.fireChanged();
193
+ }),
194
+ );
195
+
196
+ this._dfSubs.push(
197
+ DG.debounce(this.dataFrame.onSelectionChanged, 100).subscribe((_) => {
198
+ const selected = this.dataFrame.selection;
199
+ if (firedFromTable) {
200
+ firedFromTable = false;
201
+ return;
202
+ }
203
+ firedFromViewer = true;
204
+ df.selection.setAll(false, false);
205
+ if (!selected.anyTrue) {
206
+ df.selection.fireChanged();
207
+ return;
208
+ }
209
+ for (let rowIdx = -1; (rowIdx = selected.findNext(rowIdx, true)) !== -1;) {
210
+ const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(rowIdx);
211
+ if (viewerDfIdx != undefined)
212
+ df.selection.set(viewerDfIdx, true, false);
213
+ }
214
+ df.selection.fireChanged();
215
+ }),
216
+ );
217
+
218
+ // mouse over row GROUP handling one way
219
+ // legend will be responsible for this
220
+ // this._dfSubs.push(
221
+ // df.onMouseOverRowGroupChanged.subscribe((_) => {
222
+ // const func = df.rows.mouseOverRowFunc;
223
+ // if (!func || this.dataFrame.rowCount > 50_000)
224
+ // return;
225
+ // this.dataFrame.rows.highlight((i) => {
226
+ // const viewerDfIdx = dataFrameIndexToViewerDfIndex.get(i);
227
+ // return viewerDfIdx != undefined && func(viewerDfIdx);
228
+ // });
229
+ // }),
230
+ // );
231
+ return df;
232
+ }
233
+
234
+ private _positionColumns: DG.Column[] | null = null;
235
+ public get positionColumns(): DG.Column[] {
236
+ if (this._positionColumns)
237
+ return this._positionColumns;
238
+ if (!this.dataFrame)
239
+ return [];
240
+ // try to find model and its position columns if the SAR was run
241
+ const peptidesModel = PeptidesModel.getInstance(this.dataFrame);
242
+ const posCols = peptidesModel?.positionColumns;
243
+ if (posCols && posCols.length > 0) {
244
+ this._positionColumns = posCols;
245
+ return this._positionColumns;
246
+ }
247
+ // fallback: generate columns
248
+ const seqCol = this.dataFrame.col(this.sequenceColumnName)!;
249
+ const seqHelper = PeptideUtils.getSeqHelper();
250
+ const seqHandler = seqHelper.getSeqHandler(seqCol);
251
+ const length = seqHandler.maxLength;
252
+ const cols: DG.Column[] = [];
253
+ for (let i = 0; i < length; i++) {
254
+ const monomersAtPosition = seqHandler.getMonomersAtPosition(i, true);
255
+ const monomerCol = DG.Column.fromList('string', `Position ${i + 1}`, monomersAtPosition);
256
+ cols.push(monomerCol);
257
+ }
258
+ this._positionColumns = cols;
259
+ return this._positionColumns;
260
+ }
261
+
262
+ private _innerDf: Promise<DG.DataFrame> | null = null;
263
+ public get innerDf(): Promise<DG.DataFrame> {
264
+ if (this._innerDf)
265
+ return Promise.resolve(this._innerDf);
266
+ this._innerDf = this.calculateDf();
267
+ return this._innerDf;
268
+ }
269
+
270
+ private _lineChart: DG.LineChartViewer | null = null;
271
+
272
+ private async render() {
273
+ $(this.root).empty();
274
+ if (!this.dataFrame || !this.activityColumnName || !this.sequenceColumnName || !this.position) {
275
+ this.root.appendChild(ui.divText('Please set Activity column, Sequence column and Position properties.'));
276
+ return;
277
+ }
278
+
279
+ if (this._lineChart) {
280
+ try {
281
+ this._lineChart.detach();
282
+ } catch (e) {
283
+ console.error('Error detaching previous line chart:', e);
284
+ }
285
+ this._lineChart = null;
286
+ }
287
+
288
+ ui.setUpdateIndicator(this.root, true);
289
+ this.root.style.display = 'flex';
290
+ this.root.style.flexDirection = 'column';
291
+
292
+ const df = await this.innerDf;
293
+
294
+ this._lineChart = df.plot.line({
295
+ xColumnName: `Position ${this.position}`,
296
+ yColumnNames: [this.activityColumnName],
297
+ splitColumnNames: this.seriesColumnName ? [this.seriesColumnName] : [],
298
+ legendVisibility: this.seriesColumnName ? 'Always' : 'Never',
299
+ legendPosition: 'Right',
300
+ showXSelector: false,
301
+ showYSelector: true,
302
+ showSplitSelector: false,
303
+ xAxisLabelOrientation: 'Auto',
304
+ axisFont: 'normal normal 14px "Roboto"',
305
+ controlsFont: 'normal normal 14px "Roboto"',
306
+ } as Partial<DG.ILineChartSettings>) as DG.LineChartViewer;
307
+
308
+
309
+ this._lineChart.sub(this._lineChart.onPropertyValueChanged.subscribe((_e) => {
310
+ if (this._lineChart?.props?.yColumnNames && this._lineChart?.props?.yColumnNames?.[0] !== this.activityColumnName) {
311
+ const value = this._lineChart?.props?.yColumnNames?.[0];
312
+ setTimeout(() => this.getProperty('activityColumnName')!.set(this, value), 1);
313
+ }
314
+ }));
315
+
316
+ ui.setUpdateIndicator(this.root, false);
317
+ this.root.appendChild(this._lineChart.root);
318
+
319
+ const maxPosition = this.positionColumns.length + 1;
320
+ const positions = new Array<number>(maxPosition - 1).fill(0).map((_, i) => i + 1).map((v) => v.toString());
321
+ const positionInput = ui.input.choice('Position', {value: this.position.toString(), items: positions, nullable: false,
322
+ onValueChanged: (v) => {
323
+ if (!v)
324
+ return;
325
+ const intV = parseInt(v);
326
+ this.getProperty('position')!.set(this, intV);
327
+ },
328
+ });
329
+
330
+ positionInput.input.style.width = '40px';
331
+ this.root.appendChild(ui.divH([positionInput.root], {style: {justifyContent: 'center', marginTop: '4px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
332
+
333
+ const seriesColSelector = ui.input.column('Series', {table: this.dataFrame,
334
+ filter: (col: DG.Column) => {
335
+ return col.isCategorical && col.name !== this.sequenceColumnName;
336
+ }, tooltipText: 'Select column for series splitting.',
337
+ onValueChanged: (col: DG.Column | null) => {
338
+ const colName = col ? col.name : undefined;
339
+ this.getProperty('seriesColumnName')!.set(this, colName);
340
+ }, value: this.seriesColumnName ? this.dataFrame.col(this.seriesColumnName)! : undefined,
341
+ });
342
+ this.root.prepend(ui.divH([seriesColSelector.root], {style: {justifyContent: 'flex-end', paddingBottom: '4px', padding: '8px', width: '100%', font: 'normal normal 14px "Roboto"'}}));
343
+ }
344
+
345
+ private _debounceTimer: any = null;
346
+ public debouncedRender() {
347
+ ui.setUpdateIndicator(this.root, true);
348
+ if (this._debounceTimer)
349
+ clearTimeout(this._debounceTimer);
350
+ this._debounceTimer = setTimeout(() => this.render(), 300);
351
+ }
352
+
353
+ private clearCache() {
354
+ this._mutationCliffsData = null;
355
+ this._innerDf = null;
356
+ this._dfSubs.forEach((s) => s.unsubscribe());
357
+ this._dfSubs = [];
358
+ }
359
+
360
+ detach(): void {
361
+ super.detach();
362
+ this._dfSubs.forEach((s) => s.unsubscribe());
363
+ this._dfSubs = [];
364
+ clearTimeout(this._debounceTimer);
365
+ }
366
+
367
+
368
+ onPropertyChanged(property: DG.Property | null): void {
369
+ super.onPropertyChanged(property);
370
+ if (property?.name === 'activityColumnName' || property?.name === 'sequenceColumnName' || property?.name === 'position' || property?.name === 'seriesColumnName') {
371
+ this.clearCache();
372
+ this.debouncedRender();
373
+ } if (property?.name === 'yAxisType') {
374
+ if (this._lineChart)
375
+ this._lineChart.props.yAxisType = this.yAxisType.toLowerCase() as DG.AxisType;
376
+ }
377
+ }
378
+ }
@@ -27,13 +27,13 @@ export class SequencePositionStatsViewer extends DG.JsViewer {
27
27
  this.leftMotifLength = this.int('leftMotifLength', 0, {nullable: false, min: 0, max: 10});
28
28
  this.rightMotifLength = this.int('rightMotifLength', 0, {nullable: false, min: 0, max: 10});
29
29
  this.showPositionInfo = this.bool('showPositionInfo', true, {nullable: false, defaultValue: true, description: 'Show position and overhangs info in the viewer header'});
30
- grok.events.onContextMenu.subscribe((e) => {
30
+ this.subs.push(grok.events.onContextMenu.subscribe((e) => {
31
31
  if (e.causedBy && e.causedBy.target && this._boxPlotViewer?.root.contains(e.causedBy.target)) {
32
32
  e.causedBy.preventDefault();
33
33
  e.causedBy.stopPropagation();
34
34
  e.causedBy.stopImmediatePropagation();
35
35
  }
36
- });
36
+ }));
37
37
  }
38
38
 
39
39
  getPositionFromColumn(): number {
@@ -5,7 +5,8 @@ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations'
5
5
  import $ from 'cash-dom';
6
6
 
7
7
  import * as C from '../utils/constants';
8
- import {AggregationColumns, getAggregatedColumnValues, getStats, StatsItem} from '../utils/statistics';
8
+ import {AggregationColumns, bitSetToBitArray,
9
+ getAggregatedColumnValues, getStats, StatsItem} from '../utils/statistics';
9
10
  import {DistributionLabelMap, getDistributionPanel, getDistributionTable, SPLIT_CATEGORY} from '../utils/misc';
10
11
  import {SARViewer} from '../viewers/sar-viewer';
11
12
  import {CLUSTER_TYPE, LogoSummaryTable} from '../viewers/logo-summary';
@@ -13,7 +14,7 @@ import BitArray from '@datagrok-libraries/utils/src/bit-array';
13
14
  import {Selection} from '../utils/types';
14
15
 
15
16
  export type DistributionItemOptions = {
16
- peptideSelection: DG.BitSet, columns: AggregationColumns, clusterColName?: string,
17
+ columns: AggregationColumns, clusterColName?: string,
17
18
  activityCol: DG.Column<number>, monomerPositionSelection: Selection, clusterSelection: Selection,
18
19
  };
19
20
 
@@ -143,8 +144,7 @@ export function getStatsTableMap(stats: StatsItem,
143
144
  */
144
145
  function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: DistributionItemOptions,
145
146
  labelMap: DistributionLabelMap = {}): HTMLDivElement {
146
- const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection,
147
- options.peptideSelection));
147
+ const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection));
148
148
  const aggregatedColMap = getAggregatedColumnValues(table, Object.entries(options.columns),
149
149
  {filterDf: true, mask: DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, stats.mask.length)});
150
150
  const tableMap = getStatsTableMap(stats);
@@ -165,15 +165,17 @@ function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: D
165
165
  function getDistributionCategory(category: DISTRIBUTION_CATEGORIES_KEYS | typeof general, table: DG.DataFrame,
166
166
  options: DistributionItemOptions): HTMLDivElement {
167
167
  let body: HTMLDivElement = ui.divText('No distribution');
168
+ const selectionAndFilter = table.selection.clone().and(table.filter);
169
+ const selectionAndFilterBA = BitArray.fromUint32Array(
170
+ selectionAndFilter.length, new Uint32Array(selectionAndFilter.getBuffer().buffer));
168
171
  switch (category) {
169
172
  case general:
170
- const bitArray = BitArray.fromSeq(table.selection.length, (i: number) => table.selection.get(i));
171
173
  const stats = !table.selection.anyTrue || !table.selection.anyFalse ?
172
174
  {
173
- count: options.activityCol.length, pValue: null, meanDifference: 0, ratio: 1, mask: bitArray,
175
+ count: options.activityCol.length, pValue: null, meanDifference: 0, ratio: 1, mask: selectionAndFilterBA,
174
176
  mean: options.activityCol.stats.avg,
175
177
  } :
176
- getStats(options.activityCol.getRawData(), bitArray);
178
+ getStats(options.activityCol.getRawData(), selectionAndFilterBA, bitSetToBitArray(table.filter));
177
179
 
178
180
  body = getSingleDistribution(table, stats, options);
179
181
  break;
@@ -207,6 +209,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
207
209
  const clusterCol = table.getCol(options.clusterColName);
208
210
  const clusterColCategories = clusterCol.categories;
209
211
  const clusterColData = clusterCol.getRawData() as Int32Array;
212
+ const filterBitArray = bitSetToBitArray(table.filter);
210
213
 
211
214
  // Build distributions for original clusters
212
215
  const selectedClustersCategoryIndexes = selectionObject[CLUSTER_TYPE.ORIGINAL]
@@ -220,7 +223,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
220
223
  }
221
224
  for (let selectedClusterIdx = 0; selectedClusterIdx < selectedClustersCategoryIndexes.length; selectedClusterIdx++) {
222
225
  const selectedClusterCategoryIndex = selectedClustersCategoryIndexes[selectedClusterIdx];
223
- const stats = getStats(activityColData, clusterMasks[selectedClusterIdx]);
226
+ const stats = getStats(activityColData, clusterMasks[selectedClusterIdx], filterBitArray);
224
227
  distributions.push(getSingleDistribution(table, stats, options,
225
228
  {[SPLIT_CATEGORY.SELECTION]: clusterColCategories[selectedClusterCategoryIndex]}));
226
229
  }
@@ -230,7 +233,7 @@ function getDistributionForClusters(table: DG.DataFrame, options: Required<Distr
230
233
  for (const clusterColumnName of customClusterSelection) {
231
234
  const customClustCol = table.getCol(clusterColumnName);
232
235
  const bitArray = BitArray.fromUint32Array(rowCount, customClustCol.getRawData() as Uint32Array);
233
- const stats = getStats(activityColData, bitArray);
236
+ const stats = getStats(activityColData, bitArray, filterBitArray);
234
237
  distributions.push(getSingleDistribution(table, stats, options,
235
238
  {[SPLIT_CATEGORY.SELECTION]: clusterColumnName}));
236
239
  }
@@ -254,6 +257,7 @@ function getDistributionForPositions(table: DG.DataFrame, options: DistributionI
254
257
  const positionColumns: (DG.Column<string> | undefined)[] = [];
255
258
  const positionColumnsCategories: (string[] | undefined)[] = [];
256
259
  const positionColumnsData: (Int32Array | undefined)[] = [];
260
+ const filterBitArray = bitSetToBitArray(table.filter);
257
261
 
258
262
  for (let posIdx = 0; posIdx < positions.length; posIdx++) {
259
263
  const position = positions[posIdx];
@@ -276,7 +280,7 @@ function getDistributionForPositions(table: DG.DataFrame, options: DistributionI
276
280
  mask.setTrue(i);
277
281
  }
278
282
  }
279
- const stats = getStats(activityColData, mask);
283
+ const stats = getStats(activityColData, mask, filterBitArray);
280
284
  distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: position}));
281
285
  }
282
286
 
@@ -299,6 +303,7 @@ function getDistributionForMonomers(table: DG.DataFrame, options: DistributionIt
299
303
  const positionColumnsCategories: (string[] | undefined)[] = [];
300
304
  const positionColumnsData: (Int32Array | undefined)[] = [];
301
305
  const activityColData = options.activityCol.getRawData();
306
+ const filterBitArray = bitSetToBitArray(table.filter);
302
307
 
303
308
  for (const monomer of monomers) {
304
309
  const posList = reversedSelectionObject[monomer];
@@ -316,7 +321,7 @@ function getDistributionForMonomers(table: DG.DataFrame, options: DistributionIt
316
321
  mask.setTrue(i);
317
322
  }
318
323
  }
319
- const stats = getStats(activityColData, mask);
324
+ const stats = getStats(activityColData, mask, filterBitArray);
320
325
 
321
326
  distributions.push(getSingleDistribution(table, stats, options, {[SPLIT_CATEGORY.SELECTION]: monomer}));
322
327
  }
@@ -192,7 +192,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): Di
192
192
  bitsetChanged.unsubscribe();
193
193
  if (sequencesCol) {
194
194
  const model = await startAnalysis(activityColumnChoice.value!, sequencesCol, clustersColumnChoice.value, df,
195
- scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: false, addMCL: true,
195
+ scaledCol, activityScalingMethod.value ?? C.SCALING_METHODS.NONE, {addSequenceSpace: false, addMCL: generateClustersInput.value,
196
196
  useEmbeddingsClusters: generateClustersInput.value ?? false, mclSettings: mclOptions});
197
197
  return model !== null;
198
198
  }
@@ -1,8 +1,11 @@
1
+ /* eslint-disable max-len */
1
2
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
2
3
 
3
4
  onmessage = async (event): Promise<void> => {
4
5
  const {startIdx, endIdx, activityArray, monomerInfoArray, settings} = event.data;
5
6
  const filterArray: Uint32Array = settings.filter;
7
+ const singlePosition: {position: number} | undefined = settings.singlePosition;
8
+ const singlePositionIndex = singlePosition ? singlePosition.position : -1;
6
9
  const filter = filterArray ? new BitArray(filterArray, filterArray.length * 32) : null;
7
10
 
8
11
  const pos: string[] = [];
@@ -25,14 +28,15 @@ onmessage = async (event): Promise<void> => {
25
28
  if (Math.abs(delta) >= settings.minActivityDelta) {
26
29
  let substCounterFlag = false;
27
30
  let tempDataIdx = 0;
28
- for (const monomerInfo of monomerInfoArray) {
31
+ for (let positionIndex = 0; positionIndex < monomerInfoArray.length; positionIndex++) {
32
+ const monomerInfo = monomerInfoArray[positionIndex];
29
33
  const seq1categoryIdx = monomerInfo.rawData[seq1Idx];
30
34
  const seq2categoryIdx = monomerInfo.rawData[seq2Idx];
31
35
  if (seq1categoryIdx === seq2categoryIdx)
32
36
  continue;
33
37
 
34
38
  substCounter++;
35
- substCounterFlag = substCounter > settings.maxMutations;
39
+ substCounterFlag = substCounter > settings.maxMutations || (singlePositionIndex !== -1 && positionIndex !== singlePositionIndex);
36
40
  if (substCounterFlag)
37
41
  break;
38
42