@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/dist/package-test.js +1450 -592
- package/dist/package.js +1011 -793
- package/package.json +2 -2
- package/src/model.ts +167 -250
- package/src/package.ts +23 -4
- package/src/tests/algorithms.ts +51 -0
- package/src/tests/core.ts +13 -13
- package/src/utils/algorithms.ts +91 -0
- package/src/utils/cell-renderer.ts +38 -84
- package/src/utils/misc.ts +1 -1
- package/src/utils/types.ts +25 -4
- package/src/viewers/sar-viewer.ts +7 -55
- package/src/widgets/distribution.ts +1 -1
- package/src/widgets/manual-alignment.ts +2 -2
- package/src/widgets/peptides.ts +52 -28
- package/src/widgets/settings.ts +67 -0
- package/test-Peptides-62cc009524f3-0949dc07.html +276 -0
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 {
|
|
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
|
-
|
|
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,
|
|
37
|
-
simpleAlignedSeqCol.setTag(DG.TAGS.UNITS,
|
|
38
|
-
simpleAlignedSeqCol.setTag(
|
|
39
|
-
simpleScaledCol = scaleActivity('-lg'
|
|
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,
|
|
57
|
-
complexAlignedSeqCol.setTag(DG.TAGS.UNITS,
|
|
58
|
-
complexAlignedSeqCol.setTag(
|
|
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'
|
|
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,
|
|
79
|
-
simpleAlignedSeqCol.setTag(DG.TAGS.UNITS,
|
|
80
|
-
simpleAlignedSeqCol.setTag(
|
|
81
|
-
simpleScaledCol = scaleActivity('-lg'
|
|
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,
|
|
23
|
-
|
|
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
|
-
|
|
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(
|
|
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) {
|
package/src/utils/types.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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([
|
|
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
|
-
|
|
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()
|
|
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()
|
|
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 =
|
|
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) {
|