@datagrok/peptides 1.3.8 → 1.4.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/dist/package-test.js +815 -432
- package/dist/package.js +779 -394
- package/package.json +2 -2
- package/src/model.ts +154 -248
- package/src/package.ts +4 -4
- package/src/tests/algorithms.ts +51 -0
- package/src/tests/core.ts +10 -10
- package/src/utils/algorithms.ts +91 -0
- package/src/utils/cell-renderer.ts +48 -81
- package/src/utils/misc.ts +14 -22
- package/src/utils/statistics.ts +2 -3
- package/src/utils/types.ts +24 -8
- package/src/viewers/logo-summary.ts +117 -5
- package/src/viewers/sar-viewer.ts +1 -1
- package/src/widgets/manual-alignment.ts +2 -2
- package/src/widgets/peptides.ts +3 -2
- package/src/widgets/settings.ts +36 -6
- package/test-Peptides-62cc009524f3-d4fc804f.html +276 -0
package/src/package.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
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
|
-
import * as bio from '@datagrok-libraries/bio';
|
|
6
5
|
import * as C from './utils/constants';
|
|
7
6
|
|
|
8
7
|
import {analyzePeptidesUI} from './widgets/peptides';
|
|
@@ -12,14 +11,15 @@ import {MutationCliffsViewer, MostPotentResiduesViewer} from './viewers/sar-view
|
|
|
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
15
|
|
|
16
|
-
export let monomerWorks:
|
|
16
|
+
export let monomerWorks: MonomerWorks | null;
|
|
17
17
|
|
|
18
18
|
export const _package = new DG.Package();
|
|
19
19
|
let currentTable: DG.DataFrame;
|
|
20
20
|
let alignedSequenceColumn: DG.Column;
|
|
21
21
|
|
|
22
|
-
export function
|
|
22
|
+
export function getMonomerWorks() {
|
|
23
23
|
return monomerWorks;
|
|
24
24
|
};
|
|
25
25
|
|
|
@@ -42,7 +42,7 @@ export async function Peptides(): Promise<void> {
|
|
|
42
42
|
const textLink = ui.inlineText(['For more details, see our ', wikiLink, '.']);
|
|
43
43
|
if (monomerWorks == null) {
|
|
44
44
|
let lib = await grok.functions.call('Bio:getBioLib');
|
|
45
|
-
monomerWorks = new
|
|
45
|
+
monomerWorks = new MonomerWorks(lib);
|
|
46
46
|
}
|
|
47
47
|
const appDescription = ui.info(
|
|
48
48
|
[
|
|
@@ -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,9 +33,9 @@ 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(
|
|
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
39
|
simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
|
|
40
40
|
|
|
41
41
|
model = await startAnalysis(simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, '-lg');
|
|
@@ -53,9 +53,9 @@ 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
60
|
complexScaledCol = scaleActivity(complexActivityCol, '-lg');
|
|
61
61
|
|
|
@@ -75,9 +75,9 @@ 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(
|
|
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
81
|
simpleScaledCol = scaleActivity(simpleActivityCol, '-lg');
|
|
82
82
|
|
|
83
83
|
model = await startAnalysis(simpleActivityCol, simpleAlignedSeqCol, null, simpleTable, simpleScaledCol, '-lg');
|
|
@@ -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';
|
|
@@ -62,7 +62,8 @@ export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D,
|
|
|
62
62
|
if (substitutionsInfo.size > 0) {
|
|
63
63
|
canvasContext.textBaseline = 'middle';
|
|
64
64
|
canvasContext.textAlign = 'center';
|
|
65
|
-
canvasContext.fillStyle = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(coef)));
|
|
65
|
+
// canvasContext.fillStyle = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(coef)));
|
|
66
|
+
canvasContext.fillStyle = DG.Color.toHtml(DG.Color.black);
|
|
66
67
|
canvasContext.font = '13px Roboto, Roboto Local, sans-serif';
|
|
67
68
|
let substValue = 0;
|
|
68
69
|
substitutionsInfo.get(currentAAR)?.get(currentPosition)?.forEach((idxs) => substValue += idxs.length);
|
|
@@ -100,86 +101,52 @@ export function renderLogoSummaryCell(canvasContext: CanvasRenderingContext2D, c
|
|
|
100
101
|
renderCellSelection(canvasContext, bound);
|
|
101
102
|
}
|
|
102
103
|
|
|
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
104
|
|
|
181
|
-
|
|
105
|
+
export function drawLogoInBounds(ctx: CanvasRenderingContext2D, bounds: DG.Rect, statsInfo: types.StatsInfo,
|
|
106
|
+
rowCount: number, cp: bio.SeqPalette, monomerSelectionStats: {[monomer: string]: number} = {},
|
|
107
|
+
drawOptions: types.DrawOptions = {}): {[monomer: string]: DG.Rect} {
|
|
108
|
+
drawOptions.fontStyle ??= '16px Roboto, Roboto Local, sans-serif';
|
|
109
|
+
drawOptions.upperLetterHeight ??= 12.2;
|
|
110
|
+
drawOptions.upperLetterAscent ??= 0.25;
|
|
111
|
+
drawOptions.marginVertical ??= 5;
|
|
112
|
+
drawOptions.marginHorizontal ??= 5;
|
|
113
|
+
|
|
114
|
+
const totalSpaceBetweenLetters = (statsInfo.orderedIndexes.length - 1) * drawOptions.upperLetterAscent;
|
|
115
|
+
const barHeight = bounds.height - 2 * drawOptions.marginVertical - totalSpaceBetweenLetters;
|
|
116
|
+
const leftShift = drawOptions.marginHorizontal * 2;
|
|
117
|
+
const barWidth = bounds.width - leftShift * 2;
|
|
118
|
+
const xStart = bounds.x + leftShift;
|
|
119
|
+
const selectionWidth = 4;
|
|
120
|
+
const xSelection = bounds.x + 3;
|
|
121
|
+
let currentY = bounds.y + drawOptions.marginVertical;
|
|
122
|
+
|
|
123
|
+
const monomerBounds: {[monomer: string]: DG.Rect} = {};
|
|
124
|
+
for (const index of statsInfo.orderedIndexes) {
|
|
125
|
+
const monomer = statsInfo.monomerCol.get(index)!;
|
|
126
|
+
const monomerHeight = barHeight * (statsInfo.countCol.get(index)! / rowCount);
|
|
127
|
+
const selectionHeight = barHeight * ((monomerSelectionStats[monomer] ?? 0) / rowCount);
|
|
128
|
+
const currentBound = new DG.Rect(xStart, currentY, barWidth, monomerHeight);
|
|
129
|
+
monomerBounds[monomer] = currentBound;
|
|
130
|
+
|
|
131
|
+
ctx.resetTransform();
|
|
132
|
+
if (monomer !== '-') {
|
|
133
|
+
const monomerTxt = bio.monomerToShort(monomer, 5);
|
|
134
|
+
const mTm: TextMetrics = ctx.measureText(monomerTxt);
|
|
135
|
+
|
|
136
|
+
// Filling selection
|
|
137
|
+
ctx.lineWidth = selectionWidth;
|
|
138
|
+
ctx.line(xSelection, currentY, xSelection, currentY + selectionHeight, DG.Color.rowSelection);
|
|
139
|
+
|
|
140
|
+
ctx.fillStyle = cp.get(monomer) ?? cp.get('other');
|
|
141
|
+
ctx.textAlign = 'left';
|
|
142
|
+
ctx.textBaseline = 'top';
|
|
143
|
+
ctx.font = drawOptions.fontStyle;
|
|
144
|
+
// Hacks to scale uppercase characters to target rectangle
|
|
145
|
+
ctx.setTransform(barWidth / mTm.width, 0, 0, monomerHeight / drawOptions.upperLetterHeight, xStart, currentY);
|
|
146
|
+
ctx.fillText(monomerTxt, 0, 0);
|
|
147
|
+
}
|
|
148
|
+
currentY += monomerHeight + drawOptions.upperLetterAscent;
|
|
182
149
|
}
|
|
183
150
|
|
|
184
|
-
return
|
|
151
|
+
return monomerBounds;
|
|
185
152
|
}
|
package/src/utils/misc.ts
CHANGED
|
@@ -45,30 +45,22 @@ export function scaleActivity(activityCol: DG.Column<number>, scaling: string =
|
|
|
45
45
|
return scaledCol;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const colLen = col.length;
|
|
62
|
-
const colStats: type.MonomerColStats = {};
|
|
63
|
-
col.categories.forEach((monomer) => colStats[monomer] = {count: 0, selected: 0});
|
|
64
|
-
|
|
65
|
-
for (let rowIndex = 0; rowIndex < colLen; rowIndex++) {
|
|
66
|
-
const monomerStats = colStats[col.get(rowIndex)!];
|
|
67
|
-
monomerStats.count += 1;
|
|
68
|
-
monomerStats.selected += +selection.get(rowIndex);
|
|
48
|
+
export function calculateSelected(df: DG.DataFrame): type.MonomerSelectionStats {
|
|
49
|
+
const monomerColumns: DG.Column<string>[] = df.columns.bySemTypeAll(C.SEM_TYPES.MONOMER);
|
|
50
|
+
const selectedObj: type.MonomerSelectionStats = {};
|
|
51
|
+
for (const idx of df.selection.getSelectedIndexes()) {
|
|
52
|
+
for (const col of monomerColumns) {
|
|
53
|
+
const monomer = col.get(idx);
|
|
54
|
+
if (!monomer)
|
|
55
|
+
continue;
|
|
56
|
+
|
|
57
|
+
selectedObj[col.name] ??= {};
|
|
58
|
+
selectedObj[col.name][monomer] ??= 0;
|
|
59
|
+
selectedObj[col.name][monomer] += 1;
|
|
60
|
+
}
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
return
|
|
63
|
+
return selectedObj;
|
|
72
64
|
}
|
|
73
65
|
|
|
74
66
|
export function isGridCellInvalid(gc: DG.GridCell | null): boolean {
|
package/src/utils/statistics.ts
CHANGED
|
@@ -20,11 +20,10 @@ export function getStats(data: StatsData, mask: DG.BitSet): Stats {
|
|
|
20
20
|
|
|
21
21
|
const testResult = tTest(selected, rest);
|
|
22
22
|
const currentMeanDiff = testResult['Mean difference']!;
|
|
23
|
-
const realCount = selected.length || data.length;
|
|
24
23
|
return {
|
|
25
|
-
count:
|
|
24
|
+
count: selected.length,
|
|
26
25
|
pValue: testResult[currentMeanDiff >= 0 ? 'p-value more' : 'p-value less'],
|
|
27
26
|
meanDifference: currentMeanDiff,
|
|
28
|
-
ratio:
|
|
27
|
+
ratio: selected.length / data.length,
|
|
29
28
|
};
|
|
30
29
|
}
|
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,13 +7,30 @@ 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
|
|
10
|
+
export type MonomerSelectionStats = {[position: string]: {[monomer: string]: number}};
|
|
12
11
|
|
|
13
|
-
export type
|
|
14
|
-
export type
|
|
12
|
+
export type ScalingMethods = 'none' | 'lg' | '-lg';
|
|
13
|
+
export type PeptidesSettings = {
|
|
14
|
+
scaling?: ScalingMethods,
|
|
15
|
+
isBidirectional?: boolean,
|
|
16
|
+
maxMutations?: number,
|
|
17
|
+
minActivityDelta?: number,
|
|
18
|
+
columns?: {[col: string]: string},
|
|
19
|
+
};
|
|
15
20
|
|
|
16
|
-
export type
|
|
21
|
+
export type DrawOptions = {
|
|
22
|
+
fontStyle?: string,
|
|
23
|
+
upperLetterHeight?: number,
|
|
24
|
+
upperLetterAscent?: number,
|
|
25
|
+
bounds?: DG.Rect,
|
|
26
|
+
textAlign?: CanvasTextAlign,
|
|
27
|
+
textBaseline?: CanvasTextBaseline,
|
|
28
|
+
marginVertical?: number,
|
|
29
|
+
marginHorizontal?: number,
|
|
30
|
+
};
|
|
17
31
|
|
|
18
|
-
export type
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
export type StatsInfo = {
|
|
33
|
+
monomerCol: DG.Column<string>,
|
|
34
|
+
countCol: DG.Column<number>,
|
|
35
|
+
orderedIndexes: Int32Array,
|
|
36
|
+
}
|
|
@@ -3,28 +3,40 @@ import * as DG from 'datagrok-api/dg';
|
|
|
3
3
|
|
|
4
4
|
import $ from 'cash-dom';
|
|
5
5
|
import {PeptidesModel} from '../model';
|
|
6
|
+
import * as C from '../utils/constants';
|
|
7
|
+
import * as CR from '../utils/cell-renderer';
|
|
8
|
+
import * as bio from '@datagrok-libraries/bio';
|
|
6
9
|
|
|
7
10
|
export class LogoSummary extends DG.JsViewer {
|
|
8
11
|
_titleHost = ui.divText('Logo Summary Table', {id: 'pep-viewer-title'});
|
|
9
12
|
model!: PeptidesModel;
|
|
10
13
|
viewerGrid!: DG.Grid;
|
|
11
14
|
initialized: boolean = false;
|
|
15
|
+
webLogoMode: string;
|
|
16
|
+
importanceThreshold: number;
|
|
12
17
|
|
|
13
18
|
constructor() {
|
|
14
19
|
super();
|
|
20
|
+
|
|
21
|
+
this.webLogoMode = this.string('webLogoMode', bio.PositionHeight.full, {choices: [bio.PositionHeight.full, bio.PositionHeight.Entropy]});
|
|
22
|
+
this.importanceThreshold = this.float('importanceThreshold', 0.7);
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
async onTableAttached(): Promise<void> {
|
|
18
26
|
super.onTableAttached();
|
|
19
27
|
|
|
20
28
|
this.model = await PeptidesModel.getInstance(this.dataFrame);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.viewerGrid = grid;
|
|
29
|
+
this.subs.push(this.model.onSettingsChanged.subscribe(() => {
|
|
30
|
+
this.createLogoSummaryGrid();
|
|
24
31
|
this.render();
|
|
25
32
|
}));
|
|
26
|
-
this.model.
|
|
27
|
-
this.viewerGrid =
|
|
33
|
+
// this.subs.push(this.model.onLogoSummaryGridChanged.subscribe((grid) => {
|
|
34
|
+
// this.viewerGrid = grid;
|
|
35
|
+
// this.render();
|
|
36
|
+
// }));
|
|
37
|
+
// this.model.updateDefault();
|
|
38
|
+
// this.viewerGrid = this.model.logoSummaryGrid;
|
|
39
|
+
this.createLogoSummaryGrid();
|
|
28
40
|
this.initialized = true;
|
|
29
41
|
this.render();
|
|
30
42
|
}
|
|
@@ -39,4 +51,104 @@ export class LogoSummary extends DG.JsViewer {
|
|
|
39
51
|
this.viewerGrid.invalidate();
|
|
40
52
|
}
|
|
41
53
|
}
|
|
54
|
+
|
|
55
|
+
onPropertyChanged(): void {
|
|
56
|
+
this.createLogoSummaryGrid();
|
|
57
|
+
this.render();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createLogoSummaryGrid(): DG.Grid {
|
|
61
|
+
let summaryTableBuilder = this.dataFrame.groupBy([C.COLUMNS_NAMES.CLUSTERS]);
|
|
62
|
+
for (const [colName, aggregationFunc] of Object.entries(this.model.settings.columns ?? {}))
|
|
63
|
+
summaryTableBuilder = summaryTableBuilder.add(aggregationFunc as any, colName, `${aggregationFunc}(${colName})`);
|
|
64
|
+
|
|
65
|
+
const summaryTable = summaryTableBuilder.aggregate();
|
|
66
|
+
const summaryTableLength = summaryTable.rowCount;
|
|
67
|
+
const clustersCol: DG.Column<number> = summaryTable.getCol(C.COLUMNS_NAMES.CLUSTERS);
|
|
68
|
+
const membersCol: DG.Column<number> = summaryTable.columns.addNewInt('Members');
|
|
69
|
+
const webLogoCol: DG.Column<string> = summaryTable.columns.addNew('WebLogo', DG.COLUMN_TYPE.STRING);
|
|
70
|
+
const tempDfList: DG.DataFrame[] = new Array(summaryTableLength);
|
|
71
|
+
const originalClustersCol = this.dataFrame.getCol(C.COLUMNS_NAMES.CLUSTERS);
|
|
72
|
+
const peptideCol: DG.Column<string> = this.dataFrame.getCol(C.COLUMNS_NAMES.MACROMOLECULE);
|
|
73
|
+
|
|
74
|
+
for (let index = 0; index < summaryTableLength; ++index) {
|
|
75
|
+
const indexes: number[] = [];
|
|
76
|
+
for (let j = 0; j < originalClustersCol.length; ++j) {
|
|
77
|
+
if (originalClustersCol.get(j) === clustersCol.get(index))
|
|
78
|
+
indexes.push(j);
|
|
79
|
+
}
|
|
80
|
+
const tCol = DG.Column.string('peptides', indexes.length);
|
|
81
|
+
tCol.init((i) => peptideCol.get(indexes[i]));
|
|
82
|
+
|
|
83
|
+
for (const tag of peptideCol.tags)
|
|
84
|
+
tCol.setTag(tag[0], tag[1]);
|
|
85
|
+
|
|
86
|
+
const uh = new bio.UnitsHandler(tCol);
|
|
87
|
+
tCol.setTag(bio.TAGS.alphabetSize, uh.getAlphabetSize().toString());
|
|
88
|
+
|
|
89
|
+
const dfSlice = DG.DataFrame.fromColumns([tCol]);
|
|
90
|
+
tempDfList[index] = dfSlice;
|
|
91
|
+
webLogoCol.set(index, index.toString());
|
|
92
|
+
membersCol.set(index, dfSlice.rowCount);
|
|
93
|
+
//TODO: user should be able to choose threshold
|
|
94
|
+
if (dfSlice.rowCount <= Math.ceil(this.model.clusterStatsDf.getCol(C.COLUMNS_NAMES.COUNT).stats.max * this.importanceThreshold))
|
|
95
|
+
summaryTable.filter.set(index, false, false);
|
|
96
|
+
}
|
|
97
|
+
webLogoCol.setTag(DG.TAGS.CELL_RENDERER, 'html');
|
|
98
|
+
|
|
99
|
+
this.viewerGrid = summaryTable.plot.grid();
|
|
100
|
+
const gridClustersCol = this.viewerGrid.col(C.COLUMNS_NAMES.CLUSTERS)!;
|
|
101
|
+
gridClustersCol.name = 'Clusters';
|
|
102
|
+
gridClustersCol.visible = true;
|
|
103
|
+
this.viewerGrid.columns.rowHeader!.visible = false;
|
|
104
|
+
this.viewerGrid.props.rowHeight = 55;
|
|
105
|
+
this.viewerGrid.onCellPrepare((cell) => {
|
|
106
|
+
if (cell.isTableCell && cell.tableColumn?.name === 'WebLogo') {
|
|
107
|
+
tempDfList[parseInt(cell.cell.value)].plot.fromType('WebLogo', {maxHeight: 50, positionHeight: this.webLogoMode})
|
|
108
|
+
.then((viewer) => cell.element = viewer.root);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
this.viewerGrid.root.addEventListener('click', (ev) => {
|
|
112
|
+
const cell = this.viewerGrid.hitTest(ev.offsetX, ev.offsetY);
|
|
113
|
+
if (!cell || !cell.isTableCell)
|
|
114
|
+
return;
|
|
115
|
+
|
|
116
|
+
const cluster = clustersCol.get(cell.tableRowIndex!)!;
|
|
117
|
+
summaryTable.currentRowIdx = -1;
|
|
118
|
+
if (ev.shiftKey)
|
|
119
|
+
this.model.modifyClusterSelection(cluster);
|
|
120
|
+
else
|
|
121
|
+
this.model.initClusterSelection(cluster);
|
|
122
|
+
});
|
|
123
|
+
this.viewerGrid.onCellRender.subscribe((gridCellArgs) => {
|
|
124
|
+
const gc = gridCellArgs.cell;
|
|
125
|
+
if (gc.tableColumn?.name !== C.COLUMNS_NAMES.CLUSTERS || gc.isColHeader)
|
|
126
|
+
return;
|
|
127
|
+
const canvasContext = gridCellArgs.g;
|
|
128
|
+
const bound = gridCellArgs.bounds;
|
|
129
|
+
canvasContext.save();
|
|
130
|
+
canvasContext.beginPath();
|
|
131
|
+
canvasContext.rect(bound.x, bound.y, bound.width, bound.height);
|
|
132
|
+
canvasContext.clip();
|
|
133
|
+
CR.renderLogoSummaryCell(canvasContext, gc.cell.value, this.model.logoSummarySelection, bound);
|
|
134
|
+
gridCellArgs.preventDefault();
|
|
135
|
+
canvasContext.restore();
|
|
136
|
+
});
|
|
137
|
+
this.viewerGrid.onCellTooltip((cell, x, y) => {
|
|
138
|
+
if (!cell.isColHeader && cell.tableColumn?.name === C.COLUMNS_NAMES.CLUSTERS)
|
|
139
|
+
this.model.showTooltipCluster(cell.cell.value, x, y);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
const webLogoGridCol = this.viewerGrid.columns.byName('WebLogo')!;
|
|
143
|
+
webLogoGridCol.cellType = 'html';
|
|
144
|
+
webLogoGridCol.width = 350;
|
|
145
|
+
|
|
146
|
+
const gridProps = this.viewerGrid.props;
|
|
147
|
+
gridProps.allowEdit = false;
|
|
148
|
+
gridProps.allowRowSelection = false;
|
|
149
|
+
gridProps.allowBlockSelection = false;
|
|
150
|
+
gridProps.allowColSelection = false;
|
|
151
|
+
|
|
152
|
+
return this.viewerGrid;
|
|
153
|
+
}
|
|
42
154
|
}
|
|
@@ -43,7 +43,7 @@ export class SARViewerBase extends DG.JsViewer {
|
|
|
43
43
|
return;
|
|
44
44
|
if (!refreshOnly) {
|
|
45
45
|
$(this.root).empty();
|
|
46
|
-
let switchHost = ui.
|
|
46
|
+
let switchHost = ui.divText('Most Potent Residues', {id: 'pep-viewer-title'});
|
|
47
47
|
if (this.name == 'MC') {
|
|
48
48
|
const mutationCliffsMode = ui.boolInput('', this.isMutationCliffsMode === '1', () => {
|
|
49
49
|
if (this.isModeChanging)
|
|
@@ -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) {
|