@datagrok/peptides 1.11.3 → 1.12.0

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.11.3",
4
+ "version": "1.12.0",
5
5
  "author": {
6
6
  "name": "Volodymyr Dyma",
7
7
  "email": "vdyma@datagrok.ai"
@@ -13,7 +13,7 @@
13
13
  "directory": "packages/Peptides"
14
14
  },
15
15
  "dependencies": {
16
- "@datagrok-libraries/bio": "^5.36.1",
16
+ "@datagrok-libraries/bio": "^5.37.0",
17
17
  "@datagrok-libraries/ml": "^6.3.39",
18
18
  "@datagrok-libraries/statistics": "^1.2.2",
19
19
  "@datagrok-libraries/utils": "^4.1.4",
package/src/model.ts CHANGED
@@ -7,6 +7,7 @@ import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
7
7
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
8
8
  import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
9
9
  import {pickUpPalette, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
+ import {calculateScores, SCORE} from '@datagrok-libraries/bio/src/utils/macromolecule/scoring';
10
11
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
11
12
  import {DistanceMatrix} from '@datagrok-libraries/ml/src/distance-matrix';
12
13
  import {StringMetricsNames} from '@datagrok-libraries/ml/src/typed-metrics';
@@ -21,20 +22,18 @@ import $ from 'cash-dom';
21
22
 
22
23
  import * as C from './utils/constants';
23
24
  import * as type from './utils/types';
24
- import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary, prepareTableForHistogram, getTemplate} from './utils/misc';
25
+ import {calculateSelected, extractColInfo, scaleActivity, getStatsSummary, prepareTableForHistogram} from './utils/misc';
25
26
  import {MONOMER_POSITION_PROPERTIES, MonomerPosition, MostPotentResidues} from './viewers/sar-viewer';
26
27
  import * as CR from './utils/cell-renderer';
27
28
  import {mutationCliffsWidget} from './widgets/mutation-cliffs';
28
- import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap,
29
- } from './widgets/distribution';
29
+ import {getActivityDistribution, getDistributionLegend, getDistributionWidget, getStatsTableMap} from './widgets/distribution';
30
30
  import {getAggregatedValue, getStats, Stats} from './utils/statistics';
31
31
  import {LogoSummaryTable} from './viewers/logo-summary';
32
32
  import {getSettingsDialog} from './widgets/settings';
33
33
  import {_package, getMonomerWorksInstance, getTreeHelperInstance} from './package';
34
34
  import {findMutations} from './utils/algorithms';
35
35
  import {createDistanceMatrixWorker} from './utils/worker-creator';
36
- import {calculateIdentity, calculateSimilarity} from './widgets/similarity';
37
- import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
36
+ import {getSelectionWidget} from './widgets/selection';
38
37
 
