@datagrok/peptides 1.26.2 → 1.27.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/peptides",
3
3
  "friendlyName": "Peptides",
4
- "version": "1.26.2",
4
+ "version": "1.27.1",
5
5
  "author": {
6
6
  "name": "Davit Rizhinashvili",
7
7
  "email": "drizhinashvili@datagrok.ai"
package/src/model.ts CHANGED
@@ -65,6 +65,7 @@ export enum VIEWER_TYPE {
65
65
  DENDROGRAM = 'Dendrogram',
66
66
  CLUSTER_MAX_ACTIVITY = 'Active peptide selection',
67
67
  MCL = 'MCL',
68
+ SEQUENCE_MUTATION_CLIFFS = 'Sequence Mutation Cliffs',
68
69
  }
69
70
 
70
71
  export type CachedWebLogoTooltip = { bar: string, tooltip: HTMLDivElement | null };
@@ -621,6 +622,7 @@ export class PeptidesModel {
621
622
  sequenceColumnName: sarViewer.sequenceColumnName,
622
623
  positionColumns: sarViewer.positionColumns,
623
624
  activityCol: sarViewer.getScaledActivityColumn(),
625
+ mutationCliffStats: sarViewer.cliffStats,
624
626
  }).root, true);
625
627
  }
626
628
  const isModelSource = requestSource === trueModel.settings;
package/src/styles.css CHANGED
@@ -60,3 +60,14 @@
60
60
  left: 5px;
61
61
  top: 1px;
62
62
  }
63
+
64
+ .panel-base:has(.peptides-viewer-show-title:only-child) > .panel-titlebar > .panel-titlebar-text {
65
+ visibility: visible;
66
+ display: block;
67
+ text-align: center;
68
+ margin-left: 36px;
69
+ padding: 0;
70
+ width: 100%;
71
+ font-size: 14px !important;
72
+ color: #4d5261 !important;
73
+ }
@@ -146,6 +146,7 @@ category('Widgets: Mutation cliffs', () => {
146
146
  sequenceColumnName: sarViewer.sequenceColumnName,
147
147
  positionColumns: sarViewer.positionColumns,
148
148
  activityCol: scaledActivityCol,
149
+ mutationCliffStats: sarViewer.cliffStats,
149
150
  });
150
151
  });
151
152
  });
@@ -63,19 +63,31 @@ export function calculateCliffsStatistics(
63
63
  const monomerSubMap = cliffs.get(monomer)!;
64
64
  for (const position of monomerSubMap.keys()) {
65
65
  const subMap = monomerSubMap.get(position)!;
66
- const mask = new BitArray(activityArray.length, false);
67
66
  if (subMap.size === 0)
68
67
  continue;
68
+ // create two masks, one filtering the activities to only mutation cliffs (with given monomer at given position and its substitutions)
69
+ // another one corresponding to only the given monomer at given position within the mutation cliffs
70
+ const filterMask = new BitArray(activityArray.length, false);
71
+ const maskMCWithMonomerAtPosition = new BitArray(activityArray.length, false);
69
72
  for (const index of subMap.keys()) {
70
- mask.setFast(index, true);
73
+ // set the filter mask to true for all sequences within the mutation cliff pairs
74
+ filterMask.setFast(index, true);
71
75
  const toIndexes = subMap.get(index)!;
72
- toIndexes.forEach((i) => mask.setFast(i, true));
76
+ toIndexes.forEach((i) => filterMask.setFast(i, true));
77
+ // set the mask for sequences with the given monomer at the given position within the mutation cliffs
78
+ maskMCWithMonomerAtPosition.setFast(index, true);
73
79
  }
74
- const stats = getStats(activityArray, mask);
80
+ const stats = getStats(activityArray, maskMCWithMonomerAtPosition, filterMask);
81
+ stats.mask = filterMask; // store the filter mask for later use in the viewer
75
82
  minDiff = Math.min(minDiff, stats.meanDifference);
76
83
  maxDiff = Math.max(maxDiff, stats.meanDifference);
77
84
  minCount = Math.min(minCount, stats.count);
78
85
  maxCount = Math.max(maxCount, stats.count);
86
+ // here, stats will show the following
87
+ // count - number of sequences with the given monomer at the given position within the mutation cliffs
88
+ // mask.trueCount - number of unique sequences within the mutation cliffs (with given monomer at given position and its substitutions)
89
+ // meanDifference - difference between mean activity of sequences with the given monomer at the given position within the mutation cliffs
90
+ // and mean activity of other sequences within the mutation cliffs (with given monomer at given position substitutions)
79
91
  monomerStatsMap.set(position, stats);
80
92
  }
81
93
  }
