@datagrok/peptides 1.9.2 → 1.11.3

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.
@@ -7,7 +7,7 @@ import $ from 'cash-dom';
7
7
  import * as C from '../utils/constants';
8
8
  import {getStats, Stats} from '../utils/statistics';
9
9
  import {PeptidesModel} from '../model';
10
- import {getStatsSummary} from '../utils/misc';
10
+ import {getStatsSummary, prepareTableForHistogram} from '../utils/misc';
11
11
  import BitArray from '@datagrok-libraries/utils/src/bit-array';
12
12
 
13
13
  const allConst = 'All';
@@ -17,7 +17,7 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
17
17
  const activityCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
18
18
  const activityColData = activityCol.getRawData();
19
19
  const rowCount = activityCol.length;
20
- const selectionObject = model.monomerPositionSelection;
20
+ const selectionObject = model.mutationCliffsSelection;
21
21
  const clustersColName = model.settings.clustersColumnName;
22
22
  let clustersProcessedObject: string[] = [];
23
23
  if (clustersColName)
@@ -49,12 +49,12 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
49
49
  const mask = DG.BitSet.create(rowCount, (i) => posColData[i] === aarCategoryIndex);
50
50
  const distributionTable = DG.DataFrame.fromColumns(
51
51
  [activityCol, DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask)]);
52
- const hist = getActivityDistribution(distributionTable);
52
+ const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
53
53
 
54
54
  const stats = model.monomerPositionStats[position][aar];
55
- const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
55
+ const tableMap = getStatsTableMap(stats);
56
56
 
57
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask, fractionDigits: 2});
57
+ const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
58
58
 
59
59
  const resultMap = {...tableMap, ...aggregatedColMap};
60
60
  const distributionRoot = getStatsSummary(labels, hist, resultMap);
@@ -80,14 +80,14 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
80
80
  const mask = DG.BitSet.create(rowCount, (i) => aarIndexesList.includes(posColData[i]));
81
81
  const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
82
82
 
83
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask, fractionDigits: 2});
83
+ const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
84
84
 
85
85
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
86
- const hist = getActivityDistribution(distributionTable);
86
+ const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
87
87
 
88
88
  const bitArray = BitArray.fromUint32Array(rowCount, splitCol.getRawData() as Uint32Array);
89
89
  const stats = getStats(activityColData, bitArray);
90
- const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
90
+ const tableMap = getStatsTableMap(stats);
91
91
 
92
92
  const resultMap = {...tableMap, ...aggregatedColMap};
93
93
  const distributionRoot = getStatsSummary(labels, hist, resultMap);
@@ -123,15 +123,15 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
123
123
 
124
124
  const mask = DG.BitSet.create(rowCount,
125
125
  (i) => posColDataList.some((posColData, j) => posColData[i] === aarCategoryIndexList[j]));
126
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask, fractionDigits: 2});
126
+ const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
127
127
 
128
128
  const splitCol = DG.Column.fromBitSet(C.COLUMNS_NAMES.SPLIT_COL, mask);
129
129
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
130
- const hist = getActivityDistribution(distributionTable);
130
+ const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
131
131
 
132
132
  const bitArray = BitArray.fromUint32Array(rowCount, splitCol.getRawData() as Uint32Array);
133
133
  const stats = getStats(activityColData, bitArray);
134
- const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
134
+ const tableMap = getStatsTableMap(stats);
135
135
 
136
136
  const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
137
137
  const distributionRoot = getStatsSummary(labels, hist, resultMap);
@@ -160,14 +160,15 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
160
160
  const labels = getDistributionLegend(aarStr, otherStr);
161
161
 
162
162
  const distributionTable = DG.DataFrame.fromColumns([activityCol, splitCol]);
163
- const hist = getActivityDistribution(distributionTable);
163
+
164
+ const hist = getActivityDistribution(prepareTableForHistogram(distributionTable));
164
165
 
165
166
  const bitArray = BitArray.fromUint32Array(rowCount, splitCol.getRawData() as Uint32Array);
166
167
  const mask = DG.BitSet.create(rowCount, (i) => bitArray.getBit(i));
167
- const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask, fractionDigits: 2});
168
+ const aggregatedColMap = model.getAggregatedColumnValues({filterDf: true, mask});
168
169
 