39
38
  export type SummaryStats = {
40
39
  minCount: number, maxCount: number,
@@ -98,7 +97,8 @@ export class PeptidesModel {
98
97
  _distanceMatrix!: DistanceMatrix;
99
98
  _dm!: DistanceMatrix;
100
99
  _layoutEventInitialized = false;
101
- isToolboxSet: boolean = false;
100
+
101
+ subs: rxjs.Subscription[] = [];
102
102
 
103
103
  private constructor(dataFrame: DG.DataFrame) {
104
104
  this.df = dataFrame;
@@ -442,26 +442,29 @@ export class PeptidesModel {
442
442
  acc.addPane('Actions', () => {
443
443
  const newView = ui.label('New view');
444
444
  $(newView).addClass('d4-link-action');
445
- newView.onclick = () => trueModel.createNewView();
446
- newView.onmouseover = (ev) => ui.tooltip.show('Creates a new view from current selection', ev.clientX + 5, ev.clientY + 5);
445
+ newView.onclick = (): string => trueModel.createNewView();
446
+ newView.onmouseover =
447
+ (ev): void => ui.tooltip.show('Creates a new view from current selection', ev.clientX + 5, ev.clientY + 5);
447
448
  const newCluster = ui.label('New cluster');
448
449
  $(newCluster).addClass('d4-link-action');
449
- newCluster.onclick = () => {
450
+ newCluster.onclick = (): void => {
450
451
  const lstViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
451
452
  if (lstViewer === null)
452
453
  throw new Error('Logo summary table viewer is not found');
453
454
  lstViewer.clusterFromSelection();
454
455
  };
455
- newCluster.onmouseover = (ev) => ui.tooltip.show('Creates a new cluster from selection', ev.clientX + 5, ev.clientY + 5);
456
+ newCluster.onmouseover =
457
+ (ev): void => ui.tooltip.show('Creates a new cluster from selection', ev.clientX + 5, ev.clientY + 5);
456
458
  const removeCluster = ui.label('Remove cluster');
457
459
  $(removeCluster).addClass('d4-link-action');
458
- removeCluster.onclick = () => {
460
+ removeCluster.onclick = (): void => {
459
461
  const lstViewer = trueModel.findViewer(VIEWER_TYPE.LOGO_SUMMARY_TABLE) as LogoSummaryTable | null;
460
462
  if (lstViewer === null)
461
463
  throw new Error('Logo summary table viewer is not found');
462
464
  lstViewer.removeCluster();
463
465
  };
464
- removeCluster.onmouseover = (ev) => ui.tooltip.show('Removes currently selected custom cluster', ev.clientX + 5, ev.clientY + 5);
466
+ removeCluster.onmouseover =
467
+ (ev): void => ui.tooltip.show('Removes currently selected custom cluster', ev.clientX + 5, ev.clientY + 5);
465
468
  removeCluster.style.visibility = trueModel.clusterSelection.length === 0 ||
466
469
  !wu(this.customClusters).some((c) => trueModel.clusterSelection.includes(c.name)) ? 'hidden' : 'visible';
467
470
  return ui.divV([newView, newCluster, removeCluster]);
@@ -470,6 +473,7 @@ export class PeptidesModel {
470
473
  const table = trueModel.df.filter.anyFalse ? trueModel.df.clone(trueModel.df.filter, null, true) : trueModel.df;
471
474
  acc.addPane('Mutation Cliffs pairs', () => mutationCliffsWidget(trueModel.df, trueModel).root);
472
475
  acc.addPane('Distribution', () => getDistributionWidget(table, trueModel).root);
476
+ acc.addPane('Selection', () => getSelectionWidget(trueModel.df, trueModel).root);
473
477
 
474
478
  return acc;
475
479
  }
@@ -1107,80 +1111,47 @@ export class PeptidesModel {
1107
1111
  this.updateGrid();
1108
1112
  }
1109
1113
 
1110
- if (!this.isToolboxSet && this.df.getTag(C.TAGS.MULTIPLE_VIEWS) !== '1') {
1111
- let template: ISeqSplitted;
1112
- const sequencesCol = this.df.getCol(this.settings.sequenceColumnName!);
1113
- const minTemplateLength = this.splitSeqDf.columns.toList()
1114
- .filter((col) => col.stats.missingValueCount === 0).length;
1115
- const calculateIdentityBtn = ui.button('Identity', async () => {
1116
- let identityScoresCol = calculateIdentity(template, this.splitSeqDf);
1117
- identityScoresCol.name = this.df.columns.getUnusedName(identityScoresCol.name);
1118
- identityScoresCol = this.df.columns.add(identityScoresCol);
1119
- identityScoresCol.setTag(C.TAGS.IDENTITY_TEMPLATE, new Array(template).join(' '));
1120
- }, 'Calculate identity');
1121
- const calculateSimilarityBtn = ui.button('Similarity', async () => {
1122
- let similarityScoresCol = await calculateSimilarity(template, this.splitSeqDf);
1123
- similarityScoresCol.name = this.df.columns.getUnusedName(similarityScoresCol.name);
1124
- similarityScoresCol = this.df.columns.add(similarityScoresCol);
1125
- similarityScoresCol.setTag(C.TAGS.SIMILARITY_TEMPLATE, new Array(template).join(' '));
1126
- }, 'Calculate similarity');
1127
- const templateInput = ui.stringInput('Template', this.identityTemplate, async () => {
1128
- this.identityTemplate = templateInput.value;
1129
- if (isNaN(parseInt(templateInput.value))) {
1130
- if (templateInput.value.length === 0) {
1131
- calculateIdentityBtn.disabled = true;
1132
- calculateSimilarityBtn.disabled = true;
1133
- return;
1134
- }
1135
- try {
1136
- template ??= await getTemplate(this.identityTemplate, sequencesCol);
1137
- if (template.length < minTemplateLength) {
1138
- grok.shell.warning(`Template length should be at least ${minTemplateLength} amino acids.`);
1139
- calculateIdentityBtn.disabled = true;
1140
- calculateSimilarityBtn.disabled = true;
1141
- return;
1142
- } else if (new Array(template).includes('') || new Array(template).includes('-')) {
1143
- grok.shell.warning('Template shouldn\'t contain gaps or empty cells.');
1144
- calculateIdentityBtn.disabled = true;
1145
- calculateSimilarityBtn.disabled = true;
1146
- return;
1147
- }
1148
- } catch (e) {
1149
- grok.shell.warning(`Only ${sequencesCol.getTag(DG.TAGS.UNITS)} sequence format is supported.`);
1150
- grok.log.warning(e as string);
1151
- calculateIdentityBtn.disabled = true;
1152
- return;
1153
- }
1154
- } else {
1155
- const rowIndex = parseInt(templateInput.value) - 1;
1156
- const selectedIndexes = this.df.filter.getSelectedIndexes();
1157
- if (rowIndex < 0 || rowIndex >= selectedIndexes.length) {
1158
- grok.shell.warning('Invalid row index');
1159
- calculateIdentityBtn.disabled = true;
1160
- calculateSimilarityBtn.disabled = true;
1161
- return;
1162
- }
1163
- this.identityTemplate = sequencesCol.get(selectedIndexes[rowIndex]);
1164
- }
1165
- try {
1166
- template = await getTemplate(this.identityTemplate, sequencesCol);
1167
- } catch (e) {
1168
- grok.shell.warning('Couldn\'t recognize sequence format.');
1169
- grok.log.warning(e as string);
1170
- calculateIdentityBtn.disabled = true;
1171
- calculateSimilarityBtn.disabled = true;
1172
- return;
1173
- }
1174
- calculateIdentityBtn.disabled = false;
1175
- calculateSimilarityBtn.disabled = false;
1176
- }, {placeholder: 'Sequence or row index...'});
1177
- templateInput.setTooltip('Template sequence. Can be row index, peptide ID or sequence.');
1178
- templateInput.fireChanged();
1179
- const acc = this.analysisView.toolboxPage.accordion;
1180
- acc.addPane('Sequence Identity and Similarity',
1181
- () => ui.divV([ui.form([templateInput]), calculateIdentityBtn, calculateSimilarityBtn]), true, acc.panes[0]);
1182
- this.isToolboxSet = true;
1183
- }
1114
+ this.subs.push(grok.events.onAccordionConstructed.subscribe((acc) => {
1115
+ if (!(grok.shell.o instanceof DG.SemanticValue || (grok.shell.o instanceof DG.Column && this.df.columns.toList().includes(grok.shell.o))))
1116
+ return;
1117
+
1118
+ const actionsPane = acc.getPane('Actions');
1119
+
1120
+ const actionsHost = $(actionsPane.root).find('.d4-flex-col');
1121
+ const calculateIdentity = ui.label('Calculate identity');
1122
+ calculateIdentity.classList.add('d4-link-action');
1123
+ ui.tooltip.bind(calculateIdentity, 'Adds a column with fractions of matching monomers against sequence in the current row');
1124
+ calculateIdentity.onclick = (): void => {
1125
+ const seqCol = this.df.getCol(this.settings.sequenceColumnName!);
1126
+ calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.IDENTITY);
1127
+ };
1128
+ actionsHost.append(ui.span([calculateIdentity], 'd4-markdown-row'));
1129
+
1130
+ const calculateSimilarity = ui.label('Calculate similarity');
1131
+ calculateSimilarity.classList.add('d4-link-action');
1132
+ ui.tooltip.bind(calculateSimilarity, 'Adds a column with sequence similarity scores against sequence in the current row');
1133
+ calculateSimilarity.onclick = (): void => {
1134
+ const seqCol = this.df.getCol(this.settings.sequenceColumnName!);
1135
+ calculateScores(this.df, seqCol, seqCol.get(this.df.currentRowIdx), SCORE.SIMILARITY);
1136
+ };
1137
+ actionsHost.append(ui.span([calculateSimilarity], 'd4-markdown-row'));
1138
+ }));
1139
+
1140
+ this.subs.push(grok.events.onViewRemoved.subscribe((view) => {
1141
+ if (view.id === this.analysisView.id)
1142
+ this.subs.forEach((v) => v.unsubscribe());
1143
+ grok.log.debug(`Peptides: view ${view.name} removed`);
1144
+ }));
1145
+ this.subs.push(grok.events.onTableRemoved.subscribe((table: DG.DataFrame) => {
1146
+ if (table.id === this.df.id)
1147
+ this.subs.forEach((v) => v.unsubscribe());
1148
+ grok.log.debug(`Peptides: table ${table.name} removed`);
1149
+ }));
1150
+ this.subs.push(grok.events.onProjectClosed.subscribe((project: DG.Project) => {
1151
+ if (project.id === grok.shell.project.id)
1152
+ this.subs.forEach((v) => v.unsubscribe());
1153
+ grok.log.debug(`Peptides: project ${project.name} closed`);
1154
+ }));
1184
1155
 
1185
1156
  this.fireBitsetChanged(true);
1186
1157
  if (typeof this.settings.targetColumnName === 'undefined')
package/src/package.ts CHANGED
@@ -8,7 +8,6 @@ import {PeptideSimilaritySpaceWidget} from './utils/peptide-similarity-space';
8
8
  import {manualAlignmentWidget} from './widgets/manual-alignment';
9
9
  import {MonomerPosition, MostPotentResidues} from './viewers/sar-viewer';
10
10
  import {getTreeHelper, ITreeHelper} from '@datagrok-libraries/bio/src/trees/tree-helper';
11
- import {IDendrogramService, getDendrogramService} from '@datagrok-libraries/bio/src/trees/dendrogram';
12
11
  import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
13
12
  import {LogoSummaryTable} from './viewers/logo-summary';
14
13
  import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
@@ -1,10 +1,10 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
- import {category, test, before, expect, awaitCheck, expectFloat} from '@datagrok-libraries/utils/src/test';
4
+ import {category, test, before, expect, awaitCheck} from '@datagrok-libraries/utils/src/test';
5
5
  import {_package} from '../package-test';
6
6
  import {PeptidesModel, VIEWER_TYPE} from '../model';
7
- import {getTemplate, scaleActivity} from '../utils/misc';
7
+ import {scaleActivity} from '../utils/misc';
8
8
  import {startAnalysis} from '../widgets/peptides';
9
9
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
10
  import * as C from '../utils/constants';
@@ -14,7 +14,6 @@ import {mutationCliffsWidget} from '../widgets/mutation-cliffs';
14
14
  import {TEST_COLUMN_NAMES} from './utils';
15
15
  import wu from 'wu';
16
16
  import {LogoSummaryTable} from '../viewers/logo-summary';
17
- import {calculateIdentity, calculateSimilarity} from '../widgets/similarity';
18
17
 
19
18
  category('Widgets: Settings', () => {
20
19
  let df: DG.DataFrame;
@@ -237,54 +236,3 @@ category('Widgets: Actions', () => {
237
236
  'Expected to have no custom cluster in the Logo Summary Table');
238
237
  });
239
238
  }, {clear: false});
240
-
241
-
242
- category('Widgets: Identity', () => {
243
- let df: DG.DataFrame;
244
- let model: PeptidesModel;
245
- let activityCol: DG.Column<number>;
246
- let sequenceCol: DG.Column<string>;
247
- let clusterCol: DG.Column<any>;
248
- let scaledActivityCol: DG.Column<number>;
249
-
250
- before(async () => {
251
- df = DG.DataFrame.fromCsv(await _package.files.readAsText('tests/HELM_small.csv'));
252
- activityCol = df.getCol(TEST_COLUMN_NAMES.ACTIVITY);
253
- sequenceCol = df.getCol(TEST_COLUMN_NAMES.SEQUENCE);
254
- sequenceCol.semType = DG.SEMTYPE.MACROMOLECULE;
255
- sequenceCol.setTag(DG.TAGS.UNITS, NOTATION.HELM);
256
- scaledActivityCol = scaleActivity(activityCol, C.SCALING_METHODS.NONE);
257
- clusterCol = df.getCol(TEST_COLUMN_NAMES.CLUSTER);
258
- const tempModel = await startAnalysis(activityCol, sequenceCol, clusterCol, df, scaledActivityCol,
259
- C.SCALING_METHODS.NONE);
260
- if (tempModel === null)
261
- throw new Error('Model is null');
262
- model = tempModel;
263
- let overlayInit = false;
264
- model._analysisView!.grid.onAfterDrawOverlay.subscribe(() => overlayInit = true);
265
-
266
- // Ensure grid finished initializing to prevent Unhandled exceptions
267
- let accrodionInit = false;
268
- grok.events.onAccordionConstructed.subscribe((_) => accrodionInit = true);
269
- await awaitCheck(() => model!.df.currentRowIdx === 0, 'Grid cell never finished initializing', 2000);
270
- await awaitCheck(() => grok.shell.o instanceof DG.Column, 'Shell object never changed', 2000);
271
- await awaitCheck(() => accrodionInit, 'Accordion never finished initializing', 2000);
272
- await awaitCheck(() => overlayInit, 'Overlay never finished initializing', 2000);
273
- });
274
-
275
- test('Identity', async () => {
276
- const seq = 'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.D-Orn.D-aThr.Phe_4Me}$$$$';
277
- const template = await getTemplate(seq);
278
- const identityCol = calculateIdentity(template, model.splitSeqDf);
279
- expect(identityCol.get(0), 1, 'Expected 1 identity score when sequence is matching template');
280
- expectFloat(identityCol.get(3)!, 0.5625, 0.01, 'Expected 0.5625 identity score agains sequence at position 3');
281
- });
282
-
283
- test('Similarity', async () => {
284
- const seq = 'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.D-Orn.D-aThr.Phe_4Me}$$$$';
285
- const template = await getTemplate(seq);
286
- const identityCol = await calculateSimilarity(template, model.splitSeqDf);
287
- expect(identityCol.get(0), 1, 'Expected 1 identity score when sequence is matching template');
288
- expectFloat(identityCol.get(3)!, 0, 0.001, 'Expected 7 identity score agains sequence at position 3');
289
- })
290
- });
package/src/utils/misc.ts CHANGED
@@ -3,9 +3,9 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
  import * as C from './constants';
5
5
  import * as type from './types';
6
- import {getSplitterForColumn} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
6
  import {StringDictionary} from '@datagrok-libraries/utils/src/type-declarations';
8
- import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
7
+ import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
8
+ import {TAGS as bioTags} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
9
 
10
10
  export function getTypedArrayConstructor(
11
11
  maxNum: number): Uint8ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor {
@@ -101,13 +101,3 @@ export function prepareTableForHistogram(table: DG.DataFrame): DG.DataFrame {
101
101
  DG.Column.fromList(DG.TYPE.BOOL, C.COLUMNS_NAMES.SPLIT_COL, expandedMasks),
102
102
  ]);
103
103
  }
104
-
105
- export async function getTemplate(sequence: string, seqCol?: DG.Column<string>): Promise<ISeqSplitted> {
106
- if (typeof seqCol === 'undefined') {
107
- const tempDf = DG.DataFrame.fromCsv(`sequence\n${new Array(10).fill(sequence).join('\n')}`);
108
- await grok.data.detectSemanticTypes(tempDf);
109
- seqCol = tempDf.getCol('sequence');
110
- }
111
- const splitter = getSplitterForColumn(seqCol);
112
- return splitter(sequence);
113
- }
@@ -121,6 +121,7 @@ export class MonomerPosition extends DG.JsViewer {
121
121
  mutationCliffsMode.value = true;
122
122
  this.mode = MONOMER_POSITION_MODE.MUTATION_CLIFFS;
123
123
  });
124
+ mutationCliffsMode.setTooltip('Statistically significant changes in activity');
124
125
  mutationCliffsMode.addPostfix(MONOMER_POSITION_MODE.MUTATION_CLIFFS);
125
126
  const invariantMapMode = ui.boolInput('', this.mode === MONOMER_POSITION_MODE.INVARIANT_MAP);
126
127
  invariantMapMode.root.addEventListener('click', () => {
@@ -128,6 +129,7 @@ export class MonomerPosition extends DG.JsViewer {
128
129
  invariantMapMode.value = true;
129
130
  this.mode = MONOMER_POSITION_MODE.INVARIANT_MAP;
130
131
  });
132
+ invariantMapMode.setTooltip('Number of sequences having monomer-position');
131
133
  invariantMapMode.addPostfix(MONOMER_POSITION_MODE.INVARIANT_MAP);
132
134
  const setDefaultProperties = (input: DG.InputBase): void => {
133
135
  $(input.root).find('.ui-input-editor').css('margin', '0px').attr('type', 'radio');
@@ -195,10 +195,12 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
195
195
 
196
196
  const splitByPosition = ui.boolInput('', defaultValuePos, updateDistributionHost);
197
197
  splitByPosition.addPostfix('Split by position');
198
+ splitByPosition.setTooltip('Constructs distribution for each position separately');
198
199
  setDefaultProperties(splitByPosition);
199
200
  $(splitByPosition.root).css('margin-right', '10px');
200
201
  const splitByAAR = ui.boolInput('', defaultValueAAR, updateDistributionHost);
201
202
  splitByAAR.addPostfix('Split by monomer');
203
+ splitByAAR.setTooltip('Constructs distribution for each monomer separately');
202
204
  setDefaultProperties(splitByAAR);
203
205
 
204
206
  const controlsHost = ui.divH([splitByPosition.root, splitByAAR.root]);
@@ -8,7 +8,6 @@ import {PeptidesModel} from '../model';
8
8
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
9
9
 
10
10
  /** Manual sequence alignment widget.
11
- *
12
11
  * @param {DG.Column} alignedSequenceCol Aligned sequence column
13
12
  * @param {DG.DataFrame} currentDf Working table
14
13
  * @return {DG.Widget} Widget for manual sequence alignment */
@@ -32,13 +31,10 @@ export function manualAlignmentWidget(alignedSequenceCol: DG.Column<string>, cur
32
31
 
33
32
  const peptidesController = PeptidesModel.getInstance(currentDf);
34
33
  peptidesController.updateGrid();
35
- });
34
+ }, 'Apply changes');
36
35
 
37
- const resetBtn = ui.button(
38
- ui.iconFA('redo'),
39
- () => sequenceInput.value = alignedSequenceCol.get(currentDf.currentRowIdx)!,
40
- 'Reset',
41
- );
36
+ const resetBtn = ui.button(ui.iconFA('redo'),
37
+ () => sequenceInput.value = alignedSequenceCol.get(currentDf.currentRowIdx)!, 'Reset');
42
38
  $(resetBtn).addClass('pep-snippet-editor-icon pep-reset-icon');
43
39
 
44
40
  return new DG.Widget(ui.divV([resetBtn, sequenceInput.root, applyChangesBtn], 'pep-textarea-box'));
@@ -16,13 +16,12 @@ import {ALIGNMENT, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/
16
16
  * @param {DG.DataFrame} df Working table
17
17
  * @param {DG.Column} col Aligned sequence column
18
18
  * @return {Promise<DG.Widget>} Widget containing peptide analysis */
19
- export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
20
- { host: HTMLElement, callback: () => Promise<boolean> } {
19
+ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>): {host: HTMLElement, callback: () => Promise<boolean>} {
21
20
  const logoHost = ui.div();
22
- // logoHost.style.alignContent = 'center';
23
21
  let seqColInput: DG.InputBase | null = null;
24
22
  if (typeof col === 'undefined') {
25
- const sequenceColumns = df.columns.toList().filter((dfCol) => dfCol.semType === DG.SEMTYPE.MACROMOLECULE);
23
+ const sequenceColumns = df.columns.toList()
24
+ .filter((dfCol) => dfCol.semType === DG.SEMTYPE.MACROMOLECULE && dfCol.stats.missingValueCount === 0);
26
25
  const potentialCol = DG.Utils.firstOrNull(sequenceColumns);
27
26
  if (potentialCol === null)
28
27
  throw new Error('Peptides Error: table doesn\'t contain sequence columns');
@@ -39,7 +38,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
39
38
  return viewer.root;
40
39
  }));
41
40
  //TODO: add when new version of datagrok-api is available
42
- }, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE});
41
+ }, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE && col.stats.missingValueCount === 0});
42
+ seqColInput.setTooltip('Macromolecule column in FASTA, HELM or separated format');
43
43
  } else if (!(col.getTag(bioTAGS.aligned) === ALIGNMENT.SEQ_MSA) &&
44
44
  col.getTag(DG.TAGS.UNITS) !== NOTATION.HELM) {
45
45
  return {
@@ -65,10 +65,8 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
65
65
  }
66
66
 
67
67
  let scaledCol: DG.Column<number>;
68
-
69
- const defaultActivityColumn: DG.Column<number> | null =
70
- df.col('activity') || df.col('IC50') || DG.Utils.firstOrNull(df.columns.numerical);
71
- ;
68
+ const defaultActivityColumn: DG.Column<number> | null = df.col('activity') || df.col('IC50') ||
69
+ DG.Utils.firstOrNull(df.columns.numerical);
72
70
  const histogramHost = ui.div([], {id: 'pep-hist-host'});
73
71
 
74
72
  const activityScalingMethod = ui.choiceInput(
@@ -88,23 +86,26 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
88
86
  histogramHost.lastChild?.remove();
89
87
  histogramHost.appendChild(hist.root);
90
88
  }) as DG.InputBase<C.SCALING_METHODS | null>;
