@datagrok/sequence-translator 0.0.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/css/style.css +18 -0
- package/dist/package-test.js +2444 -0
- package/dist/package.js +5224 -0
- package/jest.config.js +33 -0
- package/package.json +25 -8
- package/setup.cmd +1 -1
- package/src/ICDs.ts +3 -0
- package/src/IDPs.ts +3 -0
- package/src/__jest__/remote.test.ts +49 -0
- package/src/__jest__/test-node.ts +96 -0
- package/src/defineAxolabsPattern.ts +58 -55
- package/src/package-test.ts +2 -1
- package/src/package.ts +144 -110
- package/src/salts.ts +2 -2
- package/src/sources.ts +3 -0
- package/src/structures-works/from-monomers.ts +58 -19
- package/src/structures-works/map.ts +28 -6
- package/src/structures-works/mol-transformations.ts +432 -0
- package/src/structures-works/save-sense-antisense.ts +32 -22
- package/src/structures-works/sequence-codes-tools.ts +192 -93
- package/src/tests/smiles-tests.ts +31 -27
- package/src/users.ts +3 -0
- package/test-SequenceTranslator-089b6516ed77-d62c21a9.html +245 -0
- package/tsconfig.json +2 -1
package/src/package.ts
CHANGED
|
@@ -6,13 +6,19 @@ import * as OCL from 'openchemlib/full.js';
|
|
|
6
6
|
import $ from 'cash-dom';
|
|
7
7
|
import {defineAxolabsPattern} from './defineAxolabsPattern';
|
|
8
8
|
import {saveSenseAntiSense} from './structures-works/save-sense-antisense';
|
|
9
|
-
import {sequenceToSmiles} from './structures-works/from-monomers';
|
|
10
|
-
import {convertSequence, undefinedInputSequence} from
|
|
9
|
+
import {sequenceToSmiles, sequenceToMolV3000} from './structures-works/from-monomers';
|
|
10
|
+
import {convertSequence, undefinedInputSequence, isValidSequence, getFormat} from
|
|
11
|
+
'./structures-works/sequence-codes-tools';
|
|
12
|
+
import {map, COL_NAMES, MODIFICATIONS} from './structures-works/map';
|
|
11
13
|
import {SALTS_CSV} from './salts';
|
|
14
|
+
import {USERS_CSV} from './users';
|
|
15
|
+
import {ICDS} from './ICDs';
|
|
16
|
+
import {SOURCES} from './sources';
|
|
17
|
+
import {IDPS} from './IDPs';
|
|
12
18
|
|
|
13
19
|
export const _package = new DG.Package();
|
|
14
20
|
|
|
15
|
-
const defaultInput = '
|
|
21
|
+
const defaultInput = 'fAmCmGmAmCpsmU';
|
|
16
22
|
const sequenceWasCopied = 'Copied';
|
|
17
23
|
const tooltipSequence = 'Copy sequence';
|
|
18
24
|
|
|
@@ -24,35 +30,40 @@ export function sequenceTranslator(): void {
|
|
|
24
30
|
windows.showToolbox = false;
|
|
25
31
|
windows.showHelp = false;
|
|
26
32
|
|
|
27
|
-
function updateTableAndMolecule(sequence: string): void {
|
|
33
|
+
function updateTableAndMolecule(sequence: string, inputFormat: string, isSet: boolean): void {
|
|
28
34
|
moleculeSvgDiv.innerHTML = '';
|
|
29
35
|
outputTableDiv.innerHTML = '';
|
|
30
36
|
const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
|
|
31
37
|
let errorsExist = false;
|
|
32
38
|
try {
|
|
33
|
-
|
|
39
|
+
sequence = sequence.replace(/\s/g, '');
|
|
40
|
+
const output = isValidSequence(sequence, null);
|
|
41
|
+
if (isSet)
|
|
42
|
+
output.synthesizer = [inputFormat];
|
|
43
|
+
inputFormatChoiceInput.value = output.synthesizer![0];
|
|
44
|
+
const outputSequenceObj = convertSequence(sequence, output);
|
|
34
45
|
const tableRows = [];
|
|
35
46
|
|
|
36
47
|
for (const key of Object.keys(outputSequenceObj).slice(1)) {
|
|
37
|
-
const
|
|
38
|
-
JSON.parse(outputSequenceObj.
|
|
48
|
+
const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
49
|
+
JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
|
|
39
50
|
-1;
|
|
40
|
-
if ('
|
|
41
|
-
const
|
|
42
|
-
JSON.parse(outputSequenceObj.
|
|
51
|
+
if ('indexOfFirstNotValidChar' in outputSequenceObj) {
|
|
52
|
+
const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
53
|
+
JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
|
|
43
54
|
-1;
|
|
44
|
-
if (
|
|
55
|
+
if (indexOfFirstNotValidChar != -1)
|
|
45
56
|
errorsExist = true;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
tableRows.push({
|
|
49
60
|
'key': key,
|
|
50
|
-
'value': ('
|
|
61
|
+
'value': ('indexOfFirstNotValidChar' in outputSequenceObj) ?
|
|
51
62
|
ui.divH([
|
|
52
|
-
ui.divText(sequence.slice(0,
|
|
63
|
+
ui.divText(sequence.slice(0, indexOfFirstNotValidChar), {style: {color: 'grey'}}),
|
|
53
64
|
ui.tooltip.bind(
|
|
54
|
-
ui.divText(sequence.slice(
|
|
55
|
-
'Expected format: ' + JSON.parse(outputSequenceObj.
|
|
65
|
+
ui.divText(sequence.slice(indexOfFirstNotValidChar), {style: {color: 'red'}}),
|
|
66
|
+
'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer +
|
|
56
67
|
'. See tables with valid codes on the right',
|
|
57
68
|
),
|
|
58
69
|
]) : //@ts-ignore
|
|
@@ -62,48 +73,44 @@ export function sequenceTranslator(): void {
|
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
if (errorsExist) {
|
|
65
|
-
const
|
|
66
|
-
.expectedSynthesizer.slice(0, -6);
|
|
76
|
+
const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
|
|
67
77
|
asoGapmersGrid.onCellPrepare(function(gc) {
|
|
68
|
-
gc.style.backColor = (gc.gridColumn.name ==
|
|
78
|
+
gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
|
|
69
79
|
});
|
|
70
80
|
omeAndFluoroGrid.onCellPrepare(function(gc) {
|
|
71
|
-
gc.style.backColor = (gc.gridColumn.name ==
|
|
81
|
+
gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
|
|
72
82
|
});
|
|
73
83
|
switchInput.enabled = true;
|
|
74
84
|
} else {
|
|
75
|
-
asoGapmersGrid.onCellPrepare(function(gc) {
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
omeAndFluoroGrid.onCellPrepare(function(gc) {
|
|
79
|
-
gc.style.backColor = 0xFFFFFFFF;
|
|
80
|
-
});
|
|
85
|
+
asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
86
|
+
omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
outputTableDiv.append(
|
|
84
90
|
ui.div([
|
|
85
91
|
DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
|
|
86
92
|
[item.key, item.value], ['Code', 'Sequence']).root,
|
|
87
|
-
]
|
|
93
|
+
]),
|
|
88
94
|
);
|
|
89
|
-
semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
|
|
90
95
|
|
|
91
96
|
if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
|
|
92
97
|
const canvas = ui.canvas(300, 170);
|
|
93
98
|
canvas.addEventListener('click', () => {
|
|
94
99
|
const canv = ui.canvas($(window).width(), $(window).height());
|
|
95
|
-
const
|
|
100
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
101
|
+
output.synthesizer![0]);
|
|
96
102
|
// @ts-ignore
|
|
97
|
-
OCL.StructureView.drawMolecule(canv, OCL.Molecule.
|
|
103
|
+
OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
98
104
|
ui.dialog('Molecule: ' + inputSequenceField.value)
|
|
99
105
|
.add(canv)
|
|
100
106
|
.showModal(true);
|
|
101
107
|
});
|
|
102
108
|
$(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
|
|
103
109
|
$(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
|
|
104
|
-
const
|
|
110
|
+
const mol = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, true,
|
|
111
|
+
output.synthesizer![0]);
|
|
105
112
|
// @ts-ignore
|
|
106
|
-
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.
|
|
113
|
+
OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromMolfile(mol), {suppressChiralText: true});
|
|
107
114
|
moleculeSvgDiv.append(canvas);
|
|
108
115
|
} else
|
|
109
116
|
moleculeSvgDiv.innerHTML = '';
|
|
@@ -112,10 +119,14 @@ export function sequenceTranslator(): void {
|
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
const
|
|
122
|
+
const inputFormatChoiceInput = ui.choiceInput(
|
|
123
|
+
'Input format: ', 'Janssen GCRS Codes', Object.keys(map), (format: string) => {
|
|
124
|
+
updateTableAndMolecule(inputSequenceField.value.replace(/\s/g, ''), format, true);
|
|
125
|
+
});
|
|
116
126
|
const moleculeSvgDiv = ui.block([]);
|
|
117
|
-
const outputTableDiv = ui.div([]
|
|
118
|
-
const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence
|
|
127
|
+
const outputTableDiv = ui.div([]);
|
|
128
|
+
const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence,
|
|
129
|
+
inputFormatChoiceInput.value!, false));
|
|
119
130
|
|
|
120
131
|
const asoDf = DG.DataFrame.fromObjects([
|
|
121
132
|
{'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
|
|
@@ -154,12 +165,11 @@ export function sequenceTranslator(): void {
|
|
|
154
165
|
);
|
|
155
166
|
|
|
156
167
|
const overhangModificationsGrid = DG.Viewer.grid(
|
|
157
|
-
DG.DataFrame.
|
|
158
|
-
|
|
159
|
-
{'Name': '(GalNAc-2-JNJ)'},
|
|
168
|
+
DG.DataFrame.fromColumns([
|
|
169
|
+
DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
|
|
160
170
|
])!, {showRowHeader: false, showCellTooltip: false},
|
|
161
171
|
);
|
|
162
|
-
updateTableAndMolecule(defaultInput);
|
|
172
|
+
updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!, true);
|
|
163
173
|
|
|
164
174
|
const appMainDescription = ui.info([
|
|
165
175
|
ui.divText('How to convert one sequence:', {style: {'font-weight': 'bolder'}}),
|
|
@@ -190,8 +200,8 @@ export function sequenceTranslator(): void {
|
|
|
190
200
|
ui.div([
|
|
191
201
|
inputSequenceField.root,
|
|
192
202
|
], 'input-base'),
|
|
193
|
-
], '
|
|
194
|
-
|
|
203
|
+
], 'inputSequence'),
|
|
204
|
+
ui.div([inputFormatChoiceInput], {style: {padding: '5px 0'}}),
|
|
195
205
|
ui.block([
|
|
196
206
|
ui.h1('Output'),
|
|
197
207
|
outputTableDiv,
|
|
@@ -216,15 +226,16 @@ export function sequenceTranslator(): void {
|
|
|
216
226
|
|
|
217
227
|
const topPanel = [
|
|
218
228
|
ui.iconFA('download', () => {
|
|
219
|
-
const
|
|
220
|
-
|
|
229
|
+
const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
|
|
230
|
+
inputFormatChoiceInput.value!);
|
|
221
231
|
const element = document.createElement('a');
|
|
222
232
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
223
233
|
element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
|
|
224
234
|
element.click();
|
|
225
235
|
}, 'Save .mol file'),
|
|
226
236
|
ui.iconFA('copy', () => {
|
|
227
|
-
navigator.clipboard.writeText(
|
|
237
|
+
navigator.clipboard.writeText(
|
|
238
|
+
sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
|
|
228
239
|
.then(() => grok.shell.info(sequenceWasCopied));
|
|
229
240
|
}, 'Copy SMILES'),
|
|
230
241
|
switchInput.root,
|
|
@@ -233,38 +244,29 @@ export function sequenceTranslator(): void {
|
|
|
233
244
|
tabControl.onTabChanged.subscribe((_) =>
|
|
234
245
|
v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
|
|
235
246
|
v.setRibbonPanels([topPanel]);
|
|
236
|
-
|
|
237
|
-
$('.sequence')
|
|
238
|
-
.children().css('padding', '5px 0');
|
|
239
|
-
$('.sequenceInput .input-base')
|
|
240
|
-
.css('margin', '0');
|
|
241
|
-
$('.sequenceInput textarea')
|
|
242
|
-
.css('resize', 'none')
|
|
243
|
-
.css('min-height', '50px')
|
|
244
|
-
.css('width', '100%')
|
|
245
|
-
.attr('spellcheck', 'false');
|
|
246
|
-
$('.sequenceInput select')
|
|
247
|
-
.css('width', '100%');
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
async function saveTableAsSdFile(table: DG.DataFrame) {
|
|
251
250
|
if (!table.columns.contains('Compound Name')) {
|
|
252
251
|
grok.shell.warning(
|
|
253
|
-
'File saved without columns \'
|
|
252
|
+
'File saved without columns \'' +
|
|
253
|
+
[COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
254
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
|
|
255
|
+
);
|
|
254
256
|
}
|
|
255
|
-
const structureColumn = table.
|
|
257
|
+
const structureColumn = table.col(COL_NAMES.SEQUENCE)!;
|
|
258
|
+
const typeColumn = table.col(COL_NAMES.TYPE)!;
|
|
256
259
|
let result = '';
|
|
257
260
|
for (let i = 0; i < table.rowCount; i++) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
const format = getFormat(structureColumn.get(i));
|
|
262
|
+
result += (typeColumn.get(i) == 'SS') ?
|
|
263
|
+
sequenceToMolV3000(structureColumn.get(i), false, true, format!) + '\n' + `> <Sequence>\nSense Strand\n\n` :
|
|
264
|
+
sequenceToMolV3000(structureColumn.get(i), true, true, format!) + '\n' + `> <Sequence>\nAnti Sense\n\n`;
|
|
265
|
+
for (const col of table.columns) {
|
|
266
|
+
if (col.name != COL_NAMES.SEQUENCE)
|
|
263
267
|
result += `> <${col.name}>\n${col.get(i)}\n\n`;
|
|
264
|
-
result += '$$$$';
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.error(error);
|
|
267
268
|
}
|
|
269
|
+
result += '$$$$\n\n';
|
|
268
270
|
}
|
|
269
271
|
const element = document.createElement('a');
|
|
270
272
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
|
|
@@ -272,83 +274,115 @@ async function saveTableAsSdFile(table: DG.DataFrame) {
|
|
|
272
274
|
element.click();
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
const weightsObj: {[code: string]: number} = {};
|
|
278
|
+
for (const synthesizer of Object.keys(map)) {
|
|
279
|
+
for (const technology of Object.keys(map[synthesizer])) {
|
|
280
|
+
for (const code of Object.keys(map[synthesizer][technology]))
|
|
281
|
+
weightsObj[code] = map[synthesizer][technology][code].weight;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const [key, value] of Object.entries(MODIFICATIONS))
|
|
285
|
+
weightsObj[key] = value.molecularWeight;
|
|
286
|
+
|
|
287
|
+
function sortByStringLengthInDescendingOrder(array: string[]): string[] {
|
|
288
|
+
return array.sort(function(a, b) {return b.length - a.length;});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function stringifyItems(items: string[]): string {
|
|
292
|
+
return '["' + items.join('", "') + '"]';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
|
|
296
|
+
const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
|
|
297
|
+
let weight = 0;
|
|
298
|
+
let i = 0;
|
|
299
|
+
while (i < sequence.length) {
|
|
300
|
+
const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
|
|
301
|
+
weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
|
|
302
|
+
i += matchedCode!.length;
|
|
303
|
+
}
|
|
304
|
+
return weight - 61.97;
|
|
305
|
+
}
|
|
306
|
+
|
|
275
307
|
//tags: autostart
|
|
276
308
|
export function autostartOligoSdFileSubscription() {
|
|
277
309
|
grok.events.onViewAdded.subscribe((v: any) => {
|
|
278
|
-
if (v.type == 'TableView' && v.dataFrame.columns.contains(
|
|
310
|
+
if (v.type == 'TableView' && v.dataFrame.columns.contains(COL_NAMES.TYPE))
|
|
279
311
|
oligoSdFile(v.dataFrame);
|
|
280
312
|
});
|
|
281
313
|
}
|
|
282
314
|
|
|
283
315
|
export function oligoSdFile(table: DG.DataFrame) {
|
|
284
316
|
const saltsDf = DG.DataFrame.fromCsv(SALTS_CSV);
|
|
317
|
+
const usersDf = DG.DataFrame.fromCsv(USERS_CSV);
|
|
318
|
+
const sourcesDf = DG.DataFrame.fromCsv(SOURCES);
|
|
319
|
+
const icdsDf = DG.DataFrame.fromCsv(ICDS);
|
|
320
|
+
const idpsDf = DG.DataFrame.fromCsv(IDPS);
|
|
321
|
+
|
|
285
322
|
function addColumns(t: DG.DataFrame, saltsDf: DG.DataFrame) {
|
|
286
|
-
if (t.columns.contains(
|
|
323
|
+
if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
|
|
287
324
|
return grok.shell.error('Columns already exist!');
|
|
288
325
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const sequence = t.col('Sequence')!;
|
|
293
|
-
const salt = t.col('Salt')!;
|
|
294
|
-
const equivalents = t.col('Equivalents')!;
|
|
326
|
+
const sequence = t.col(COL_NAMES.SEQUENCE)!;
|
|
327
|
+
const salt = t.col(COL_NAMES.SALT)!;
|
|
328
|
+
const equivalents = t.col(COL_NAMES.EQUIVALENTS)!;
|
|
295
329
|
|
|
296
|
-
t.columns.addNewString(
|
|
297
|
-
t.columns.addNewString(
|
|
330
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => sequence.get(i));
|
|
331
|
+
t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => (i > 0 && i % 2 == 0) ?
|
|
298
332
|
sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
|
|
299
333
|
sequence.getString(i),
|
|
300
334
|
);
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
t.columns.addNewFloat(
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
return
|
|
335
|
+
const molWeightCol = saltsDf.col('MOLWEIGHT')!;
|
|
336
|
+
const saltNamesList = saltsDf.col('DISPLAY')!.toList();
|
|
337
|
+
t.columns.addNewFloat(COL_NAMES.CPD_MW)
|
|
338
|
+
.init((i: number) => molecularWeight(sequence.get(i), weightsObj));
|
|
339
|
+
t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) => {
|
|
340
|
+
const saltRowIndex = saltNamesList.indexOf(salt.get(i));
|
|
341
|
+
const mw = molWeightCol.get(saltRowIndex);
|
|
342
|
+
return mw * equivalents.get(i);
|
|
309
343
|
});
|
|
310
|
-
t.columns.addNewCalculated(
|
|
344
|
+
t.columns.addNewCalculated(COL_NAMES.BATCH_MW,
|
|
345
|
+
'${' + COL_NAMES.CPD_MW + '} + ${' + COL_NAMES.SALT_MASS + '}', DG.COLUMN_TYPE.FLOAT, false,
|
|
346
|
+
);
|
|
311
347
|
|
|
312
348
|
addColumnsPressed = true;
|
|
313
349
|
return newDf = t;
|
|
314
350
|
}
|
|
315
351
|
|
|
316
|
-
const columnsOrder = ['Chemistry', 'Number', 'Type', 'Chemistry Name', 'Internal compound ID',
|
|
317
|
-
'IDP', 'Sequence', 'Compound Name', 'Compound Comments', 'Salt', 'Equivalents', 'Purity', 'Cpd MW', 'Salt mass',
|
|
318
|
-
'Batch MW', 'Source', 'ICD', 'Owner'];
|
|
319
352
|
let newDf: DG.DataFrame;
|
|
320
353
|
let addColumnsPressed = false;
|
|
321
354
|
|
|
322
355
|
const d = ui.div([
|
|
323
356
|
ui.icons.edit(() => {
|
|
324
357
|
d.innerHTML = '';
|
|
358
|
+
if (table.col(COL_NAMES.IDP)!.type != DG.COLUMN_TYPE.STRING)
|
|
359
|
+
table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
|
|
325
360
|
d.append(
|
|
326
|
-
ui.link('Add Columns',
|
|
327
|
-
|
|
328
|
-
grok.shell.tableView(table.name).grid.columns.setOrder(
|
|
329
|
-
}, 'Add columns:
|
|
361
|
+
ui.link('Add Columns', () => {
|
|
362
|
+
addColumns(table, saltsDf);
|
|
363
|
+
grok.shell.tableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
|
|
364
|
+
}, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
|
|
365
|
+
COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), ''),
|
|
330
366
|
ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
|
|
331
367
|
);
|
|
332
|
-
const view = grok.shell.getTableView(table.name);
|
|
333
|
-
const typeCol = view.grid.col('Type')!;
|
|
334
|
-
const saltCol = view.grid.col('Salt')!;
|
|
335
|
-
saltCol.cellType = 'html';
|
|
336
|
-
typeCol.cellType = 'html';
|
|
337
|
-
view.grid.onCellPrepare(function(gc: DG.GridCell) {
|
|
338
|
-
if (gc.isTableCell) {
|
|
339
|
-
if (gc.gridColumn.name == 'Type')
|
|
340
|
-
gc.style.element = ui.choiceInput('', gc.cell.value, ['AS', 'SS', 'Duplex']).root;
|
|
341
|
-
else if (gc.gridColumn.name == 'Salt') {
|
|
342
|
-
gc.style.element = ui.choiceInput('', gc.cell.value, saltsDf.columns.byIndex(1).toList(), () => {
|
|
343
|
-
view.dataFrame.col('Salt')!.set(gc.gridRow, '');
|
|
344
|
-
}).root;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
368
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
369
|
+
const view = grok.shell.getTableView(table.name)!;
|
|
370
|
+
|
|
371
|
+
view.table!.col(COL_NAMES.TYPE)!.setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
|
|
372
|
+
view.table!.col(COL_NAMES.OWNER)!.setTag(DG.TAGS.CHOICES, stringifyItems(usersDf.columns.byIndex(0).toList()));
|
|
373
|
+
view.table!.col(COL_NAMES.SALT)!.setTag(DG.TAGS.CHOICES, stringifyItems(saltsDf.columns.byIndex(0).toList()));
|
|
374
|
+
view.table!.col(COL_NAMES.SOURCE)!.setTag(DG.TAGS.CHOICES, stringifyItems(sourcesDf.columns.byIndex(0).toList()));
|
|
375
|
+
view.table!.col(COL_NAMES.ICD)!.setTag(DG.TAGS.CHOICES, stringifyItems(icdsDf.columns.byIndex(0).toList()));
|
|
376
|
+
view.table!.col(COL_NAMES.IDP)!.setTag(DG.TAGS.CHOICES, stringifyItems(idpsDf.columns.byIndex(0).toList()));
|
|
377
|
+
|
|
378
|
+
grok.events.onContextMenu.subscribe((args) => {
|
|
379
|
+
if ([COL_NAMES.TYPE, COL_NAMES.OWNER, COL_NAMES.SALT, COL_NAMES.SOURCE, COL_NAMES.ICD, COL_NAMES.IDP]
|
|
380
|
+
.includes(args.args.context.table.currentCol.name)) {
|
|
381
|
+
args.args.menu.item('Fill Column With Value', () => {
|
|
382
|
+
const v = args.args.context.table.currentCell.value;
|
|
383
|
+
args.args.context.table.currentCell.column.init(v);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
352
386
|
});
|
|
353
387
|
}),
|
|
354
388
|
]);
|
package/src/salts.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const SALTS_CSV = `
|
|
2
|
-
|
|
1
|
+
export const SALTS_CSV = `DISPLAY,MOLWEIGHT
|
|
2
|
+
no Data Added,100`;
|
package/src/sources.ts
ADDED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
|
|
2
2
|
import {isValidSequence} from './sequence-codes-tools';
|
|
3
|
+
import {getNucleotidesMol} from './mol-transformations';
|
|
3
4
|
|
|
4
|
-
export function
|
|
5
|
-
|
|
5
|
+
export function sequenceToMolV3000(sequence: string, inverted: boolean = false, oclRender: boolean = false,
|
|
6
|
+
format: string): string {
|
|
7
|
+
const obj = getObjectWithCodesAndSmiles(sequence, format);
|
|
6
8
|
let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
|
|
7
9
|
let i = 0;
|
|
8
|
-
|
|
10
|
+
const smilesCodes:string[] = [];
|
|
9
11
|
const codesList = [];
|
|
10
12
|
const links = ['s', 'ps', '*'];
|
|
11
13
|
const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
|
|
@@ -18,15 +20,45 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false): s
|
|
|
18
20
|
}
|
|
19
21
|
for (let i = 0; i < codesList.length; i++) {
|
|
20
22
|
if (dropdowns.includes(codesList[i])) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
smilesCodes.push((i >= codesList.length / 2) ?
|
|
24
|
+
MODIFICATIONS[codesList[i]].right : MODIFICATIONS[codesList[i]].left);
|
|
25
|
+
smilesCodes.push(stadardPhosphateLinkSmiles);
|
|
26
|
+
} else {
|
|
27
|
+
if (links.includes(codesList[i]) ||
|
|
28
|
+
includesStandardLinkAlready.includes(codesList[i]) ||
|
|
29
|
+
(i < codesList.length - 1 && links.includes(codesList[i + 1]))
|
|
30
|
+
)
|
|
31
|
+
smilesCodes.push(obj[codesList[i]]);
|
|
32
|
+
else {
|
|
33
|
+
smilesCodes.push(obj[codesList[i]]);
|
|
34
|
+
smilesCodes.push(stadardPhosphateLinkSmiles);
|
|
29
35
|
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return getNucleotidesMol(smilesCodes, oclRender);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function sequenceToSmiles(sequence: string, inverted: boolean = false, format: string): string {
|
|
43
|
+
const obj = getObjectWithCodesAndSmiles(sequence, format);
|
|
44
|
+
let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
|
|
45
|
+
let i = 0;
|
|
46
|
+
let smiles = '';
|
|
47
|
+
const codesList = [];
|
|
48
|
+
const links = ['s', 'ps', '*'];
|
|
49
|
+
const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
|
|
50
|
+
const dropdowns = Object.keys(MODIFICATIONS);
|
|
51
|
+
codes = codes.concat(dropdowns);
|
|
52
|
+
while (i < sequence.length) {
|
|
53
|
+
const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
|
|
54
|
+
i += code.length;
|
|
55
|
+
inverted ? codesList.unshift(code) : codesList.push(code);
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < codesList.length; i++) {
|
|
58
|
+
if (dropdowns.includes(codesList[i])) {
|
|
59
|
+
smiles += (i >= codesList.length / 2) ?
|
|
60
|
+
MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
|
|
61
|
+
MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
|
|
30
62
|
} else {
|
|
31
63
|
if (links.includes(codesList[i]) ||
|
|
32
64
|
includesStandardLinkAlready.includes(codesList[i]) ||
|
|
@@ -51,19 +83,26 @@ export function sequenceToSmiles(sequence: string, inverted: boolean = false): s
|
|
|
51
83
|
smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
|
|
52
84
|
}
|
|
53
85
|
|
|
54
|
-
function getObjectWithCodesAndSmiles(sequence: string) {
|
|
86
|
+
function getObjectWithCodesAndSmiles(sequence: string, format: string) {
|
|
55
87
|
const obj: { [code: string]: string } = {};
|
|
56
|
-
|
|
57
|
-
for (const
|
|
58
|
-
for (const
|
|
59
|
-
|
|
88
|
+
if (format == null) {
|
|
89
|
+
for (const synthesizer of Object.keys(map)) {
|
|
90
|
+
for (const technology of Object.keys(map[synthesizer])) {
|
|
91
|
+
for (const code of Object.keys(map[synthesizer][technology]))
|
|
92
|
+
obj[code] = map[synthesizer][technology][code].SMILES;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
for (const technology of Object.keys(map[format])) {
|
|
97
|
+
for (const code of Object.keys(map[format][technology]))
|
|
98
|
+
obj[code] = map[format][technology][code].SMILES;
|
|
60
99
|
}
|
|
61
100
|
}
|
|
62
101
|
// TODO: create object based from synthesizer type to avoid key(codes) duplicates
|
|
63
|
-
const output = isValidSequence(sequence);
|
|
64
|
-
if (output.
|
|
102
|
+
const output = isValidSequence(sequence, format);
|
|
103
|
+
if (output.synthesizer!.includes(SYNTHESIZERS.MERMADE_12))
|
|
65
104
|
obj['g'] = map[SYNTHESIZERS.MERMADE_12][TECHNOLOGIES.SI_RNA]['g'].SMILES;
|
|
66
|
-
else if (output.
|
|
105
|
+
else if (output.synthesizer!.includes(SYNTHESIZERS.AXOLABS))
|
|
67
106
|
obj['g'] = map[SYNTHESIZERS.AXOLABS][TECHNOLOGIES.SI_RNA]['g'].SMILES;
|
|
68
107
|
return obj;
|
|
69
108
|
}
|
|
@@ -11,14 +11,36 @@ export const TECHNOLOGIES = {
|
|
|
11
11
|
ASO_GAPMERS: 'For ASO Gapmers',
|
|
12
12
|
SI_RNA: 'For 2\'-OMe and 2\'-F modified siRNA',
|
|
13
13
|
};
|
|
14
|
+
export const COL_NAMES = {
|
|
15
|
+
CHEMISTRY: 'Chemistry',
|
|
16
|
+
NUMBER: 'Number',
|
|
17
|
+
TYPE: 'Type',
|
|
18
|
+
CHEMISTRY_NAME: 'Chemistry Name',
|
|
19
|
+
INTERNAL_COMPOUND_ID: 'Internal compound ID',
|
|
20
|
+
IDP: 'IDP',
|
|
21
|
+
SEQUENCE: 'Sequence',
|
|
22
|
+
COMPOUND_NAME: 'Compound Name',
|
|
23
|
+
COMPOUND_COMMENTS: 'Compound Comments',
|
|
24
|
+
SALT: 'Salt',
|
|
25
|
+
EQUIVALENTS: 'Equivalents',
|
|
26
|
+
PURITY: 'Purity',
|
|
27
|
+
CPD_MW: 'Cpd MW',
|
|
28
|
+
SALT_MASS: 'Salt mass',
|
|
29
|
+
BATCH_MW: 'Batch MW',
|
|
30
|
+
SOURCE: 'Source',
|
|
31
|
+
ICD: 'ICD',
|
|
32
|
+
OWNER: 'Owner',
|
|
33
|
+
};
|
|
14
34
|
// interface CODES {
|
|
15
35
|
// }
|
|
16
|
-
export const MODIFICATIONS: {[index: string]: {left: string, right: string}} = {
|
|
36
|
+
export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: string, right: string}} = {
|
|
17
37
|
'(invabasic)': {
|
|
38
|
+
molecularWeight: 118.13,
|
|
18
39
|
left: 'O[C@@H]1C[C@@H]O[C@H]1CO',
|
|
19
40
|
right: 'O[C@@H]1C[C@@H]O[C@H]1CO',
|
|
20
41
|
},
|
|
21
42
|
'(GalNAc-2-JNJ)': {
|
|
43
|
+
molecularWeight: 1273.3,
|
|
22
44
|
left: 'C(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
|
|
23
45
|
'(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)' +
|
|
24
46
|
'(COCCC(=O)NCCCNC(=O)CCCCOC2OC(CO)C(O)C(O)C2NC(=O)C)NC(=O)CCCC(=O)NCC(O)CO',
|
|
@@ -49,7 +71,7 @@ export const map: {[synthesizer: string]:
|
|
|
49
71
|
'name': 'Guanine',
|
|
50
72
|
'weight': 329.21,
|
|
51
73
|
'normalized': 'dG',
|
|
52
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
74
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
53
75
|
},
|
|
54
76
|
'C': {
|
|
55
77
|
'name': 'Cytosine',
|
|
@@ -75,7 +97,7 @@ export const map: {[synthesizer: string]:
|
|
|
75
97
|
'name': 'Guanine',
|
|
76
98
|
'weight': 329.21,
|
|
77
99
|
'normalized': 'dG',
|
|
78
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
100
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
79
101
|
},
|
|
80
102
|
'C': {
|
|
81
103
|
'name': 'Cytosine',
|
|
@@ -139,7 +161,7 @@ export const map: {[synthesizer: string]:
|
|
|
139
161
|
'name': 'Guanine',
|
|
140
162
|
'weight': 329.21,
|
|
141
163
|
'normalized': 'dG',
|
|
142
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
164
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
143
165
|
},
|
|
144
166
|
'T': {
|
|
145
167
|
'name': 'Tyrosine',
|
|
@@ -341,13 +363,13 @@ export const map: {[synthesizer: string]:
|
|
|
341
363
|
'name': 'Guanine',
|
|
342
364
|
'weight': 329.21,
|
|
343
365
|
'normalized': 'dG',
|
|
344
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
366
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
345
367
|
},
|
|
346
368
|
'dG': {
|
|
347
369
|
'name': 'Guanine',
|
|
348
370
|
'weight': 329.21,
|
|
349
371
|
'normalized': 'dG',
|
|
350
|
-
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C
|
|
372
|
+
'SMILES': 'OC[C@H]1O[C@@H](N2C3N=C(N)NC(=O)C=3N=C2)C[C@@H]1O',
|
|
351
373
|
},
|
|
352
374
|
'T': {
|
|
353
375
|
'name': 'Tyrosine',
|