169
170
  const stats = getStats(activityColData, bitArray);
170
- const tableMap = getStatsTableMap(stats, {fractionDigits: 2});
171
+ const tableMap = getStatsTableMap(stats);
171
172
 
172
173
  const resultMap: {[key: string]: any} = {...tableMap, ...aggregatedColMap};
173
174
  const distributionRoot = getStatsSummary(labels, hist, resultMap);
@@ -225,10 +226,9 @@ export function getActivityDistribution(table: DG.DataFrame, isTooltip: boolean
225
226
  }
226
227
 
227
228
  export function getStatsTableMap(stats: Stats, options: {fractionDigits?: number} = {}): StringDictionary {
229
+ options.fractionDigits ??= 3;
228
230
  const tableMap = {
229
- 'Statistics:': '',
230
- 'Count': stats.count.toString(),
231
- 'Ratio': stats.ratio.toFixed(options.fractionDigits),
231
+ 'Count': `${stats.count} (${stats.ratio.toFixed(options.fractionDigits)}%)`,
232
232
  'p-value': stats.pValue < 0.01 ? '<0.01' : stats.pValue.toFixed(options.fractionDigits),
233
233
  'Mean difference': stats.meanDifference.toFixed(options.fractionDigits),
234
234
  };
@@ -4,11 +4,12 @@ import * as C from '../utils/constants';
4
4
  import * as type from '../utils/types';
5
5
  import {PeptidesModel} from '../model';
6
6
  import {getSeparator} from '../utils/misc';
7
+ import {renderCellSelection} from '../utils/cell-renderer';
7
8
 
8
9
  export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
9
10
  const filteredIndexes = table.filter.getSelectedIndexes();
10
11
  const substInfo = model.mutationCliffs;
11
- const currentCell = model.monomerPositionSelection;
12
+ const currentCell = model.mutationCliffsSelection;
12
13
  const positions = Object.keys(currentCell);
13
14
 
14
15
  if (!positions.length || substInfo === null)
@@ -17,12 +18,15 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
17
18
  const substitutionsArray: string[] = [];
18
19
  const deltaArray: number[] = [];
19
20
  const substitutedToArray: string[] = [];
21
+ const fromIdxArray: number[] = [];
22
+ const toIdxArray: number[] = [];
20
23
  const alignedSeqCol = table.getCol(model.settings.sequenceColumnName!);
21
24
  const alignedSeqColCategories = alignedSeqCol.categories;
22
25
  const alignedSeqColData = alignedSeqCol.getRawData();
23
26
  const activityScaledCol = table.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED);
24
27
  const activityScaledColData = activityScaledCol.getRawData();
25
28
  const seenIndexes = new Map<number, number[]>();
29
+ const uniqueSequencesBitSet = DG.BitSet.create(table.rowCount);
26
30
 
27
31
  for (const pos of positions) {
28
32
  const posCol = table.getCol(pos);
@@ -39,9 +43,7 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
39
43
  continue;
40
44
 
41
45
  const forbiddentIndexes = seenIndexes.get(referenceIdx) ?? [];
42
- // const baseSequence = alignedSeqCol.get(referenceIdx);
43
46
  const baseSequence = alignedSeqColCategories[alignedSeqColData[referenceIdx]];
44
- // const baseActivity = activityScaledCol.get(referenceIdx);
45
47
  const baseActivity = activityScaledColData[referenceIdx];
46
48
 
47
49
  for (const subIdx of indexArray) {
@@ -56,6 +58,10 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
56
58
  substitutionsArray.push(`${baseSequence}#${subSeq}`);
57
59
  deltaArray.push(baseActivity - activityScaledColData[subIdx]);
58
60
  substitutedToArray.push(posColCategories[posColData[subIdx]]);
61
+ fromIdxArray.push(referenceIdx);
62
+ toIdxArray.push(subIdx);
63
+ uniqueSequencesBitSet.set(referenceIdx, true);
64
+ uniqueSequencesBitSet.set(subIdx, true);
59
65
  }
60
66
  }
61
67
  }
@@ -65,27 +71,93 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
65
71
  return new DG.Widget(ui.label('No mutations table generated'));
66
72
 
