@datagrok/sequence-translator 1.0.2 → 1.0.5
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/detectors.js +17 -16
- package/dist/package-test.js +41 -44
- package/dist/package.js +694 -567
- package/package.json +22 -17
- package/src/__jest__/remote.test.ts +33 -14
- package/src/autostart/registration.ts +194 -0
- package/src/{axolabsMap.ts → axolabs/constants.ts} +1 -1
- package/src/{defineAxolabsPattern.ts → axolabs/define-pattern.ts} +87 -62
- package/src/{drawAxolabsPattern.ts → axolabs/draw-svg.ts} +1 -1
- package/src/main/main-view.ts +225 -0
- package/src/package.ts +11 -370
- package/src/structures-works/converters.ts +4 -1
- package/src/structures-works/from-monomers.ts +4 -3
- package/src/structures-works/map.ts +1 -0
- package/src/structures-works/mol-transformations.ts +7 -5
- package/src/structures-works/sequence-codes-tools.ts +24 -24
- package/test-SequenceTranslator-4f0c8bae6479-6545fe31.html +276 -0
- package/vendors/openchemlib-full.js +293 -0
- package/webpack.config.js +1 -1
- package/test-SequenceTranslator-b5ff4c12f22d-f3bfb562.html +0 -245
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/sequence-translator",
|
|
3
3
|
"friendlyName": "Sequence Translator",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Vadym Kovadlo",
|
|
7
|
+
"email": "vkovadlo@datagrok.ai"
|
|
8
|
+
},
|
|
5
9
|
"description": "SequenceTranslator is a [package](https://datagrok.ai/help/develop/develop#packages) for the [Datagrok](https://datagrok.ai) platform, used to translate [oligonucleotide](https://en.wikipedia.org/wiki/Oligonucleotide) sequences between [different representations](https://github.com/datagrok-ai/public/tree/master/packages/SequenceTranslator#sequence-representations).",
|
|
6
10
|
"repository": {
|
|
7
11
|
"type": "git",
|
|
@@ -10,46 +14,47 @@
|
|
|
10
14
|
},
|
|
11
15
|
"dependencies": {
|
|
12
16
|
"@datagrok-libraries/utils": "^0.1.0",
|
|
13
|
-
"@types/react": "
|
|
17
|
+
"@types/react": "^18.0.15",
|
|
14
18
|
"datagrok-api": "^1.1.7",
|
|
15
19
|
"datagrok-tools": "^4.1.2",
|
|
16
20
|
"npm": "^8.11.0",
|
|
21
|
+
"openchemlib": "6.0.1",
|
|
17
22
|
"save-svg-as-png": "^1.4.17",
|
|
18
|
-
"ts-loader": "
|
|
19
|
-
"typescript": "
|
|
20
|
-
"openchemlib": "6.0.1"
|
|
23
|
+
"ts-loader": "^9.3.1",
|
|
24
|
+
"typescript": "^4.7.4"
|
|
21
25
|
},
|
|
22
26
|
"scripts": {
|
|
23
27
|
"link-api": "npm link datagrok-api",
|
|
24
|
-
"debug-sequencetranslator": "grok publish
|
|
25
|
-
"release-sequencetranslator": "grok publish
|
|
28
|
+
"debug-sequencetranslator": "grok publish",
|
|
29
|
+
"release-sequencetranslator": "grok publish localhost --release",
|
|
26
30
|
"build-sequencetranslator": "webpack",
|
|
27
31
|
"build": "webpack",
|
|
28
|
-
"debug-sequencetranslator-public": "grok publish public
|
|
29
|
-
"release-sequencetranslator-public": "grok publish public --
|
|
30
|
-
"debug-sequencetranslator-local": "grok publish local
|
|
31
|
-
"release-sequencetranslator-local": "grok publish local --
|
|
32
|
+
"debug-sequencetranslator-public": "grok publish public",
|
|
33
|
+
"release-sequencetranslator-public": "grok publish public --release",
|
|
34
|
+
"debug-sequencetranslator-local": "grok publish local",
|
|
35
|
+
"release-sequencetranslator-local": "grok publish local --release",
|
|
32
36
|
"test": "set HOST=dev && jest",
|
|
33
37
|
"test-dev": "set HOST=dev && jest",
|
|
34
38
|
"test-local": "set HOST=localhost && jest"
|
|
35
39
|
},
|
|
36
40
|
"sources": [
|
|
37
41
|
"css/style.css",
|
|
38
|
-
"
|
|
42
|
+
"vendors/openchemlib-full.js"
|
|
39
43
|
],
|
|
40
44
|
"devDependencies": {
|
|
45
|
+
"@types/jest": "^27.0.0",
|
|
46
|
+
"@types/jquery": "^3.5.14",
|
|
41
47
|
"@typescript-eslint/eslint-plugin": "^4.29.1",
|
|
42
48
|
"@typescript-eslint/parser": "^4.29.1",
|
|
43
49
|
"cash-dom": "^8.1.0",
|
|
44
50
|
"eslint": "^7.32.0",
|
|
45
51
|
"eslint-config-google": "^0.14.0",
|
|
46
|
-
"webpack": "^5.31.0",
|
|
47
|
-
"webpack-cli": "^4.6.0",
|
|
48
|
-
"jest-html-reporter": "^3.5.0",
|
|
49
52
|
"jest": "^27.0.0",
|
|
50
|
-
"
|
|
53
|
+
"jest-html-reporter": "^3.5.0",
|
|
54
|
+
"puppeteer": "^13.7.0",
|
|
51
55
|
"ts-jest": "^27.0.0",
|
|
52
|
-
"
|
|
56
|
+
"webpack": "^5.31.0",
|
|
57
|
+
"webpack-cli": "^4.6.0"
|
|
53
58
|
},
|
|
54
59
|
"category": "Bioinformatics"
|
|
55
60
|
}
|
|
@@ -16,34 +16,53 @@ beforeAll(async () => {
|
|
|
16
16
|
}, P_START_TIMEOUT);
|
|
17
17
|
|
|
18
18
|
afterAll(async () => {
|
|
19
|
-
await browser
|
|
19
|
+
await browser?.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect.extend({
|
|
23
|
+
checkOutput(received, expected, context) {
|
|
24
|
+
if (received === expected) {
|
|
25
|
+
return {
|
|
26
|
+
message: () => context,
|
|
27
|
+
pass: true
|
|
28
|
+
};
|
|
29
|
+
} else {
|
|
30
|
+
return {
|
|
31
|
+
message: () => context,
|
|
32
|
+
pass: false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
20
36
|
});
|
|
21
37
|
|
|
22
38
|
it('TEST', async () => {
|
|
23
|
-
const
|
|
24
|
-
console.log(`Testing ${
|
|
39
|
+
const targetPackage:string = process.env.TARGET_PACKAGE ?? 'SequenceTranslator';
|
|
40
|
+
console.log(`Testing ${targetPackage} package`);
|
|
25
41
|
|
|
26
|
-
|
|
27
|
-
let r = await page.evaluate((target_package):Promise<object> => {
|
|
42
|
+
let r = await page.evaluate((targetPackage):Promise<object> => {
|
|
28
43
|
return new Promise<object>((resolve, reject) => {
|
|
29
|
-
(<any>window).grok.functions.eval(
|
|
44
|
+
(<any>window).grok.functions.eval(targetPackage + ':test()').then((df: any) => {
|
|
30
45
|
let cStatus = df.columns.byName('success');
|
|
31
46
|
let cMessage = df.columns.byName('result');
|
|
32
47
|
let cCat = df.columns.byName('category');
|
|
33
48
|
let cName = df.columns.byName('name');
|
|
34
49
|
let failed = false;
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
let passReport = '';
|
|
51
|
+
let failReport = '';
|
|
52
|
+
for (let i = 0; i < df.rowCount; i++) {
|
|
53
|
+
if (cStatus.get(i)) {
|
|
54
|
+
passReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
|
|
55
|
+
} else {
|
|
39
56
|
failed = true;
|
|
57
|
+
failReport += `Test result : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
|
|
40
58
|
}
|
|
41
|
-
|
|
59
|
+
}
|
|
60
|
+
resolve({failReport, passReport, failed});
|
|
42
61
|
}).catch((e: any) => reject(e));
|
|
43
62
|
});
|
|
44
|
-
},
|
|
63
|
+
}, targetPackage);
|
|
45
64
|
// @ts-ignore
|
|
46
|
-
console.log(r.
|
|
65
|
+
console.log(r.passReport);
|
|
47
66
|
// @ts-ignore
|
|
48
|
-
expect(r.failed).
|
|
67
|
+
expect(r.failed).checkOutput(false, r.failReport);
|
|
49
68
|
}, 100000);
|
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
import {siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
|
|
5
|
+
} from '../structures-works/converters';
|
|
6
|
+
import {map, COL_NAMES, MODIFICATIONS} from '../structures-works/map';
|
|
7
|
+
import {getFormat} from '../structures-works/sequence-codes-tools';
|
|
8
|
+
import {sequenceToMolV3000} from '../structures-works/from-monomers';
|
|
9
|
+
|
|
10
|
+
import {SALTS_CSV} from '../salts';
|
|
11
|
+
import {USERS_CSV} from '../users';
|
|
12
|
+
import {ICDS} from '../ICDs';
|
|
13
|
+
import {SOURCES} from '../sources';
|
|
14
|
+
import {IDPS} from '../IDPs';
|
|
15
|
+
|
|
16
|
+
const weightsObj: {[code: string]: number} = {};
|
|
17
|
+
for (const synthesizer of Object.keys(map)) {
|
|
18
|
+
for (const technology of Object.keys(map[synthesizer])) {
|
|
19
|
+
for (const code of Object.keys(map[synthesizer][technology]))
|
|
20
|
+
weightsObj[code] ?? map[synthesizer][technology][code].weight;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const [key, value] of Object.entries(MODIFICATIONS))
|
|
24
|
+
weightsObj[key] = value.molecularWeight;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function sortByStringLengthInDescendingOrder(array: string[]): string[] {
|
|
28
|
+
return array.sort(function(a, b) {return b.length - a.length;});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function stringify(items: string[]): string {
|
|
32
|
+
return '["' + items.join('", "') + '"]';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
|
|
36
|
+
const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
|
|
37
|
+
let weight = 0;
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < sequence.length) {
|
|
40
|
+
const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
|
|
41
|
+
weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
|
|
42
|
+
i += matchedCode.length;
|
|
43
|
+
}
|
|
44
|
+
return weight - 61.97;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function saveTableAsSdFile(table: DG.DataFrame) {
|
|
48
|
+
if (!table.columns.contains('Compound Name')) {
|
|
49
|
+
grok.shell.warning(
|
|
50
|
+
'File saved without columns \'' +
|
|
51
|
+
[COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
52
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const structureColumn = table.getCol(COL_NAMES.SEQUENCE);
|
|
56
|
+
const typeColumn = table.getCol(COL_NAMES.TYPE);
|
|
57
|
+
let result = '';
|
|
58
|
+
for (let i = 0; i < table.rowCount; i++) {
|
|
59
|
+
const format = getFormat(structureColumn.get(i))!;
|
|
60
|
+
result += (typeColumn.get(i) == 'SS') ?
|
|
61
|
+
sequenceToMolV3000(structureColumn.get(i), false, true, format) + '\n' + `> <Sequence>\nSense Strand\n\n` :
|
|
62
|
+
sequenceToMolV3000(structureColumn.get(i), true, true, format) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
|
|
63
|
+
for (const col of table.columns) {
|
|
64
|
+
if (col.name != COL_NAMES.SEQUENCE)
|
|
65
|
+
result += `> <${col.name}>\n${col.get(i)}\n\n`;
|
|
66
|
+
}
|
|
67
|
+
result += '$$$$\n\n';
|
|
68
|
+
}
|
|
69
|
+
const element = document.createElement('a');
|
|
70
|
+
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
71
|
+
element.setAttribute('download', table.name + '.sdf');
|
|
72
|
+
element.click();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function autostartOligoSdFileSubscription() {
|
|
76
|
+
grok.events.onViewAdded.subscribe((v: any) => {
|
|
77
|
+
if (v.type == 'TableView') {
|
|
78
|
+
if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
|
|
79
|
+
oligoSdFile(v.dataFrame);
|
|
80
|
+
grok.events.onContextMenu.subscribe((args) => {
|
|
81
|
+
const seqCol = args.args.context.table.currentCol;
|
|
82
|
+
if (DG.Detector.sampleCategories(seqCol, (s) => /^[fsACGUacgu]{6,}$/.test(s))) {
|
|
83
|
+
args.args.menu.item('Convert Axolabs to GCRS', () => {
|
|
84
|
+
args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
|
|
85
|
+
return siRnaAxolabsToGcrs(seqCol.get(i));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
} else if (DG.Detector.sampleCategories(seqCol, (s) => /^[fmpsACGU]{6,}$/.test(s)) ||
|
|
89
|
+
DG.Detector.sampleCategories(seqCol, (s) => /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(s))) {
|
|
90
|
+
args.args.menu.item('Convert GCRS to raw', () => {
|
|
91
|
+
args.args.context.table.columns.addNewString(seqCol.name + ' to raw').init((i: number) => {
|
|
92
|
+
return gcrsToNucleotides(seqCol.get(i));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
args.args.menu.item('Convert GCRS to MM12', () => {
|
|
96
|
+
args.args.context.table.columns.addNewString(seqCol.name + ' to MM12').init((i: number) => {
|
|
97
|
+
return gcrsToMermade12(seqCol.get(i));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
} else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*56789ATGC]{6,}$/.test(s))) {
|
|
101
|
+
args.args.menu.item('Convert Biospring to GCRS', () => {
|
|
102
|
+
const seqCol = args.args.context.table.currentCol;
|
|
103
|
+
args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
|
|
104
|
+
return asoGapmersBioSpringToGcrs(seqCol.get(i));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
} else if (DG.Detector.sampleCategories(seqCol, (s) => /^[*1-8]{6,}$/.test(s))) {
|
|
108
|
+
args.args.menu.item('Convert Biospring to GCRS', () => {
|
|
109
|
+
args.args.context.table.columns.addNewString(seqCol.name + ' to GCRS').init((i: number) => {
|
|
110
|
+
return siRnaAxolabsToGcrs(seqCol.get(i));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function oligoSdFile(table: DG.DataFrame) {
|
|
120
|
+
const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
|
|
121
|
+
const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
|
|
122
|
+
const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
|
|
123
|
+
const icdsDf = DG.DataFrame.fromCsv(ICDS);
|
|
124
|
+
const idpsDf = DG.DataFrame.fromCsv(IDPS);
|
|
125
|
+
|
|
126
|
+
function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
|
|
127
|
+
if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
|
|
128
|
+
return grok.shell.error('Columns already exist');
|
|
129
|
+
|
|
130
|
+
const sequence = t.getCol(COL_NAMES.SEQUENCE);
|
|
131
|
+
const salt = t.getCol(COL_NAMES.SALT);
|
|
132
|
+
const equivalents = t.getCol(COL_NAMES.EQUIVALENTS);
|
|
133
|
+
|
|
134
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
|
|
135
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
|
|
136
|
+
sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
|
|
137
|
+
sequence.getString(i),
|
|
138
|
+
);
|
|
139
|
+
const molWeightCol = saltsDf.getCol('MOLWEIGHT');
|
|
140
|
+
const saltNamesList = saltsDf.getCol('DISPLAY').toList();
|
|
141
|
+
t.columns.addNewFloat(COL_NAMES.CPD_MW)
|
|
142
|
+
.init((i: number) => molecularWeight(sequence.get(i), weightsObj));
|
|
143
|
+
t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
|
|
144
|
+
const saltRowIndex = saltNamesList.indexOf(salt.get(i));
|
|
145
|
+
const mw = molWeightCol.get(saltRowIndex);
|
|
146
|
+
return mw * equivalents.get(i);
|
|
147
|
+
});
|
|
148
|
+
t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
|
|
149
|
+
'${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
addColumnsPressed = true;
|
|
153
|
+
return newDf = t;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let newDf: DG.DataFrame;
|
|
157
|
+
let addColumnsPressed = false;
|
|
158
|
+
|
|
159
|
+
const d = ui.div([
|
|
160
|
+
ui.icons.edit(() => {
|
|
161
|
+
d.innerHTML = '';
|
|
162
|
+
if (table.getCol(COL_NAMES.IDP).type != DG.COLUMN_TYPE.STRING)
|
|
163
|
+
table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
|
|
164
|
+
d.append(
|
|
165
|
+
ui.link('Add Columns', () => {
|
|
166
|
+
addColumns(table, saltsDf);
|
|
167
|
+
grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
|
|
168
|
+
}, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
169
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
|
|
170
|
+
ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const view = grok.shell.getTableView(table.name);
|
|
174
|
+
|
|
175
|
+
view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
|
|
176
|
+
view.dataFrame.getCol(COL_NAMES.OWNER).setTag(DG.TAGS.CHOICES, stringify(usersDf.columns.byIndex(0).toList()));
|
|
177
|
+
view.dataFrame.getCol(COL_NAMES.SALT).setTag(DG.TAGS.CHOICES, stringify(saltsDf.columns.byIndex(0).toList()));
|
|
178
|
+
view.dataFrame.getCol(COL_NAMES.SOURCE).setTag(DG.TAGS.CHOICES, stringify(sourcesDf.columns.byIndex(0).toList()));
|
|
179
|
+
view.dataFrame.getCol(COL_NAMES.ICD).setTag(DG.TAGS.CHOICES, stringify(icdsDf.columns.byIndex(0).toList()));
|
|
180
|
+
view.dataFrame.getCol(COL_NAMES.IDP).setTag(DG.TAGS.CHOICES, stringify(idpsDf.columns.byIndex(0).toList()));
|
|
181
|
+
|
|
182
|
+
grok.events.onContextMenu.subscribe((args) => {
|
|
183
|
+
if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
|
|
184
|
+
.includes(args.args.context.table.currentCol.name)) {
|
|
185
|
+
args.args.menu.item('Fill Column With Value', () => {
|
|
186
|
+
const v = args.args.context.table.currentCell.value;
|
|
187
|
+
args.args.context.table.currentCell.column.init(v);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}),
|
|
192
|
+
]);
|
|
193
|
+
grok.shell.v.setRibbonPanels([[d]]);
|
|
194
|
+
}
|
|
@@ -94,7 +94,7 @@ export const axolabsMap:
|
|
|
94
94
|
color: invAbasicColor,
|
|
95
95
|
},
|
|
96
96
|
'2\'-OMe-U(o)': {
|
|
97
|
-
fullName: 'Nucleotide Uridine with 2
|
|
97
|
+
fullName: 'Nucleotide Uridine with 2\'O-Methyl protection (overhang)',
|
|
98
98
|
symbols: ['mU', 'mU', 'mU', 'mU'],
|
|
99
99
|
color: 'rgb(65,233,80)',
|
|
100
100
|
},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* Do not change these import lines. Datagrok will import API library in exactly the same manner */
|
|
2
1
|
import * as grok from 'datagrok-api/grok';
|
|
3
2
|
import * as ui from 'datagrok-api/ui';
|
|
4
3
|
import * as DG from 'datagrok-api/dg';
|
|
@@ -6,8 +5,8 @@ import * as DG from 'datagrok-api/dg';
|
|
|
6
5
|
import * as svg from 'save-svg-as-png';
|
|
7
6
|
import $ from 'cash-dom';
|
|
8
7
|
|
|
9
|
-
import {drawAxolabsPattern} from './
|
|
10
|
-
import {axolabsMap} from './
|
|
8
|
+
import {drawAxolabsPattern} from './draw-svg';
|
|
9
|
+
import {axolabsMap} from './constants';
|
|
11
10
|
|
|
12
11
|
const baseChoices: string[] = Object.keys(axolabsMap);
|
|
13
12
|
const defaultBase: string = baseChoices[0];
|
|
@@ -57,17 +56,23 @@ function getUserName(patternName: string): string[] {
|
|
|
57
56
|
|
|
58
57
|
function translateSequence(
|
|
59
58
|
sequence: string,
|
|
60
|
-
bases:
|
|
61
|
-
ptoLinkages:
|
|
62
|
-
startModification:
|
|
63
|
-
endModification:
|
|
59
|
+
bases: DG.InputBase[],
|
|
60
|
+
ptoLinkages: DG.InputBase[],
|
|
61
|
+
startModification: DG.InputBase,
|
|
62
|
+
endModification: DG.InputBase,
|
|
64
63
|
firstPtoExist: boolean): string {
|
|
65
|
-
let
|
|
64
|
+
let i: number = -1;
|
|
66
65
|
let mainSequence = sequence.replace(/[AUGC]/g, function(x: string) {
|
|
67
|
-
|
|
66
|
+
i++;
|
|
68
67
|
const indexOfSymbol = axolabsMap['RNA']['symbols'].indexOf(x);
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let symbol = axolabsMap[bases[i].value]['symbols'][indexOfSymbol];
|
|
69
|
+
if (bases[i].value.slice(-3) == '(o)') {
|
|
70
|
+
if (i < sequence.length / 2 && bases[i + 1].value.slice(-3) != '(o)')
|
|
71
|
+
symbol = symbol + x + 'f';
|
|
72
|
+
else if (i > sequence.length / 2 && bases[i - 1].value.slice(-3) != '(o)')
|
|
73
|
+
symbol = x + 'f' + symbol;
|
|
74
|
+
}
|
|
75
|
+
return (ptoLinkages[i].value) ? symbol + 's' : symbol;
|
|
71
76
|
});
|
|
72
77
|
if (mainSequence.slice(0, 5).split('mU').length == 3)
|
|
73
78
|
mainSequence = '(uu)' + mainSequence.slice(4);
|
|
@@ -82,16 +87,18 @@ function addColumnWithIds(tableName: string, columnName: string, patternName: st
|
|
|
82
87
|
if (columns.contains(nameOfNewColumn))
|
|
83
88
|
columns.remove(nameOfNewColumn);
|
|
84
89
|
const columnWithIds = columns.byName(columnName);
|
|
85
|
-
return columns.addNewString(nameOfNewColumn).init((i: number) =>
|
|
90
|
+
return columns.addNewString(nameOfNewColumn).init((i: number) => {
|
|
91
|
+
return (columnWithIds.getString(i) == '') ? '' : columnWithIds.get(i) + '_' + patternName;
|
|
92
|
+
});
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
function addColumnWithTranslatedSequences(
|
|
89
96
|
tableName: string,
|
|
90
97
|
columnName: string,
|
|
91
|
-
bases:
|
|
92
|
-
ptoLinkages:
|
|
93
|
-
startModification:
|
|
94
|
-
endModification:
|
|
98
|
+
bases: DG.InputBase[],
|
|
99
|
+
ptoLinkages: DG.InputBase[],
|
|
100
|
+
startModification: DG.InputBase,
|
|
101
|
+
endModification: DG.InputBase,
|
|
95
102
|
firstPtoExist: boolean) {
|
|
96
103
|
const nameOfNewColumn = 'Axolabs ' + columnName;
|
|
97
104
|
const columns = grok.shell.table(tableName).columns;
|
|
@@ -99,8 +106,10 @@ function addColumnWithTranslatedSequences(
|
|
|
99
106
|
columns.remove(nameOfNewColumn);
|
|
100
107
|
const columnWithInputSequences = columns.byName(columnName);
|
|
101
108
|
return columns.addNewString(nameOfNewColumn).init((i: number) => {
|
|
102
|
-
return
|
|
103
|
-
|
|
109
|
+
return columnWithInputSequences.getString(i) == '' ?
|
|
110
|
+
'' :
|
|
111
|
+
translateSequence(columnWithInputSequences.getString(i), bases, ptoLinkages, startModification, endModification,
|
|
112
|
+
firstPtoExist);
|
|
104
113
|
});
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -236,8 +245,9 @@ export function defineAxolabsPattern() {
|
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
function updateInputExamples() {
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
if (inputSsColumn.value == '')
|
|
249
|
+
ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
|
|
250
|
+
if (createAsStrand.value && inputAsColumn.value == '')
|
|
241
251
|
asInputExample.value = generateExample(asLength.value!, sequenceBase.value!);
|
|
242
252
|
}
|
|
243
253
|
|
|
@@ -329,10 +339,10 @@ export function defineAxolabsPattern() {
|
|
|
329
339
|
}
|
|
330
340
|
|
|
331
341
|
function checkWhetherAllValuesInColumnHaveTheSameLength(colName: string): boolean {
|
|
332
|
-
const col = tables.value!.
|
|
342
|
+
const col = tables.value!.getCol(colName);
|
|
333
343
|
let allLengthsAreTheSame = true;
|
|
334
344
|
for (let i = 1; i < col.length; i++) {
|
|
335
|
-
if (col.get(i - 1).length != col.get(i).length) {
|
|
345
|
+
if (col.get(i - 1).length != col.get(i).length && col.get(i).length != 0) {
|
|
336
346
|
allLengthsAreTheSame = false;
|
|
337
347
|
break;
|
|
338
348
|
}
|
|
@@ -361,12 +371,13 @@ export function defineAxolabsPattern() {
|
|
|
361
371
|
}
|
|
362
372
|
|
|
363
373
|
async function postPatternToUserStorage() {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
saveAs.value
|
|
374
|
+
const currUserName = await getCurrentUserName();
|
|
375
|
+
saveAs.value = (saveAs.stringValue.includes('(created by ')) ?
|
|
376
|
+
getShortName(saveAs.value) + currUserName :
|
|
377
|
+
saveAs.stringValue + currUserName;
|
|
367
378
|
return grok.dapi.userDataStorage.postValue(
|
|
368
379
|
userStorageKey,
|
|
369
|
-
saveAs.
|
|
380
|
+
saveAs.value,
|
|
370
381
|
JSON.stringify({
|
|
371
382
|
'ssBases': ssBases.slice(0, ssLength.value!).map((e) => e.value),
|
|
372
383
|
'asBases': asBases.slice(0, asLength.value!).map((e) => e.value),
|
|
@@ -387,11 +398,12 @@ export function defineAxolabsPattern() {
|
|
|
387
398
|
const lstMy: string[] = [];
|
|
388
399
|
const lstOthers: string[] = [];
|
|
389
400
|
|
|
401
|
+
// TODO: display short name, but use long for querying userdataStorage
|
|
390
402
|
for (const ent of Object.keys(entities)) {
|
|
391
403
|
if (await isCurrentUserCreatedThisPattern(ent))
|
|
392
404
|
lstOthers.push(ent);
|
|
393
405
|
else
|
|
394
|
-
lstMy.push(getShortName(ent));
|
|
406
|
+
lstMy.push(ent);//getShortName(ent));
|
|
395
407
|
}
|
|
396
408
|
|
|
397
409
|
let loadPattern = ui.choiceInput('Load Pattern', '', lstMy, (v: string) => parsePatternAndUpdateUi(v));
|
|
@@ -504,29 +516,27 @@ export function defineAxolabsPattern() {
|
|
|
504
516
|
const asLength = ui.intInput('AS Length', defaultSequenceLength, () => updateUiForNewSequenceLength());
|
|
505
517
|
const asLengthDiv = ui.div([asLength.root]);
|
|
506
518
|
|
|
507
|
-
function validateSsColumn(colName: string) {
|
|
519
|
+
function validateSsColumn(colName: string): void {
|
|
508
520
|
const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
|
|
509
|
-
const firstSequence = tables.value!.
|
|
521
|
+
const firstSequence = tables.value!.getCol(colName).get(0);
|
|
510
522
|
if (allLengthsAreTheSame && firstSequence.length != ssLength.value)
|
|
511
|
-
ssLength.value = tables.value!.
|
|
523
|
+
ssLength.value = tables.value!.getCol(colName).get(0).length;
|
|
512
524
|
ssInputExample.value = firstSequence;
|
|
513
525
|
}
|
|
514
526
|
|
|
515
|
-
function validateAsColumn(colName: string) {
|
|
527
|
+
function validateAsColumn(colName: string): void {
|
|
516
528
|
const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
|
|
517
|
-
const firstSequence = tables.value!.
|
|
529
|
+
const firstSequence = tables.value!.getCol(colName).get(0);
|
|
518
530
|
if (allLengthsAreTheSame && firstSequence.length != asLength.value)
|
|
519
|
-
asLength.value = tables.value!.
|
|
520
|
-
asLengthDiv.innerHTML = '';
|
|
521
|
-
asLengthDiv.append(asLength.root);
|
|
531
|
+
asLength.value = tables.value!.getCol(colName).get(0).length;
|
|
522
532
|
asInputExample.value = firstSequence;
|
|
523
533
|
}
|
|
524
534
|
|
|
525
535
|
function validateIdsColumn(colName: string) {
|
|
526
|
-
const col = tables.value!.
|
|
536
|
+
const col = tables.value!.getCol(colName);
|
|
527
537
|
if (col.type != DG.TYPE.INT)
|
|
528
538
|
grok.shell.error('Column should contain integers only');
|
|
529
|
-
else if (col.categories.length < col.length) {
|
|
539
|
+
else if (col.categories.filter((e) => e != '').length < col.toList().filter((e) => e != '').length) {
|
|
530
540
|
const duplicates = findDuplicates(col.getRawData());
|
|
531
541
|
ui.dialog('Non-unique IDs')
|
|
532
542
|
.add(ui.divText('Press \'OK\' to select rows with non-unique values'))
|
|
@@ -541,25 +551,43 @@ export function defineAxolabsPattern() {
|
|
|
541
551
|
}
|
|
542
552
|
|
|
543
553
|
const tables = ui.tableInput('Tables', grok.shell.tables[0], grok.shell.tables, (t: DG.DataFrame) => {
|
|
544
|
-
const inputSsColumn =
|
|
545
|
-
|
|
554
|
+
const inputSsColumn = ui.choiceInput('SS Column', '', t.columns.names(), (colName: string) => {
|
|
555
|
+
validateSsColumn(colName);
|
|
556
|
+
ssVar = colName;
|
|
557
|
+
});
|
|
546
558
|
inputSsColumnDiv.innerHTML = '';
|
|
547
559
|
inputSsColumnDiv.append(inputSsColumn.root);
|
|
548
|
-
const inputAsColumn =
|
|
549
|
-
|
|
560
|
+
const inputAsColumn = ui.choiceInput('AS Column', '', t.columns.names(), (colName: string) => {
|
|
561
|
+
validateAsColumn(colName);
|
|
562
|
+
asVar = colName;
|
|
563
|
+
});
|
|
550
564
|
inputAsColumnDiv.innerHTML = '';
|
|
551
565
|
inputAsColumnDiv.append(inputAsColumn.root);
|
|
552
|
-
const inputIdColumn =
|
|
553
|
-
|
|
566
|
+
const inputIdColumn = ui.choiceInput('ID Column', '', t.columns.names(), (colName: string) => {
|
|
567
|
+
validateIdsColumn(colName);
|
|
568
|
+
idVar = colName;
|
|
569
|
+
});
|
|
554
570
|
inputIdColumnDiv.innerHTML = '';
|
|
555
571
|
inputIdColumnDiv.append(inputIdColumn.root);
|
|
556
572
|
});
|
|
557
573
|
|
|
558
|
-
|
|
574
|
+
let ssVar = '';
|
|
575
|
+
const inputSsColumn = ui.choiceInput('SS Column', '', [], (colName: string) => {
|
|
576
|
+
validateSsColumn(colName);
|
|
577
|
+
ssVar = colName;
|
|
578
|
+
});
|
|
559
579
|
inputSsColumnDiv.append(inputSsColumn.root);
|
|
560
|
-
|
|
580
|
+
let asVar = '';
|
|
581
|
+
const inputAsColumn = ui.choiceInput('AS Column', '', [], (colName: string) => {
|
|
582
|
+
validateAsColumn(colName);
|
|
583
|
+
asVar = colName;
|
|
584
|
+
});
|
|
561
585
|
inputAsColumnDiv.append(inputAsColumn.root);
|
|
562
|
-
|
|
586
|
+
let idVar = '';
|
|
587
|
+
const inputIdColumn = ui.choiceInput('ID Column', '', [], (colName: string) => {
|
|
588
|
+
validateIdsColumn(colName);
|
|
589
|
+
idVar = colName;
|
|
590
|
+
});
|
|
563
591
|
inputIdColumnDiv.append(inputIdColumn.root);
|
|
564
592
|
|
|
565
593
|
updatePatternsList();
|
|
@@ -638,7 +666,7 @@ export function defineAxolabsPattern() {
|
|
|
638
666
|
});
|
|
639
667
|
|
|
640
668
|
const convertSequenceButton = ui.button('Convert Sequences', () => {
|
|
641
|
-
if (
|
|
669
|
+
if (ssVar == '' || (createAsStrand.value && asVar == ''))
|
|
642
670
|
grok.shell.info('Please select table and columns on which to apply pattern');
|
|
643
671
|
else if (ssLength.value != ssInputExample.value.length || asLength.value != asInputExample.value.length) {
|
|
644
672
|
const dialog = ui.dialog('Length Mismatch');
|
|
@@ -646,20 +674,20 @@ export function defineAxolabsPattern() {
|
|
|
646
674
|
dialog
|
|
647
675
|
.add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
|
|
648
676
|
.addButton('YES', () => {
|
|
649
|
-
ssLength.value = tables.value!.
|
|
650
|
-
asLength.value = tables.value!.
|
|
677
|
+
ssLength.value = tables.value!.getCol(inputSsColumn.value!).getString(0).length;
|
|
678
|
+
asLength.value = tables.value!.getCol(inputAsColumn.value!).getString(0).length;
|
|
651
679
|
dialog.close();
|
|
652
680
|
})
|
|
653
681
|
.show();
|
|
654
682
|
} else {
|
|
655
|
-
if (
|
|
656
|
-
addColumnWithIds(tables.value!.name,
|
|
683
|
+
if (idVar != '')
|
|
684
|
+
addColumnWithIds(tables.value!.name, idVar, getShortName(saveAs.value));
|
|
657
685
|
addColumnWithTranslatedSequences(
|
|
658
|
-
tables.value!.name,
|
|
686
|
+
tables.value!.name, ssVar, ssBases, ssPtoLinkages,
|
|
659
687
|
ssFiveModification, ssThreeModification, firstSsPto.value!);
|
|
660
688
|
if (createAsStrand.value) {
|
|
661
689
|
addColumnWithTranslatedSequences(
|
|
662
|
-
tables.value!.name,
|
|
690
|
+
tables.value!.name, asVar, asBases, asPtoLinkages,
|
|
663
691
|
asFiveModification, asThreeModification, firstAsPto.value!);
|
|
664
692
|
}
|
|
665
693
|
grok.shell.v = grok.shell.getTableView(tables.value!.name);
|
|
@@ -668,10 +696,7 @@ export function defineAxolabsPattern() {
|
|
|
668
696
|
}
|
|
669
697
|
});
|
|
670
698
|
|
|
671
|
-
const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!)
|
|
672
|
-
ssOutputExample.value = translateSequence(ssInputExample.value, ssBases, ssPtoLinkages,
|
|
673
|
-
ssFiveModification, ssThreeModification, firstSsPto.value!);
|
|
674
|
-
});
|
|
699
|
+
const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!));
|
|
675
700
|
const ssOutputExample = ui.textInput(' ', translateSequence(
|
|
676
701
|
ssInputExample.value, ssBases, ssPtoLinkages, ssThreeModification, ssFiveModification, firstSsPto.value!));
|
|
677
702
|
(ssInputExample.input as HTMLElement).style.resize = 'none';
|
|
@@ -689,10 +714,7 @@ export function defineAxolabsPattern() {
|
|
|
689
714
|
], 'ui-input-options'),
|
|
690
715
|
);
|
|
691
716
|
|
|
692
|
-
const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!)
|
|
693
|
-
asOutputExample.value = translateSequence(
|
|
694
|
-
asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!);
|
|
695
|
-
});
|
|
717
|
+
const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!));
|
|
696
718
|
const asOutputExample = ui.textInput(' ', translateSequence(
|
|
697
719
|
asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!));
|
|
698
720
|
(asInputExample.input as HTMLElement).style.resize = 'none';
|
|
@@ -736,11 +758,14 @@ export function defineAxolabsPattern() {
|
|
|
736
758
|
]),
|
|
737
759
|
], 'ui-form');
|
|
738
760
|
|
|
761
|
+
const downloadButton = ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
|
|
762
|
+
{backgroundColor: 'white'}));
|
|
763
|
+
|
|
739
764
|
const mainSection = ui.panel([
|
|
740
765
|
ui.block([
|
|
741
766
|
svgDiv,
|
|
742
767
|
], {style: {overflowX: 'scroll'}}),
|
|
743
|
-
|
|
768
|
+
downloadButton,
|
|
744
769
|
isEnumerateModificationsDiv,
|
|
745
770
|
ui.div([
|
|
746
771
|
ui.div([
|