91
- activityScalingMethod.setTooltip('Function to apply for each value in activity column');
89
+ activityScalingMethod.setTooltip('Activity column transformation method');
92
90
 
93
91
  const activityScalingMethodState = (): void => {
94
- activityScalingMethod.enabled = (activityColumnChoice.value ?? false) &&
95
- DG.Stats.fromColumn(activityColumnChoice.value!).min > 0;
96
- activityScalingMethod.fireChanged();
92
+ activityScalingMethod.enabled = (activityColumnChoice.value ?? false) && DG.Stats.fromColumn(activityColumnChoice.value!).min > 0;
93
+ activityScalingMethod.value = C.SCALING_METHODS.NONE;
97
94
  };
98
95
  //TODO: add when new version of datagrok-api is available
99
96
  const activityColumnChoice = ui.columnInput('Activity', df, defaultActivityColumn, activityScalingMethodState,
100
- {filter: (col: DG.Column) => col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT});
101
- const clustersColumnChoice = ui.columnInput('Clusters', df, null);
97
+ {filter: (col: DG.Column) => (col.type === DG.TYPE.INT || col.type === DG.TYPE.FLOAT) && col.stats.missingValueCount === 0});
98
+ activityColumnChoice.setTooltip('Numerical activity column');
99
+ const clustersColumnChoice = ui.columnInput('Clusters', df, null, null, {filter: (col: DG.Column) => col.stats.missingValueCount === 0});
100
+ clustersColumnChoice.setTooltip('Optional. Clusters column is used to create Logo Summary Table');
102
101
  clustersColumnChoice.nullable = true;