67
73
  const substCol = DG.Column.fromStrings('Mutation', substitutionsArray);
68
- substCol.semType = C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;
69
- substCol.tags[C.TAGS.SEPARATOR] = getSeparator(alignedSeqCol);
70
- substCol.tags[DG.TAGS.UNITS] = alignedSeqCol.tags[DG.TAGS.UNITS];
71
- substCol.tags[DG.TAGS.CELL_RENDERER] = 'MacromoleculeDifference';
72
- const toColName = '~to';
73
- const hiddenSubstToAarCol = DG.Column.fromStrings(toColName, substitutedToArray);
74
- const substTable =
75
- DG.DataFrame.fromColumns([substCol, DG.Column.fromList('double', 'Delta', deltaArray), hiddenSubstToAarCol]);
74
+ const activityDeltaCol = DG.Column.fromList('double', 'Delta', deltaArray);
75
+ const hiddenSubstToAarCol = DG.Column.fromStrings('~to', substitutedToArray);
76
+ const toIdxCol = DG.Column.fromList(DG.COLUMN_TYPE.INT, '~toIdx', toIdxArray);
77
+ const fromIdxCol = DG.Column.fromList(DG.COLUMN_TYPE.INT, '~fromIdx', fromIdxArray);
78
+ const pairsTable = DG.DataFrame.fromColumns([substCol, activityDeltaCol, hiddenSubstToAarCol, toIdxCol, fromIdxCol]);
76
79
 
77
80
  const aminoToInput = ui.stringInput('Mutated to:', '', () => {
78
81
  const substitutedToAar = aminoToInput.stringValue;
79
82
  if (substitutedToAar !== '')
80
- substTable.filter.init((idx) => hiddenSubstToAarCol.get(idx) === substitutedToAar);
83
+ pairsTable.filter.init((idx) => hiddenSubstToAarCol.get(idx) === substitutedToAar);
81
84
  else
82
- substTable.filter.setAll(true);
85
+ pairsTable.filter.setAll(true);
86
+ });
87
+ aminoToInput.setTooltip('Monomer to which the mutation was made');
88
+
89
+ const pairsGrid = pairsTable.plot.grid();
90
+ pairsGrid.props.allowEdit = false;
91
+ pairsGrid.props.allowRowSelection = false;
92
+ pairsGrid.props.allowBlockSelection = false;
93
+ pairsGrid.props.allowColSelection = false;
94
+ pairsGrid.root.style.width = '100%';
95
+ pairsGrid.root.style.height = '150px';
96
+ substCol.semType = C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;
97
+ substCol.tags[C.TAGS.SEPARATOR] = getSeparator(alignedSeqCol);
98
+ substCol.tags[DG.TAGS.UNITS] = alignedSeqCol.tags[DG.TAGS.UNITS];
99
+ substCol.tags[DG.TAGS.CELL_RENDERER] = 'MacromoleculeDifference';
100
+
101
+ const pairsSelectedIndexes: number[] = [];
102
+ pairsGrid.root.addEventListener('click', (event) => {
103
+ const gridCell = pairsGrid.hitTest(event.offsetX, event.offsetY);
104
+ if (gridCell === null || gridCell.tableRowIndex === null)
105
+ return;
106
+
107
+ const rowIdx = gridCell.tableRowIndex;
108
+ if (!event.shiftKey) {
109
+ pairsSelectedIndexes.length = 0;
110
+ pairsSelectedIndexes.push(rowIdx);
111
+ } else {
112
+ const rowIdxIdx = pairsSelectedIndexes.indexOf(rowIdx);
113
+ if (rowIdxIdx === -1)
114
+ pairsSelectedIndexes.push(rowIdx);
115
+ else
116
+ pairsSelectedIndexes.splice(rowIdxIdx, 1);
117
+ }
118
+ uniqueSequencesTable.filter.fireChanged();
119
+ gridCell.cell.dataFrame.currentRowIdx = -1;
120
+ pairsGrid.invalidate();
121
+ });
122
+ pairsGrid.onCellRender.subscribe((gcArgs) => {
123
+ if (gcArgs.cell.tableColumn?.name !== substCol.name || !pairsSelectedIndexes.includes(gcArgs.cell.tableRowIndex!))
124
+ return;
125
+
126
+ renderCellSelection(gcArgs.g, gcArgs.bounds);
127
+ });
128
+
129
+ const gridCols = model.analysisView.grid.columns;
130
+ const originalGridColCount = gridCols.length;
131
+ const positionColumns = model.splitSeqDf.columns;
132
+ const columnNames: string[] = [];
133
+ for (let colIdx = 1; colIdx < originalGridColCount; colIdx++) {
134
+ const gridCol = gridCols.byIndex(colIdx);
135
+ if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.contains(gridCol.name)))
136
+ columnNames.push(gridCol!.name);
137
+ }
138
+
139
+ const uniqueSequencesTable = table.clone(uniqueSequencesBitSet, columnNames);
140
+ const seqIdxCol = uniqueSequencesTable.columns.addNewInt('~seqIdx');
141
+ const seqIdxColData = seqIdxCol.getRawData();
142
+ const selectedIndexes = uniqueSequencesBitSet.getSelectedIndexes();
143
+ seqIdxCol.init((idx) => selectedIndexes[idx]);
144
+ const uniqueSequencesGrid = uniqueSequencesTable.plot.grid();
145
+ uniqueSequencesGrid.props.allowEdit = false;
146
+ uniqueSequencesGrid.props.allowRowSelection = false;
147
+ uniqueSequencesGrid.props.allowBlockSelection = false;
148
+ uniqueSequencesGrid.props.allowColSelection = false;
149
+ uniqueSequencesGrid.props.rowHeight = 20;
150
+ uniqueSequencesGrid.root.style.width = '100%';
151
+ uniqueSequencesGrid.root.style.height = '250px';
152
+ uniqueSequencesTable.filter.onChanged.subscribe(() => {
153
+ const uniqueSelectedIndexes: number[] = [];
154
+ for (const idx of pairsSelectedIndexes) {
155
+ uniqueSelectedIndexes.push(fromIdxCol.get(idx)!);
156
+ uniqueSelectedIndexes.push(toIdxCol.get(idx)!);
157
+ }
158
+ uniqueSequencesTable.filter.init(
159
+ (idx) => pairsSelectedIndexes.length === 0 || uniqueSelectedIndexes.includes(seqIdxColData[idx]), false);
83
160
  });
