@datagrok/peptides 1.17.21 → 1.17.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
3
  "friendlyName": "Peptides",
4
- "version": "1.17.21",
4
+ "version": "1.17.23",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
@@ -18,7 +18,7 @@
18
18
  "@datagrok-libraries/ml": "^6.6.13",
19
19
  "@datagrok-libraries/statistics": "^1.2.12",
20
20
  "@datagrok-libraries/tutorials": "^1.3.12",
21
- "@datagrok-libraries/utils": "^4.2.11",
21
+ "@datagrok-libraries/utils": "^4.2.13",
22
22
  "datagrok-api": "^1.19.0",
23
23
  "@webgpu/types": "^0.1.40",
24
24
  "cash-dom": "latest",
package/src/package.ts CHANGED
@@ -51,7 +51,6 @@ async function openDemoData(chosenFile: string): Promise<void> {
51
51
  }
52
52
 
53
53
  //name: Peptides
54
- //tags: app
55
54
  //output: view v
56
55
  export function Peptides(): DG.View {
57
56
  const appHeader = u2.appHeader({
@@ -17,7 +17,7 @@ const benchmarkDatasetSizes = [5, 50, 100, 200];
17
17
  category('Benchmarks: Mutation Cliffs', () => {
18
18
  for (const size of benchmarkDatasetSizes)
19
19
  test(`${size}k sequences`, async () => await mutationCliffsBenchmark(size), {timeout: 300000});
20
- });
20
+ }, {benchmarks: true});
21
21
 
22
22
  category('Benchmarks: Cluster stats', () => {
23
23
  for (const size of benchmarkDatasetSizes) {
@@ -38,7 +38,7 @@ category('Benchmarks: Cluster stats', () => {
38
38
  () => calculateClusterStatistics(df, clustersColumnName, [], scaledActivity));
39
39
  }, {timeout: 100000});
40
40
  }
41
- });
41
+ }, {benchmarks: true});
42
42
 
43
43
  category('Benchmarks: Monomer-Position stats', () => {
44
44
  for (const size of benchmarkDatasetSizes) {
@@ -64,7 +64,7 @@ category('Benchmarks: Monomer-Position stats', () => {
64
64
  () => calculateMonomerPositionStatistics(scaledActivity, DG.BitSet.create(0), positionCols));
65
65
  }, {timeout: 100000});
66
66
  }
67
- });
67
+ }, {benchmarks: true});
68
68
 
69
69
  category('Benchmarks: Analysis start', () => {
70
70
  for (const size of benchmarkDatasetSizes) {
@@ -89,7 +89,7 @@ category('Benchmarks: Analysis start', () => {
89
89
  });
90
90
  }, {timeout: 100000});
91
91
  }
92
- });
92
+ }, {benchmarks: true});
93
93
 
