@datagrok/peptides 1.3.7 → 1.3.9

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/src/package.ts CHANGED
@@ -2,21 +2,27 @@
2
2
  import * as grok from 'datagrok-api/grok';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
-
6
5
  import * as C from './utils/constants';
7
6
 
8
- import {analyzePeptidesWidget} from './widgets/peptides';
7
+ import {analyzePeptidesUI} from './widgets/peptides';
9
8
  import {PeptideSimilaritySpaceWidget} from './utils/peptide-similarity-space';
10
9
  import {manualAlignmentWidget} from './widgets/manual-alignment';
11
10
  import {MutationCliffsViewer, MostPotentResiduesViewer} from './viewers/sar-viewer';
12
11
 
13
12
  import {PeptideSpaceViewer} from './viewers/peptide-space-viewer';
14
13
  import {LogoSummary} from './viewers/logo-summary';
14
+ import {MonomerWorks} from '@datagrok-libraries/bio';
15
+
16
+ export let monomerWorks: MonomerWorks | null;
15
17
 
16
18
  export const _package = new DG.Package();
17
19
  let currentTable: DG.DataFrame;
18
20
  let alignedSequenceColumn: DG.Column;
19
21
 
22
+ export function getMonomerWorks() {
23
+ return monomerWorks;
24
+ };
25
+
20
26
  async function main(chosenFile: string): Promise<void> {
21
27
  const pi = DG.TaskBarProgressIndicator.create('Loading Peptides');
22
28
  const path = _package.webRoot + 'files/' + chosenFile;
@@ -34,7 +40,10 @@ async function main(chosenFile: string): Promise<void> {
34
40
  export async function Peptides(): Promise<void> {
35
41
  const wikiLink = ui.link('wiki', 'https://github.com/datagrok-ai/public/blob/master/help/domains/bio/peptides.md');
36
42
  const textLink = ui.inlineText(['For more details, see our ', wikiLink, '.']);
37
-
43
+ if (monomerWorks == null) {
44
+ let lib = await grok.functions.call('Bio:getBioLib');
45
+ monomerWorks = new MonomerWorks(lib);
46
+ }
38
47
  const appDescription = ui.info(
39
48
  [
40
49
  ui.list([
@@ -71,13 +80,23 @@ export async function Peptides(): Promise<void> {
71
80
  ]);
72
81
  }
73
82
 
83
+ //top-menu: Bio | Peptides...
84
+ //name: Bio Peptides
85
+ export async function peptidesDialog(): Promise<DG.Dialog> {
86
+ const analyzeObject = await analyzePeptidesUI(grok.shell.t);
87
+ const dialog = ui.dialog('Analyze Peptides').add(analyzeObject.host).onOK(analyzeObject.callback);
88
+ dialog.show();
89
+ return dialog.show();
90
+ }
91
+
74
92
  //name: Peptides
75
93
  //tags: panel, widgets
76
94
  //input: column col {semType: Macromolecule}
77
95
  //output: widget result
78
96
  export async function peptidesPanel(col: DG.Column): Promise<DG.Widget> {
79
97
  [currentTable, alignedSequenceColumn] = getOrDefine(col.dataFrame, col);
80
- return analyzePeptidesWidget(currentTable, alignedSequenceColumn);
98
+ const analyzeObject = await analyzePeptidesUI(currentTable, alignedSequenceColumn);
99
+ return new DG.Widget(analyzeObject.host);
81
100
  }
82
101
 
83
102
  //name: peptide-sar-viewer
@@ -0,0 +1,51 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import {category, test, expect, delay, before} from '@datagrok-libraries/utils/src/test';
5
+
6
+ import {_package} from '../package-test';
7
+ import {startAnalysis} from '../widgets/peptides';
8
+ import {PeptidesModel} from '../model';
9
+ import * as C from '../utils/constants';
10
+ import {scaleActivity} from '../utils/misc';
11
+ import {ALPHABET, TAGS, NOTATION, ALIGNMENT} from '@datagrok-libraries/bio';
12
+ import {findMutations} from '../utils/algorithms';
13
+ import * as type from '../utils/types';
14
+
15
+ category('Algorithms', () => {
16
+ let activityCol: DG.Column<number>;
17
+ let monomerColumns: DG.Column<string>[];
18
+ let settings: type.PeptidesSettings;
19
+
20
+ before(async () => {
21
+ activityCol = DG.Column.fromList('int', 'test', [1, 2, 5]);
22
+ monomerColumns = [
23
+ DG.Column.fromList('string', '1', 'ABC'.split('')),
24
+ DG.Column.fromList('string', '2', 'ACC'.split('')),
25
+ DG.Column.fromList('string', '3', 'ACD'.split('')),
26
+ ];
27
+ settings = {maxMutations: 1, minActivityDelta: 2};
28
+ });
29
+
30
+ test('MutationCliffs', async () => {
31
+ const substInfo: type.SubstitutionsInfo = findMutations(activityCol, monomerColumns, settings);
32
+ expect(substInfo.has('C'), true);
33
+ expect(substInfo.has('D'), true);
34
+ expect(substInfo.has('A'), false);
35
+
36
+ const c = substInfo.get('C')!;
37
+ const d = substInfo.get('D')!;
38
+ expect(c.has('3'), true);
39
+ expect(d.has('3'), true);
40
+
41
+ const c3 = c.get('3')!;
42
+ const d3 = d.get('3')!;
43
+ expect(c3.has(2), true);
44
+ expect(d3.has(3), true);
45
+
46
+ const c32 = c3.get(2)!;
47
+ const d33 = d3.get(3)!;
48
+ expect(c32[0], 3);
49
+ expect(d33[0], 2);
50
+ });
51
+ });
package/src/tests/core.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
- import * as bio from '@datagrok-libraries/bio';
4
3
 
5
4
  import {category, test, expect, delay} from '@datagrok-libraries/utils/src/test';
6
5
 
@@ -9,6 +8,7 @@ import {startAnalysis} from '../widgets/peptides';
9
8
  import {PeptidesModel} from '../model';
10
9
  import * as C from '../utils/constants';
11
10
  import {scaleActivity} from '../utils/misc';
11
+ import {ALPHABET, TAGS, NOTATION, ALIGNMENT} from '@datagrok-libraries/bio';
12
12
 
13
13
  category('Core', () => {
14
14
  let simpleTable: DG.DataFrame;
@@ -33,10 +33,10 @@ category('Core', () => {
33
33
  simpleActivityCol = simpleTable.getCol(simpleActivityColName);
34
34
  simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
35
35
  simpleAlignedSeqCol.semType = C.SEM_TYPES.MACROMOLECULE;
36
- simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, bio.ALPHABET.PT);
37
- simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, bio.NOTATION.FASTA);
38
- simpleAlignedSeqCol.setTag(bio.TAGS.aligned, bio.ALIGNMENT.SEQ_MSA);
39
- simpleScaledCol = scaleActivity('-lg', simpleActivityCol);
36
+ simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
37
+ simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
38
+ simpleAlignedSeqCol.setTag(TAGS.aligned, ALIGNMENT.SEQ_MSA);
39
+ simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
40
40
 
41
41
  model = await startAnalysis(simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, '-lg');
42
42
  expect(model instanceof PeptidesModel, true);
@@ -53,11 +53,11 @@ category('Core', () => {
53
53
  complexActivityCol = complexTable.getCol(complexActivityColName);
54
54
  complexAlignedSeqCol = complexTable.getCol('MSA');
55
55
  complexAlignedSeqCol.semType = C.SEM_TYPES.MACROMOLECULE;
56
- complexAlignedSeqCol.setTag(C.TAGS.ALPHABET, bio.ALPHABET.UN);
57
- complexAlignedSeqCol.setTag(DG.TAGS.UNITS, bio.NOTATION.SEPARATOR);
58
- complexAlignedSeqCol.setTag(bio.TAGS.aligned, bio.ALIGNMENT.SEQ_MSA);
56
+ complexAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.UN);
57
+ complexAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
58
+ complexAlignedSeqCol.setTag(TAGS.aligned, ALIGNMENT.SEQ_MSA);
59
59
  complexAlignedSeqCol.tags[C.TAGS.SEPARATOR] = '/';
60
- complexScaledCol = scaleActivity('-lg', complexActivityCol);
60
+ complexScaledCol = scaleActivity(complexActivityCol, '-lg');
61
61
 
62
62
  model = await startAnalysis(
63
63
  complexActivityCol, complexAlignedSeqCol, null, complexTable, complexScaledCol, '-lg');
@@ -75,10 +75,10 @@ category('Core', () => {
75
75
  simpleActivityCol = simpleTable.getCol(simpleActivityColName);
76
76
  simpleAlignedSeqCol = simpleTable.getCol(alignedSequenceCol);
77
77
  simpleAlignedSeqCol.semType = C.SEM_TYPES.MACROMOLECULE;
78
- simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, bio.ALPHABET.PT);
79
- simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, bio.NOTATION.FASTA);
80
- simpleAlignedSeqCol.setTag(bio.TAGS.aligned, bio.ALIGNMENT.SEQ_MSA);
81
- simpleScaledCol = scaleActivity('-lg', simpleActivityCol);
78
+ simpleAlignedSeqCol.setTag(C.TAGS.ALPHABET, ALPHABET.PT);
79
+ simpleAlignedSeqCol.setTag(DG.TAGS.UNITS, NOTATION.FASTA);
80
+ simpleAlignedSeqCol.setTag(TAGS.aligned, ALIGNMENT.SEQ_MSA);
81
+ simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
82
82
 
83
83
  model = await startAnalysis(simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, '-lg');
84
84
  let v = grok.shell.getTableView('Peptides analysis');
@@ -0,0 +1,91 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ import * as C from './constants';
4
+ import * as type from './types';
5
+ import {getTypedArrayConstructor} from './misc';
6
+
7
+ //TODO: move out
8
+ export function findMutations(activityCol: DG.Column<number>, monomerColumns: DG.Column<string>[],
9
+ settings: type.PeptidesSettings = {}): type.SubstitutionsInfo {
10
+ const nCols = monomerColumns.length;
11
+ if (nCols == 0)
12
+ throw new Error(`PepAlgorithmError: Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
13
+
14
+ const substitutionsInfo: type.SubstitutionsInfo = new Map();
15
+ const nRows = activityCol.length;
16
+ for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
17
+ for (let seq2Idx = seq1Idx + 1; seq2Idx < nRows; seq2Idx++) {
18
+ let substCounter = 0;
19
+ const activityValSeq1 = activityCol.get(seq1Idx)!;
20
+ const activityValSeq2 = activityCol.get(seq2Idx)!;
21
+ const delta = activityValSeq1 - activityValSeq2;
22
+ if (Math.abs(delta) < (settings.minActivityDelta ?? 0))
23
+ continue;
24
+
25
+ let substCounterFlag = false;
26
+ const tempData: { pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number }[] =
27
+ [];
28
+ for (const currentPosCol of monomerColumns) {
29
+ const seq1monomer = currentPosCol.get(seq1Idx)!;
30
+ const seq2monomer = currentPosCol.get(seq2Idx)!;
31
+ if (seq1monomer == seq2monomer)
32
+ continue;
33
+
34
+ substCounter++;
35
+ substCounterFlag = substCounter > (settings.maxMutations ?? 1);
36
+ if (substCounterFlag)
37
+ break;
38
+
39
+ tempData.push({
40
+ pos: currentPosCol.name,
41
+ seq1monomer: seq1monomer,
42
+ seq2monomer: seq2monomer,
43
+ seq1Idx: seq1Idx,
44
+ seq2Idx: seq2Idx,
45
+ });
46
+ }
47
+
48
+ if (substCounterFlag || substCounter == 0)
49
+ continue;
50
+
51
+ for (const tempDataElement of tempData) {
52
+ const position = tempDataElement.pos;
53
+
54
+ //Working with seq1monomer
55
+ const seq1monomer = tempDataElement.seq1monomer;
56
+ if (!substitutionsInfo.has(seq1monomer))
57
+ substitutionsInfo.set(seq1monomer, new Map());
58
+
59
+ let positionsMap = substitutionsInfo.get(seq1monomer)!;
60
+ if (!positionsMap.has(position))
61
+ positionsMap.set(position, new Map());
62
+
63
+ let indexes = positionsMap.get(position)!;
64
+
65
+ !indexes.has(seq1Idx) ? indexes.set(seq1Idx, [seq2Idx]) : (indexes.get(seq1Idx)! as number[]).push(seq2Idx);
66
+
67
+ //Working with seq2monomer
68
+ const seq2monomer = tempDataElement.seq2monomer;
69
+ if (!substitutionsInfo.has(seq2monomer))
70
+ substitutionsInfo.set(seq2monomer, new Map());
71
+
72
+ positionsMap = substitutionsInfo.get(seq2monomer)!;
73
+ if (!positionsMap.has(position))
74
+ positionsMap.set(position, new Map());
75
+
76
+ indexes = positionsMap.get(position)!;
77
+ !indexes.has(seq2Idx) ? indexes.set(seq2Idx, [seq1Idx]) : (indexes.get(seq2Idx)! as number[]).push(seq1Idx);
78
+ }
79
+ }
80
+ }
81
+
82
+ const TypedArray = getTypedArrayConstructor(nRows);
83
+ for (const positionMap of substitutionsInfo.values()) {
84
+ for (const indexMap of positionMap.values()) {
85
+ for (const [index, indexArray] of indexMap.entries())
86
+ indexMap.set(index, new TypedArray(indexArray));
87
+ }
88
+ }
89
+
90
+ return substitutionsInfo;
91
+ }
@@ -1,8 +1,8 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
- import * as bio from '@datagrok-libraries/bio';
3
2
 
4
3
  import * as C from './constants';
5
4
  import * as types from './types';
5
+ import * as bio from '@datagrok-libraries/bio';
6
6
 
7
7
  function renderCellSelection(canvasContext: CanvasRenderingContext2D, bound: DG.Rect): void {
8
8
  canvasContext.strokeStyle = '#000';
@@ -19,9 +19,9 @@ export function setAARRenderer(col: DG.Column, alphabet: string, grid: DG.Grid,
19
19
  }
20
20
 
21
21
  export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D, currentAAR: string,
22
- currentPosition: string, statsDf: DG.DataFrame, twoColorMode: boolean, mdCol: DG.Column<number>, bound: DG.Rect,
23
- cellValue: number, mutationCliffsSelection: types.PositionToAARList, substitutionsInfo: types.SubstitutionsInfo,
24
- ): void {
22
+ currentPosition: string, statsDf: DG.DataFrame, mdCol: DG.Column<number>, bound: DG.Rect, cellValue: number,
23
+ mutationCliffsSelection: types.PositionToAARList, substitutionsInfo: types.SubstitutionsInfo,
24
+ twoColorMode: boolean = false): void {
25
25
  const queryAAR = `${C.COLUMNS_NAMES.MONOMER} = ${currentAAR}`;
26
26
  const query = `${queryAAR} and ${C.COLUMNS_NAMES.POSITION} = ${currentPosition}`;
27
27
  const pVal: number = statsDf
@@ -100,86 +100,40 @@ export function renderLogoSummaryCell(canvasContext: CanvasRenderingContext2D, c
100
100
  renderCellSelection(canvasContext, bound);
101
101
  }
102
102
 
103
- export function renderBarchart(ctx: CanvasRenderingContext2D, col: DG.Column, monomerColStats: types.MonomerColStats,
104
- bounds: DG.Rect, max: number): types.BarCoordinates {
105
- let sum = col.length - (monomerColStats['-']?.count ?? 0);
106
- const colorPalette = bio.getPaletteByType(col.tags[C.TAGS.ALPHABET]);
107
- const name = col.name;
108
- const colNameSize = ctx.measureText(name);
109
- const margin = 0.2;
110
- const innerMargin = 0.02;
111
- const selectLineRatio = 0.1;
112
- const fontSize = 11;
113
-
114
- const xMargin = bounds.x + bounds.width * margin;
115
- const yMargin = bounds.y + bounds.height * margin / 4;
116
- const wMargin = bounds.width - bounds.width * margin * 2;
117
- const hMargin = bounds.height - bounds.height * margin;
118
- const barWidth = 10;
119
- ctx.fillStyle = 'black';
120
- ctx.textBaseline = 'top';
121
- ctx.font = `${hMargin * margin / 2}px`;
122
- ctx.fillText(name, xMargin + (wMargin - colNameSize.width) / 2, yMargin + hMargin + hMargin * margin / 4);
123
-
124
-
125
- const barCoordinates: types.BarCoordinates = {};
126
-
127
- const xStart = xMargin + (wMargin - barWidth) / 2;
128
- for (const [monomer, monomerStats] of Object.entries(monomerColStats)) {
129
- if (monomer == '-')
130
- continue;
131
-
132
- const count = monomerStats.count;
133
- const sBarHeight = hMargin * count / max;
134
- const gapSize = sBarHeight * innerMargin;
135
- const verticalShift = (max - sum) / max;
136
- const textSize = ctx.measureText(monomer);
137
- const subBarHeight = sBarHeight - gapSize;
138
- const yStart = yMargin + hMargin * verticalShift + gapSize / 2;
139
- barCoordinates[monomer] = new DG.Rect(xStart, yStart, barWidth, subBarHeight);
140
-
141
- const color = colorPalette.get(monomer);
142
- ctx.strokeStyle = color;
143
- ctx.fillStyle = color;
144
-
145
- if (textSize.width <= subBarHeight) {
146
- if (color != bio.SeqPaletteBase.undefinedColor)
147
- ctx.fillRect(xStart, yStart, barWidth, subBarHeight);
148
- else {
149
- ctx.strokeRect(xStart + 0.5, yStart, barWidth - 1, subBarHeight);
150
- barCoordinates[monomer].x -= 0.5;
151
- barCoordinates[monomer].width -= 1;
152
- }
153
-
154
- const leftMargin = (wMargin - (monomer.length > 1 ? fontSize : textSize.width - 8)) / 2;
155
- const absX = xMargin + leftMargin;
156
- const absY = yStart + subBarHeight / 2 + (monomer.length == 1 ? 4 : 0);
157
- const origTransform = ctx.getTransform();
158
-
159
- if (monomer.length > 1) {
160
- ctx.translate(absX, absY);
161
- ctx.rotate(Math.PI / 2);
162
- ctx.translate(-absX, -absY);
163
- }
164
-
165
- ctx.fillStyle = 'black';
166
- ctx.font = `${fontSize}px monospace`;
167
- ctx.textAlign = 'center';
168
- ctx.textBaseline = 'bottom';
169
- ctx.fillText(monomer, absX, absY);
170
- ctx.setTransform(origTransform);
171
- } else
172
- ctx.fillRect(xStart, yStart, barWidth, subBarHeight);
173
-
174
- const selectedCount = monomerStats.selected;
175
- if (selectedCount) {
176
- ctx.fillStyle = 'rgb(255,165,0)';
177
- ctx.fillRect(xStart - wMargin * selectLineRatio * 2, yStart,
178
- barWidth * selectLineRatio, hMargin * selectedCount / max - gapSize);
179
- }
180
103
 
181
- sum -= count;
104
+ export function drawLogoInBounds(ctx: CanvasRenderingContext2D, bounds: DG.Rect, statsInfo: types.StatsInfo,
105
+ rowCount: number, cp: bio.SeqPalette, drawOptions: types.DrawOptions = {}): void {
106
+ drawOptions.fontStyle ??= '16px Roboto, Roboto Local, sans-serif';
107
+ drawOptions.upperLetterHeight ??= 12.2;
108
+ drawOptions.upperLetterAscent ??= 0.25;
109
+ drawOptions.marginVertical ??= 5;
110
+ drawOptions.marginHorizontal ??= 5;
111
+
112
+ const totalSpaceBetweenLetters = (statsInfo.orderedIndexes.length - 1) * drawOptions.upperLetterAscent;
113
+ const barHeight = bounds.height - 2 * drawOptions.marginVertical - totalSpaceBetweenLetters;
114
+ const leftShift = drawOptions.marginHorizontal * 2;
115
+ const barWidth = bounds.width - leftShift - drawOptions.marginHorizontal;
116
+ const xStart = bounds.x + leftShift;
117
+ let currentY = bounds.y + drawOptions.marginVertical;
118
+
119
+
120
+ for (const index of statsInfo.orderedIndexes) {
121
+ const monomer = statsInfo.monomerCol.get(index)!;
122
+ const monomerHeight = barHeight * (statsInfo.countCol.get(index)! / rowCount);
123
+
124
+ ctx.resetTransform();
125
+ if (monomer !== '-') {
126
+ const monomerTxt = bio.monomerToShort(monomer, 5);
127
+ const mTm: TextMetrics = ctx.measureText(monomerTxt);
128
+
129
+ ctx.fillStyle = cp.get(monomer) ?? cp.get('other');
130
+ ctx.textAlign = 'left';
131
+ ctx.textBaseline = 'top';
132
+ ctx.font = drawOptions.fontStyle;
133
+ // Hacks to scale uppercase characters to target rectangle
134
+ ctx.setTransform(barWidth / mTm.width, 0, 0, monomerHeight / drawOptions.upperLetterHeight, xStart, currentY);
135
+ ctx.fillText(monomerTxt, 0, 0);
136
+ }
137
+ currentY += monomerHeight + drawOptions.upperLetterAscent;
182
138
  }
183
-
184
- return barCoordinates;
185
139
  }
package/src/utils/misc.ts CHANGED
@@ -18,7 +18,7 @@ export function getSeparator(col: DG.Column<string>): string {
18
18
  return col.getTag(C.TAGS.SEPARATOR) ?? '';
19
19
  }
20
20
 
21
- export function scaleActivity(scaling: string, activityCol: DG.Column<number>): DG.Column<number> {
21
+ export function scaleActivity(activityCol: DG.Column<number>, scaling: string = 'none'): DG.Column<number> {
22
22
  let formula = (x: number): number => x;
23
23
  let newColName = 'activity';
24
24
  switch (scaling) {
@@ -1,5 +1,4 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
- import * as bio from '@datagrok-libraries/bio';
3
2
 
4
3
  export type DataFrameDict = {[key: string]: DG.DataFrame};
5
4
 
@@ -8,9 +7,31 @@ export type UTypedArray = Uint8Array | Uint16Array | Uint32Array;
8
7
  export type SubstitutionsInfo = Map<string, Map<string, Map<number, number[] | UTypedArray>>>;
9
8
  export type PositionToAARList = {[postiton: string]: string[]};
10
9
 
11
- export type HELMMonomer = bio.Monomer;
12
-
13
10
  export type MonomerColStats = {[monomer: string]: {count: number, selected: number}};
14
11
  export type MonomerDfStats = {[position: string]: MonomerColStats};
15
12
 
16
- export type BarCoordinates = {[monomer: string]: DG.Rect};
13
+ export type ScalingMethods = 'none' | 'lg' | '-lg';
14
+ export type PeptidesSettings = {
15
+ scaling?: ScalingMethods,
16
+ isBidirectional?: boolean,
17
+ maxMutations?: number,
18
+ minActivityDelta?: number,
19
+ columns?: {[col: string]: string},
20
+ };
21
+
22
+ export type DrawOptions = {
23
+ fontStyle?: string,
24
+ upperLetterHeight?: number,
25
+ upperLetterAscent?: number,
26
+ bounds?: DG.Rect,
27
+ textAlign?: CanvasTextAlign,
28
+ textBaseline?: CanvasTextBaseline,
29
+ marginVertical?: number,
30
+ marginHorizontal?: number,
31
+ };
32
+
33
+ export type StatsInfo = {
34
+ monomerCol: DG.Column<string>,
35
+ countCol: DG.Column<number>,
36
+ orderedIndexes: Int32Array,
37
+ }
@@ -6,18 +6,11 @@ import $ from 'cash-dom';
6
6
  import * as C from '../utils/constants';
7
7
  import {PeptidesModel} from '../model';
8
8
 
9
- let IS_PROPERTY_CHANGING = false;
10
-
11
9
  export class SARViewerBase extends DG.JsViewer {
12
10
  tempName!: string;
13
11
  viewerGrid!: DG.Grid;
14
12
  sourceGrid!: DG.Grid;
15
13
  model!: PeptidesModel;
16
- scaling: string;
17
- bidirectionalAnalysis: boolean;
18
- maxSubstitutions: number;
19
- minActivityDelta: number;
20
- _titleHost = ui.divText('SAR Viewer', {id: 'pep-viewer-title'});
21
14
  initialized = false;
22
15
  isPropertyChanging: boolean = false;
23
16
  _isVertical = false;
@@ -25,11 +18,6 @@ export class SARViewerBase extends DG.JsViewer {
25
18
 
26
19
  constructor() {
27
20
  super();
28
-
29
- this.scaling = this.string('scaling', 'none', {choices: ['none', 'lg', '-lg']});
30
- this.bidirectionalAnalysis = this.bool('bidirectionalAnalysis', false);
31
- this.maxSubstitutions = this.int('maxSubstitutions', 1);
32
- this.minActivityDelta = this.float('minActivityDelta', 0);
33
21
  }
34
22
 
35
23
  get name(): string {return '';}
@@ -39,16 +27,6 @@ export class SARViewerBase extends DG.JsViewer {
39
27
  this.sourceGrid = this.view?.grid ?? (grok.shell.v as DG.TableView).grid;
40
28
  this.model = await PeptidesModel.getInstance(this.dataFrame);
41
29
  this.helpUrl = '/help/domains/bio/peptides.md';
42
-
43
- this.initProperties();
44
- }
45
-
46
- initProperties(): void {
47
- const props = this.model.usedProperties;
48
- IS_PROPERTY_CHANGING = true;
49
- for (const [propName, propVal] of Object.entries(props))
50
- this.props.set(propName, propVal as any as object);
51
- IS_PROPERTY_CHANGING = false;
52
30
  }
53
31
 
54
32
  detach(): void {this.subs.forEach((sub) => sub.unsubscribe());}
@@ -74,7 +52,6 @@ export class SARViewerBase extends DG.JsViewer {
74
52
  invariantMapMode.value = !invariantMapMode.value;
75
53
  this.isMutationCliffsMode = '1';
76
54
  this.isModeChanging = false;
77
- this._titleHost.innerText = 'Mutation Cliffs';
78
55
  this.model.isInvariantMap = false;
79
56
  this.viewerGrid.invalidate();
80
57
  });
@@ -86,7 +63,6 @@ export class SARViewerBase extends DG.JsViewer {
86
63
  mutationCliffsMode.value = !mutationCliffsMode.value;
87
64
  this.isMutationCliffsMode = '0';
88
65
  this.isModeChanging = false;
89
- this._titleHost.innerText = 'Invariant Map';
90
66
  this.model.isInvariantMap = true;
91
67
  this.viewerGrid.invalidate();
92
68
  });
@@ -99,34 +75,21 @@ export class SARViewerBase extends DG.JsViewer {
99
75
  setDefaultProperties(invariantMapMode);
100
76
  $(mutationCliffsMode.root).css('padding-right', '10px').css('padding-left', '5px');
101
77
 
102
- switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root]);
103
- switchHost.style.position = 'absolute';
78
+ switchHost = ui.divH([mutationCliffsMode.root, invariantMapMode.root], {id: 'pep-viewer-title'});
79
+ $(switchHost).css('width', 'auto').css('align-self', 'center');
104
80
  }
105
81
  const viewerRoot = this.viewerGrid.root;
106
82
  viewerRoot.style.width = 'auto';
107
- this.root.appendChild(ui.divV([ui.divH([switchHost, this._titleHost]), viewerRoot]));
83
+ this.root.appendChild(ui.divV([switchHost, viewerRoot]));
108
84
  }
109
85
  this.viewerGrid?.invalidate();
110
86
  }
111
87
 
112
88
  onPropertyChanged(property: DG.Property): void {
113
89
  super.onPropertyChanged(property);
114
- this.dataFrame.tags[property.name] = `${property.get(this)}`;
115
- if (!this.initialized || IS_PROPERTY_CHANGING)
116
- return;
117
90
 
118
- const propName = property.name;
119
-
120
- if (propName === 'scaling' && typeof this.dataFrame !== 'undefined') {
121
- const activityCol = this.dataFrame.columns.bySemType(C.SEM_TYPES.ACTIVITY)!;
122
- const minActivity = activityCol.stats.min;
123
- if (minActivity && minActivity <= 0 && this.scaling !== 'none') {
124
- grok.shell.warning(`Could not apply ${this.scaling}: ` +
125
- `activity column ${activityCol.name} contains zero or negative values, falling back to 'none'.`);
126
- property.set(this, 'none');
127
- return;
128
- }
129
- }
91
+ if (!this.initialized)
92
+ return;
130
93
 
131
94
  this.model.updateDefault();
132
95
  this.render(true);
@@ -147,7 +110,6 @@ export class MutationCliffsViewer extends SARViewerBase {
147
110
 
148
111
  async onTableAttached(): Promise<void> {
149
112
  await super.onTableAttached();
150
- this.model.mutationCliffsViewer ??= this;
151
113
 
152
114
  this.subs.push(this.model.onMutationCliffsGridChanged.subscribe((data) => {
153
115
  this.viewerGrid = data;
@@ -164,16 +126,10 @@ export class MutationCliffsViewer extends SARViewerBase {
164
126
 
165
127
  //1. debouncing in rxjs; 2. flags?
166
128
  onPropertyChanged(property: DG.Property): void {
167
- if (!this.isInitialized() || IS_PROPERTY_CHANGING)
129
+ if (!this.isInitialized())
168
130
  return;
169
131
 
170
- if (property.name == 'invariantMap')
171
- this._titleHost = ui.divText(property.get(this) ? 'Invariant Map' : 'Mutation Cliffs', {id: 'pep-viewer-title'});
172
-
173
132
  super.onPropertyChanged(property);
174
- IS_PROPERTY_CHANGING = true;
175
- this.model.syncProperties(true);
176
- IS_PROPERTY_CHANGING = false;
177
133
  }
178
134
  }
179
135
 
@@ -191,7 +147,6 @@ export class MostPotentResiduesViewer extends SARViewerBase {
191
147
 
192
148
  async onTableAttached(): Promise<void> {
193
149
  await super.onTableAttached();
194
- this.model.mostPotentResiduesViewer ??= this;
195
150
 
196
151
  this.subs.push(this.model.onMostPotentResiduesGridChanged.subscribe((data) => {
197
152
  this.viewerGrid = data;
@@ -208,12 +163,9 @@ export class MostPotentResiduesViewer extends SARViewerBase {
208
163
  isInitialized(): DG.Grid {return this.model?.mostPotentResiduesGrid;}
209
164
 
210
165
  onPropertyChanged(property: DG.Property): void {
211
- if (!this.isInitialized() || IS_PROPERTY_CHANGING)
166
+ if (!this.isInitialized())
212
167
  return;
213
168
 
214
169
  super.onPropertyChanged(property);
215
- IS_PROPERTY_CHANGING = true;
216
- this.model.syncProperties(false);
217
- IS_PROPERTY_CHANGING = false;
218
170
  }
219
171
  }
@@ -152,7 +152,7 @@ export function getDistributionWidget(table: DG.DataFrame, model: PeptidesModel)
152
152
  if (!model.isLogoSummarySelectionEmpty && model.isMutationCliffSelectionEmpty) {
153
153
  defaultValuePos = false;
154
154
  defaultValueAAR = false;
155
- }
155
+ }
156
156
 
157
157
  const splitByPosition = ui.boolInput('', defaultValuePos, updateDistributionHost);
158
158
  splitByPosition.addPostfix('Split by position');
@@ -1,11 +1,11 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
2
  import * as grok from 'datagrok-api/grok';
3
3
  import * as DG from 'datagrok-api/dg';
4
- import * as bio from '@datagrok-libraries/bio';
5
4
 
6
5
  import $ from 'cash-dom';
7
6
  import '../styles.css';
8
7
  import {PeptidesModel} from '../model';
8
+ import {splitAlignedSequences} from '@datagrok-libraries/bio';
9
9
 
10
10
  /** Manual sequence alignment widget.
11
11
  *
@@ -19,7 +19,7 @@ export function manualAlignmentWidget(alignedSequenceCol: DG.Column<string>, cur
19
19
  const applyChangesBtn = ui.button('Apply', async () => {
20
20
  const newSequence = sequenceInput.value;
21
21
  const affectedRowIndex = currentDf.currentRowIdx;
22
- const splitSequence = bio.splitAlignedSequences(DG.Column.fromStrings('splitSequence', [newSequence]));
22
+ const splitSequence = splitAlignedSequences(DG.Column.fromStrings('splitSequence', [newSequence]));
23
23
 
24
24
  alignedSequenceCol.set(affectedRowIndex, newSequence);
25
25
  for (const part of splitSequence.columns) {