84
161
 
85
- const grid = substTable.plot.grid();
86
- grid.props.allowEdit = false;
87
- const gridRoot = grid.root;
88
- gridRoot.style.width = 'auto';
89
- gridRoot.style.height = '150px';
90
- return new DG.Widget(ui.divV([aminoToInput.root, gridRoot]));
162
+ return new DG.Widget(ui.divV([aminoToInput.root, pairsGrid.root, uniqueSequencesGrid.root], {style: {width: '100%'}}));
91
163
  }
@@ -13,7 +13,6 @@ import {scaleActivity} from '../utils/misc';
13
13
  import {ALIGNMENT, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
14
14
 
15
15
  /** Peptide analysis widget.
16
- *
17
16
  * @param {DG.DataFrame} df Working table
18
17
  * @param {DG.Column} col Aligned sequence column
19
18
  * @return {Promise<DG.Widget>} Widget containing peptide analysis */
@@ -39,7 +38,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
39
38
  viewer.root.style.setProperty('height', '130px');
40
39
  return viewer.root;
41
40
  }));
42
- });
41
+ //TODO: add when new version of datagrok-api is available
42
+ }, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE});
43
43
  } else if (!(col.getTag(bioTAGS.aligned) === ALIGNMENT.SEQ_MSA) &&
44
44
  col.getTag(DG.TAGS.UNITS) !== NOTATION.HELM) {
45
45
  return {
@@ -95,18 +95,16 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
95
95
  DG.Stats.fromColumn(activityColumnChoice.value!).min > 0;
96
96
  activityScalingMethod.fireChanged();
97
97
  };
98
- const activityColumnChoice = ui.columnInput('Activity', df, defaultActivityColumn, activityScalingMethodState);
98
+ //TODO: add when new version of datagrok-api is available
99
+ const activityColumnChoice = ui.columnInput('Activity', df, defaultActivityColumn, activityScalingMethodState,
100
+ {filter: (col: DG.Column) => col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT});
99
101
  const clustersColumnChoice = ui.columnInput('Clusters', df, null);
100
102
  clustersColumnChoice.nullable = true;
101
103
  activityColumnChoice.fireChanged();
102
104
  activityScalingMethod.fireChanged();
103
105
 
104
- const targetColumnChoice = ui.columnInput('Target', df, null, () => {
105
- if (targetColumnChoice.value?.type !== DG.COLUMN_TYPE.STRING) {
106
- grok.shell.warning('Target column should be of string type');
107
- targetColumnChoice.value = null;
108
- }
109
- });
106
+ const targetColumnChoice = ui.columnInput('Target', df, null, null,
107
+ {filter: (col: DG.Column) => col.type === DG.TYPE.STRING});
110
108
  targetColumnChoice.nullable = true;
111
109
 
112
110
  const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice, targetColumnChoice];