94
94
  async function mutationCliffsBenchmark(size: number): Promise<void> {
95
95
  if (!DG.Test.isInBenchmark)
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  import * as DG from 'datagrok-api/dg';
2
3
  import * as C from './constants';
3
4
  import * as type from './types';
@@ -89,18 +90,27 @@ export function calculateCliffsStatistics(
89
90
  * @param [options] - Options for the algorithm.
90
91
  * @param [options.isFiltered] - Whether the dataframe is filtered.
91
92
  * @param [options.columns] - Columns to consider when calculating statistics.
93
+ * @param [options.target] - Target column and category to consider.
94
+ * @param [options.aggValue] - Column and aggregation type to consider instead of count.
92
95
  * @return - Statistics for each monomer position.
93
96
  */
94
97
  export function calculateMonomerPositionStatistics(activityCol: DG.Column<number>, filter: DG.BitSet,
95
98
  positionColumns: DG.Column<string>[], options: {
96
99
  isFiltered?: boolean,
97
- columns?: string[]
100
+ columns?: string[],
101
+ target?: {
102
+ col: DG.Column<string>,
103
+ cat: string,
104
+ },
105
+ aggValue?: {
106
+ col: DG.Column,
107
+ type: DG.AGG
108
+ }
98
109
  } = {}): MonomerPositionStats {
99
110
  options.isFiltered ??= false;
100
111
  const monomerPositionObject = {general: {}} as MonomerPositionStats & { general: SummaryStats };
101
112
  let activityColData: Float64Array = activityCol.getRawData() as Float64Array;
102
113
  let sourceDfLen = activityCol.length;
103
-
104
114
  if (options.isFiltered) {
105
115
  sourceDfLen = filter.trueCount;
106
116
  const tempActivityData = new Float64Array(sourceDfLen);
@@ -111,9 +121,15 @@ export function calculateMonomerPositionStatistics(activityCol: DG.Column<number
111
121
 
112
122
  activityColData = tempActivityData;
113
123
  positionColumns = DG.DataFrame.fromColumns(positionColumns).clone(filter).columns.toList();
124
+ if (options.target)
125
+ options.target.col = options.target.col.clone(filter);
126
+ if (options.aggValue)
127
+ options.aggValue.col = options.aggValue.col.clone(filter);
114
128
  }
115
129
  options.columns ??= positionColumns.map((col) => col.name);
116
-
130
+ const targetColIndexes = options.target?.col?.getRawData();
131
+ const targetColCat = options.target?.col.categories;
132
+ const targetIndex = options.target?.cat ? targetColCat?.indexOf(options.target.cat) : -1;
117
133
  for (const posCol of positionColumns) {
118
134
  if (!options.columns.includes(posCol.name))
119
135
  continue;
@@ -131,13 +147,13 @@ export function calculateMonomerPositionStatistics(activityCol: DG.Column<number
131
147
 
132
148
  const boolArray: boolean[] = new Array(sourceDfLen).fill(false);
133
149
  for (let i = 0; i < sourceDfLen; ++i) {
134
- if (posColData[i] === categoryIndex)
150
+ if (posColData[i] === categoryIndex && (!targetColIndexes || targetIndex === -1 || targetColIndexes[i] === targetIndex))
135
151
  boolArray[i] = true;
136
152
  }
137
153
  const bitArray = BitArray.fromValues(boolArray);
138
- const stats = bitArray.allFalse || bitArray.allTrue ?
139
- {count: sourceDfLen, meanDifference: 0, ratio: 1.0, pValue: null, mask: bitArray, mean: activityCol.stats.avg} :
140
- getStats(activityColData, bitArray);
154
+ if (bitArray.allFalse)
155
+ continue;
156
+ const stats = getStats(activityColData, bitArray, options.aggValue);
141
157
  currentPositionObject[monomer] = stats;
142
158
  getSummaryStats(currentPositionObject.general, stats);
143
159
  }
@@ -47,6 +47,8 @@ export enum TAGS {
47
47
  POSITION_COL = 'isPositionCol',
48
48
  M_P_STATS_CACHE = '.MPStatsCache',
49
49
  INVARIANT_MAP_COLOR_CACHE = '.InvariantMapColorCache',
50
+ INVARIANT_MAP_COLOR_MAX_CACHE = '.InvariantMapColorMaxCache',
51
+ INVARIANT_MAP_COLOR_MIN_CACHE = '.InvariantMapColorMinCache',
50
52
  }
51
53
 
52
54
  export enum SEM_TYPES {
@@ -13,6 +13,7 @@ export type StatsItem = {
13
13
  ratio: number,
14
14
  mask: BitArray,
15
15
  mean: number,
16
+ aggValue?: number,
16
17
  };
17
18
 
18
19
  export type PositionStats = { [monomer: string]: StatsItem } & { general: SummaryStats };
@@ -42,18 +43,23 @@ export type AggregationColumns = { [col: string]: DG.AggregationType };
42
43
  * @param bitArray - Bit array to use for the calculation.
43
44
  * @return - Statistics for the given data and bit array.
44
45
  */
45
- export function getStats(data: RawData | number[], bitArray: BitArray): StatsItem {
46
+ export function getStats(data: RawData | number[], bitArray: BitArray,
47
+ aggData?: {col: DG.Column, type: DG.AGG}): StatsItem {
46
48
  if (data.length !== bitArray.length && data.some((v, i) => i >= bitArray.length ? v !== 0 : false))
47
49
  throw new Error('PeptidesError: Data and bit array have different lengths');
48
50
 
49
-
50
- if (bitArray.trueCount() === 0)
51
- throw new Error('PeptidesError: One of the samples is empty');
52
-
53
-
54
51
  const selected = new Float32Array(bitArray.trueCount());
55
52
  const rest = new Float32Array(bitArray.falseCount());
56
-
53
+ let aggValue: number | undefined;
54
+ if (aggData) {
55
+ try {
56
+ aggValue = DG.DataFrame.fromColumns([aggData.col])
57
+ .clone(DG.BitSet.fromBytes(bitArray.buffer.buffer, bitArray.length)).col(aggData.col.name)
58
+ ?.aggregate(aggData.type);
59
+ } catch (e) {
60
+ console.error(e);
61
+ }
62
+ }
57
63
  let selectedIndex = 0;
58
64
  let restIndex = 0;
59
65
  for (let i = 0; i < bitArray.length; ++i) {
@@ -63,9 +69,9 @@ export function getStats(data: RawData | number[], bitArray: BitArray): StatsIte
63
69
  rest[restIndex++] = data[i];
64
70
  }
65
71
 
66
- const selectedMean = selected.reduce((a, b) => a + b, 0) / selected.length;
67
- if (selected.length === 1 || rest.length === 1) {
68
- const restMean = rest.reduce((a, b) => a + b, 0) / rest.length;
72
+ const selectedMean = selected.reduce((a, b) => a + b, 0) / Math.max(selected.length, 1);
73
+ if (selected.length < 2 || rest.length < 2) {
74
+ const restMean = rest.reduce((a, b) => a + b, 0) / Math.max(rest.length, 1);
69
75
  return {
70
76
  count: selected.length,
71
77
  pValue: null,
@@ -73,6 +79,7 @@ export function getStats(data: RawData | number[], bitArray: BitArray): StatsIte
73
79
  meanDifference: selectedMean - restMean,
74
80
  ratio: selected.length / (bitArray.length),
75
81
  mask: bitArray,
82
+ aggValue,
76
83
  };
77
84
  }
78
85
 
@@ -85,6 +92,7 @@ export function getStats(data: RawData | number[], bitArray: BitArray): StatsIte
85
92
  meanDifference: currentMeanDiff,
86
93
  ratio: selected.length / (bitArray.length),
87
94
  mask: bitArray,
95
+ aggValue,
88
96
  };
89
97
  }
90
98
 
@@ -290,7 +290,7 @@ export class ClusterMaxActivityViewer extends DG.JsViewer implements IClusterMax
290
290
 
291
291
  this.render();
292
292
 
293
- this.dataFrame.onDataChanged.subscribe(() => {
293
+ this.dataFrame?.onDataChanged.subscribe(() => {
294
294
  this.render();
295
295
  });
296
296
  }
@@ -299,6 +299,8 @@ export class ClusterMaxActivityViewer extends DG.JsViewer implements IClusterMax
299
299
  if (this.renderTimeout)
300
300
  clearTimeout(this.renderTimeout);
301
301
  this.renderTimeout = setTimeout(() => {
302
+ if (!this.dataFrame)
303
+ return;
302
304
  $(this.root).empty();
303
305
  const scViewer = this.scViewer;
304
306
  if (scViewer == null) {
@@ -97,8 +97,11 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
97
97
  {
98
98
  category: LST_CATEGORIES.GENERAL,
99
99
  nullable: false,
100
+ columnTypeFilter: DG.TYPE.CATEGORICAL,
100
101
  });
101
- this.activityColumnName = this.column(LST_PROPERTIES.ACTIVITY, {category: LST_CATEGORIES.GENERAL, nullable: false});
102
+ this.activityColumnName = this.column(LST_PROPERTIES.ACTIVITY, {
103
+ category: LST_CATEGORIES.GENERAL, nullable: false, columnTypeFilter: DG.TYPE.NUMERICAL,
104
+ });
102
105
  this.activityScaling = this.string(LST_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
103
106
  {
104
107
  category: LST_CATEGORIES.GENERAL,
@@ -672,6 +675,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
672
675
  showBinSelector: false,
673
676
  backColor: DG.Color.toHtml(DG.Color.white),
674
677
  xAxisHeight: 1,
678
+ showSplitSelector: false,
675
679
  });
676
680
  viewer.root.style.width = 'auto';
677
681
  distCache.set(currentRowIdx, viewer);
@@ -55,11 +55,20 @@ export enum SAR_PROPERTIES {
55
55
  COLUMNS = 'columns',
56
56
  AGGREGATION = 'aggregation',
57
57
  ACTIVITY_TARGET = 'activityTarget',
58
+ VALUE_INVARIANT_MAP = 'value',
59
+ AGGREGATION_INVARIANT_MAP_VALUE = 'valueAggregation',
58
60
  }
59
61
 
60
62
  export enum MONOMER_POSITION_PROPERTIES {
61
63
  COLOR = 'color',
62
64
  COLOR_AGGREGATION = 'colorAggregation',
65
+ CUSTOM_COLOR_RANGE = 'customColorRange',
66
+ MIN_COLOR_VALUE = 'minColorValue',
67
+ MAX_COLOR_VALUE = 'maxColorValue',
68
+ LOWER_BOUND_COLOR = 'lowerBoundColor',
69
+ MIDDLE_COLOR = 'middleColor',
70
+ UPPER_BOUND_COLOR = 'upperBoundColor',
71
+ LOG_SCALE_COLOR = 'logScaleColor',
63
72
  }
64
73
 
65
74
  export enum PROPERTY_CATEGORIES {
@@ -90,12 +99,16 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
90
99
  columns: string[];
91
100
  aggregation: string;
92
101
  targetColumnName: string;
93
- targetCategory: string;
94
102
  minActivityDelta: number;
95
103
  maxMutations: number;
96
104
  _scaledActivityColumn: DG.Column | null = null;
97
105
  doRender: boolean = true;
98
106
  activityTarget: C.ACTIVITY_TARGET;
107
+ targetColumnInput?: DG.InputBase<DG.Column | null>;
108
+ targetCategoryInput: DG.ChoiceInput<string | null | undefined>;
109
+ valueColumnName: string;
110
+ valueAggregation: DG.AGG;
111
+
99
112
  mutationCliffsDebouncer: (
100
113
  activityArray: type.RawData, monomerInfoArray: type.RawColumn[], options?: MutationCliffsOptions
101
114
  ) => Promise<type.MutationCliffs>;
@@ -114,10 +127,10 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
114
127
  this.activityTarget = this.string(SAR_PROPERTIES.ACTIVITY_TARGET, C.ACTIVITY_TARGET.HIGH,
115
128
  {category: PROPERTY_CATEGORIES.GENERAL, choices: Object.values(C.ACTIVITY_TARGET), nullable: false},
116
129
  ) as C.ACTIVITY_TARGET;
117
- // Mutation Cliffs properties
118
- this.targetColumnName = this.column(SAR_PROPERTIES.TARGET, {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS});
119
- this.targetCategory = this.string(SAR_PROPERTIES.TARGET_CATEGORY, null,
120
- {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, choices: []});
130
+ // Mutation Cliffs/invariant map properties
131
+ // hide it and make it editable through the code
132
+ this.targetColumnName = this.column(SAR_PROPERTIES.TARGET, {
133
+ category: PROPERTY_CATEGORIES.GENERAL, nullable: true, columnTypeFilter: 'categorical', userEditable: true});
121
134
  this.minActivityDelta = this.float(SAR_PROPERTIES.MIN_ACTIVITY_DELTA, 0,
122
135
  {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 0, max: 100});
123
136
  this.maxMutations = this.int(SAR_PROPERTIES.MAX_MUTATIONS, 1,
@@ -125,11 +138,35 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
125
138
  this.columns = this.columnList(SAR_PROPERTIES.COLUMNS, [], {category: PROPERTY_CATEGORIES.AGGREGATION});
126
139
  this.aggregation = this.string(SAR_PROPERTIES.AGGREGATION, DG.AGG.AVG,
127
140
  {category: PROPERTY_CATEGORIES.AGGREGATION, choices: C.AGGREGATION_TYPES});
141
+ this.valueColumnName = this.column(SAR_PROPERTIES.VALUE_INVARIANT_MAP, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, userEditable: true,
142
+ nullable: false, columnTypeFilter: 'numerical'});
143
+ this.valueAggregation = this.string(SAR_PROPERTIES.AGGREGATION_INVARIANT_MAP_VALUE, DG.AGG.TOTAL_COUNT, {
144
+ category: PROPERTY_CATEGORIES.INVARIANT_MAP, choices: C.AGGREGATION_TYPES, userEditable: true, nullable: false}) as DG.AGG;
128
145
 
129
146
  this.mutationCliffsDebouncer = debounce(
130
147
  async (activityArray: type.RawData, monomerInfoArray: type.RawColumn[], options?: MutationCliffsOptions) => {
131
148
  return await findMutations(activityArray, monomerInfoArray, options);
132
149
  });
150
+
151
+ this.targetCategoryInput = ui.input.choice('Category', {value: null, items: [], nullable: true,
152
+ onValueChanged: () => {
153
+ this._mutationCliffs = null;
154
+ this._mutationCliffStats = null;
155
+ this._mutationCliffsSelection = null;
156
+ this._invariantMapSelection = null;
157
+ this.doRender = false;
158
+ this._monomerPositionStats = null;
159
+ this.positionColumns?.forEach((col) => {
160
+ col.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = null;
161
+ });
162
+ if (this.sequenceColumnName && this.activityColumnName)
163
+ this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
164
+ this.viewerGrid.invalidate();
165
+ },
166
+ });
167
+ this.targetCategoryInput.root.style.display = 'none';
168
+ this.targetCategoryInput.root.style.width = '50%';
169
+ this.targetCategoryInput.root.style.marginLeft = '8px';
133
170
  }
134
171
 
135
172
  _viewerGrid: DG.Grid | null = null;
@@ -209,14 +246,21 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
209
246
  const isMonomerPositionStatsEqual = (other: SARViewer | PeptidesSettings | null): boolean =>
210
247
  this.sequenceColumnName === other?.sequenceColumnName &&
211
248
  this.activityColumnName === other?.activityColumnName &&
212
- this.activityScaling === other?.activityScaling;
249
+ this.activityScaling === other?.activityScaling &&
250
+ ((other instanceof SARViewer && this.targetColumnName == other?.targetColumnName &&
251
+ this.targetCategoryInput?.value === other?.targetCategoryInput?.value) ||
252
+ (!(other instanceof SARViewer) && (this.targetColumnName == null || this.targetCategoryInput?.value == null))
253
+ ) &&
254
+ ((other instanceof SARViewer && this.valueColumnName == other?.valueColumnName && this.valueAggregation == other?.valueAggregation) ||
255
+ (!(other instanceof SARViewer) &&
256
+ (!this.valueColumnName || !this.valueAggregation || this.valueAggregation == DG.AGG.VALUE_COUNT || this.valueAggregation == DG.AGG.TOTAL_COUNT))
257
+ );
213
258
 
214
259
  const getSharedStats = (viewerType: VIEWER_TYPE): MonomerPositionStats | null => {
215
260
  const viewer = this.model.findViewer(viewerType) as SARViewer | null;
216
261
  if (isMonomerPositionStatsEqual(viewer))
217
262
  return viewer!._monomerPositionStats;
218
263
 
219
-
220
264
  return null;
221
265
  };
222
266
 
@@ -228,14 +272,22 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
228
272
  this._monomerPositionStats = getSharedStats(VIEWER_TYPE.MONOMER_POSITION);
229
273
 
230
274
 
275
+ const targetCol = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
276
+ const targetCategory = this.targetCategoryInput.value;
277
+ const invariantMapValueCol = this.dataFrame.col(this.valueColumnName);
278
+ const invariantMapValueAgg = this.valueAggregation;
279
+
231
280
  this._monomerPositionStats ??= calculateMonomerPositionStatistics(this.getScaledActivityColumn(),
232
- this.dataFrame.filter, this.positionColumns);
281
+ this.dataFrame.filter, this.positionColumns,
282
+ {target: (targetCol && targetCategory) ? {col: targetCol, cat: targetCategory} : undefined,
283
+ aggValue: (invariantMapValueAgg && invariantMapValueCol) ? {col: invariantMapValueCol, type: invariantMapValueAgg} : undefined,
284
+ });
233
285
  return this._monomerPositionStats;
234
286
  }
235
287
 
236
288
  _mutationCliffs: type.MutationCliffs | null = null;
237
289
  _mutationCliffStats: type.MutationCliffStats | null = null;
238
-
290
+ _invariantMapSelection: type.Selection | null = null;
239
291
  /**
240
292
  * Gets mutation cliffs. If mutation cliffs are not attached to the viewer, it tries to get them from other viewers,
241
293
  * or calculates its own.
@@ -251,7 +303,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
251
303
  v1.activityColumnName === v2.activityColumnName &&
252
304
  v1.activityScaling === v2.activityScaling &&
253
305
  v1.targetColumnName === v2?.targetColumnName &&
254
- v1.targetCategory === v2?.targetCategory &&
306
+ v1.targetCategoryInput?.value === v2?.targetCategoryInput?.value &&
255
307
  v1.minActivityDelta === v2?.minActivityDelta &&
256
308
  v1.maxMutations === v2?.maxMutations;
257
309
 
@@ -368,12 +420,25 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
368
420
  this._mutationCliffsSelection = modifySelection(this.mutationCliffsSelection, monomerPosition, options);
369
421
  }
370
422
 
423
+ private resetTargetCategoryValue(): void {
424
+ const colName = this.targetColumnName;
425
+ const col = this.dataFrame.col(colName);
426
+ this.targetCategoryInput.items = col?.categories ?? [];
427
+ this.targetCategoryInput.value = null;
428
+ if (!colName)
429
+ this.targetCategoryInput.root.style.display = 'none';
430
+ else
431
+ this.targetCategoryInput.root.style.display = 'flex';
432
+ }
433
+
371
434
  /**
372
435
  * Processes property changes.
373
436
  * @param property - changed property.
374
437
  */
375
438
  onPropertyChanged(property: DG.Property): void {
376
439
  super.onPropertyChanged(property);
440
+
441
+
377
442
  this.doRender = true;
378
443
  switch (property.name) {
379
444
  case `${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`:
@@ -393,8 +458,12 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
393
458
  this._viewerGrid = null;
394
459
  this._scaledActivityColumn = null;
395
460
  break;
396
- case `${SAR_PROPERTIES.TARGET}${COLUMN_NAME}`:
397
- case SAR_PROPERTIES.TARGET_CATEGORY:
461
+ case `${SAR_PROPERTIES.VALUE_INVARIANT_MAP}${COLUMN_NAME}`:
462
+ case SAR_PROPERTIES.AGGREGATION_INVARIANT_MAP_VALUE:
463
+ this._monomerPositionStats = null;
464
+ this._viewerGrid = null;
465
+ this._invariantMapSelection = null;
466
+ break;
398
467
  case SAR_PROPERTIES.MIN_ACTIVITY_DELTA:
399
468
  case SAR_PROPERTIES.MAX_MUTATIONS:
400
469
  this._mutationCliffs = null;
@@ -414,6 +483,12 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
414
483
  }
415
484
  if (this._mutationCliffs === null && this.sequenceColumnName && this.activityColumnName)
416
485
  this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
486
+
487
+ // do this last to avoid recalculating mutation cliffs
488
+ if (property.name === `${SAR_PROPERTIES.TARGET}${COLUMN_NAME}` && this.targetColumnInput) {
489
+ this.targetColumnInput.value = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
490
+ this.resetTargetCategoryValue();
491
+ }
417
492
  }
418
493
 
419
494
  /**
@@ -454,6 +529,8 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
454
529
  ?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
455
530
  this.getProperty(`${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
456
531
  ?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
532
+ this.getProperty(`${SAR_PROPERTIES.VALUE_INVARIANT_MAP}${COLUMN_NAME}`)
533
+ ?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
457
534
  if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName)
458
535
  this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
459
536
  } else {
@@ -475,7 +552,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
475
552
 
476
553
  const options: MutationCliffsOptions = {
477
554
  maxMutations: this.maxMutations, minActivityDelta: this.minActivityDelta,
478
- targetCol, currentTarget: this.targetCategory,
555
+ targetCol, currentTarget: this.targetCategoryInput.value,
479
556
  };
480
557
  const activityRawData = scaledActivityCol.getRawData();
481
558
 
@@ -490,16 +567,29 @@ export class MonomerPosition extends SARViewer {
490
567
  colorColumnName: string;
491
568
  colorAggregation: string;
492
569
  currentGridCell: DG.GridCell | null = null;
493
-
570
+ customColorRange: boolean = false;
571
+ minColorValue: number = 0;
572
+ maxColorValue: number = 0;
573
+ lowerBoundColor: number;
574
+ middleColor: number;
575
+ upperBoundColor: number;
576
+ logScaleColor: boolean = false;
494
577
  /** Sets MonomerPosition properties. */
495
578
  constructor() {
496
579
  super();
497
580
 
498
- const colorChoices = wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name);
499
581
  this.colorColumnName = this.column(MONOMER_POSITION_PROPERTIES.COLOR,
500
- {category: PROPERTY_CATEGORIES.INVARIANT_MAP, choices: colorChoices, nullable: false});
582
+ {category: PROPERTY_CATEGORIES.INVARIANT_MAP, nullable: false, columnTypeFilter: 'numerical'});
501
583
  this.colorAggregation = this.string(MONOMER_POSITION_PROPERTIES.COLOR_AGGREGATION, DG.AGG.AVG,
502
584
  {category: PROPERTY_CATEGORIES.INVARIANT_MAP, choices: C.AGGREGATION_TYPES});
585
+ this.lowerBoundColor = this.int(MONOMER_POSITION_PROPERTIES.LOWER_BOUND_COLOR, 0xFF0000FF, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
586
+ this.middleColor = this.int(MONOMER_POSITION_PROPERTIES.MIDDLE_COLOR, 0xFFFFFFFF, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
587
+ this.upperBoundColor = this.int(MONOMER_POSITION_PROPERTIES.UPPER_BOUND_COLOR, 0xFFFF0000, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
588
+
589
+ this.logScaleColor = this.bool(MONOMER_POSITION_PROPERTIES.LOG_SCALE_COLOR, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
590
+ this.customColorRange = this.bool(MONOMER_POSITION_PROPERTIES.CUSTOM_COLOR_RANGE, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
591
+ this.minColorValue = this.float(MONOMER_POSITION_PROPERTIES.MIN_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
592
+ this.maxColorValue = this.float(MONOMER_POSITION_PROPERTIES.MAX_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
503
593
  }
504
594
 
505
595
  /**
@@ -529,8 +619,6 @@ export class MonomerPosition extends SARViewer {
529
619
  // setTimeout(() => this.viewerGrid.invalidate(), 300);
530
620
  }
531
621
 
532
- _invariantMapSelection: type.Selection | null = null;
533
-
534
622
  /**
535
623
  * Gets invariant map selection. Initializes it if it is null.
536
624
  * @return - invariant map selection.
@@ -559,6 +647,13 @@ export class MonomerPosition extends SARViewer {
559
647
  if (isApplicableDataframe(this.dataFrame)) {
560
648
  this.getProperty(`${MONOMER_POSITION_PROPERTIES.COLOR}${COLUMN_NAME}`)
561
649
  ?.set(this, this.activityColumnName);
650
+ this.targetColumnInput = ui.input.column('Target', {value: undefined, nullable: true, table: this.dataFrame,
651
+ onValueChanged: () => {
652
+ const prop = this.getProperty(`${SAR_PROPERTIES.TARGET}${COLUMN_NAME}`);
653
+ if (prop && prop.get(this) !== this.targetColumnInput!.value?.name)
654
+ prop?.set(this, this.targetColumnInput!.value?.name ?? null);
655
+ },
656
+ });
562
657
  } else {
563
658
  const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
564
659
  grok.log.error(msg);
@@ -593,21 +688,19 @@ export class MonomerPosition extends SARViewer {
593
688
  onPropertyChanged(property: DG.Property): void {
594
689
  super.onPropertyChanged(property);
595
690
  switch (property.name) {
596
- case MONOMER_POSITION_PROPERTIES.COLOR:
597
- case MONOMER_POSITION_PROPERTIES.COLOR_AGGREGATION:
598
- this.viewerGrid.invalidate();
599
- break;
600
691
  case SAR_PROPERTIES.SEQUENCE:
601
692
  this._invariantMapSelection = null;
602
693
  break;
603
694
  }
604
695
 
605
696
  // this will cause colors to recalculate
606
- this.model.df.columns.toList().forEach((col) => {
697
+ this.positionColumns?.forEach((col) => {
607
698
  col.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = null;
608
699
  });
609
700
  if (this.doRender)
610
701
  this.render();
702
+ else
703
+ this.viewerGrid.invalidate();
611
704
  }
612
705
 
613
706
  /**
@@ -642,6 +735,10 @@ export class MonomerPosition extends SARViewer {
642
735
  const colorColData = colorCol!.getRawData();
643
736
  let minColorVal = 9999999;
644
737
  let maxColorVal = -9999999;
738
+ const targetCol = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
739
+ const targetColRawData = targetCol?.getRawData();
740
+ const targetCategory = this.targetCategoryInput.value;
741
+ const targetCategoryIndex = targetCategory == null ? null : targetCol?.categories.indexOf(targetCategory);
645
742
  for (const pCol of this.positionColumns) {
646
743
  pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = {};
647
744
  const colorCache = pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE];
@@ -657,7 +754,9 @@ export class MonomerPosition extends SARViewer {
657
754
  //const pStatItem = pStats[pMonomer]!;
658
755
  const colorValuesIndexes: number[] = [];
659
756
  for (let i = 0; i < pCol.length; ++i) {
660
- if (positionColCategories[positionColData[i]] === pMonomer)
757
+ const isCurrentMonomer = positionColCategories[positionColData[i]] === pMonomer;
758
+ const isTarget = !targetColRawData || targetCategoryIndex == null || targetCategoryIndex == -1 || targetColRawData[i] === targetCategoryIndex;
759
+ if (isCurrentMonomer && isTarget)
661
760
  colorValuesIndexes.push(i);
662
761
  }
663
762
  const cellColorDataCol = DG.Column.float('color', colorValuesIndexes.length)
@@ -670,6 +769,14 @@ export class MonomerPosition extends SARViewer {
670
769
  pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = colorCache;
671
770
  }
672
771
 
772
+ const isCustomRangeSet = this.customColorRange && this.minColorValue != null && this.maxColorValue != null;
773
+ let usedMinValue = isCustomRangeSet ? this.minColorValue : minColorVal;
774
+ let usedMaxValue = isCustomRangeSet ? this.maxColorValue : maxColorVal;
775
+ const logScaleUsed = this.logScaleColor && usedMinValue > 1e-30 && usedMaxValue > 1e-30 && minColorVal > 1e-30 && maxColorVal > 1e-30;
776
+ if (logScaleUsed) {
777
+ usedMinValue = Math.log(usedMinValue);
778
+ usedMaxValue = Math.log(usedMaxValue);
779
+ }
673
780
  // do another swing to normalize colors
674
781
  for (const pCol of this.positionColumns) {
675
782
  const colorCache = pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE];
@@ -677,10 +784,15 @@ export class MonomerPosition extends SARViewer {
677
784
  continue;
678
785
  for (const pMonomer of Object.keys(colorCache)) {
679
786
  if (this.activityTarget === C.ACTIVITY_TARGET.LOW)
680
- colorCache[pMonomer] = maxColorVal - colorCache[pMonomer] + minColorVal;
681
- colorCache[pMonomer] = DG.Color.scaleColor(colorCache[pMonomer], minColorVal, maxColorVal);
787
+ colorCache[pMonomer] = usedMaxValue - colorCache[pMonomer] + usedMinValue;
788
+ colorCache[pMonomer] = DG.Color.scaleColor(
789
+ logScaleUsed ? Math.log(colorCache[pMonomer]) : colorCache[pMonomer], usedMinValue, usedMaxValue, undefined,
790
+ [this.lowerBoundColor, this.middleColor, this.upperBoundColor],
791
+ );
682
792
  }
683
793
  pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = colorCache;
794
+ pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_MIN_CACHE] = minColorVal;
795
+ pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_MAX_CACHE] = maxColorVal;
684
796
  }
685
797
  }
686
798
  }
@@ -713,7 +825,12 @@ export class MonomerPosition extends SARViewer {
713
825
  highlightMonomerPosition(monomerPosition, this.dataFrame, this.monomerPositionStats);
714
826
  this.model.isHighlighting = true;
715
827
  const columnEntries = this.getTotalViewerAggColumns();
716
-
828
+ if (this.mode === SELECTION_MODE.INVARIANT_MAP) {
829
+ if (this.colorColumnName && this.colorAggregation)
830
+ columnEntries.unshift([this.colorColumnName, this.colorAggregation as DG.AGG]);
831
+ if (this.valueColumnName && this.valueAggregation && this.valueAggregation !== DG.AGG.VALUE_COUNT && this.valueAggregation !== DG.AGG.TOTAL_COUNT)
832
+ columnEntries.unshift([this.valueColumnName, this.valueAggregation as DG.AGG]);
833
+ }
717
834
  return showTooltip(this.model.df, this.getScaledActivityColumn(), columnEntries, {
718
835
  fromViewer: true,
719
836
  isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
@@ -938,8 +1055,10 @@ export class MonomerPosition extends SARViewer {
938
1055
  this.viewerGrid.invalidate();
939
1056
  }, 'Show Monomer Position Table in full screen');
940
1057
  $(expand).addClass('pep-help-icon');
941
-
942
- const header = ui.divH([expand, switchHost], {style: {alignSelf: 'center', lineHeight: 'normal'}});
1058
+ this.targetColumnInput && (this.targetColumnInput.root.style.width = '50%');
1059
+ const targetInputsHost = ui.divH([this.targetColumnInput?.root ?? ui.div(), this.targetCategoryInput.root],
1060
+ {style: {alignSelf: 'center', justifyContent: 'center'}});
1061
+ const header = ui.divH([expand, switchHost, targetInputsHost], {style: {alignSelf: 'center', lineHeight: 'normal', flexDirection: 'column'}});
943
1062
  this.root.appendChild(ui.divV([header, viewerRoot]));
944
1063
  this.viewerGrid?.invalidate();
945
1064
  }
@@ -1013,7 +1132,7 @@ export class MostPotentResidues extends SARViewer {
1013
1132
  continue;
1014
1133
 
1015
1134
 
1016
- if ((monomerStats as StatsItem).count > 1 && (monomerStats as StatsItem).pValue === null)
1135
+ if ((monomerStats as StatsItem).count > 1 && ((monomerStats as StatsItem).pValue == null || ((monomerStats as StatsItem).pValue ?? 1) <= 0.05))
1017
1136
  filteredMonomerStats.push([monomer, monomerStats as StatsItem]);
1018
1137
 
1019
1138
 
@@ -1281,12 +1400,12 @@ function renderCell(args: DG.GridCellRenderArgs, viewer: SARViewer, isInvariantM
1281
1400
  }
1282
1401
 
1283
1402
  if (isInvariantMap) {
1284
- const value = currentPosStats![currentMonomer]!.count;
1403
+ const value = currentPosStats![currentMonomer]!.aggValue ?? currentPosStats![currentMonomer]!.count;
1285
1404
  const positionCol = viewer.positionColumns.find((col) => col.name === currentPosition)!;
1286
1405
  const colorCache: { [_: string]: number } = positionCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] ?? {};
1287
1406
  let color: number = DG.Color.white;
1288
1407
  // const colorColStats = colorCol!.stats;
1289
- if (colorCache[currentMonomer])
1408
+ if (colorCache[currentMonomer] != null)
1290
1409
  color = colorCache[currentMonomer];
1291
1410
  else if (viewer instanceof MonomerPosition) {
1292
1411
  viewer.cacheInvariantMapColors();