@datagrok/bio 2.9.0 → 2.10.2
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/CHANGELOG.md +26 -1
- package/detectors.js +5 -3
- package/dist/452.js +1 -1
- package/dist/452.js.map +1 -1
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/data/sample_HELM_empty_unkn.csv +11 -0
- package/package.json +4 -4
- package/src/analysis/sequence-space.ts +5 -6
- package/src/apps/get-region-app.ts +65 -0
- package/src/apps/web-logo-app.ts +3 -6
- package/src/package-test.ts +1 -0
- package/src/package-types.ts +13 -0
- package/src/package.ts +165 -59
- package/src/tests/converters-test.ts +2 -3
- package/src/tests/detectors-tests.ts +13 -2
- package/src/tests/renderers-monomer-placer.ts +25 -12
- package/src/tests/scoring.ts +8 -4
- package/src/tests/units-handler-get-region.ts +116 -0
- package/src/utils/cell-renderer.ts +5 -32
- package/src/utils/context-menu.ts +2 -3
- package/src/utils/convert.ts +6 -7
- package/src/utils/get-region-func-editor.ts +261 -0
- package/src/utils/get-region.ts +65 -0
- package/src/utils/multiple-sequence-alignment-ui.ts +21 -17
- package/src/viewers/web-logo-viewer.ts +73 -44
|
@@ -2,12 +2,14 @@ import * as grok from 'datagrok-api/grok';
|
|
|
2
2
|
import * as DG from 'datagrok-api/dg';
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import wu from 'wu';
|
|
6
6
|
import {category, test} from '@datagrok-libraries/utils/src/test';
|
|
7
7
|
import {MonomerPlacer} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
|
|
8
8
|
import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
9
9
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
10
10
|
|
|
11
|
+
import {_package} from '../package-test';
|
|
12
|
+
|
|
11
13
|
category('renderers: monomerPlacer', () => {
|
|
12
14
|
const tests = {
|
|
13
15
|
splitter: {
|
|
@@ -34,21 +36,27 @@ category('renderers: monomerPlacer', () => {
|
|
|
34
36
|
},
|
|
35
37
|
splitterMsa: {
|
|
36
38
|
/** For charWidth=7 and sepWidth=12, MSA
|
|
37
|
-
* Array(10) [
|
|
39
|
+
* Array(10) [5, 38, 71, 104, 137, 170, 203, 222, 255, 281]
|
|
38
40
|
*/
|
|
39
41
|
csv: 'id,seq\n' +
|
|
40
|
-
'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' +
|
|
41
|
-
'id2,m1-mon2-m3-mon4-mon5-Num--MON8-N9\n' +
|
|
42
|
-
'id3
|
|
42
|
+
'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' +
|
|
43
|
+
'id2,m1-mon2-m3-mon4-mon5-Num--MON8-N9\n' +
|
|
44
|
+
'id3,\n' + // empty
|
|
45
|
+
'id4,mon1-M-mon3-mon4-mon5---MON8-N9\n',
|
|
43
46
|
testList: [
|
|
44
47
|
{src: {row: 0, x: -1}, tgt: {pos: null}},
|
|
45
48
|
{src: {row: 1, x: 0}, tgt: {pos: null}},
|
|
46
49
|
{src: {row: 1, x: 1}, tgt: {pos: null}},
|
|
47
|
-
{src: {row: 1, x:
|
|
50
|
+
{src: {row: 1, x: 4}, tgt: {pos: null}},
|
|
51
|
+
{src: {row: 1, x: 5}, tgt: {pos: 0}},
|
|
52
|
+
{src: {row: 1, x: 37}, tgt: {pos: 0}},
|
|
53
|
+
{src: {row: 1, x: 38}, tgt: {pos: 1}},
|
|
48
54
|
{src: {row: 1, x: 170}, tgt: {pos: 4}},
|
|
49
55
|
{src: {row: 1, x: 200}, tgt: {pos: 5}},
|
|
50
|
-
{src: {row: 2, x:
|
|
51
|
-
{src: {row:
|
|
56
|
+
{src: {row: 2, x: 20}, tgt: {pos: null}}, // empty value
|
|
57
|
+
{src: {row: 3, x: 170}, tgt: {pos: 4}},
|
|
58
|
+
{src: {row: 3, x: 200}, tgt: {pos: 5}},
|
|
59
|
+
{src: {row: 3, x: 282}, tgt: {pos: null}},
|
|
52
60
|
]
|
|
53
61
|
},
|
|
54
62
|
fastaMsa: {
|
|
@@ -58,6 +66,7 @@ category('renderers: monomerPlacer', () => {
|
|
|
58
66
|
csv: `id,seq
|
|
59
67
|
id1,QQYNIYPLT
|
|
60
68
|
id2,QQWSSFPYT
|
|
69
|
+
id3,
|
|
61
70
|
id3,QHIRE--LT
|
|
62
71
|
`,
|
|
63
72
|
testList: [
|
|
@@ -67,14 +76,15 @@ id3,QHIRE--LT
|
|
|
67
76
|
{src: {row: 1, x: 19}, tgt: {pos: 0}},
|
|
68
77
|
{src: {row: 1, x: 170}, tgt: {pos: 8}},
|
|
69
78
|
{src: {row: 1, x: 171}, tgt: {pos: 8}},
|
|
70
|
-
{src: {row: 2, x:
|
|
71
|
-
{src: {row:
|
|
79
|
+
{src: {row: 2, x: 5}, tgt: {pos: null}}, // empty value
|
|
80
|
+
{src: {row: 3, x: 170}, tgt: {pos: 8}},
|
|
81
|
+
{src: {row: 3, x: 181}, tgt: {pos: null}},
|
|
72
82
|
]
|
|
73
83
|
},
|
|
74
84
|
};
|
|
75
85
|
|
|
76
86
|
for (const [testName, testData] of Object.entries(tests)) {
|
|
77
|
-
test(`
|
|
87
|
+
test(`getPosition-${testName}`, async () => {
|
|
78
88
|
const df: DG.DataFrame = DG.DataFrame.fromCsv(testData.csv);
|
|
79
89
|
await grok.data.detectSemanticTypes(df);
|
|
80
90
|
const seqCol: DG.Column = df.getCol('seq');
|
|
@@ -94,9 +104,12 @@ id3,QHIRE--LT
|
|
|
94
104
|
});
|
|
95
105
|
|
|
96
106
|
const testList = testData.testList;
|
|
107
|
+
// simulate rendering
|
|
108
|
+
for (let rowI: number = 0; rowI < seqCol.length; ++rowI)
|
|
109
|
+
colTemp.getCellMonomerLengths(rowI);
|
|
97
110
|
|
|
98
111
|
const errorList: string[] = [];
|
|
99
|
-
for (const test of testList) {
|
|
112
|
+
for (const [test, _testI] of wu.enumerate(testList)) {
|
|
100
113
|
const res = {pos: colTemp.getPosition(test.src.row, test.src.x)};
|
|
101
114
|
if (test.tgt.pos != res.pos) {
|
|
102
115
|
errorList.push(`Test src ${JSON.stringify(test.src)} expected tgt ${JSON.stringify(test.tgt)},` +
|
package/src/tests/scoring.ts
CHANGED
|
@@ -26,13 +26,17 @@ category('Scoring', () => {
|
|
|
26
26
|
|
|
27
27
|
test('Identity', async () => {
|
|
28
28
|
const scoresCol = await sequenceIdentityScoring(table, seqCol, reference);
|
|
29
|
-
for (let i = 0; i < scoresCol.length; i++)
|
|
30
|
-
expectFloat(scoresCol.get(i)!, table.get(expectedIdentity, i), 0.01,
|
|
29
|
+
for (let i = 0; i < scoresCol.length; i++) {
|
|
30
|
+
expectFloat(scoresCol.get(i)!, table.get(expectedIdentity, i), 0.01,
|
|
31
|
+
`Wrong identity score for sequence at position ${i}`);
|
|
32
|
+
}
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
test('Similarity', async () => {
|
|
34
36
|
const scoresCol = await sequenceSimilarityScoring(table, seqCol, reference);
|
|
35
|
-
for (let i = 0; i < scoresCol.length; i++)
|
|
36
|
-
expectFloat(scoresCol.get(i)!, table.get(expectedSimilarity, i), 0.01,
|
|
37
|
+
for (let i = 0; i < scoresCol.length; i++) {
|
|
38
|
+
expectFloat(scoresCol.get(i)!, table.get(expectedSimilarity, i), 0.01,
|
|
39
|
+
`Wrong similarity score for sequence at position ${i}`);
|
|
40
|
+
}
|
|
37
41
|
});
|
|
38
42
|
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as DG from 'datagrok-api/dg';
|
|
3
|
+
|
|
4
|
+
import {category, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
|
|
5
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
6
|
+
import {ALPHABET, NOTATION, TAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
|
+
|
|
8
|
+
category('UnitsHandler: getRegion', () => {
|
|
9
|
+
const data: {
|
|
10
|
+
[testName: string]: {
|
|
11
|
+
srcCsv: string,
|
|
12
|
+
startIdx: number | null,
|
|
13
|
+
endIdx: number | null,
|
|
14
|
+
tgtCsv: string,
|
|
15
|
+
units: NOTATION,
|
|
16
|
+
alphabet: ALPHABET | null, /* alphabet is not applicable for units 'helm' */
|
|
17
|
+
|
|
18
|
+
positionNames?: { tag: string | null, start: string | null, end: string | null }
|
|
19
|
+
}
|
|
20
|
+
} = {
|
|
21
|
+
'fastaDna': {
|
|
22
|
+
srcCsv: `seq
|
|
23
|
+
ATTCGT
|
|
24
|
+
ACTGCTC
|
|
25
|
+
ATTCCGTA`,
|
|
26
|
+
startIdx: 2,
|
|
27
|
+
endIdx: 4,
|
|
28
|
+
tgtCsv: `seq
|
|
29
|
+
TCG
|
|
30
|
+
TGC
|
|
31
|
+
TCC`,
|
|
32
|
+
units: NOTATION.FASTA,
|
|
33
|
+
alphabet: ALPHABET.DNA,
|
|
34
|
+
|
|
35
|
+
positionNames: {tag: 'a, b, c, d, e, f, g, h', start: 'c', end: 'e'},
|
|
36
|
+
},
|
|
37
|
+
'separatorPt': {
|
|
38
|
+
srcCsv: `seq
|
|
39
|
+
M-D-Y-K-E-T-L
|
|
40
|
+
M-I-E-V-F-L-F-G-I
|
|
41
|
+
M-M-`,
|
|
42
|
+
startIdx: 5,
|
|
43
|
+
endIdx: null,
|
|
44
|
+
tgtCsv: `seq
|
|
45
|
+
T-L--
|
|
46
|
+
L-F-G-I
|
|
47
|
+
---`,
|
|
48
|
+
units: NOTATION.SEPARATOR,
|
|
49
|
+
alphabet: ALPHABET.PT,
|
|
50
|
+
|
|
51
|
+
positionNames: {tag: '1, 1A, 1B, 2, 3, 4, 4A, 4A, 4C', start: '4', end: null},
|
|
52
|
+
},
|
|
53
|
+
'helm': {
|
|
54
|
+
srcCsv: `seq
|
|
55
|
+
PEPTIDE1{[meI].[hHis].[Aca].N.T.[dE].[Thr_PO3H2].[Aca].[D-Tyr_Et].[Tyr_ab-dehydroMe].[dV].E.N.[D-Orn]}$$$$
|
|
56
|
+
PEPTIDE1{[meI].[hHis].[Aca].[Cys_SEt].T.[dK].[Thr_PO3H2].[Aca].[Tyr_PO3H2].[D-Chg].[dV].[Phe_ab-dehydro]}$$$$
|
|
57
|
+
PEPTIDE1{[Lys_Boc].[hHis].[Aca].[Cys_SEt].T}$$$$`,
|
|
58
|
+
startIdx: 3,
|
|
59
|
+
endIdx: 6,
|
|
60
|
+
tgtCsv: `seq
|
|
61
|
+
PEPTIDE1{N.T.[dE].[Thr_PO3H2]}$$$$
|
|
62
|
+
PEPTIDE1{[Cys_SEt].T.[dK].[Thr_PO3H2]}$$$$
|
|
63
|
+
PEPTIDE1{[Cys_SEt].T.*.*}$$$$`,
|
|
64
|
+
units: NOTATION.HELM,
|
|
65
|
+
alphabet: null,
|
|
66
|
+
|
|
67
|
+
positionNames: {tag: null, start: '4', end: '7'}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (const [testName, testData] of Object.entries(data)) {
|
|
72
|
+
test(`${testName}-idx`, async () => {
|
|
73
|
+
const srcDf = DG.DataFrame.fromCsv(testData.srcCsv);
|
|
74
|
+
const srcSeqCol = srcDf.getCol('seq');
|
|
75
|
+
|
|
76
|
+
const semType: string | null = await grok.functions.call('Bio:detectMacromolecule', {col: srcSeqCol});
|
|
77
|
+
if (semType) srcSeqCol.semType = semType;
|
|
78
|
+
|
|
79
|
+
const srcUh = UnitsHandler.getOrCreate(srcSeqCol);
|
|
80
|
+
const resSeqCol = srcUh.getRegion(testData.startIdx, testData.endIdx, 'regSeq');
|
|
81
|
+
|
|
82
|
+
const tgtDf = DG.DataFrame.fromCsv(testData.tgtCsv);
|
|
83
|
+
const tgtSeqCol = tgtDf.getCol('seq');
|
|
84
|
+
|
|
85
|
+
expect(srcSeqCol.getTag(DG.TAGS.UNITS), testData.units);
|
|
86
|
+
expect(resSeqCol.getTag(DG.TAGS.UNITS), testData.units);
|
|
87
|
+
expect(srcSeqCol.getTag(TAGS.alphabet), testData.alphabet);
|
|
88
|
+
expect(resSeqCol.getTag(TAGS.alphabet), testData.alphabet);
|
|
89
|
+
expectArray(resSeqCol.toList(), tgtSeqCol.toList());
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (testData.positionNames) {
|
|
93
|
+
test(`${testName}-positionNames`, async () => {
|
|
94
|
+
const srcDf = DG.DataFrame.fromCsv(testData.srcCsv);
|
|
95
|
+
const srcSeqCol = srcDf.getCol('seq');
|
|
96
|
+
if (testData.positionNames!.tag)
|
|
97
|
+
srcSeqCol.setTag(TAGS.positionNames, testData.positionNames!.tag);
|
|
98
|
+
|
|
99
|
+
const semType: string | null = await grok.functions.call('Bio:detectMacromolecule', {col: srcSeqCol});
|
|
100
|
+
if (semType) srcSeqCol.semType = semType;
|
|
101
|
+
|
|
102
|
+
const resSeqCol = await grok.functions.call('Bio:getRegion',
|
|
103
|
+
{sequence: srcSeqCol, start: testData.positionNames!.start, end: testData.positionNames!.end});
|
|
104
|
+
|
|
105
|
+
const tgtDf = DG.DataFrame.fromCsv(testData.tgtCsv);
|
|
106
|
+
const tgtSeqCol = tgtDf.getCol('seq');
|
|
107
|
+
|
|
108
|
+
expect(srcSeqCol.getTag(DG.TAGS.UNITS), testData.units);
|
|
109
|
+
expect(resSeqCol.getTag(DG.TAGS.UNITS), testData.units);
|
|
110
|
+
expect(srcSeqCol.getTag(TAGS.alphabet), testData.alphabet);
|
|
111
|
+
expect(resSeqCol.getTag(TAGS.alphabet), testData.alphabet);
|
|
112
|
+
expectArray(resSeqCol.toList(), tgtSeqCol.toList());
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -143,8 +143,11 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
143
143
|
// Cell renderer settings
|
|
144
144
|
const tempMonomerWidth: string | null = tableColTemp[tempTAGS.monomerWidth];
|
|
145
145
|
const monomerWidth: string = (tempMonomerWidth != null) ? tempMonomerWidth : 'short';
|
|
146
|
-
if (monomerWidth === 'short')
|
|
147
|
-
|
|
146
|
+
if (monomerWidth === 'short') {
|
|
147
|
+
// Renderer can start to work before Bio package initialized, in that time _package.properties is null.
|
|
148
|
+
// TODO: Render function is available but package init method is not completed
|
|
149
|
+
maxLengthOfMonomer = tableColTemp[mmcrTemps.maxMonomerLength] ?? _package.properties?.MaxMonomerLength ?? 4;
|
|
150
|
+
}
|
|
148
151
|
|
|
149
152
|
|
|
150
153
|
let seqColTemp: MonomerPlacer = tableCol.temp[tempTAGS.bioSeqCol];
|
|
@@ -206,36 +209,6 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
206
209
|
((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
|
|
207
210
|
tempReferenceSequence : tempCurrentWord ?? '');
|
|
208
211
|
|
|
209
|
-
// let maxLengthWords: { [pos: number]: number } = {};
|
|
210
|
-
// if (tableCol.getTag(rndrTAGS.calculatedCellRender) !== splitLimit.toString()) {
|
|
211
|
-
// let sampleCount = 0;
|
|
212
|
-
// while (sampleCount < Math.min(tableCol.length, 100)) {
|
|
213
|
-
// const rowIdx: number = sampleCount;
|
|
214
|
-
// const column = tableCol.get(rowIdx);
|
|
215
|
-
// const subParts: string[] = splitterFunc(column);
|
|
216
|
-
// for (const [index, amino] of subParts.entries()) {
|
|
217
|
-
// const textSize = monomerToShortFunction(amino, maxLengthOfMonomer).length * 7 + gapRenderer;
|
|
218
|
-
// if (textSize > (maxLengthWords[index] ?? 0))
|
|
219
|
-
// maxLengthWords[index] = textSize;
|
|
220
|
-
// if (index > maxIndex) maxIndex = index;
|
|
221
|
-
// }
|
|
222
|
-
// sampleCount += 1;
|
|
223
|
-
// }
|
|
224
|
-
// const minLength = 3 * 7;
|
|
225
|
-
// for (let i = 0; i <= maxIndex; i++) {
|
|
226
|
-
// if (maxLengthWords[i] < minLength) maxLengthWords[i] = minLength;
|
|
227
|
-
// const maxLengthWordSum: { [pos: number]: number } = {};
|
|
228
|
-
// maxLengthWordSum[0] = maxLengthWords[0];
|
|
229
|
-
// for (let i = 1; i <= maxIndex; i++) maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
|
|
230
|
-
// colTemp[tempTAGS.bioSumMaxLengthWords] = maxLengthWordSum;
|
|
231
|
-
// colTemp[tempTAGS.bioMaxIndex] = maxIndex;
|
|
232
|
-
// colTemp[tempTAGS.bioMaxLengthWords] = maxLengthWords;
|
|
233
|
-
// tableCol.setTag(rndrTAGS.calculatedCellRender, splitLimit.toString());
|
|
234
|
-
// }
|
|
235
|
-
// } else {
|
|
236
|
-
// maxLengthWords = colTemp[tempTAGS.bioMaxLengthWords];
|
|
237
|
-
// }
|
|
238
|
-
|
|
239
212
|
const subParts: ISeqSplitted = splitterFunc(value);
|
|
240
213
|
/* let x1 = x; */
|
|
241
214
|
let color = undefinedColor;
|
|
@@ -4,7 +4,6 @@ import * as ui from 'datagrok-api/ui';
|
|
|
4
4
|
|
|
5
5
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
6
6
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
|
-
import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
|
|
8
7
|
|
|
9
8
|
import {_package} from '../package';
|
|
10
9
|
|
|
@@ -15,9 +14,9 @@ export function addCopyMenuUI(cell: DG.Cell, menu: DG.Menu): void {
|
|
|
15
14
|
|
|
16
15
|
menu.group('Copy')
|
|
17
16
|
.items(tgtNotationList, (tgtNotation) => {
|
|
18
|
-
const
|
|
17
|
+
const ncUH = UnitsHandler.getOrCreate(cell.column);
|
|
19
18
|
const separator = tgtNotation === NOTATION.SEPARATOR ? _package.properties.DefaultSeparator : undefined;
|
|
20
|
-
const converter =
|
|
19
|
+
const converter = ncUH.getConverter(tgtNotation as NOTATION, separator);
|
|
21
20
|
const tgtSeq = converter(cell.value);
|
|
22
21
|
|
|
23
22
|
if (!navigator.clipboard) {
|
package/src/utils/convert.ts
CHANGED
|
@@ -5,7 +5,6 @@ import * as grok from 'datagrok-api/grok';
|
|
|
5
5
|
import $ from 'cash-dom';
|
|
6
6
|
import {Subscription} from 'rxjs';
|
|
7
7
|
import {NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
|
-
import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
|
|
9
8
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
10
9
|
import {expect} from '@datagrok-libraries/utils/src/test';
|
|
11
10
|
|
|
@@ -22,8 +21,8 @@ export function convert(col?: DG.Column): void {
|
|
|
22
21
|
let tgtCol = col ?? grok.shell.t.columns.bySemType('Macromolecule')!;
|
|
23
22
|
if (!tgtCol)
|
|
24
23
|
throw new Error('No column with Macromolecule semantic type found');
|
|
25
|
-
let
|
|
26
|
-
let currentNotation: NOTATION =
|
|
24
|
+
let converterUH = UnitsHandler.getOrCreate(tgtCol);
|
|
25
|
+
let currentNotation: NOTATION = converterUH.notation;
|
|
27
26
|
const dialogHeader = ui.divText(
|
|
28
27
|
'Current notation: ' + currentNotation,
|
|
29
28
|
{
|
|
@@ -47,8 +46,8 @@ export function convert(col?: DG.Column): void {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
tgtCol = newCol;
|
|
50
|
-
|
|
51
|
-
currentNotation =
|
|
49
|
+
converterUH = UnitsHandler.getOrCreate(tgtCol);
|
|
50
|
+
currentNotation = converterUH.notation;
|
|
52
51
|
if (currentNotation === NOTATION.HELM)
|
|
53
52
|
separatorInput.value = '/'; // helm monomers can have - in the name like D-aThr;
|
|
54
53
|
dialogHeader.textContent = 'Current notation: ' + currentNotation;
|
|
@@ -117,8 +116,8 @@ export function convert(col?: DG.Column): void {
|
|
|
117
116
|
* @param {string | null} separator Separator for SEPARATOR notation
|
|
118
117
|
*/
|
|
119
118
|
export async function convertDo(srcCol: DG.Column, targetNotation: NOTATION, separator?: string): Promise<DG.Column> {
|
|
120
|
-
const
|
|
121
|
-
const newColumn =
|
|
119
|
+
const converterUH = UnitsHandler.getOrCreate(srcCol);
|
|
120
|
+
const newColumn = converterUH.convert(targetNotation, separator);
|
|
122
121
|
srcCol.dataFrame.columns.add(newColumn);
|
|
123
122
|
|
|
124
123
|
// Call detector directly to escape some error on detectSemanticTypes
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
6
|
+
|
|
7
|
+
export interface GetRegionParams {
|
|
8
|
+
table: DG.DataFrame,
|
|
9
|
+
sequence: DG.Column<string>,
|
|
10
|
+
start: string | null,
|
|
11
|
+
end: string | null,
|
|
12
|
+
/** Name for the column with sequence of the region */ name: string | null,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
16
|
+
import {_package} from '../package';
|
|
17
|
+
|
|
18
|
+
export interface SeqRegion {
|
|
19
|
+
name: string,
|
|
20
|
+
description: string,
|
|
21
|
+
start: string,
|
|
22
|
+
end: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class GetRegionFuncEditor {
|
|
26
|
+
inputs = new class {
|
|
27
|
+
table: DG.InputBase<DG.DataFrame | null>;
|
|
28
|
+
sequence: DG.InputBase<DG.Column | null>;
|
|
29
|
+
region: DG.InputBase<SeqRegion>;
|
|
30
|
+
start: DG.InputBase<string>;
|
|
31
|
+
end: DG.InputBase<string>;
|
|
32
|
+
name: DG.InputBase<string>;
|
|
33
|
+
}();
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly call: DG.FuncCall
|
|
37
|
+
) {
|
|
38
|
+
const getDesc = (paramName: string) => this.call.inputParams[paramName].property.description;
|
|
39
|
+
|
|
40
|
+
this.inputs.table = ui.tableInput('Table',
|
|
41
|
+
this.call.inputParams['table'].value ?? grok.shell.tv.dataFrame, undefined,
|
|
42
|
+
() => {});
|
|
43
|
+
|
|
44
|
+
const seqColValue = this.call.inputParams['sequence'].value ??
|
|
45
|
+
this.inputs.table.value!.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
46
|
+
const seqColOptions = {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE};
|
|
47
|
+
this.inputs.sequence = ui.columnInput('Sequence', grok.shell.tv.dataFrame, seqColValue,
|
|
48
|
+
this.sequenceInputChanged.bind(this), seqColOptions);
|
|
49
|
+
this.inputs.start = ui.choiceInput(
|
|
50
|
+
'Start', undefined, [], this.startInputChanged.bind(this)) as unknown as DG.InputBase<string>;
|
|
51
|
+
this.inputs.end = ui.choiceInput(
|
|
52
|
+
'End', undefined, [], this.endInputChanged.bind(this)) as unknown as DG.InputBase<string>;
|
|
53
|
+
|
|
54
|
+
this.inputs.region = ui.choiceInput<SeqRegion>('Region', null as unknown as SeqRegion, [],
|
|
55
|
+
this.regionInputChanged.bind(this)) as DG.InputBase<SeqRegion>;
|
|
56
|
+
|
|
57
|
+
this.inputs.name = ui.stringInput('Column name', this.getDefaultName(),
|
|
58
|
+
this.nameInputChanged.bind(this), {clearIcon: true});
|
|
59
|
+
this.inputs.name.onInput(this.nameInputInput.bind(this)); // To catch clear event
|
|
60
|
+
|
|
61
|
+
// tooltips
|
|
62
|
+
for (const paramName in this.call.inputParams) {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
ui.tooltip.bind(this.inputs[paramName].captionLabel, getDesc(paramName));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// initial
|
|
68
|
+
this.sequenceInputChanged();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private sequenceInputChanged(): void {
|
|
72
|
+
const seqCol = this.inputs.sequence.value;
|
|
73
|
+
const uh = seqCol ? UnitsHandler.getOrCreate(seqCol) : null;
|
|
74
|
+
this.updateRegionItems();
|
|
75
|
+
this.updateStartEndInputItems();
|
|
76
|
+
this.updateRegion(true);
|
|
77
|
+
this.updateNameInput();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fixRegion: boolean = false;
|
|
81
|
+
|
|
82
|
+
private regionInputChanged(): void {
|
|
83
|
+
this.fixRegion = true;
|
|
84
|
+
try {
|
|
85
|
+
const regJsonStr = this.inputs.region.stringValue;
|
|
86
|
+
const reg: SeqRegion | null = regJsonStr ? JSON.parse(regJsonStr) as SeqRegion : null;
|
|
87
|
+
|
|
88
|
+
if (reg !== null) {
|
|
89
|
+
this.inputs.start.value = reg?.start;
|
|
90
|
+
this.inputs.end.value = reg?.end;
|
|
91
|
+
} else {
|
|
92
|
+
const uh = UnitsHandler.getOrCreate(this.inputs.sequence.value!);
|
|
93
|
+
this.inputs.start.value = uh.posList[0];
|
|
94
|
+
this.inputs.end.value = uh.posList[uh.posList.length - 1];
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
this.fixRegion = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private startInputChanged(): void {
|
|
102
|
+
this.updateRegion(false);
|
|
103
|
+
this.updateNameInput();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private endInputChanged(): void {
|
|
107
|
+
this.updateRegion(false);
|
|
108
|
+
this.updateNameInput();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private nameInputChanged(): void {
|
|
112
|
+
if (!this.defaultNameUpdating)
|
|
113
|
+
this.defaultName = false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private nameInputInput(): void {
|
|
117
|
+
if (!this.inputs.name.value) {
|
|
118
|
+
this.defaultName = true;
|
|
119
|
+
this.inputs.name.input.focus();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private updateStartEndInputItems(): void {
|
|
124
|
+
const seqCol = this.inputs.sequence.value;
|
|
125
|
+
const uh = seqCol ? UnitsHandler.getOrCreate(seqCol) : null;
|
|
126
|
+
|
|
127
|
+
const startSE = (this.inputs.start.input as HTMLSelectElement);
|
|
128
|
+
const endSE = (this.inputs.end.input as HTMLSelectElement);
|
|
129
|
+
for (let i = startSE.options.length - 1; i >= 0; --i) startSE.options.remove(i);
|
|
130
|
+
for (let i = endSE.options.length - 1; i >= 0; --i) endSE.options.remove(i);
|
|
131
|
+
for (const pos of uh?.posList ?? []) {
|
|
132
|
+
const startPosOE = document.createElement('option');
|
|
133
|
+
const endPosOE = document.createElement('option');
|
|
134
|
+
startPosOE.text = endPosOE.text = pos;
|
|
135
|
+
startPosOE.value = endPosOE.value = pos;
|
|
136
|
+
startSE.options.add(startPosOE);
|
|
137
|
+
endSE.options.add(endPosOE);
|
|
138
|
+
}
|
|
139
|
+
startSE.value = uh?.posList[0] ?? '';
|
|
140
|
+
endSE.value = uh?.posList[uh?.posList.length - 1] ?? '';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private updateRegionItems(): void {
|
|
144
|
+
const seqCol = this.inputs.sequence.value;
|
|
145
|
+
const regionsTagTxt: string | null = seqCol ? seqCol.getTag(bioTAGS.regions) : null;
|
|
146
|
+
const regionList: SeqRegion[] | null = regionsTagTxt ? JSON.parse(regionsTagTxt) : null;
|
|
147
|
+
|
|
148
|
+
const regionSE = (this.inputs.region.input as HTMLSelectElement);
|
|
149
|
+
for (let i = regionSE.options.length - 1; i >= 0; --i) regionSE.options.remove(i);
|
|
150
|
+
|
|
151
|
+
const nullOE = document.createElement('option');
|
|
152
|
+
nullOE.text = '';
|
|
153
|
+
nullOE.value = JSON.stringify(null);
|
|
154
|
+
regionSE.options.add(nullOE);
|
|
155
|
+
|
|
156
|
+
if (regionList != null) {
|
|
157
|
+
this.inputs.region.root.style.removeProperty('display');
|
|
158
|
+
for (const region of regionList) {
|
|
159
|
+
const regionOE = document.createElement('option');
|
|
160
|
+
regionOE.text = `${region.name}: ${region.start}-${region.end}`;
|
|
161
|
+
regionOE.value = JSON.stringify(region);
|
|
162
|
+
regionSE.options.add(regionOE);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
this.inputs.region.root.style.display = 'none';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private updateRegion(reset: boolean): void {
|
|
170
|
+
const startPos: string = this.inputs.start.stringValue ?? '';
|
|
171
|
+
const endPos: string = this.inputs.end.stringValue ?? '';
|
|
172
|
+
|
|
173
|
+
if (!this.fixRegion) {
|
|
174
|
+
const regionSE = (this.inputs.region.input as HTMLSelectElement);
|
|
175
|
+
regionSE.selectedIndex = -1;
|
|
176
|
+
for (let i = regionSE.options.length - 1; i >= 0; --i) {
|
|
177
|
+
const regionOE = regionSE.options[i];
|
|
178
|
+
const reg: SeqRegion = JSON.parse(regionOE.value);
|
|
179
|
+
if (reg && startPos === reg.start && endPos === reg.end) regionSE.selectedIndex = i;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private defaultName: boolean = true;
|
|
185
|
+
private defaultNameUpdating: boolean = false;
|
|
186
|
+
|
|
187
|
+
private updateNameInput(): void {
|
|
188
|
+
this.defaultNameUpdating = true;
|
|
189
|
+
try {
|
|
190
|
+
if (this.defaultName) this.inputs.name.value = this.getDefaultName();
|
|
191
|
+
} finally {
|
|
192
|
+
this.defaultNameUpdating = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private getDefaultName(): string {
|
|
197
|
+
const regionJsonStr = this.inputs.region.stringValue;
|
|
198
|
+
const reg: SeqRegion | null = regionJsonStr ? JSON.parse(regionJsonStr) : null;
|
|
199
|
+
|
|
200
|
+
const seqCol: DG.Column<string> = this.inputs.sequence.value!;
|
|
201
|
+
|
|
202
|
+
const startPos: string = this.inputs.start.stringValue ?? '';
|
|
203
|
+
const endPos: string = this.inputs.end.stringValue ?? '';
|
|
204
|
+
|
|
205
|
+
return reg != null ? `${seqCol.name}(${reg.name}): ${reg.start}-${reg.end}` :
|
|
206
|
+
`${seqCol?.name}: (${startPos}-${endPos})`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private getParams(): {} {
|
|
210
|
+
return {
|
|
211
|
+
table: this.inputs.table.value!,
|
|
212
|
+
sequence: this.inputs.sequence.value!,
|
|
213
|
+
start: this.getStart(),
|
|
214
|
+
end: this.getEnd(),
|
|
215
|
+
name: this.getName(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private getStart(): string | null {
|
|
220
|
+
return this.inputs.start.stringValue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private getEnd(): string | null {
|
|
224
|
+
return this.inputs.end.stringValue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private getName(): string | null {
|
|
228
|
+
const str = this.inputs.name.stringValue;
|
|
229
|
+
return str == '' ? null : str;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// -- UI --
|
|
233
|
+
|
|
234
|
+
public dialog(): void {
|
|
235
|
+
const inputsForm = ui.inputs(Object.values(this.inputs), {style: {minWidth: '320px'}});
|
|
236
|
+
ui.dialog({title: 'Get Region'})
|
|
237
|
+
.add(inputsForm)
|
|
238
|
+
.onOK(async () => {
|
|
239
|
+
(async () => {
|
|
240
|
+
const callParams = this.getParams();
|
|
241
|
+
await this.call.func.prepare(callParams).call(true);
|
|
242
|
+
})()
|
|
243
|
+
.catch((err: any) => { _package.handleErrorUI(err); });
|
|
244
|
+
})
|
|
245
|
+
.show();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public widget(): DG.Widget {
|
|
249
|
+
const inputsForm = ui.inputs(Object.entries(this.inputs)
|
|
250
|
+
.filter(([inputName, input]) => !['table', 'sequence'].includes(inputName))
|
|
251
|
+
.map(([inputName, input]) => input));
|
|
252
|
+
const doBtn = ui.button('Get Region', () => {
|
|
253
|
+
(async () => {
|
|
254
|
+
const callParams = this.getParams();
|
|
255
|
+
await this.call.func.prepare(callParams).call(true);
|
|
256
|
+
})()
|
|
257
|
+
.catch((err: any) => { _package.handleErrorUI(err); });
|
|
258
|
+
});
|
|
259
|
+
return DG.Widget.fromRoot(ui.divV([inputsForm, ui.div(doBtn)]));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
6
|
+
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
7
|
+
import {getRegion} from '../package';
|
|
8
|
+
import {TaskBarProgressIndicator} from 'datagrok-api/dg';
|
|
9
|
+
|
|
10
|
+
export function getRegionUI(col: DG.Column<string>): void {
|
|
11
|
+
const uh = UnitsHandler.getOrCreate(col);
|
|
12
|
+
|
|
13
|
+
const nameInput = ui.stringInput('Name', '');
|
|
14
|
+
const startPositionInput = ui.choiceInput('Start Position', uh.posList[0], uh.posList,
|
|
15
|
+
() => { /* TODO: update name placeholder with getDefaultName() */ });
|
|
16
|
+
const endPositionInput = ui.choiceInput('End Position', uh.posList[uh.posList.length], uh.posList,
|
|
17
|
+
() => { /* TODO: update name placeholder with getDefaultName() */ });
|
|
18
|
+
|
|
19
|
+
const getDefaultName = (): string => {
|
|
20
|
+
return `${col.name}:${startPositionInput.value}-${endPositionInput.value}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
ui.dialog({title: 'Get Region'}).add(ui.inputs([
|
|
24
|
+
nameInput,
|
|
25
|
+
startPositionInput,
|
|
26
|
+
endPositionInput,
|
|
27
|
+
])).onOK(() => {
|
|
28
|
+
const pi = TaskBarProgressIndicator.create('Getting region...');
|
|
29
|
+
try {
|
|
30
|
+
const name: string = nameInput.value ?? getDefaultName();
|
|
31
|
+
const regCol = getRegionDo(col, name, startPositionInput.value, endPositionInput.value);
|
|
32
|
+
col.dataFrame.columns.add(regCol);
|
|
33
|
+
regCol.setTag(DG.TAGS.CELL_RENDERER, 'sequence');
|
|
34
|
+
} catch (err: any) {
|
|
35
|
+
grok.shell.error(err.toString());
|
|
36
|
+
} finally { pi.close(); }
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** {@link startPosName} and {@link endPosName} are according positionNames tag (or default ['1', '2',...]) */
|
|
41
|
+
export function getRegionDo(
|
|
42
|
+
col: DG.Column<string>, startPosName: string | null, endPosName: string | null, name: string | null
|
|
43
|
+
): DG.Column<string> {
|
|
44
|
+
const uh = UnitsHandler.getOrCreate(col);
|
|
45
|
+
|
|
46
|
+
let startPosIdx: number | null = null;
|
|
47
|
+
let endPosIdx: number | null = null;
|
|
48
|
+
|
|
49
|
+
for (let posJ: number = 0; posJ < uh.posList.length; ++posJ) {
|
|
50
|
+
if (uh.posList[posJ] == startPosName) startPosIdx = posJ;
|
|
51
|
+
if (uh.posList[posJ] == endPosName) endPosIdx = posJ;
|
|
52
|
+
}
|
|
53
|
+
if (startPosIdx === null && startPosName !== null)
|
|
54
|
+
throw new Error(`Start position ${startPosName} not found.`);
|
|
55
|
+
if (endPosIdx === null && endPosName !== null)
|
|
56
|
+
throw new Error(`End position ${endPosName} not found.`);
|
|
57
|
+
|
|
58
|
+
if (uh.posList.length < endPosIdx!)
|
|
59
|
+
throw new Error(`End position ${endPosIdx} exceeds positions length`);
|
|
60
|
+
|
|
61
|
+
const regColName: string = !!name ? name : `${col.name}: (${startPosName ?? ''}-${endPosName ?? ''})`;
|
|
62
|
+
|
|
63
|
+
const regCol = uh.getRegion(startPosIdx, endPosIdx, regColName);
|
|
64
|
+
return regCol;
|
|
65
|
+
}
|