@@ -158,8 +156,7 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
158
156
  targetColumn: DG.Column<string> | null = null): Promise<PeptidesModel | null> {
159
157
  const progress = DG.TaskBarProgressIndicator.create('Loading SAR...');
160
158
  let model = null;
161
- if (activityColumn.type === DG.COLUMN_TYPE.FLOAT || activityColumn.type === DG.COLUMN_TYPE.INT ||
162
- activityColumn.type === DG.COLUMN_TYPE.BIG_INT || activityColumn.type === DG.COLUMN_TYPE.QNUM) {
159
+ if (activityColumn.type === DG.COLUMN_TYPE.FLOAT || activityColumn.type === DG.COLUMN_TYPE.INT) {
163
160
  //prepare new DF
164
161
  const newDf = DG.DataFrame.create(currentDf.rowCount);
165
162
  const newDfCols = newDf.columns;
@@ -200,8 +197,10 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
200
197
  const dfUuid = uuid.v4();
201
198
  newDf.setTag(C.TAGS.UUID, dfUuid);
202
199
  newDf.setTag('monomerType', monomerType);
203
- model = PeptidesModel.getInstance(newDf);
204
- // await model.addViewers();
200
+
201
+ // Cloning dataframe with applied filter. If filter is not applied, cloning is
202
+ // needed anyway to allow filtering on the original dataframe
203
+ model = PeptidesModel.getInstance(newDf.clone(currentDf.filter));
205
204
  if (clustersColumn) await model.addLogoSummaryTable();
206
205
  await model.addMonomerPosition();
207
206
  await model.addMostPotentResidues();
@@ -7,6 +7,7 @@ import {PeptidesModel, VIEWER_TYPE} from '../model';
7
7
 
8
8
  import $ from 'cash-dom';
9
9
  import wu from 'wu';
10
+ import {getTreeHelperInstance} from '../package';
10
11
 
11
12
  type PaneInputs = {[paneName: string]: DG.InputBase[]};
12
13
  type SettingsElements = {dialog: DG.Dialog, accordion: DG.Accordion, inputs: PaneInputs};
@@ -19,6 +20,7 @@ export enum SETTINGS_PANES {
19
20
  };
20
21
 
21
22
  export enum GENERAL_INPUTS {
23
+ ACTIVITY = 'Activity',
22
24
  ACTIVITY_SCALING = 'Activity scaling',
23
25
  BIDIRECTIONAL_ANALYSIS = 'Bidirectional analysis',
24
26
  }
@@ -48,17 +50,27 @@ export const PANES_INPUTS = {
48
50
  export function getSettingsDialog(model: PeptidesModel): SettingsElements {
49
51
  const accordion = ui.accordion();
50
52
  const settings = model.settings;
51
- const result: type.PeptidesSettings = {columns: {}};
53
+ const currentScaling = settings.scaling ?? C.SCALING_METHODS.NONE;
54
+ const currentBidirectional = settings.isBidirectional ?? false;
55
+ const currentMaxMutations = settings.maxMutations ?? 1;
56
+ const currentMinActivityDelta = settings.minActivityDelta ?? 0;
57
+ const currentColumns = settings.columns ?? {};
58
+
59
+ const result: type.PeptidesSettings = {};
52
60
  const inputs: PaneInputs = {};
53
61
 
54
62
  // General pane options
55
- const activityScaling = ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, settings.scaling ?? C.SCALING_METHODS.NONE,
56
- Object.values(C.SCALING_METHODS), () => result.scaling = activityScaling.value! as C.SCALING_METHODS);
57
- const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, settings.isBidirectional ?? false,
58
- () => result.isBidirectional = bidirectionalAnalysis.value!);
59
-
60
- accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityScaling, bidirectionalAnalysis]), true);
61
- inputs[SETTINGS_PANES.GENERAL] = [activityScaling, bidirectionalAnalysis];
63
+ const activityCol = ui.columnInput(GENERAL_INPUTS.ACTIVITY, model.df,
64
+ model.df.getCol(model.settings.activityColumnName!), () => result.activityColumnName = activityCol.value!.name,
65
+ {filter: (col: DG.Column) => (col.type === DG.TYPE.FLOAT || col.type === DG.TYPE.INT) && col.name !== C.COLUMNS_NAMES.ACTIVITY_SCALED});
66
+ const activityScaling =
67
+ ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, currentScaling, Object.values(C.SCALING_METHODS),
68
+ () => result.scaling = activityScaling.value as C.SCALING_METHODS) as DG.InputBase<C.SCALING_METHODS>;
69
+ const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, currentBidirectional,
70
+ () => result.isBidirectional = bidirectionalAnalysis.value) as DG.InputBase<boolean>;
71
+
72
+ accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling, bidirectionalAnalysis]), true);
73
+ inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling, bidirectionalAnalysis];
62
74
 
