@datagrok/peptides 1.24.0 → 1.25.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.
@@ -41,6 +41,7 @@ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/types/monomer-lib
41
41
  import {PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
42
42
  import {PeptideUtils} from '../peptideUtils';
43
43
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
44
+ import BitArray from '@datagrok-libraries/utils/src/bit-array';
44
45
 
45
46
  export enum SELECTION_MODE {
46
47
  MUTATION_CLIFFS = 'Mutation Cliffs',
@@ -60,6 +61,7 @@ export enum SAR_PROPERTIES {
60
61
  ACTIVITY_TARGET = 'activityTarget',
61
62
  VALUE_INVARIANT_MAP = 'value',
62
63
  AGGREGATION_INVARIANT_MAP_VALUE = 'valueAggregation',
64
+ DATA_SOURCE = 'dataSource',
63
65
  }
64
66
 
65
67
  export enum MONOMER_POSITION_PROPERTIES {
@@ -73,6 +75,7 @@ export enum MONOMER_POSITION_PROPERTIES {
73
75
  UPPER_BOUND_COLOR = 'upperBoundColor',
74
76
  LOG_SCALE_COLOR = 'logScaleColor',
75
77
  SHOW_FILTER_CONTROLS = 'showFilterControls',
78
+ SHOW_TOTAL_COUNT_COLUMN = 'showTotalCountColumn',
76
79
  }
77
80
 
78
81
  export enum PROPERTY_CATEGORIES {
@@ -95,6 +98,7 @@ export interface ISARViewer {
95
98
  }
96
99
 
97
100
  /** Abstract class for MonomerPosition and MostPotentResidues viewers. */
101
+
98
102
  export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
99
103
  keyPressed: boolean = false;
100
104
  sequenceColumnName: string;
@@ -108,10 +112,9 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
108
112
  _scaledActivityColumn: DG.Column | null = null;
109
113
  doRender: boolean = true;
110
114
  activityTarget: C.ACTIVITY_TARGET;
111
- targetColumnInput?: DG.InputBase<DG.Column | null>;
112
- targetCategoryInput: DG.ChoiceInput<string | null | undefined>;
113
115
  valueColumnName: string;
114
116
  valueAggregation: DG.AGG;
117
+ dataSource: 'All' | 'Filtered' = 'Filtered';
115
118
 
116
119
  mutationCliffsDebouncer: (
117
120
  activityArray: type.RawData, monomerInfoArray: type.RawColumn[], options?: MutationCliffsOptions
@@ -123,6 +126,10 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
123
126
  // General properties
124
127
  this.sequenceColumnName = this.column(SAR_PROPERTIES.SEQUENCE,
125
128
  {category: PROPERTY_CATEGORIES.GENERAL, semType: DG.SEMTYPE.MACROMOLECULE, nullable: false});
129
+ this.dataSource = this.string(SAR_PROPERTIES.DATA_SOURCE, 'Filtered', {category: PROPERTY_CATEGORIES.GENERAL, choices: ['All', 'Filtered'], nullable: false,
130
+ description: 'Data source for calculations and rendering. Can be set to whole data set or filtered data only.',
131
+ }) as 'All' | 'Filtered';
132
+
126
133
  this.activityColumnName = this.column(SAR_PROPERTIES.ACTIVITY,
127
134
  {category: PROPERTY_CATEGORIES.GENERAL, nullable: false});
128
135
  this.activityScaling = this.string(SAR_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
@@ -132,9 +139,8 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
132
139
  {category: PROPERTY_CATEGORIES.GENERAL, choices: Object.values(C.ACTIVITY_TARGET), nullable: false},
133
140
  ) as C.ACTIVITY_TARGET;
134
141
  // Mutation Cliffs/invariant map properties
135
- // hide it and make it editable through the code
136
142
  this.targetColumnName = this.column(SAR_PROPERTIES.TARGET, {
137
- category: PROPERTY_CATEGORIES.GENERAL, nullable: true, columnTypeFilter: 'categorical', userEditable: true});
143
+ category: PROPERTY_CATEGORIES.GENERAL, nullable: true, columnTypeFilter: 'categorical', userEditable: false}); // not userEditable to account for reverse compatibility
138
144
  this.minActivityDelta = this.float(SAR_PROPERTIES.MIN_ACTIVITY_DELTA, 0,
139
145
  {category: PROPERTY_CATEGORIES.MUTATION_CLIFFS, min: 0, max: 100});
140
146
  this.maxMutations = this.int(SAR_PROPERTIES.MAX_MUTATIONS, 1,
@@ -152,25 +158,25 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
152
158
  return await findMutations(activityArray, monomerInfoArray, options);
153
159
  });
154
160
 
155
- this.targetCategoryInput = ui.input.choice('Category', {value: null, items: [], nullable: true,
156
- onValueChanged: () => {
157
- this._mutationCliffs = null;
158
- this._mutationCliffStats = null;
159
- this._mutationCliffsSelection = null;
160
- this._invariantMapSelection = null;
161
- this.doRender = false;
162
- this._monomerPositionStats = null;
163
- this.positionColumns?.forEach((col) => {
164
- col.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = null;
165
- });
166
- if (this.sequenceColumnName && this.activityColumnName)
167
- this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
168
- this.viewerGrid.invalidate();
169
- },
170
- });
171
- this.targetCategoryInput.root.style.display = 'none';
172
- this.targetCategoryInput.root.style.maxWidth = '50%';
173
- this.targetCategoryInput.root.style.marginLeft = '8px';
161
+ // this.targetCategoryInput = ui.input.choice('Category', {value: null, items: [], nullable: true,
162
+ // onValueChanged: () => {
163
+ // this._mutationCliffs = null;
164
+ // this._mutationCliffStats = null;
165
+ // this._mutationCliffsSelection = null;
166
+ // this._invariantMapSelection = null;
167
+ // this.doRender = false;
168
+ // this._monomerPositionStats = null;
169
+ // this.positionColumns?.forEach((col) => {
170
+ // col.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = null;
171
+ // });
172
+ // if (this.sequenceColumnName && this.activityColumnName)
173
+ // this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
174
+ // this.viewerGrid.invalidate();
175
+ // },
176
+ // });
177
+ // this.targetCategoryInput.root.style.display = 'none';
178
+ // this.targetCategoryInput.root.style.maxWidth = '50%';
179
+ // this.targetCategoryInput.root.style.marginLeft = '8px'
174
180
  }
175
181
 
176
182
  _viewerGrid: DG.Grid | null = null;
@@ -253,10 +259,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
253
259
  this.sequenceColumnName === other?.sequenceColumnName &&
254
260
  this.activityColumnName === other?.activityColumnName &&
255
261
  this.activityScaling === other?.activityScaling &&
256
- ((other instanceof SARViewer && this.targetColumnName == other?.targetColumnName &&
257
- this.targetCategoryInput?.value === other?.targetCategoryInput?.value) ||
258
- (!(other instanceof SARViewer) && (this.targetColumnName == null || this.targetCategoryInput?.value == null))
259
- ) &&
262
+ ((other instanceof SARViewer && other.dataSource === this.dataSource)) &&
260
263
  ((other instanceof SARViewer && this.valueColumnName == other?.valueColumnName && this.valueAggregation == other?.valueAggregation) ||
261
264
  (!(other instanceof SARViewer) &&
262
265
  (!this.valueColumnName || !this.valueAggregation || this.valueAggregation == DG.AGG.VALUE_COUNT || this.valueAggregation == DG.AGG.TOTAL_COUNT))
@@ -277,15 +280,12 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
277
280
  else if (this instanceof MostPotentResidues)
278
281
  this._monomerPositionStats = getSharedStats(VIEWER_TYPE.SEQUENCE_VARIABILITY_MAP);
279
282
 
280
-
281
- const targetCol = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
282
- const targetCategory = this.targetCategoryInput.value;
283
283
  const invariantMapValueCol = this.dataFrame.col(this.valueColumnName);
284
284
  const invariantMapValueAgg = this.valueAggregation;
285
285
 
286
286
  this._monomerPositionStats ??= calculateMonomerPositionStatistics(this.getScaledActivityColumn(),
287
287
  this.dataFrame.filter, this.positionColumns,
288
- {target: (targetCol && targetCategory) ? {col: targetCol, cat: targetCategory} : undefined,
288
+ {isFiltered: this.dataSource === 'Filtered' && this.dataFrame.filter.anyFalse,
289
289
  aggValue: (invariantMapValueAgg && invariantMapValueCol) ? {col: invariantMapValueCol, type: invariantMapValueAgg} : undefined,
290
290
  });
291
291
  return this._monomerPositionStats;
@@ -306,12 +306,11 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
306
306
 
307
307
  const isMutationCliffsEqual = (v1: SARViewer, v2: SARViewer | null): boolean =>
308
308
  v1.sequenceColumnName === v2?.sequenceColumnName &&
309
- v1.activityColumnName === v2.activityColumnName &&
310
- v1.activityScaling === v2.activityScaling &&
311
- v1.targetColumnName === v2?.targetColumnName &&
312
- v1.targetCategoryInput?.value === v2?.targetCategoryInput?.value &&
309
+ v1.activityColumnName === v2?.activityColumnName &&
310
+ v1.activityScaling === v2?.activityScaling &&
313
311
  v1.minActivityDelta === v2?.minActivityDelta &&
314
- v1.maxMutations === v2?.maxMutations;
312
+ v1.maxMutations === v2?.maxMutations &&
313
+ v1?.dataSource === v2?.dataSource;
315
314
 
316
315
  const getSharedMutationCliffs = (viewerType: VIEWER_TYPE): type.MutationCliffs | null => {
317
316
  const viewer = this.model.findViewer(viewerType) as SARViewer | null;
@@ -351,7 +350,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
351
350
  }
352
351
  set cliffStats(stats: type.MutationCliffStats | null) {
353
352
  this._mutationCliffStats = stats;
354
- this.viewerGrid.invalidate;
353
+ this.viewerGrid.invalidate();
355
354
  }
356
355
 
357
356
  _mutationCliffsSelection: type.Selection | null = null;
@@ -361,10 +360,9 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
361
360
  * @return - mutation cliffs selection.
362
361
  */
363
362
  get mutationCliffsSelection(): type.Selection {
364
- const tagSuffix = this instanceof MonomerPosition ? C.SUFFIXES.MP : C.SUFFIXES.MPR;
365
- const tagSelection = this.dataFrame.getTag(`${tagSuffix}${C.TAGS.MUTATION_CLIFFS_SELECTION}`);
366
- this._mutationCliffsSelection ??= tagSelection === null ? initSelection(this.positionColumns) :
367
- JSON.parse(tagSelection);
363
+ // const tagSuffix = this instanceof MonomerPosition ? C.SUFFIXES.MP : C.SUFFIXES.MPR;
364
+ // const tagSelection = this.dataFrame.getTag(`${tagSuffix}${C.TAGS.MUTATION_CLIFFS_SELECTION}`);
365
+ this._mutationCliffsSelection ??= initSelection(this.positionColumns);
368
366
  return this._mutationCliffsSelection!;
369
367
  }
370
368
 
@@ -374,15 +372,13 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
374
372
  */
375
373
  set mutationCliffsSelection(selection: type.Selection) {
376
374
  this._mutationCliffsSelection = selection;
377
- const tagSuffix = this instanceof MonomerPosition ? C.SUFFIXES.MP : C.SUFFIXES.MPR;
378
- this.dataFrame.setTag(`${tagSuffix}${C.TAGS.MUTATION_CLIFFS_SELECTION}`, JSON.stringify(selection));
379
375
  this.model.fireBitsetChanged(this instanceof MonomerPosition ? VIEWER_TYPE.SEQUENCE_VARIABILITY_MAP :
380
376
  VIEWER_TYPE.MOST_POTENT_RESIDUES);
381
377
 
382
378
  const mpViewer = this.model.findViewer(VIEWER_TYPE.SEQUENCE_VARIABILITY_MAP) as MonomerPosition | null;
383
- mpViewer?.viewerGrid.invalidate();
379
+ mpViewer?.viewerGrid?.invalidate();
384
380
  const mprViewer = this.model.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
385
- mprViewer?.viewerGrid.invalidate();
381
+ mprViewer?.viewerGrid?.invalidate();
386
382
 
387
383
  this.model.analysisView.grid.invalidate();
388
384
  }
@@ -450,9 +446,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
450
446
  * @return - invariant map selection.
451
447
  */
452
448
  get invariantMapSelection(): type.Selection {
453
- const tagSelection = this.dataFrame.getTag(`${C.SUFFIXES.MP}${C.TAGS.INVARIANT_MAP_SELECTION}`);
454
- this._invariantMapSelection ??= tagSelection === null ? initSelection(this.positionColumns) :
455
- JSON.parse(tagSelection);
449
+ this._invariantMapSelection ??= initSelection(this.positionColumns);
456
450
  return this._invariantMapSelection!;
457
451
  }
458
452
 
@@ -462,22 +456,10 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
462
456
  */
463
457
  set invariantMapSelection(selection: type.Selection) {
464
458
  this._invariantMapSelection = selection;
465
- this.dataFrame.setTag(`${C.SUFFIXES.MP}${C.TAGS.INVARIANT_MAP_SELECTION}`, JSON.stringify(selection));
466
459
  this.model.fireBitsetChanged(VIEWER_TYPE.SEQUENCE_VARIABILITY_MAP);
467
460
  this.model.analysisView.grid.invalidate();
468
461
  }
469
462
 
470
- private resetTargetCategoryValue(): void {
471
- const colName = this.targetColumnName;
472
- const col = colName ? this.dataFrame.col(colName) : null;
473
- this.targetCategoryInput.items = col?.categories ?? [];
474
- this.targetCategoryInput.value = null;
475
- if (!colName)
476
- this.targetCategoryInput.root.style.display = 'none';
477
- else
478
- this.targetCategoryInput.root.style.display = 'flex';
479
- }
480
-
481
463
  /**
482
464
  * Processes property changes.
483
465
  * @param property - changed property.
@@ -527,15 +509,13 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
527
509
  if (this instanceof MostPotentResidues || this instanceof MonomerPosition)
528
510
  this._viewerGrid = null;
529
511
  break;
512
+ case SAR_PROPERTIES.DATA_SOURCE:
513
+ this.onFilterChanged(false, false);
514
+ this.doRender = true;
515
+ break;
530
516
  }
531
517
  if (this._mutationCliffs === null && this.sequenceColumnName && this.activityColumnName && this.dataFrame)
532
518
  this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
533
-
534
- // do this last to avoid recalculating mutation cliffs
535
- if (property.name === `${SAR_PROPERTIES.TARGET}${COLUMN_NAME}` && this.targetColumnInput) {
536
- this.targetColumnInput.value = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
537
- this.resetTargetCategoryValue();
538
- }
539
519
  }
540
520
 
541
521
  /**
@@ -603,6 +583,18 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
603
583
  }, {isChecked: (meta) => this._monomerMetaColumns.has(meta)});
604
584
  });
605
585
  }));
586
+ this.subs.push(DG.debounce(this.dataFrame.onFilterChanged, 300).subscribe(() => {
587
+ if (this.dataSource === 'Filtered') {
588
+ // this._monomerPositionStats = null;
589
+ // this._invariantMapSelection = null;
590
+ // this._mutationCliffs = null;
591
+ // this._mutationCliffStats = null;
592
+ // this._mutationCliffsSelection = null;
593
+ // this._viewerGrid = null;
594
+ // this.render();
595
+ this.onFilterChanged(true, false);
596
+ }
597
+ }));
606
598
  } else {
607
599
  const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
608
600
  grok.log.error(msg);
@@ -610,6 +602,27 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
610
602
  }
611
603
  }
612
604
 
605
+ /// Override in inheritors if needed
606
+ onFilterChanged(render?: boolean, onlySetNulls = false): void {
607
+ this._monomerPositionStats = null;
608
+ this._invariantMapSelection = null;
609
+ this._mutationCliffStats = null;
610
+ this._mutationCliffsSelection = null;
611
+ this._mutationCliffs = null;
612
+ if (onlySetNulls)
613
+ return;
614
+
615
+ this._viewerGrid = null;
616
+ const mprViewer = this.model.findViewer(VIEWER_TYPE.MOST_POTENT_RESIDUES) as MostPotentResidues | null;
617
+ if (mprViewer && !(this instanceof MostPotentResidues) && mprViewer.dataSource === 'Filtered')
618
+ mprViewer.onFilterChanged(false, true);
619
+
620
+ if (render)
621
+ this.render();
622
+ if (this.sequenceColumnName && this.activityColumnName)
623
+ this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
624
+ }
625
+
613
626
  /**
614
627
  * Calculates Mutation Cliffs.
615
628
  * @return - mutation cliffs.
@@ -618,11 +631,11 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
618
631
  const scaledActivityCol: DG.Column<number> = this.dataFrame.getCol(this.activityColumnName);
619
632
  //TODO: set categories ordering the same to share compare indexes instead of strings
620
633
  const monomerCols: type.RawColumn[] = this.positionColumns.map(extractColInfo);
621
- const targetCol = this.targetColumnName ? extractColInfo(this.dataFrame.getCol(this.targetColumnName)) : null;
634
+ const filter = (this.dataSource === 'Filtered' && this.dataFrame.filter.anyFalse) ?
635
+ this.dataFrame.filter : null;
622
636
 
623
637
  const options: MutationCliffsOptions = {
624
- maxMutations: this.maxMutations, minActivityDelta: this.minActivityDelta,
625
- targetCol, currentTarget: this.targetCategoryInput.value,
638
+ maxMutations: this.maxMutations, minActivityDelta: this.minActivityDelta, filter: (filter?.getBuffer() as unknown as Uint32Array) ?? undefined,
626
639
  };
627
640
  const activityRawData = scaledActivityCol.getRawData();
628
641
 
@@ -646,6 +659,7 @@ export class MonomerPosition extends SARViewer {
646
659
  upperBoundColor: number;
647
660
  logScaleColor: boolean = false;
648
661
  showFilterControls: boolean = true;
662
+ showTotalCountColumn: boolean = false;
649
663
  /** Sets MonomerPosition properties. */
650
664
  constructor() {
651
665
  super();
@@ -657,6 +671,7 @@ export class MonomerPosition extends SARViewer {
657
671
  this.lowerBoundColor = this.int(MONOMER_POSITION_PROPERTIES.LOWER_BOUND_COLOR, 0xFF0000FF, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
658
672
  this.middleColor = this.int(MONOMER_POSITION_PROPERTIES.MIDDLE_COLOR, 0xFFFFFFFF, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
659
673
  this.upperBoundColor = this.int(MONOMER_POSITION_PROPERTIES.UPPER_BOUND_COLOR, 0xFFFF0000, {category: PROPERTY_CATEGORIES.INVARIANT_MAP, editor: 'color'});
674
+ this.showTotalCountColumn = this.bool(MONOMER_POSITION_PROPERTIES.SHOW_TOTAL_COUNT_COLUMN, false, {category: PROPERTY_CATEGORIES.GENERAL, description: 'Show total monomer count column'});
660
675
 
661
676
  this.logScaleColor = this.bool(MONOMER_POSITION_PROPERTIES.LOG_SCALE_COLOR, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
662
677
  this.customColorRange = this.bool(MONOMER_POSITION_PROPERTIES.CUSTOM_COLOR_RANGE, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
@@ -717,13 +732,6 @@ export class MonomerPosition extends SARViewer {
717
732
  if (isApplicableDataframe(this.dataFrame)) {
718
733
  this.getProperty(`${MONOMER_POSITION_PROPERTIES.COLOR}${COLUMN_NAME}`)
719
734
  ?.set(this, this.activityColumnName);
720
- this.targetColumnInput = ui.input.column('Target', {value: undefined, nullable: true, table: this.dataFrame, filter: (col: DG.Column) => col.isCategorical,
721
- onValueChanged: (value) => {
722
- const prop = this.getProperty(`${SAR_PROPERTIES.TARGET}${COLUMN_NAME}`);
723
- if (prop && prop.get(this) != (value?.name ?? null))
724
- prop?.set(this, value?.name ?? null);
725
- },
726
- });
727
735
  } else {
728
736
  const msg = 'PeptidesError: dataframe is missing Macromolecule or numeric columns';
729
737
  grok.log.error(msg);
@@ -742,6 +750,11 @@ export class MonomerPosition extends SARViewer {
742
750
  case SAR_PROPERTIES.SEQUENCE:
743
751
  this._invariantMapSelection = null;
744
752
  break;
753
+ case MONOMER_POSITION_PROPERTIES.SHOW_TOTAL_COUNT_COLUMN:
754
+ if (this._viewerGrid && this._viewerGrid.columns.byName(C.COLUMNS_NAMES.TOTAL_COUNT)) {
755
+ this._viewerGrid.columns.byName(C.COLUMNS_NAMES.TOTAL_COUNT)!.visible = this.showTotalCountColumn;
756
+ this.doRender = false;
757
+ }
745
758
  }
746
759
 
747
760
  // this will cause colors to recalculate
@@ -777,6 +790,25 @@ export class MonomerPosition extends SARViewer {
777
790
  return monomersArray.map((_m) => ({}));
778
791
  return monomersArray.map((m) => lib.getMonomer(PolymerTypes.PEPTIDE, m)?.meta ?? {});
779
792
  });
793
+ // add sum column
794
+ const sumCol = monomerPositionDf.columns.addNewInt(C.COLUMNS_NAMES.TOTAL_COUNT);
795
+ const monomerCounts: Record<string, number> = {};
796
+ const stats = this.monomerPositionStats;
797
+ for (const posCol of Object.keys(stats)) {
798
+ if (posCol === 'general')
799
+ continue;
800
+ const posStats = stats[posCol]!;
801
+ for (const monomer of Object.keys(posStats)) {
802
+ if (monomer === 'general')
803
+ continue;
804
+ if (!monomerCounts[monomer])
805
+ monomerCounts[monomer] = 0;
806
+ monomerCounts[monomer] += posStats[monomer]!.count;
807
+ }
808
+ }
809
+ sumCol.init((i) => monomerCounts[monomerCol.get(i)] ?? 0);
810
+
811
+ // add meta columns
780
812
  this._monomerMetaColumns.forEach((meta) => {
781
813
  const metaCol = monomerPositionDf.columns.addNewString(meta);
782
814
  monomersMetaPromise.then((metaInfo) => {
@@ -797,10 +829,9 @@ export class MonomerPosition extends SARViewer {
797
829
  const colorColData = colorCol!.getRawData();
798
830
  let minColorVal = 9999999;
799
831
  let maxColorVal = -9999999;
800
- const targetCol = this.targetColumnName ? this.dataFrame.col(this.targetColumnName) : null;
801
- const targetColRawData = targetCol?.getRawData();
802
- const targetCategory = this.targetCategoryInput.value;
803
- const targetCategoryIndex = targetCategory == null ? null : targetCol?.categories.indexOf(targetCategory);
832
+ const filter = (this.dataSource === 'Filtered' && this.dataFrame.filter.anyFalse) ?
833
+ this.dataFrame.filter : null;
834
+ const isTarget = filter == null ? (index: number) => true : (index: number) => filter.get(index);
804
835
  for (const pCol of this.positionColumns) {
805
836
  pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = {};
806
837
  const colorCache = pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE];
@@ -817,8 +848,7 @@ export class MonomerPosition extends SARViewer {
817
848
  const colorValuesIndexes: number[] = [];
818
849
  for (let i = 0; i < pCol.length; ++i) {
819
850
  const isCurrentMonomer = positionColCategories[positionColData[i]] === pMonomer;
820
- const isTarget = !targetColRawData || targetCategoryIndex == null || targetCategoryIndex == -1 || targetColRawData[i] === targetCategoryIndex;
821
- if (isCurrentMonomer && isTarget)
851
+ if (isCurrentMonomer && isTarget(i))
822
852
  colorValuesIndexes.push(i);
823
853
  }
824
854
  const cellColorDataCol = DG.Column.float('color', colorValuesIndexes.length)
@@ -869,7 +899,13 @@ export class MonomerPosition extends SARViewer {
869
899
  const grid = monomerPositionDf.plot.grid();
870
900
  grid.sort([C.COLUMNS_NAMES.MONOMER]);
871
901
  const positionColumns = this.positionColumns.map((col) => col.name);
872
- grid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...this._monomerMetaColumns, ...positionColumns]);
902
+ grid.columns.setOrder([C.COLUMNS_NAMES.TOTAL_COUNT, C.COLUMNS_NAMES.MONOMER, ...this._monomerMetaColumns, ...positionColumns]);
903
+
904
+ const sumGridCol = grid.columns.byName(C.COLUMNS_NAMES.TOTAL_COUNT);
905
+ if (this.showTotalCountColumn && sumGridCol != null)
906
+ sumGridCol.visible = true;
907
+ else if (sumGridCol != null)
908
+ sumGridCol.visible = false;
873
909
  const monomerCol = monomerPositionDf.getCol(C.COLUMNS_NAMES.MONOMER);
874
910
  CR.setMonomerRenderer(monomerCol, this.alphabet, true);
875
911
  this.cacheInvariantMapColors();
@@ -879,7 +915,7 @@ export class MonomerPosition extends SARViewer {
879
915
  this.colorAggregation as DG.AGG));
880
916
 
881
917
  grid.onCellTooltip((gridCell: DG.GridCell, x: number, y: number) => {
882
- if (!gridCell.isTableCell || !gridCell?.cell.column?.name || this._monomerMetaColumns.has(gridCell.cell.column.name)) {
918
+ if (!gridCell.isTableCell || !gridCell?.cell.column?.name || this._monomerMetaColumns.has(gridCell.cell.column.name) || gridCell.cell.column.name === C.COLUMNS_NAMES.TOTAL_COUNT) {
883
919
  this.model.unhighlight();
884
920
  return true;
885
921
  }
@@ -916,7 +952,7 @@ export class MonomerPosition extends SARViewer {
916
952
  grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
917
953
  DG.debounce(grid.onCurrentCellChanged, 500).subscribe((gridCell: DG.GridCell) => {
918
954
  try {
919
- if (!gridCell || !gridCell.dart || !gridCell?.cell?.column?.name || this._monomerMetaColumns.has(gridCell.cell.column.name))
955
+ if (!gridCell || !gridCell.dart || !gridCell?.cell?.column?.name || this._monomerMetaColumns.has(gridCell.cell.column.name) || gridCell.cell.column.name == C.COLUMNS_NAMES.TOTAL_COUNT ||!gridCell.isTableCell)
920
956
  return;
921
957
  if (gridCell.gridRow === -1) {
922
958
  if (this.mode === SELECTION_MODE.INVARIANT_MAP)
@@ -928,7 +964,7 @@ export class MonomerPosition extends SARViewer {
928
964
  grid.invalidate();
929
965
  }
930
966
 
931
- if (!this.keyPressed)
967
+ if (!this.keyPressed || gridCell.cell.column!.name === C.COLUMNS_NAMES.MONOMER)
932
968
  return;
933
969
 
934
970
 
@@ -964,9 +1000,12 @@ export class MonomerPosition extends SARViewer {
964
1000
 
965
1001
  grid.invalidate();
966
1002
  setTimeout(() => grid?.invalidate(), 300);
1003
+ } catch (e) {
1004
+ console.error(e);
967
1005
  } finally {
968
1006
  this.keyPressed = false;
969
- if (gridCell.tableColumn?.name && gridCell.grid)
1007
+ if (gridCell.tableColumn?.name && gridCell.grid && gridCell.isTableCell && gridCell?.cell?.column.name != C.COLUMNS_NAMES.MONOMER &&
1008
+ !this._monomerMetaColumns.has(gridCell.cell?.column?.name) && gridCell.cell?.column.name !== C.COLUMNS_NAMES.TOTAL_COUNT)
970
1009
  this.currentGridCell = DG.GridCell.fromColumnRow(gridCell.grid, gridCell.tableColumn.name, gridCell.gridRow);
971
1010
  else
972
1011
  this.currentGridCell = null;
@@ -1010,7 +1049,7 @@ export class MonomerPosition extends SARViewer {
1010
1049
  grid.root.addEventListener('click', (ev) => {
1011
1050
  const gridCell = grid.hitTest(ev.offsetX, ev.offsetY);
1012
1051
  if (!gridCell?.isTableCell || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.MONOMER ||
1013
- (gridCell?.tableColumn?.name && this._monomerMetaColumns.has(gridCell.tableColumn.name))
1052
+ (gridCell?.tableColumn?.name && this._monomerMetaColumns.has(gridCell.tableColumn.name) || gridCell?.tableColumn?.name === C.COLUMNS_NAMES.TOTAL_COUNT)
1014
1053
  )
1015
1054
  return;
1016
1055
 
@@ -1032,6 +1071,7 @@ export class MonomerPosition extends SARViewer {
1032
1071
  });
1033
1072
 
1034
1073
  setViewerGridProps(grid);
1074
+ grid.props.showRowHeader = true;
1035
1075
 
1036
1076
  // Monomer cell renderer overrides width settings. This way I ensure is "initially" set.
1037
1077
  const afterDraw = grid.onAfterDrawContent.subscribe(() => {
@@ -1117,9 +1157,8 @@ export class MonomerPosition extends SARViewer {
1117
1157
  this.viewerGrid.invalidate();
1118
1158
  }, 'Show Sequence Variability Map Table in full screen');
1119
1159
  $(expand).addClass('pep-help-icon');
1120
- this.targetColumnInput && (this.targetColumnInput.root.style.maxWidth = '50%');
1121
1160
  this.monomerSearchInput.root.style.marginRight = '8px';
1122
- const targetInputsHost = ui.divH([this.monomerSearchInput.input, this.targetColumnInput?.root ?? ui.div(), this.targetCategoryInput.root],
1161
+ const targetInputsHost = ui.divH([this.monomerSearchInput.input],
1123
1162
  {style: {alignSelf: 'center', justifyContent: 'center', width: '100%', flexWrap: 'wrap'}});
1124
1163
  targetInputsHost.style.display = this.showFilterControls ? 'flex' : 'none';
1125
1164
  const header = ui.divH([expand, switchHost, targetInputsHost], {style: {alignSelf: 'center', lineHeight: 'normal', flexDirection: 'column', width: '100%'}});
@@ -1162,6 +1201,23 @@ export class MostPotentResidues extends SARViewer {
1162
1201
  this.render();
1163
1202
  }
1164
1203
 
1204
+ onFilterChanged(render?: boolean, onlySetNulls = false): void {
1205
+ this._monomerPositionStats = null;
1206
+ this._invariantMapSelection = null;
1207
+ this._mutationCliffStats = null;
1208
+ this._mutationCliffsSelection = null;
1209
+ this._mutationCliffs = null;
1210
+ if (onlySetNulls)
1211
+ return;
1212
+ this._viewerGrid = null;
1213
+ const sarViewer = this.model.findViewer(VIEWER_TYPE.SEQUENCE_VARIABILITY_MAP) as MonomerPosition | null;
1214
+ if (sarViewer?.dataSource === 'Filtered')
1215
+ sarViewer.onFilterChanged(false, true);
1216
+ if (render)
1217
+ this.render();
1218
+ // do not recalculate mutation cliffs on filter change for MostPotentResidues viewer
1219
+ }
1220
+
1165
1221
  /**
1166
1222
  * Creates most potent residues dataframe to be used in MostPotentResidues grid.
1167
1223
  * @return - most potent residues dataframe.
@@ -1442,12 +1498,12 @@ function renderCell(args: DG.GridCellRenderArgs, viewer: SARViewer, isInvariantM
1442
1498
 
1443
1499
  // Hide row column
1444
1500
  const cell = args.cell;
1445
- if (cell.isRowHeader && cell.gridColumn.visible) {
1446
- cell.gridColumn.visible = false;
1447
- args.preventDefault();
1448
- canvasContext.restore();
1449
- return;
1450
- }
1501
+ // if (cell.isRowHeader && cell.gridColumn.visible) {
1502
+ // cell.gridColumn.visible = false;
1503
+ // args.preventDefault();
1504
+ // canvasContext.restore();
1505
+ // return;
1506
+ // }
1451
1507
 
1452
1508
  const tableColName = cell.tableColumn?.name;
1453
1509
  const tableRowIndex = cell.tableRowIndex!;
@@ -104,7 +104,7 @@ export function getSelectionWidget(table: DG.DataFrame, options: SelectionWidget
104
104
  }, 500);
105
105
 
106
106
  const mpStats = calculateMonomerPositionStatistics(options.activityColumn, newTable.filter,
107
- options.positionColumns, {isFiltered: newTable.filter.anyTrue || newTable.filter.anyFalse});
107
+ options.positionColumns, {isFiltered: true});
108
108
 
109
109
  const cachedWebLogoTooltip: () => CachedWebLogoTooltip = () => {
110
110
  return {bar: '', tooltip: null};
@@ -1,5 +1,10 @@
1
+ import BitArray from '@datagrok-libraries/utils/src/bit-array';
2
+
1
3
  onmessage = async (event): Promise<void> => {
2
- const {startIdx, endIdx, activityArray, monomerInfoArray, settings, currentTargetIdx} = event.data;
4
+ const {startIdx, endIdx, activityArray, monomerInfoArray, settings} = event.data;
5
+ const filterArray: Uint32Array = settings.filter;
6
+ const filter = filterArray ? new BitArray(filterArray, filterArray.length * 32) : null;
7
+
3
8
  const pos: string[] = [];
4
9
  const seq1Idxs: number[] = [];
5
10
  const seq2Idxs: number[] = [];
@@ -12,8 +17,7 @@ onmessage = async (event): Promise<void> => {
12
17
  let seq2Idx = startCol;
13
18
  const tempData = new Array(monomerInfoArray.length);
14
19
  while (cnt < chunkSize) {
15
- if (!(currentTargetIdx !== -1 && (settings.targetCol?.rawData[seq1Idx] !== currentTargetIdx ||
16
- settings.targetCol?.rawData[seq2Idx] !== currentTargetIdx))) {
20
+ if (!filter || (filter.getBit(seq1Idx) && filter.getBit(seq2Idx))) {
17
21
  let substCounter = 0;
18
22
  const activityValSeq1 = activityArray[seq1Idx];
19
23
  const activityValSeq2 = activityArray[seq2Idx];