103
102
  activityColumnChoice.fireChanged();
104
103
  activityScalingMethod.fireChanged();
105
104
 
106
105
  const targetColumnChoice = ui.columnInput('Target', df, null, null,
107
- {filter: (col: DG.Column) => col.type === DG.TYPE.STRING});
106
+ {filter: (col: DG.Column) => col.type === DG.TYPE.STRING && col.stats.missingValueCount === 0});
107
+ targetColumnChoice.setTooltip('Optional. Target represents a unique binding construct for every peptide in the data. ' +
108
+ 'Target can be used to split mutation cliff analysis for peptides specific to a certain set of targets');
108
109
  targetColumnChoice.nullable = true;
109
110
 
110
111
  const inputsList = [activityColumnChoice, activityScalingMethod, clustersColumnChoice, targetColumnChoice];
@@ -128,7 +129,7 @@ export function analyzePeptidesUI(df: DG.DataFrame, col?: DG.Column<string>):
128
129
  const inputElements: HTMLElement[] = [ui.inputs(inputsList)];
129
130
  $(inputElements[0]).find('label').css('width', 'unset');
130
131
  if (typeof col !== 'undefined') {
131
- const startBtn = ui.button('Launch SAR', startAnalysisCallback);
132
+ const startBtn = ui.button('Launch SAR', startAnalysisCallback, '');
132
133
  startBtn.style.alignSelf = 'center';
133
134
  inputElements.push(startBtn);
134
135
  bottomHeight = '215px';
@@ -0,0 +1,31 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import {PeptidesModel} from '../model';
5
+
6
+ import wu from 'wu';
7
+
8
+ export function getSelectionWidget(table: DG.DataFrame, model: PeptidesModel): DG.Widget {
9
+ const compBitset = model.getCompoundBitset();
10
+ if (compBitset.trueCount === 0)
11
+ return new DG.Widget(ui.divText('No compounds selected'));
12
+ const newTable = DG.DataFrame.create(table.rowCount);
13
+ newTable.filter.copyFrom(compBitset);
14
+ const sourceGrid = model.analysisView.grid;
15
+ const numericalCols = wu(table.columns.numerical);
16
+ for (let gridColIdx = 1; gridColIdx < sourceGrid.columns.length; gridColIdx++) {
17
+ const gridCol = sourceGrid.columns.byIndex(gridColIdx)!;
18
+ if (!gridCol.visible)
19
+ continue;
20
+ const sourceCol = gridCol.column!;
21
+ const sourceColRawData = sourceCol.getRawData();
22
+ const sourceColCategories = sourceCol.categories;
23
+ const getValue = numericalCols.some((col) => col.name === sourceCol.name) ? (i: number): number => sourceColRawData[i] :
24
+ (i: number): string => sourceColCategories[sourceColRawData[i]];
25
+ const col = newTable.columns.addNewVirtual(gridCol.name, (i) => getValue(i), sourceCol.type as DG.TYPE);
26
+ for (const [tag, value] of sourceCol.tags)
27
+ col.setTag(tag, value);
28
+ }
29
+ const newGrid = newTable.plot.grid();
30
+ return new DG.Widget(ui.box(newGrid.root, {style: {width: '100%'}}));
31
+ }
@@ -62,12 +62,17 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
62
62
  // General pane options
63
63
  const activityCol = ui.columnInput(GENERAL_INPUTS.ACTIVITY, model.df,
64
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});
65
+ {filter: (col: DG.Column) => (col.type === DG.TYPE.FLOAT || col.type === DG.TYPE.INT) &&
66
+ col.name !== C.COLUMNS_NAMES.ACTIVITY_SCALED && col.stats.missingValueCount === 0});
67
+ activityCol.setTooltip('Numeric activity column');
66
68
  const activityScaling =