63
75
  // Viewers pane options
64
76
  /* FIXME: combinations of adding and deleting viewers are not working properly
@@ -75,26 +87,27 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
75
87
  */
76
88
  const isDendrogramEnabled = wu(model.analysisView.viewers).some((v) => v.type === VIEWER_TYPE.DENDROGRAM);
77
89
  const dendrogram = ui.boolInput(VIEWER_TYPE.DENDROGRAM, isDendrogramEnabled ?? false,
78
- () => result.showDendrogram = dendrogram.value!);
90
+ () => result.showDendrogram = dendrogram.value) as DG.InputBase<boolean>;
91
+ dendrogram.enabled = getTreeHelperInstance() !== null;
79
92
 
80
93
  accordion.addPane(SETTINGS_PANES.VIEWERS, () => ui.inputs([dendrogram]), true);
81
94
  inputs[SETTINGS_PANES.VIEWERS] = [dendrogram];
82
95
 
83
96
  // Mutation Cliffs pane options
84
- const maxMutations = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MAX_MUTATIONS, settings.maxMutations ?? 1, 0, 50, () => {
85
- const val = Math.round(maxMutations.value!);
97
+ const maxMutations = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MAX_MUTATIONS, currentMaxMutations, 1, 50, () => {
98
+ const val = Math.round(maxMutations.value);
86
99
  $(maxMutations.root).find('label.ui-input-description').remove();
87
100
  result.maxMutations = val;
88
101
  maxMutations.addPostfix(val.toString());
89
- });
102
+ }) as DG.InputBase<number>;
90
103
  maxMutations.addPostfix((settings.maxMutations ?? 1).toString());
91
- const minActivityDelta = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MIN_ACTIVITY_DELTA, settings.minActivityDelta ?? 0, 0,
104
+ const minActivityDelta = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MIN_ACTIVITY_DELTA, currentMinActivityDelta, 0,
92
105
  100, () => {
93
- const val = minActivityDelta.value!.toFixed(3);
106
+ const val = minActivityDelta.value.toFixed(3);
94
107
  result.minActivityDelta = parseFloat(val);
95
108
  $(minActivityDelta.root).find('label.ui-input-description').remove();
96
109
  minActivityDelta.addPostfix(val);
97
- });
110
+ }) as DG.InputBase<number>;
98
111
  minActivityDelta.addPostfix((settings.minActivityDelta ?? 0).toString());
99
112
  accordion.addPane(SETTINGS_PANES.MUTATION_CLIFFS, () => ui.inputs([maxMutations, minActivityDelta]), true);
100
113
  inputs[SETTINGS_PANES.MUTATION_CLIFFS] = [maxMutations, minActivityDelta];
@@ -107,19 +120,28 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
107
120
  if (colName === settings.activityColumnName || colName === C.COLUMNS_NAMES.ACTIVITY_SCALED)
