@datagrok/sequence-translator 1.0.4 → 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/dist/package-test.js +37 -29
- package/dist/package.js +614 -542
- package/package.json +15 -10
- package/src/autostart/registration.ts +194 -0
- package/src/{axolabsMap.ts → axolabs/constants.ts} +1 -1
- package/src/{defineAxolabsPattern.ts → axolabs/define-pattern.ts} +39 -38
- package/src/{drawAxolabsPattern.ts → axolabs/draw-svg.ts} +1 -1
- package/src/main/main-view.ts +225 -0
- package/src/package.ts +11 -410
- 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 +5 -3
- package/src/structures-works/sequence-codes-tools.ts +24 -24
- package/{test-SequenceTranslator-4f0c8bae6479-18ff1615.html → test-SequenceTranslator-4f0c8bae6479-6545fe31.html} +7 -7
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,14 +14,14 @@
|
|
|
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",
|
|
@@ -38,18 +42,19 @@
|
|
|
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
|
}
|
|
@@ -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);
|
|
@@ -90,10 +95,10 @@ function addColumnWithIds(tableName: string, columnName: string, patternName: st
|
|
|
90
95
|
function addColumnWithTranslatedSequences(
|
|
91
96
|
tableName: string,
|
|
92
97
|
columnName: string,
|
|
93
|
-
bases:
|
|
94
|
-
ptoLinkages:
|
|
95
|
-
startModification:
|
|
96
|
-
endModification:
|
|
98
|
+
bases: DG.InputBase[],
|
|
99
|
+
ptoLinkages: DG.InputBase[],
|
|
100
|
+
startModification: DG.InputBase,
|
|
101
|
+
endModification: DG.InputBase,
|
|
97
102
|
firstPtoExist: boolean) {
|
|
98
103
|
const nameOfNewColumn = 'Axolabs ' + columnName;
|
|
99
104
|
const columns = grok.shell.table(tableName).columns;
|
|
@@ -240,8 +245,9 @@ export function defineAxolabsPattern() {
|
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
function updateInputExamples() {
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
if (inputSsColumn.value == '')
|
|
249
|
+
ssInputExample.value = generateExample(ssLength.value!, sequenceBase.value!);
|
|
250
|
+
if (createAsStrand.value && inputAsColumn.value == '')
|
|
245
251
|
asInputExample.value = generateExample(asLength.value!, sequenceBase.value!);
|
|
246
252
|
}
|
|
247
253
|
|
|
@@ -333,7 +339,7 @@ export function defineAxolabsPattern() {
|
|
|
333
339
|
}
|
|
334
340
|
|
|
335
341
|
function checkWhetherAllValuesInColumnHaveTheSameLength(colName: string): boolean {
|
|
336
|
-
const col = tables.value!.
|
|
342
|
+
const col = tables.value!.getCol(colName);
|
|
337
343
|
let allLengthsAreTheSame = true;
|
|
338
344
|
for (let i = 1; i < col.length; i++) {
|
|
339
345
|
if (col.get(i - 1).length != col.get(i).length && col.get(i).length != 0) {
|
|
@@ -510,26 +516,24 @@ export function defineAxolabsPattern() {
|
|
|
510
516
|
const asLength = ui.intInput('AS Length', defaultSequenceLength, () => updateUiForNewSequenceLength());
|
|
511
517
|
const asLengthDiv = ui.div([asLength.root]);
|
|
512
518
|
|
|
513
|
-
function validateSsColumn(colName: string) {
|
|
519
|
+
function validateSsColumn(colName: string): void {
|
|
514
520
|
const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
|
|
515
|
-
const firstSequence = tables.value!.
|
|
521
|
+
const firstSequence = tables.value!.getCol(colName).get(0);
|
|
516
522
|
if (allLengthsAreTheSame && firstSequence.length != ssLength.value)
|
|
517
|
-
ssLength.value = tables.value!.
|
|
523
|
+
ssLength.value = tables.value!.getCol(colName).get(0).length;
|
|
518
524
|
ssInputExample.value = firstSequence;
|
|
519
525
|
}
|
|
520
526
|
|
|
521
|
-
function validateAsColumn(colName: string) {
|
|
527
|
+
function validateAsColumn(colName: string): void {
|
|
522
528
|
const allLengthsAreTheSame: boolean = checkWhetherAllValuesInColumnHaveTheSameLength(colName);
|
|
523
|
-
const firstSequence = tables.value!.
|
|
529
|
+
const firstSequence = tables.value!.getCol(colName).get(0);
|
|
524
530
|
if (allLengthsAreTheSame && firstSequence.length != asLength.value)
|
|
525
|
-
asLength.value = tables.value!.
|
|
526
|
-
asLengthDiv.innerHTML = '';
|
|
527
|
-
asLengthDiv.append(asLength.root);
|
|
531
|
+
asLength.value = tables.value!.getCol(colName).get(0).length;
|
|
528
532
|
asInputExample.value = firstSequence;
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
function validateIdsColumn(colName: string) {
|
|
532
|
-
const col = tables.value!.
|
|
536
|
+
const col = tables.value!.getCol(colName);
|
|
533
537
|
if (col.type != DG.TYPE.INT)
|
|
534
538
|
grok.shell.error('Column should contain integers only');
|
|
535
539
|
else if (col.categories.filter((e) => e != '').length < col.toList().filter((e) => e != '').length) {
|
|
@@ -670,8 +674,8 @@ export function defineAxolabsPattern() {
|
|
|
670
674
|
dialog
|
|
671
675
|
.add(ui.divText('Length of sequences in columns doesn\'t match entered length. Update length value?'))
|
|
672
676
|
.addButton('YES', () => {
|
|
673
|
-
ssLength.value = tables.value!.
|
|
674
|
-
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;
|
|
675
679
|
dialog.close();
|
|
676
680
|
})
|
|
677
681
|
.show();
|
|
@@ -692,10 +696,7 @@ export function defineAxolabsPattern() {
|
|
|
692
696
|
}
|
|
693
697
|
});
|
|
694
698
|
|
|
695
|
-
const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!)
|
|
696
|
-
ssOutputExample.value = translateSequence(ssInputExample.value, ssBases, ssPtoLinkages,
|
|
697
|
-
ssFiveModification, ssThreeModification, firstSsPto.value!);
|
|
698
|
-
});
|
|
699
|
+
const ssInputExample = ui.textInput('Sense Strand', generateExample(ssLength.value!, sequenceBase.value!));
|
|
699
700
|
const ssOutputExample = ui.textInput(' ', translateSequence(
|
|
700
701
|
ssInputExample.value, ssBases, ssPtoLinkages, ssThreeModification, ssFiveModification, firstSsPto.value!));
|
|
701
702
|
(ssInputExample.input as HTMLElement).style.resize = 'none';
|
|
@@ -713,10 +714,7 @@ export function defineAxolabsPattern() {
|
|
|
713
714
|
], 'ui-input-options'),
|
|
714
715
|
);
|
|
715
716
|
|
|
716
|
-
const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!)
|
|
717
|
-
asOutputExample.value = translateSequence(
|
|
718
|
-
asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!);
|
|
719
|
-
});
|
|
717
|
+
const asInputExample = ui.textInput('Antisense Strand', generateExample(asLength.value!, sequenceBase.value!));
|
|
720
718
|
const asOutputExample = ui.textInput(' ', translateSequence(
|
|
721
719
|
asInputExample.value, asBases, asPtoLinkages, asFiveModification, asThreeModification, firstSsPto.value!));
|
|
722
720
|
(asInputExample.input as HTMLElement).style.resize = 'none';
|
|
@@ -760,11 +758,14 @@ export function defineAxolabsPattern() {
|
|
|
760
758
|
]),
|
|
761
759
|
], 'ui-form');
|
|
762
760
|
|
|
761
|
+
const downloadButton = ui.button('Download', () => svg.saveSvgAsPng(document.getElementById('mySvg'), saveAs.value,
|
|
762
|
+
{backgroundColor: 'white'}));
|
|
763
|
+
|
|
763
764
|
const mainSection = ui.panel([
|
|
764
765
|
ui.block([
|
|
765
766
|
svgDiv,
|
|
766
767
|
], {style: {overflowX: 'scroll'}}),
|
|
767
|
-
|
|
768
|
+
downloadButton,
|
|
768
769
|
isEnumerateModificationsDiv,
|
|
769
770
|
ui.div([
|
|
770
771
|
ui.div([
|
|
@@ -0,0 +1,225 @@
|
|
|
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 {convertSequence, undefinedInputSequence, isValidSequence} from '../structures-works/sequence-codes-tools';
|
|
5
|
+
import {map, MODIFICATIONS} from '../structures-works/map';
|
|
6
|
+
import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
|
|
7
|
+
|
|
8
|
+
import $ from 'cash-dom';
|
|
9
|
+
|
|
10
|
+
const defaultInput = 'fAmCmGmAmCpsmU';
|
|
11
|
+
const sequenceWasCopied = 'Copied';
|
|
12
|
+
const tooltipSequence = 'Copy sequence';
|
|
13
|
+
|
|
14
|
+
export function mainView() {
|
|
15
|
+
function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
|
|
16
|
+
moleculeSvgDiv.innerHTML = '';
|
|
17
|
+
outputTableDiv.innerHTML = '';
|
|
18
|
+
const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
|
|
19
|
+
let errorsExist = false;
|
|
20
|
+
try {
|
|
21
|
+
sequence = sequence.replace(/\s/g, '');
|
|
22
|
+
const output = isValidSequence(sequence, null);
|
|
23
|
+
if (isSet)
|
|
24
|
+
output.synthesizer = [inputFormat];
|
|
25
|
+
inputFormatChoiceInput.value = output.synthesizer![0];
|
|
26
|
+
const outputSequenceObj = convertSequence(sequence, output);
|
|
27
|
+
const tableRows = [];
|
|
28
|
+
|
|
29
|
+
for (const key of Object.keys(outputSequenceObj).slice(1)) {
|
|
30
|
+
const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
31
|
+
JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
|
|
32
|
+
-1;
|
|
33
|
+
if ('indexOfFirstNotValidChar' in outputSequenceObj) {
|
|
34
|
+
const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
35
|
+
JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
|
|
36
|
+
-1;
|
|
37
|
+
if (indexOfFirstNotValidChar != -1)
|
|
38
|
+
errorsExist = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
tableRows.push({
|
|
42
|
+
'key': key,
|
|
43
|
+
'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
44
|
+
ui.divH([
|
|
45
|
+
ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
|
|
46
|
+
ui.tooltip.bind(
|
|
47
|
+
ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
|
|
48
|
+
'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
|
|
49
|
+
'. See tables with valid codes on the right',
|
|
50
|
+
),
|
|
51
|
+
]) : //@ts-ignore
|
|
52
|
+
ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key])
|
|
53
|
+
.then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, ''),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (errorsExist) {
|
|
58
|
+
const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
|
|
59
|
+
asoGapmersGrid.onCellPrepare(function(gc) {
|
|
60
|
+
gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
|
|
61
|
+
});
|
|
62
|
+
omeAndFluoroGrid.onCellPrepare(function(gc) {
|
|
63
|
+
gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
|
|
64
|
+
});
|
|
65
|
+
switchInput.enabled = true;
|
|
66
|
+
} else {
|
|
67
|
+
asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
68
|
+
omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
outputTableDiv.append(
|
|
72
|
+
ui.div([
|
|
73
|
+
DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
|
|
74
|
+
[item.key, item.value], ['Code', 'Sequence']).root,
|
|
75
|
+
]),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
|
|
79
|
+
const canvas = ui.canvas(300, 170);
|
|
80
|
+
canvas.addEventListener('click', () => {
|
|
81
|
+
const canv = ui.canvas($(window).width(), $(window).height());
|
|
82
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
83
|
+
output.synthesizer![0]);
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
86
|
+
ui.dialog('Molecule: ' + inputSequenceField.value)
|
|
87
|
+
.add(canv)
|
|
88
|
+
.showModal(true);
|
|
89
|
+
});
|
|
90
|
+
$(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
|
|
91
|
+
$(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
|
|
92
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
93
|
+
output.synthesizer![0]);
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
96
|
+
moleculeSvgDiv.append(canvas);
|
|
97
|
+
} else
|
|
98
|
+
moleculeSvgDiv.innerHTML = '';
|
|
99
|
+
} finally {
|
|
100
|
+
pi.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const inputFormatChoiceInput = ui.choiceInput(
|
|
105
|
+
'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
|
|
106
|
+
updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
|
|
107
|
+
});
|
|
108
|
+
const moleculeSvgDiv = ui.block([]);
|
|
109
|
+
const outputTableDiv = ui.div([]);
|
|
110
|
+
const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
|
|
111
|
+
inputFormatChoiceInput.value!, false));
|
|
112
|
+
|
|
113
|
+
const asoDf = DG.DataFrame.fromObjects([
|
|
114
|
+
{'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
|
|
115
|
+
{'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
|
|
116
|
+
{'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
|
|
117
|
+
{'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
|
|
118
|
+
{'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
|
|
119
|
+
{'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
|
|
120
|
+
{'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
|
|
121
|
+
{'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
|
|
122
|
+
{'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
|
|
123
|
+
{'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
|
|
124
|
+
{'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
|
|
125
|
+
{'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
|
|
126
|
+
{'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
|
|
127
|
+
{'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
|
|
128
|
+
])!;
|
|
129
|
+
const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
|
|
130
|
+
|
|
131
|
+
asoDf.onCurrentCellChanged.subscribe((_) => {
|
|
132
|
+
navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const omeAndFluoroGrid = DG.Viewer.grid(
|
|
136
|
+
DG.DataFrame.fromObjects([
|
|
137
|
+
{'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
|
|
138
|
+
{'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
|
|
139
|
+
{'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
|
|
140
|
+
{'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
|
|
141
|
+
{'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
|
|
142
|
+
{'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
|
|
143
|
+
{'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
|
|
144
|
+
{'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
|
|
145
|
+
{'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
|
|
146
|
+
])!, {showRowHeader: false, showCellTooltip: false},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const overhangModificationsGrid = DG.Viewer.grid(
|
|
150
|
+
DG.DataFrame.fromColumns([
|
|
151
|
+
DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
|
|
152
|
+
])!, {showRowHeader: false, showCellTooltip: false},
|
|
153
|
+
);
|
|
154
|
+
updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
|
|
155
|
+
|
|
156
|
+
const codesTablesDiv = ui.splitV([
|
|
157
|
+
ui.box(ui.h2('ASO Gapmers'), {style: {maxHeight: '40px'}}),
|
|
158
|
+
asoGapmersGrid.root,
|
|
159
|
+
ui.box(ui.h2('2\'-OMe and 2\'-F siRNA'), {style: {maxHeight: '40px'}}),
|
|
160
|
+
omeAndFluoroGrid.root,
|
|
161
|
+
ui.box(ui.h2('Overhang modifications'), {style: {maxHeight: '40px'}}),
|
|
162
|
+
overhangModificationsGrid.root,
|
|
163
|
+
], {style: {maxWidth: '350px'}});
|
|
164
|
+
|
|
165
|
+
const appMainDescription = ui.info([
|
|
166
|
+
ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
|
|
167
|
+
ui.divText('Paste sequence into the text field below'),
|
|
168
|
+
ui.divText('\n How to convert many sequences:', {style: {'font-weight': 'bolder'}}),
|
|
169
|
+
ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok'),
|
|
170
|
+
ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
|
|
171
|
+
ui.divText('This will add the result column to the right of the table'),
|
|
172
|
+
], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.');
|
|
173
|
+
|
|
174
|
+
$(codesTablesDiv).hide();
|
|
175
|
+
const switchInput = ui.switchInput('Codes', false, (v: boolean) => (v) ?
|
|
176
|
+
$(codesTablesDiv).show() :
|
|
177
|
+
$(codesTablesDiv).hide(),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const topPanel = [
|
|
181
|
+
ui.iconFA('download', () => {
|
|
182
|
+
const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
|
|
183
|
+
inputFormatChoiceInput.value!);
|
|
184
|
+
const element = document.createElement('a');
|
|
185
|
+
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
186
|
+
element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
|
|
187
|
+
element.click();
|
|
188
|
+
}, 'Save .mol file'),
|
|
189
|
+
ui.iconFA('copy', () => {
|
|
190
|
+
navigator.clipboard.writeText(
|
|
191
|
+
sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
|
|
192
|
+
.then(() => grok.shell.info(sequenceWasCopied));
|
|
193
|
+
}, 'Copy SMILES'),
|
|
194
|
+
switchInput.root,
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
const v = grok.shell.v;
|
|
198
|
+
const tabControl = grok.shell.sidebar;
|
|
199
|
+
tabControl.onTabChanged.subscribe((_) =>
|
|
200
|
+
v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
|
|
201
|
+
v.setRibbonPanels([topPanel]);
|
|
202
|
+
|
|
203
|
+
return ui.box(
|
|
204
|
+
ui.splitH([
|
|
205
|
+
ui.splitV([
|
|
206
|
+
ui.panel([
|
|
207
|
+
appMainDescription,
|
|
208
|
+
ui.div([
|
|
209
|
+
ui.h1('Input sequence'),
|
|
210
|
+
ui.div([
|
|
211
|
+
inputSequenceField.root,
|
|
212
|
+
], 'input-base'),
|
|
213
|
+
], 'inputSequence'),
|
|
214
|
+
ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
|
|
215
|
+
ui.block([
|
|
216
|
+
ui.h1('Output'),
|
|
217
|
+
outputTableDiv,
|
|
218
|
+
]),
|
|
219
|
+
moleculeSvgDiv,
|
|
220
|
+
], 'sequence'),
|
|
221
|
+
]),
|
|
222
|
+
codesTablesDiv,
|
|
223
|
+
], {style: {height: '100%', width: '100%'}}),
|
|
224
|
+
);
|
|
225
|
+
}
|