67
69
  ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, currentScaling, Object.values(C.SCALING_METHODS),
68
70
  () => result.scaling = activityScaling.value as C.SCALING_METHODS) as DG.InputBase<C.SCALING_METHODS>;
71
+ activityScaling.setTooltip('Activity column transformation method');
69
72
  const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, currentBidirectional,
70
73
  () => result.isBidirectional = bidirectionalAnalysis.value) as DG.InputBase<boolean>;
74
+ bidirectionalAnalysis.setTooltip('Distinguish between positive and negative mean activity difference in ' +
75
+ 'Monomer-Position and Most Potent Residues viewers');
71
76
 
72
77
  accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling, bidirectionalAnalysis]), true);
73
78
  inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling, bidirectionalAnalysis];
@@ -88,6 +93,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
88
93
  const isDendrogramEnabled = wu(model.analysisView.viewers).some((v) => v.type === VIEWER_TYPE.DENDROGRAM);
89
94
  const dendrogram = ui.boolInput(VIEWER_TYPE.DENDROGRAM, isDendrogramEnabled ?? false,
90
95
  () => result.showDendrogram = dendrogram.value) as DG.InputBase<boolean>;
96
+ dendrogram.setTooltip('Show dendrogram viewer');
91
97
  dendrogram.enabled = getTreeHelperInstance() !== null;