package/src/utils/misc.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-len */
1
2
  import * as ui from 'datagrok-api/ui';
2
3
  import * as DG from 'datagrok-api/dg';
3
4
  import * as grok from 'datagrok-api/grok';
@@ -111,13 +112,10 @@ export function getDistributionPanel(hist: DG.Viewer<DG.IHistogramSettings>, sta
111
112
  const splitCol = hist.dataFrame.getCol(C.COLUMNS_NAMES.SPLIT_COL);
112
113
  const labels = [];
113
114
  const categories = splitCol.categories as SPLIT_CATEGORY[];
114
- const rawData = splitCol.getRawData();
115
115
  for (let categoryIdx = 0; categoryIdx < categories.length; ++categoryIdx) {
116
- if (!Object.values(SPLIT_CATEGORY).includes(categories[categoryIdx]))
116
+ if (!categories[categoryIdx])
117
117
  continue;
118
-
119
-
120
- const color = DG.Color.toHtml(splitCol.meta.colors.getColor(rawData.indexOf(categoryIdx)));
118
+ const color = DG.Color.toHtml(DG.Color.categoricalPalette[categoryIdx % DG.Color.categoricalPalette.length]);
121
119
  const label = ui.label(labelMap[categories[categoryIdx]] ?? categories[categoryIdx], {style: {color}});
122
120
  labels.push(label);
123
121
  }
@@ -133,10 +131,8 @@ export function getDistributionPanel(hist: DG.Viewer<DG.IHistogramSettings>, sta
133
131
  * @param selection - Selection bitset.
134
132
  * @return - Dataframe with activity distribution.
135
133
  */
136
- export function getDistributionTable(activityCol: DG.Column<number>, selection: DG.BitSet): DG.DataFrame {
137
- if (!activityCol.dataFrame)
138
- DG.DataFrame.fromColumns([activityCol]); // to make sure that activityCol has a parent dataframe
139
- const filter = activityCol.dataFrame!.filter;
134
+ export function getDistributionTable(activityCol: DG.Column<number>, selection: DG.BitSet, dataFrame: DG.DataFrame): DG.DataFrame {
135
+ const filter = dataFrame.filter;
140
136
  const selectionAndFilter = selection.clone().and(filter);
141
137
  const rowCount = activityCol.length;
142
138
  const activityColData = activityCol.getRawData();
@@ -166,6 +162,44 @@ export function getDistributionTable(activityCol: DG.Column<number>, selection:
166
162
  return DG.DataFrame.fromColumns([DG.Column.fromFloat32Array(C.COLUMNS_NAMES.ACTIVITY, activityData), splitCol]);
167
163
  }
168
164
 
165
+ /**
166
+ * Creates distribution table for mutation cliffs.
167
+ * @param activityCol
168
+ * @param mpCliffs
169
+ */
170
+ export function getMutationCliffsDistributionTable(activityCol: DG.Column<number>, mpCliffs: Map<type.INDEX, type.INDEXES>, monomerName: string) {
171
+ // instead of stats.selection vs everything, here we want to show mutated vs non-mutated
172
+ if (!activityCol.dataFrame)
173
+ DG.DataFrame.fromColumns([activityCol]); // to make sure that activityCol has a parent dataframe
174
+ const tableFilter = activityCol.dataFrame!.filter;
175
+ const activityData = activityCol.getRawData();
176
+ const uniqueMPCliffsIndexes = Array.from(new Set(mpCliffs.keys())).map((k) => Number(k)).filter((i) => tableFilter.get(i));
177
+ const uniqueMutatedCliffsIndexesSet = new Set<number>();
178
+ for (const indexes of mpCliffs.values()) {
179
+ for (const index of indexes as number[])
180
+ uniqueMutatedCliffsIndexesSet.add(index);
181
+ }
182
+ const uniqueMutatedCliffsIndexes = Array.from(uniqueMutatedCliffsIndexesSet).filter((i) => tableFilter.get(i));
183
+ const totalRowCount = uniqueMPCliffsIndexes.length + uniqueMutatedCliffsIndexes.length;
184
+ const categories: string[] = new Array(totalRowCount);
185
+ const activityValues = new Float32Array(totalRowCount);
186
+ for (let i = 0; i < uniqueMPCliffsIndexes.length; ++i) {
187
+ const rowIndex = uniqueMPCliffsIndexes[i];
188
+ activityValues[i] = activityData[rowIndex];
189
+ categories[i] = monomerName;
190
+ }
191
+ for (let i = 0; i < uniqueMutatedCliffsIndexes.length; ++i) {
192
+ const rowIndex = uniqueMutatedCliffsIndexes[i];
193
+ activityValues[uniqueMPCliffsIndexes.length + i] = activityData[rowIndex];
194
+ categories[uniqueMPCliffsIndexes.length + i] = 'Mutated';
195
+ }
196
+ const splitCol = DG.Column.fromStrings(C.COLUMNS_NAMES.SPLIT_COL, categories);
197
+ const categoryOrder = [monomerName, 'Mutated'];
198
+ splitCol.setCategoryOrder(categoryOrder);
199
+ splitCol.meta.colors.setCategorical();
200
+ return DG.DataFrame.fromColumns([DG.Column.fromFloat32Array(C.COLUMNS_NAMES.ACTIVITY, activityValues), splitCol]);
201
+ }
202
+
169
203
  /**
170
204
  * Adds expand in full screen icon to the grid.
171
205
  * @param grid - Grid to add expand icon to.
@@ -466,3 +500,13 @@ export function debounce<T extends Array<any>, K>(f: (...args: T) => Promise<K>,
466
500
  export function isInDemo(): boolean {
467
501
  return 'isInDemo' in grok.shell && !!grok.shell.isInDemo;
468
502
  }
503
+
504
+ export function dartLike<T extends any>(obj: T) {
505
+ return {
506
+ set: function<K extends keyof T>(key: K, value: T[K]) {
507
+ (obj as any)[key] = value;
508
+ return this;
509
+ },
510
+ };
511
+ }
512
+
@@ -6,7 +6,7 @@ import * as type from './types';
6
6
  import * as C from '../utils/constants';
7
7
 
8
8
  import {getActivityDistribution, getStatsTableMap} from '../widgets/distribution';
9
- import {getDistributionPanel, getDistributionTable} from './misc';
9
+ import {getDistributionPanel, getDistributionTable, getMutationCliffsDistributionTable} from './misc';
10
10
  import {getAggregatedColumnValues, MonomerPositionStats} from './statistics';
11
11
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
12
12
 
@@ -14,7 +14,7 @@ export type TooltipOptions = {
14
14
  fromViewer?: boolean, isMutationCliffs?: boolean, x: number, y: number, monomerPosition: type.SelectionItem,
15
15
  mpStats: MonomerPositionStats, aggrColValues?: StringDictionary,
16
16
  isMostPotentResidues?: boolean, cliffStats?: type.MutationCliffStats['stats'],
17
- postfixes?: StringDictionary, additionalStats?: StringDictionary
17
+ postfixes?: StringDictionary, additionalStats?: StringDictionary, cliffIndexes?: Map<type.INDEX, type.INDEXES>,
18
18
  };
19
19
 
20
20
  /**
@@ -52,7 +52,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
52
52
  options.isMutationCliffs ??= false;
53
53
  options.isMostPotentResidues ??= false;
54
54
  options.additionalStats ??= {};
55
- if (!options.cliffStats || !options.isMutationCliffs) {
55
+ if (!options.cliffStats || !options.isMutationCliffs || !options.cliffIndexes) { // monomer position stats
56
56
  const stats = options
57
57
  .mpStats[options.monomerPosition.positionOrClusterType]![options.monomerPosition.monomerOrCluster];
58
58
  if (!stats?.count)
@@ -61,7 +61,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
61
61
 
62
62
  const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, activityCol.length);
63
63
  mask.and(df.filter);
64
- const hist = getActivityDistribution(getDistributionTable(activityCol, mask), true);
64
+ const hist = getActivityDistribution(getDistributionTable(activityCol, mask, df), true);
65
65
  const tableMap = getStatsTableMap(stats);
66
66
  if (options.fromViewer) {
67
67
  tableMap['Mean difference'] = `${tableMap['Mean difference']}${options.isMostPotentResidues ? ' (size)' : ''}`;
@@ -79,7 +79,7 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
79
79
  if (!options.fromViewer)
80
80
  setTimeout(() => hist.props.legendVisibility = 'Never', 100); // cause rerendering
81
81
  return distroStatsElem;
82
- } else {
82
+ } else { // mutation cliffs
83
83
  const stats = options.cliffStats?.get(options.monomerPosition.monomerOrCluster)
84
84
  ?.get(options.monomerPosition.positionOrClusterType)
85
85
  ;
@@ -87,12 +87,16 @@ export function showTooltipAt(df: DG.DataFrame, activityCol: DG.Column<number>,
87
87
  return null;
88
88
  const mask = DG.BitSet.fromBytes(stats.mask.buffer.buffer as ArrayBuffer, activityCol.length);
89
89
  mask.and(df.filter);
90
- const hist = getActivityDistribution(getDistributionTable(activityCol, mask), true);
91
- const tableMap = getStatsTableMap(stats, {countName: 'Unique count'});
90
+ const hist = getActivityDistribution(
91
+ getMutationCliffsDistributionTable(activityCol, options.cliffIndexes, options.monomerPosition.monomerOrCluster),
92
+ true);
93
+ const countName = 'MP in cliffs count';
94
+ const tableMap = getStatsTableMap(stats, {countName: countName});
92
95
  if (options.fromViewer) {
93
96
  tableMap['Mean difference'] = `${tableMap['Mean difference']}${' (Color)'}`;
94
- if (tableMap['Unique count'])
95
- tableMap['Unique count'] = `${tableMap['Unique count']}${' (Size)'}`;
97
+ if (tableMap[countName])
98
+ tableMap[countName] = `${tableMap[countName]}${' (Size)'}`;
99
+ options.additionalStats!['Unique count'] = mask.trueCount.toString();
96
100
  }
97
101
  const aggregatedColMap = options.aggrColValues ?? getAggregatedColumnValues(df, columns, {mask: mask});
98
102
  const resultMap = {...options.additionalStats, ...tableMap, ...aggregatedColMap};
@@ -8,8 +8,8 @@ import {getGPUAdapterDescription} from '@datagrok-libraries/math/src/webGPU/getG
8
8
  export type RawData = Int32Array | Uint32Array | Float32Array | Float64Array;
9
9
  type MONOMER = string;
10
10
  type POSITION = string;
11
- type INDEX = number;
12
- type INDEXES = number[] | UTypedArray;
11
+ export type INDEX = number;
12
+ export type INDEXES = number[] | UTypedArray;
13
13
  export type UTypedArray = Uint8Array | Uint16Array | Uint32Array;
14
14
  //Monomer: (Position: (index: indexList))
15
15
  export type MutationCliffs = Map<MONOMER, Map<POSITION, Map<INDEX, INDEXES>>>;
@@ -76,7 +76,6 @@ export interface ILogoSummaryTable {
76
76
 
77
77
  /** LogoSummaryTable viewer shows per cluster information. */
78
78
  export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
79
- _titleHost = ui.divText(VIEWER_TYPE.LOGO_SUMMARY_TABLE, {id: 'pep-viewer-title'});
80
79
  sequenceColumnName: string;
81
80
  clustersColumnName: string;
82
81
  webLogoMode: string;
@@ -98,6 +97,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
98
97
  /** Creates LogoSummaryTable properties. */
99
98
  constructor() {
100
99
  super();
100
+ this.root.classList.add('peptides-viewer-show-title');
101
101
 
102
102
  this.sequenceColumnName = this.column(LST_PROPERTIES.SEQUENCE,
103
103
  {
@@ -365,7 +365,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
365
365
  if (!this.logoSummaryTable.filter.anyTrue) {
366
366
  const emptyDf = ui.divText('No clusters to satisfy the threshold. ' +
367
367
  'Please, lower the threshold in viewer proeperties to include clusters');
368
- this.root.appendChild(ui.divV([this._titleHost, emptyDf]));
368
+ this.root.appendChild(ui.divV([emptyDf]));
369
369
  return;
370
370
  }
371
371
  const expand = ui.iconFA('expand-alt', () => {
@@ -380,7 +380,7 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
380
380
  this.viewerGrid.root.style.height = 'auto';
381
381
  this.viewerGrid.root.style.overflow = 'hidden';
382
382
  this.root.appendChild(ui.divV([
383
- ui.divH([this._titleHost, expand], {
383
+ ui.divH([expand], {
384
384
  style: {
385
385
  alignSelf: 'center',
386
386
  lineHeight: 'normal',
@@ -761,7 +761,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
761
761
  } else if (gridCell.tableColumn?.name === C.LST_COLUMN_NAMES.DISTRIBUTION) {
762
762
  let viewer = distCache.get(currentRowIdx);
763
763
  if (viewer === undefined) {
764
- const distributionDf = getDistributionTable(activityCol, clusterBitSet);
764
+ if (!activityCol.dataFrame)
765
+ DG.DataFrame.fromColumns([activityCol]);
766
+ const distributionDf = getDistributionTable(activityCol, clusterBitSet, activityCol.dataFrame);
765
767
  viewer = distributionDf.plot.histogram({
766
768
  filteringEnabled: false,
767
769
  valueColumnName: activityCol.name,
@@ -1070,7 +1072,9 @@ export class LogoSummaryTable extends DG.JsViewer implements ILogoSummaryTable {
1070
1072
 
1071
1073
 
1072
1074
  const mask = DG.BitSet.fromBytes(bitArray.buffer.buffer as ArrayBuffer, rowCount);
1073
- const distributionTable = getDistributionTable(activityCol, mask);
1075
+ if (!activityCol.dataFrame)
1076
+ DG.DataFrame.fromColumns([activityCol]);
1077
+ const distributionTable = getDistributionTable(activityCol, mask, activityCol.dataFrame);
1074
1078
  const hist = getActivityDistribution(distributionTable, true);
1075
1079
  const tableMap = getStatsTableMap(stats);
1076
1080
  const aggregatedColMap = getAggregatedColumnValues(this.dataFrame,
@@ -30,6 +30,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
30
30
  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'});
31
31
  this.yAxisType = this.string('yAxisType', 'Linear', {choices: ['Linear', 'Logarithmic'], description: 'Y-Axis scale type.', nullable: false, category: 'Data'}) as 'Linear' | 'Logarithmic';
32
32
  this.currentRowMutationsOnly = this.bool('currentRowMutationsOnly', false, {nullable: false, defaultValue: false, description: 'When enabled, the viewer will show mutations related to the peptide in current row in the dataframe and selected position.', category: 'Data'});
33
+ this.root.classList.add('peptides-viewer-show-title');
33
34
  }
34
35
 
35
36
  onTableAttached(): void {
@@ -320,8 +321,7 @@ export class MutationCliffsViewer extends DG.JsViewer {
320
321
  this.root.appendChild(noDataDiv('No mutations cliffs found for the current peptide at the selected position.'));
321
322
  else
322
323
  this.root.appendChild(noDataDiv('Please select a row in the main table to see mutation cliffs for the corresponding peptide at the selected position.'));
323
- }
324
- else
324
+ } else
325
325
  this.root.appendChild(noDataDiv('No mutation cliffs found for the selected position.'));
326
326
  } else {
327
327
  this._lineChart = df.plot.line({
@@ -427,4 +427,4 @@ function noDataDiv(message: string): HTMLDivElement {
427
427
  noDataDiv.style.marginTop = '10px';
428
428
  noDataDiv.style.textAlign = 'center';
429
429
  return noDataDiv;
430
- }
430
+ }
@@ -23,6 +23,7 @@ import {
23
23
  import {showTooltip} from '../utils/tooltips';
24
24
  import {calculateCliffsStatistics, calculateMonomerPositionStatistics, findMutations, MutationCliffsOptions} from '../utils/algorithms';
25
25
  import {
26
+ dartLike,
26
27
  debounce,
27
28
  extractColInfo,
28
29
  getTotalAggColumns,
@@ -41,7 +42,6 @@ import {getMonomerLibHelper} from '@datagrok-libraries/bio/src/types/monomer-lib
41
42
  import {PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
42
43
  import {PeptideUtils} from '../peptideUtils';
43
44
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
44
- import BitArray from '@datagrok-libraries/utils/src/bit-array';
45
45
 
46
46
  export enum SELECTION_MODE {
47
47
  MUTATION_CLIFFS = 'Mutation Cliffs',
@@ -131,7 +131,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
131
131
  }) as 'All' | 'Filtered';
132
132
 
133
133
  this.activityColumnName = this.column(SAR_PROPERTIES.ACTIVITY,
134
- {category: PROPERTY_CATEGORIES.GENERAL, nullable: false});
134
+ {category: PROPERTY_CATEGORIES.GENERAL, nullable: false, columnTypeFilter: 'numerical'});
135
135
  this.activityScaling = this.string(SAR_PROPERTIES.ACTIVITY_SCALING, C.SCALING_METHODS.NONE,
136
136
  {category: PROPERTY_CATEGORIES.GENERAL, choices: Object.values(C.SCALING_METHODS), nullable: false},
137
137
  ) as C.SCALING_METHODS;
@@ -177,6 +177,7 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
177
177
  // this.targetCategoryInput.root.style.display = 'none';
178
178
  // this.targetCategoryInput.root.style.maxWidth = '50%';
179
179
  // this.targetCategoryInput.root.style.marginLeft = '8px'
180
+ this.root.classList.add('peptides-viewer-show-title');
180
181
  }
181
182
 
182
183
  _viewerGrid: DG.Grid | null = null;
@@ -555,10 +556,11 @@ export abstract class SARViewer extends DG.JsViewer implements ISARViewer {
555
556
  if (isApplicableDataframe(this.dataFrame)) {
556
557
  this.getProperty(`${SAR_PROPERTIES.SEQUENCE}${COLUMN_NAME}`)
557
558
  ?.set(this, this.dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE)!.name);
559
+ const potentialActivityColumn = wu(this.dataFrame.columns.numerical).find((col) => col.name.toLowerCase().includes('activity'))?.name;
558
560
  this.getProperty(`${SAR_PROPERTIES.ACTIVITY}${COLUMN_NAME}`)
559
- ?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
561
+ ?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
560
562
  this.getProperty(`${SAR_PROPERTIES.VALUE_INVARIANT_MAP}${COLUMN_NAME}`)
561
- ?.set(this, wu(this.dataFrame.columns.numerical).next().value.name);
563
+ ?.set(this, potentialActivityColumn ?? wu(this.dataFrame.columns.numerical).next().value.name);
562
564
  if (this.mutationCliffs === null && this.sequenceColumnName && this.activityColumnName)
563
565
  this.calculateMutationCliffs().then((mc) => {this.mutationCliffs = mc.cliffs; this.cliffStats = mc.cliffStats;});
564
566
  this.subs.push(grok.events.onContextMenu.subscribe((a: DG.EventData) => {
@@ -677,7 +679,7 @@ export class MonomerPosition extends SARViewer {
677
679
  this.customColorRange = this.bool(MONOMER_POSITION_PROPERTIES.CUSTOM_COLOR_RANGE, false, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
678
680
  this.minColorValue = this.float(MONOMER_POSITION_PROPERTIES.MIN_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
679
681
  this.maxColorValue = this.float(MONOMER_POSITION_PROPERTIES.MAX_COLOR_VALUE, 0, {category: PROPERTY_CATEGORIES.INVARIANT_MAP});
680
- this.showFilterControls = this.bool(MONOMER_POSITION_PROPERTIES.SHOW_FILTER_CONTROLS, true, {category: PROPERTY_CATEGORIES.GENERAL, description: 'Show monomer search and target controls'});
682
+ this.showFilterControls = this.bool(MONOMER_POSITION_PROPERTIES.SHOW_FILTER_CONTROLS, true, {category: PROPERTY_CATEGORIES.GENERAL, description: 'Show monomer search and target controls', userEditable: false}); // Old stuff. Not used anymore
681
683
  this.monomerSearchInput = ui.input.string('Search', {
682
684
  value: '', nullable: true, placeholder: 'Search monomer', tooltipText: 'Search for monomer by symbol. For multiple monomers use comma as a separator.',
683
685
  onValueChanged: () => {
@@ -831,7 +833,7 @@ export class MonomerPosition extends SARViewer {
831
833
  let maxColorVal = -9999999;
832
834
  const filter = (this.dataSource === 'Filtered' && this.dataFrame.filter.anyFalse) ?
833
835
  this.dataFrame.filter : null;
834
- const isTarget = filter == null ? (index: number) => true : (index: number) => filter.get(index);
836
+ const isTarget = filter == null ? (_index: number) => true : (index: number) => filter.get(index);
835
837
  for (const pCol of this.positionColumns) {
836
838
  pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE] = {};
837
839
  const colorCache = pCol.temp[C.TAGS.INVARIANT_MAP_COLOR_CACHE];
@@ -934,7 +936,7 @@ export class MonomerPosition extends SARViewer {
934
936
  if (this.valueColumnName && this.valueAggregation && this.valueAggregation !== DG.AGG.VALUE_COUNT && this.valueAggregation !== DG.AGG.TOTAL_COUNT)
935
937
  columnEntries.unshift([this.valueColumnName, this.valueAggregation as DG.AGG]);
936
938
  } else {
937
- // in invariant map, show pairs count along with unique sequences count
939
+ // in mutation cliffs, show pairs count along with unique sequences count
938
940
  const pairs = this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType);
939
941
  if (pairs) {
940
942
  let pairsCount = 0;
@@ -947,6 +949,7 @@ export class MonomerPosition extends SARViewer {
947
949
  fromViewer: true,
948
950
  isMutationCliffs: this.mode === SELECTION_MODE.MUTATION_CLIFFS, monomerPosition, x, y,
949
951
  mpStats: this.monomerPositionStats, cliffStats: this.cliffStats?.stats ?? undefined, postfixes, additionalStats,
952
+ cliffIndexes: this.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType),
950
953
  });
951
954
  });
952
955
  grid.root.addEventListener('mouseleave', (_ev) => this.model.unhighlight());
@@ -1102,6 +1105,8 @@ export class MonomerPosition extends SARViewer {
1102
1105
  };
1103
1106
  }
1104
1107
 
1108
+ private _showSearchInput = false;
1109
+
1105
1110
  /** Renders the MonomerPosition viewer body. */
1106
1111
  render(): void {
1107
1112
  $(this.root).empty();
@@ -1148,20 +1153,20 @@ export class MonomerPosition extends SARViewer {
1148
1153
  }
1149
1154
  const viewerRoot = this.viewerGrid.root;
1150
1155
  viewerRoot.style.width = 'auto';
1151
- // expand button
1152
- const expand = ui.iconFA('expand-alt', () => {
1153
- const dialog = ui.dialog();
1154
- dialog.add(ui.divV([switchHost, viewerRoot], {style: {height: '100%'}}));
1155
- dialog.onCancel(() => this.render());
1156
- dialog.showModal(true);
1157
- this.viewerGrid.invalidate();
1158
- }, 'Show Sequence Variability Map Table in full screen');
1159
- $(expand).addClass('pep-help-icon');
1160
- this.monomerSearchInput.root.style.marginRight = '8px';
1161
- const targetInputsHost = ui.divH([this.monomerSearchInput.input],
1156
+ // search icon
1157
+ const searchIcon = ui.icons.search(() => {
1158
+ this._showSearchInput = !this._showSearchInput;
1159
+ this.monomerSearchInput.input.style.display = this._showSearchInput ? 'block' : 'none';
1160
+ }, 'Toggle monomer search input visibility');
1161
+
1162
+ this.monomerSearchInput.input.style.display = this._showSearchInput ? 'block' : 'none';
1163
+ $(searchIcon).addClass('pep-help-icon');
1164
+ dartLike(searchIcon.style).set('top', '3px').set('fontSize', '14px');
1165
+
1166
+ const filtersHost = ui.divH([this.monomerSearchInput.input], // plural because might expand in future
1162
1167
  {style: {alignSelf: 'center', justifyContent: 'center', width: '100%', flexWrap: 'wrap'}});
1163
- targetInputsHost.style.display = this.showFilterControls ? 'flex' : 'none';
1164
- const header = ui.divH([expand, switchHost, targetInputsHost], {style: {alignSelf: 'center', lineHeight: 'normal', flexDirection: 'column', width: '100%'}});
1168
+ // targetInputsHost.style.display = this.showFilterControls ? 'flex' : 'none';
1169
+ const header = ui.divH([searchIcon, switchHost, filtersHost], {style: {alignSelf: 'center', lineHeight: 'normal', flexDirection: 'column', width: '100%'}});
1165
1170
  this.root.appendChild(ui.divV([header, viewerRoot]));
1166
1171
  this.viewerGrid?.invalidate();
1167
1172
  this.monomerSearchInput.fireChanged();
@@ -1468,11 +1473,11 @@ export class MostPotentResidues extends SARViewer {
1468
1473
  this.root.appendChild(ui.divText('Please, select a sequence and activity columns in the viewer properties'));
1469
1474
  return;
1470
1475
  }
1471
- const switchHost = ui.divText(VIEWER_TYPE.MOST_POTENT_RESIDUES, {id: 'pep-viewer-title'});
1476
+
1472
1477
  const viewerRoot = this.viewerGrid.root;
1473
1478
  viewerRoot.style.width = 'auto';
1474
- const header = ui.divH([switchHost], {style: {alignSelf: 'center', lineHeight: 'normal'}});
1475
- this.root.appendChild(ui.divV([header, viewerRoot]));
1479
+
1480
+ this.root.appendChild(ui.divV([viewerRoot]));
1476
1481
  this.viewerGrid?.invalidate();
1477
1482
  }
1478
1483
  }
@@ -144,7 +144,7 @@ export function getStatsTableMap(stats: StatsItem,
144
144
  */
145
145
  function getSingleDistribution(table: DG.DataFrame, stats: StatsItem, options: DistributionItemOptions,
146
146
  labelMap: DistributionLabelMap = {}): HTMLDivElement {
147
- const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection));
147
+ const hist = getActivityDistribution(getDistributionTable(options.activityCol, table.selection, table));
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);
@@ -3,12 +3,14 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
  import * as C from '../utils/constants';
5
5
  import * as type from '../utils/types';
6
- import {addExpandIconGen, getSeparator, setGridProps} from '../utils/misc';
6
+ import {addExpandIconGen, getDistributionPanel, getMutationCliffsDistributionTable, getSeparator, setGridProps} from '../utils/misc';
7
7
  import {renderCellSelection} from '../utils/cell-renderer';
8
8
  import {SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
9
+ import {getActivityDistribution, getStatsTableMap} from './distribution';
10
+ import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
9
11
 
10
- export type MutationCliffsOptions = {
11
- mutationCliffs: type.MutationCliffs, mutationCliffsSelection: type.Selection, sequenceColumnName: string,
12
+ export type MutationCliffsWidgetOptions = {
13
+ mutationCliffs: type.MutationCliffs, mutationCliffStats: type.MutationCliffStats | null, mutationCliffsSelection: type.Selection, sequenceColumnName: string,
12
14
  positionColumns: DG.Column<string>[], gridColumns: DG.GridColumnList, activityCol: DG.Column<number>,
13
15
  };
14
16
 
@@ -21,7 +23,7 @@ export type MutationCliffsOptions = {
21
23
  * @return - mutation cliffs widget.
22
24
  */
23
25
  export function mutationCliffsWidget(
24
- table: DG.DataFrame, options: MutationCliffsOptions, allowExpand = true,
26
+ table: DG.DataFrame, options: MutationCliffsWidgetOptions, allowExpand = true,
25
27
  ): DG.Widget {
26
28
  //addExpandIcon(pairsGrid);
27
29
  //addExpandIcon(uniqueSequencesGrid);
@@ -32,6 +34,37 @@ export function mutationCliffsWidget(
32
34
  const comboGrids = [pairsGrid, uniqueSequencesGrid];
33
35
  const widgetRoot = ui.divV([aminoToInput.root, ...comboGrids.map((grid) => grid.root)],
34
36
  {style: {width: '100%'}});
37
+ // add mutation cliffs distribution histogram
38
+ const isSingleMutationPosition = Object.values(options.mutationCliffsSelection).filter((monomers) => monomers.length > 0).length === 1;
39
+ if (isSingleMutationPosition) {
40
+ const position = Object.keys(options.mutationCliffsSelection).find((pos) => options.mutationCliffsSelection[pos].length > 0)!;
41
+ const monomers = options.mutationCliffsSelection[position];
42
+ if (monomers.length === 1) {
43
+ const monomerName = monomers[0];
44
+ const cliffs = options.mutationCliffs?.get(monomerName)?.get(position);
45
+ if (cliffs && cliffs.size > 0) {
46
+ const distributionTable = getMutationCliffsDistributionTable(options.activityCol, cliffs, monomerName);
47
+ const hist = getActivityDistribution(distributionTable, false);
48
+ // quick stats
49
+ const stats = options.mutationCliffStats?.stats?.get(monomerName)?.get(position);
50
+ const tableMap = {} as StringDictionary;
51
+ let pairsCount = 0;
52
+ for (const indexArray of cliffs.values())
53
+ pairsCount += indexArray.length;
54
+ tableMap['Pairs count'] = pairsCount.toString();
55
+ if (stats) {
56
+ tableMap['Unique Count'] = stats.mask.trueCount().toString();
57
+ Object.assign(tableMap, getStatsTableMap(stats, {countName: 'MP in cliffs count'}));
58
+ }
59
+ const distributionPanel = getDistributionPanel(hist, tableMap); // no need to show stats here
60
+ hist.root.style.maxHeight = '100px';
61
+ distributionPanel.style.marginTop = '10px';
62
+ widgetRoot.appendChild(distributionPanel);
63
+ }
64
+ }
65
+ }
66
+
67
+
35
68
  if (allowExpand) {
36
69
  addExpandIconGen('Mutation Cliffs pairs', aminoToInput.root, widgetRoot,
37
70
  () => {
@@ -74,7 +107,7 @@ export function mutationCliffsWidget(
74
107
  }
75
108
 
76
109
 
77
- function cliffsPairsWidgetParts(table: DG.DataFrame, options: MutationCliffsOptions):
110
+ function cliffsPairsWidgetParts(table: DG.DataFrame, options: MutationCliffsWidgetOptions):
78
111
  {pairsGrid: DG.Grid, uniqueSequencesGrid: DG.Grid, aminoToInput: DG.InputBase<string>} | null {
79
112
  const filteredIndexes = table.filter.getSelectedIndexes();
80
113
  const positions = Object.keys(options.mutationCliffsSelection);