108
121
  continue;
109
122
 
110
- const isIncludedInput = ui.boolInput(COLUMNS_INPUTS.IS_INCLUDED,
111
- typeof (settings.columns ?? {})[colName] !== 'undefined', () => {
123
+ const isIncludedInput = ui.boolInput(COLUMNS_INPUTS.IS_INCLUDED, typeof (currentColumns)[colName] !== 'undefined',
124
+ () => {
125
+ result.columns ??= {};
112
126
  if (isIncludedInput.value)
113
- result.columns![colName] = aggregationInput.value;
114
- else
115
- delete result.columns![colName];
127
+ result.columns[colName] = aggregationInput.value;
128
+ else {
129
+ delete result.columns[colName];
130
+ if (Object.keys(result.columns).length === Object.keys(currentColumns).length)
131
+ delete result.columns;
132
+ }
116
133
  }) as DG.InputBase<boolean>;
117
- const aggregationInput = ui.choiceInput(COLUMNS_INPUTS.AGGREGATION,
118
- (settings.columns ?? {})[colName] ?? DG.AGG.AVG, Object.values(DG.STATS), () => {
134
+
135
+ const aggregationInput = ui.choiceInput(COLUMNS_INPUTS.AGGREGATION, (currentColumns)[colName] ?? DG.AGG.AVG,
136
+ Object.values(DG.STATS), () => {
137
+ result.columns ??= {};
119
138
  if (isIncludedInput.value)
120
- result.columns![colName] = aggregationInput.value;
121
- else
122
- delete result.columns![col.name];
139
+ result.columns[colName] = aggregationInput.value;
140
+ else {
141
+ delete result.columns[col.name];
142
+ if (Object.keys(result.columns).length === Object.keys(currentColumns).length)
143
+ delete result.columns;
144
+ }
123
145
  }) as DG.InputBase<DG.AggregationType>;
124
146
  $(aggregationInput.root).find('label').css('width', 'auto');
125
147
  const inputsRow = ui.inputsRow(col.name, [isIncludedInput, aggregationInput]);
@@ -0,0 +1,39 @@
1
+
2
+ import {sequenceChemSimilarity} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
3
+ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ export function calculateIdentity(template: ISeqSplitted, splitSeqDf: DG.DataFrame): DG.Column<number> {
7
+ const numPositions = splitSeqDf.columns.length;
8
+ const positionCols: Uint32Array[] = new Array(numPositions);
9
+ const positionEmptyCategories: number[] = new Array(numPositions);
10
+ const categoryIndexesTemplate: number[] = new Array(numPositions);
11
+
12
+ for (let posIdx = 0; posIdx < numPositions; ++posIdx) {
13
+ const posCol = splitSeqDf.columns.byIndex(posIdx);
14
+ positionCols[posIdx] = posCol.getRawData() as Uint32Array;
15
+ positionEmptyCategories[posIdx] = posCol.categories.indexOf('');
16
+ categoryIndexesTemplate[posIdx] = posCol.categories.indexOf(template[posIdx] ?? '');
17
+ }
18
+
19
+ const identityScoresCol = DG.Column.float('Identity', splitSeqDf.rowCount);
20
+ const identityScoresData = identityScoresCol.getRawData();
21
+ for (let rowIndex = 0; rowIndex < splitSeqDf.rowCount; ++rowIndex) {
22
+ identityScoresData[rowIndex] = 0;
23
+ for (let posIdx = 0; posIdx < template.length; ++posIdx) {
24
+ const categoryIndex = positionCols[posIdx][rowIndex];
25
+ if (categoryIndex === categoryIndexesTemplate[posIdx])
26
+ ++identityScoresData[rowIndex];
27
+ }
28
+ identityScoresData[rowIndex] /= template.length;
29
+ }
30
+
31
+ return identityScoresCol;
32
+ }
33
+
34
+
35
+ export async function calculateSimilarity(template: ISeqSplitted, splitSeqDf: DG.DataFrame): Promise<DG.Column<number>> {
36
+ const columns = splitSeqDf.columns.toList() as DG.Column<string>[];
37
+ const scoresCol = await sequenceChemSimilarity(columns, template);
38
+ return scoresCol;
39
+ }