92
98
 
93
99
  accordion.addPane(SETTINGS_PANES.VIEWERS, () => ui.inputs([dendrogram]), true);
@@ -100,6 +106,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
100
106
  result.maxMutations = val;
101
107
  maxMutations.addPostfix(val.toString());
102
108
  }) as DG.InputBase<number>;
109
+ maxMutations.setTooltip('Maximum number of mutations between reference and mutated sequences');
103
110
  maxMutations.addPostfix((settings.maxMutations ?? 1).toString());
104
111
  const minActivityDelta = ui.sliderInput(MUTATION_CLIFFS_INPUTS.MIN_ACTIVITY_DELTA, currentMinActivityDelta, 0,
105
112
  100, () => {
@@ -108,6 +115,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
108
115
  $(minActivityDelta.root).find('label.ui-input-description').remove();
109
116
  minActivityDelta.addPostfix(val);
110
117
  }) as DG.InputBase<number>;
118
+ minActivityDelta.setTooltip('Minimum activity difference between reference and mutated sequences');
111
119
  minActivityDelta.addPostfix((settings.minActivityDelta ?? 0).toString());
112
120
  accordion.addPane(SETTINGS_PANES.MUTATION_CLIFFS, () => ui.inputs([maxMutations, minActivityDelta]), true);
113
121
  inputs[SETTINGS_PANES.MUTATION_CLIFFS] = [maxMutations, minActivityDelta];
@@ -131,6 +139,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
131
139
  delete result.columns;
132
140
  }
133
141
  }) as DG.InputBase<boolean>;
142
+ isIncludedInput.setTooltip('Include aggregated column value in tooltips, Logo Summary Table and Distribution panel');
134
143
 
135
144
  const aggregationInput = ui.choiceInput(COLUMNS_INPUTS.AGGREGATION, (currentColumns)[colName] ?? DG.AGG.AVG,
136
145
  Object.values(DG.STATS), () => {
@@ -143,6 +152,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
143
152
  delete result.columns;
144
153
  }
145
154
  }) as DG.InputBase<DG.AggregationType>;
155
+ aggregationInput.setTooltip('Aggregation method');
146
156
  $(aggregationInput.root).find('label').css('width', 'auto');
147
157
  const inputsRow = ui.inputsRow(col.name, [isIncludedInput, aggregationInput]);
148
158
  includedColumnsInputs.push(...[isIncludedInput, aggregationInput]);
@@ -1,39 +0,0 @@
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
- }