@datagrok/sequence-translator 1.0.11 → 1.0.13

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/src/helpers.ts ADDED
@@ -0,0 +1,28 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ export function sortByStringLengthInDescendingOrder(array: string[]): string[] {
4
+ return array.sort(function(a, b) {return b.length - a.length;});
5
+ }
6
+
7
+ export function stringify(items: string[]): string {
8
+ return '["' + items.join('", "') + '"]';
9
+ }
10
+
11
+ export function download(name: string, href: string): void {
12
+ const element = document.createElement('a');
13
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + href);
14
+ element.setAttribute('download', name);
15
+ element.click();
16
+ }
17
+
18
+ export function removeEmptyRows(t: DG.DataFrame, colToCheck: DG.Column): DG.DataFrame {
19
+ for (let i = t.rowCount - 1; i > -1; i--) {
20
+ if (colToCheck.getString(i) == '')
21
+ t.rows.removeAt(i, 1, false);
22
+ }
23
+ return t;
24
+ }
25
+
26
+ export function differenceOfTwoArrays(a: string[], b: string[]): string[] {
27
+ return a.filter((x) => !b.includes(x));
28
+ }
@@ -5,6 +5,7 @@ import {convertSequence, undefinedInputSequence, isValidSequence} from '../struc
5
5
  import {map, MODIFICATIONS} from '../structures-works/map';
6
6
  import {sequenceToSmiles, sequenceToMolV3000} from '../structures-works/from-monomers';
7
7
  import $ from 'cash-dom';
8
+ import {download} from '../helpers';
8
9
 
9
10
  const defaultInput = 'fAmCmGmAmCpsmU';
10
11
  const sequenceWasCopied = 'Copied';
@@ -15,7 +16,6 @@ export function mainView() {
15
16
  moleculeSvgDiv.innerHTML = '';
16
17
  outputTableDiv.innerHTML = '';
17
18
  const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
18
- let errorsExist = false;
19
19
  try {
20
20
  sequence = sequence.replace(/\s/g, '');
21
21
  const output = isValidSequence(sequence, null);
@@ -27,13 +27,6 @@ export function mainView() {
27
27
  const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
28
28
  JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
29
29
  -1;
30
- if ('indexOfFirstNotValidChar' in outputSequenceObj) {
31
- const indexOfFirstNotValidChar = ('indexOfFirstNotValidChar' in outputSequenceObj) ?
32
- JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).indexOfFirstNotValidChar :
33
- -1;
34
- if (indexOfFirstNotValidChar != -1)
35
- errorsExist = true;
36
- }
37
30
 
38
31
  tableRows.push({
39
32
  'key': key,
@@ -51,20 +44,6 @@ export function mainView() {
51
44
  });
52
45
  }
53
46
 
54
- if (errorsExist) {
55
- const synthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidChar!).synthesizer.slice(0, -6);
56
- asoGapmersGrid.onCellPrepare(function(gc) {
57
- gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
58
- });
59
- omeAndFluoroGrid.onCellPrepare(function(gc) {
60
- gc.style.backColor = (gc.gridColumn.name == synthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
61
- });
62
- switchInput.enabled = true;
63
- } else {
64
- asoGapmersGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
65
- omeAndFluoroGrid.onCellPrepare(function(gc) {gc.style.backColor = 0xFFFFFFFF;});
66
- }
67
-
68
47
  outputTableDiv.append(
69
48
  ui.div([
70
49
  DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) =>
@@ -108,46 +87,43 @@ export function mainView() {
108
87
  updateTableAndMolecule(sequence, inputFormatChoiceInput.value!);
109
88
  });
110
89
 
111
- const asoDf = DG.DataFrame.fromObjects([
112
- {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
113
- {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
114
- {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
115
- {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
116
- {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
117
- {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
118
- {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
119
- {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
120
- {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
121
- {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
122
- {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
123
- {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
124
- {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
125
- {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
126
- ])!;
127
- const asoGapmersGrid = DG.Viewer.grid(asoDf, {showRowHeader: false, showCellTooltip: false});
128
-
129
- asoDf.onCurrentCellChanged.subscribe((_) => {
130
- navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'));
131
- });
90
+ const asoGapmersGrid = DG.Viewer.grid(
91
+ DG.DataFrame.fromObjects([
92
+ {'Name': '2\'MOE-5Me-rU', 'BioSpring': '5', 'Janssen GCRS': 'moeT'},
93
+ {'Name': '2\'MOE-rA', 'BioSpring': '6', 'Janssen GCRS': 'moeA'},
94
+ {'Name': '2\'MOE-5Me-rC', 'BioSpring': '7', 'Janssen GCRS': 'moe5mC'},
95
+ {'Name': '2\'MOE-rG', 'BioSpring': '8', 'Janssen GCRS': 'moeG'},
96
+ {'Name': '5-Methyl-dC', 'BioSpring': '9', 'Janssen GCRS': '5mC'},
97
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Janssen GCRS': 'ps'},
98
+ {'Name': 'dA', 'BioSpring': 'A', 'Janssen GCRS': 'A, dA'},
99
+ {'Name': 'dC', 'BioSpring': 'C', 'Janssen GCRS': 'C, dC'},
100
+ {'Name': 'dG', 'BioSpring': 'G', 'Janssen GCRS': 'G, dG'},
101
+ {'Name': 'dT', 'BioSpring': 'T', 'Janssen GCRS': 'T, dT'},
102
+ {'Name': 'rA', 'BioSpring': '', 'Janssen GCRS': 'rA'},
103
+ {'Name': 'rC', 'BioSpring': '', 'Janssen GCRS': 'rC'},
104
+ {'Name': 'rG', 'BioSpring': '', 'Janssen GCRS': 'rG'},
105
+ {'Name': 'rU', 'BioSpring': '', 'Janssen GCRS': 'rU'},
106
+ ])!, {showRowHeader: false, showCellTooltip: false, allowEdit: false},
107
+ );
132
108
 
133
109
  const omeAndFluoroGrid = DG.Viewer.grid(
134
- DG.DataFrame.fromObjects([
135
- {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
136
- {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
137
- {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
138
- {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
139
- {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
140
- {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
141
- {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
142
- {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
143
- {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
144
- ])!, {showRowHeader: false, showCellTooltip: false},
110
+ DG.DataFrame.fromObjects([
111
+ {'Name': '2\'-fluoro-U', 'BioSpring': '1', 'Axolabs': 'Uf', 'Janssen GCRS': 'fU'},
112
+ {'Name': '2\'-fluoro-A', 'BioSpring': '2', 'Axolabs': 'Af', 'Janssen GCRS': 'fA'},
113
+ {'Name': '2\'-fluoro-C', 'BioSpring': '3', 'Axolabs': 'Cf', 'Janssen GCRS': 'fC'},
114
+ {'Name': '2\'-fluoro-G', 'BioSpring': '4', 'Axolabs': 'Gf', 'Janssen GCRS': 'fG'},
115
+ {'Name': '2\'OMe-rU', 'BioSpring': '5', 'Axolabs': 'u', 'Janssen GCRS': 'mU'},
116
+ {'Name': '2\'OMe-rA', 'BioSpring': '6', 'Axolabs': 'a', 'Janssen GCRS': 'mA'},
117
+ {'Name': '2\'OMe-rC', 'BioSpring': '7', 'Axolabs': 'c', 'Janssen GCRS': 'mC'},
118
+ {'Name': '2\'OMe-rG', 'BioSpring': '8', 'Axolabs': 'g', 'Janssen GCRS': 'mG'},
119
+ {'Name': 'ps linkage', 'BioSpring': '*', 'Axolabs': 's', 'Janssen GCRS': 'ps'},
120
+ ])!, {showRowHeader: false, showCellTooltip: false, allowEdit: false},
145
121
  );
146
122
 
147
123
  const overhangModificationsGrid = DG.Viewer.grid(
148
124
  DG.DataFrame.fromColumns([
149
125
  DG.Column.fromStrings('Name', Object.keys(MODIFICATIONS)),
150
- ])!, {showRowHeader: false, showCellTooltip: false},
126
+ ])!, {showRowHeader: false, showCellTooltip: false, allowEdit: false},
151
127
  );
152
128
  updateTableAndMolecule(defaultInput, inputFormatChoiceInput.value!);
153
129
 
@@ -175,27 +151,29 @@ export function mainView() {
175
151
  $(codesTablesDiv).hide(),
176
152
  );
177
153
 
154
+ const downloadMolFileIcon = ui.iconFA('download', () => {
155
+ const clearSequence = inputSequenceField.value.replace(/\s/g, '');
156
+ const result = sequenceToMolV3000(clearSequence, false, false, inputFormatChoiceInput.value!);
157
+ download(clearSequence + '.mol', encodeURIComponent(result));
158
+ }, 'Save .mol file');
159
+
160
+ const copySmilesIcon = ui.iconFA('copy', () => {
161
+ navigator.clipboard.writeText(
162
+ sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!),
163
+ ).then(() => grok.shell.info(sequenceWasCopied));
164
+ }, 'Copy SMILES');
165
+
178
166
  const topPanel = [
179
- ui.iconFA('download', () => {
180
- const result = sequenceToMolV3000(inputSequenceField.value.replace(/\s/g, ''), false, false,
181
- inputFormatChoiceInput.value!);
182
- const element = document.createElement('a');
183
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
184
- element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
185
- element.click();
186
- }, 'Save .mol file'),
187
- ui.iconFA('copy', () => {
188
- navigator.clipboard.writeText(
189
- sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''), false, inputFormatChoiceInput.value!))
190
- .then(() => grok.shell.info(sequenceWasCopied));
191
- }, 'Copy SMILES'),
167
+ downloadMolFileIcon,
168
+ copySmilesIcon,
192
169
  switchInput.root,
193
170
  ];
194
171
 
195
172
  const v = grok.shell.v;
196
173
  const tabControl = grok.shell.sidebar;
197
- tabControl.onTabChanged.subscribe((_) =>
198
- v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
174
+ tabControl.onTabChanged.subscribe((_) => {
175
+ v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]);
176
+ });
199
177
  v.setRibbonPanels([topPanel]);
200
178
 
201
179
  return ui.box(
package/src/package.ts CHANGED
@@ -17,14 +17,13 @@ export function sequenceTranslator(): void {
17
17
  windows.showToolbox = false;
18
18
  windows.showHelp = false;
19
19
 
20
- const v = grok.shell.newView('Sequence Translator', [
21
- ui.tabControl({
22
- 'MAIN': mainView(),
23
- 'AXOLABS': defineAxolabsPattern(),
24
- 'SDF': saveSenseAntiSense(),
25
- }),
26
- ]);
20
+ const v = grok.shell.newView('Sequence Translator', []);
27
21
  v.box = true;
22
+ v.append(ui.tabControl({
23
+ 'MAIN': mainView(),
24
+ 'AXOLABS': defineAxolabsPattern(),
25
+ 'SDF': saveSenseAntiSense(),
26
+ }));
28
27
  }
29
28
 
30
29
  //tags: autostart
@@ -1,6 +1,7 @@
1
1
  import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS, delimiter} from './map';
2
2
  import {isValidSequence} from './sequence-codes-tools';
3
3
  import {getNucleotidesMol} from './mol-transformations';
4
+ import {sortByStringLengthInDescendingOrder} from '../helpers';
4
5
 
5
6
  export function sequenceToMolV3000(sequence: string, inverted: boolean = false, oclRender: boolean = false,
6
7
  format: string): string {
@@ -108,7 +109,3 @@ function getObjectWithCodesAndSmiles(sequence: string, format: string) {
108
109
  obj['g'] = map[SYNTHESIZERS.AXOLABS][TECHNOLOGIES.SI_RNA]['g'].SMILES;
109
110
  return obj;
110
111
  }
111
-
112
- function sortByStringLengthInDescendingOrder(array: string[]): string[] {
113
- return array.sort(function(a: string, b: string) {return b.length - a.length;});
114
- }
@@ -1,5 +1,6 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
  import {getAllCodesOfSynthesizer} from './sequence-codes-tools';
3
+ import {differenceOfTwoArrays} from '../helpers';
3
4
 
4
5
  export const delimiter = ';';
5
6
  export const SYNTHESIZERS = {
@@ -16,29 +17,6 @@ export const TECHNOLOGIES = {
16
17
  ASO_GAPMERS: 'For ASO Gapmers',
17
18
  SI_RNA: 'For 2\'-OMe and 2\'-F modified siRNA',
18
19
  };
19
- export const COL_NAMES = {
20
- CHEMISTRY: 'Chemistry',
21
- NUMBER: 'Number',
22
- TYPE: 'Type',
23
- CHEMISTRY_NAME: 'Chemistry Name',
24
- INTERNAL_COMPOUND_ID: 'Internal compound ID',
25
- IDP: 'IDP',
26
- SEQUENCE: 'Sequence',
27
- COMPOUND_NAME: 'Compound Name',
28
- COMPOUND_COMMENTS: 'Compound Comments',
29
- SALT: 'Salt',
30
- EQUIVALENTS: 'Equivalents',
31
- PURITY: 'Purity',
32
- CPD_MW: 'Cpd MW',
33
- SALT_MOL_WEIGHT: 'Salt MW',
34
- SALT_MASS: 'Salt mass',
35
- BATCH_MW: 'Batch MW',
36
- SOURCE: 'Source',
37
- ICD: 'ICD',
38
- OWNER: 'Owner',
39
- };
40
- // interface CODES {
41
- // }
42
20
  export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: string, right: string}} = {
43
21
  '(invabasic)': {
44
22
  molecularWeight: 118.13,
@@ -58,7 +36,7 @@ export const MODIFICATIONS: {[index: string]: {molecularWeight: number, left: st
58
36
  export const stadardPhosphateLinkSmiles = 'OP(=O)(O)O';
59
37
  export const map: {[synthesizer: string]:
60
38
  {[technology: string]: {[code: string]:
61
- {'name'?: string, 'weight'?: number, 'normalized'?: string, 'SMILES': string}}}} = {
39
+ {'name': string, 'weight': number, 'normalized': string, 'SMILES': string}}}} = {
62
40
  'Raw Nucleotides': {
63
41
  'DNA': {
64
42
  'A': {
@@ -724,12 +702,20 @@ fU, fU
724
702
  /J-CbCS/, J-CbCS
725
703
  /J-MtCD/, J-MtCD`;
726
704
 
727
- function differenceOfTwoArrays(a: string[], b: string[]): string[] {
728
- return a.filter((x) => !b.includes(x));
729
- }
730
705
 
731
706
  const codesWithSmiles = getAllCodesOfSynthesizer(SYNTHESIZERS.GCRS);
732
707
  const allGcrsCodes = DG.DataFrame.fromCsv(lcmsToGcrs).getCol('GCRS').toList();
733
708
  export const gcrsCodesWithoutSmiles = differenceOfTwoArrays(allGcrsCodes, codesWithSmiles);
734
709
  for (const e of gcrsCodesWithoutSmiles)
735
- map[SYNTHESIZERS.GCRS]['Others'][e] = {'SMILES': ''};
710
+ map[SYNTHESIZERS.GCRS]['Others'][e] = {name: '', weight: 0, normalized: '', SMILES: ''};
711
+
712
+
713
+ export const weightsObj: {[code: string]: number} = {};
714
+ for (const synthesizer of Object.keys(map)) {
715
+ for (const technology of Object.keys(map[synthesizer])) {
716
+ for (const code of Object.keys(map[synthesizer][technology]))
717
+ weightsObj[code] = map[synthesizer][technology][code].weight;
718
+ }
719
+ }
720
+ for (const [key, value] of Object.entries(MODIFICATIONS))
721
+ weightsObj[key] = value.molecularWeight;
@@ -564,10 +564,10 @@ export function getNucleotidesMol(smilesCodes: string[]) {
564
564
  molBlocks.push(rotateNucleotidesV3000(smilesCodes[i]));
565
565
  }
566
566
 
567
- return linkV3000(molBlocks, false);
567
+ return linkV3000(molBlocks);
568
568
  }
569
569
 
570
- export function linkV3000(molBlocks: string[], twoChains: boolean = false, useChirality: boolean = true) {
570
+ export function linkStrandsV3000(strands:{senseStrands: string[], antiStrands: string[]}, useChirality: boolean = true) {
571
571
  let macroMolBlock = '\nDatagrok macromolecule handler\n\n';
572
572
  macroMolBlock += ' 0 0 0 0 0 0 999 V3000\n';
573
573
  macroMolBlock += 'M V30 BEGIN CTAB\n';
@@ -579,18 +579,34 @@ export function linkV3000(molBlocks: string[], twoChains: boolean = false, useCh
579
579
  let nbond = 0;
580
580
  let xShift = 0;
581
581
 
582
- if (twoChains && molBlocks.length > 1)
583
- molBlocks[1] = invertNucleotidesV3000(molBlocks[1]);
582
+ // if (twoChains && molBlocks.length > 1)
583
+ // molBlocks[1] = invertNucleotidesV3000(molBlocks[1]);
584
+
585
+ if (strands.antiStrands.length > 0) {
586
+ for(let i = 0; i < strands.antiStrands.length; i++) {
587
+ strands.antiStrands[i] = invertNucleotidesV3000(strands.antiStrands[i]);
588
+ }
589
+ }
590
+
591
+ let inverted = false;
592
+ let molBlocks = strands.senseStrands.concat(strands.antiStrands);
584
593
 
585
594
  for (let i = 0; i < molBlocks.length; i++) {
595
+
596
+ if (i >= strands.senseStrands.length && inverted == false) {
597
+ inverted = true;
598
+ xShift = 0;
599
+ }
600
+
601
+
586
602
  molBlocks[i] = molBlocks[i].replaceAll('(-\nM V30 ', '(')
587
603
  .replaceAll('-\nM V30 ', '').replaceAll(' )', ')');
588
604
  const numbers = extractAtomsBondsNumbersV3000(molBlocks[i]);
589
605
  const coordinates = extractAtomDataV3000(molBlocks[i]);
590
606
 
591
- if (twoChains) {
592
- const xShiftRight = Math.min(...coordinates.x);
593
- const yShift = i == 0 ? Math.min(...coordinates.y) - 1 : Math.max(...coordinates.y) + 1;
607
+ if (inverted) {
608
+ const xShiftRight = Math.min(...coordinates.x) - xShift;
609
+ const yShift = !inverted ? Math.min(...coordinates.y) - 1 : Math.max(...coordinates.y) + 10;
594
610
  for (let j = 0; j < coordinates.x.length; j++)
595
611
  coordinates.x[j] -= xShiftRight;
596
612
  for (let j = 0; j < coordinates.y.length; j++)
@@ -603,7 +619,154 @@ export function linkV3000(molBlocks: string[], twoChains: boolean = false, useCh
603
619
  let indexEnd = indexAtoms;
604
620
 
605
621
  for (let j = 0; j < numbers.natom; j++) {
606
- if (coordinates.atomIndex[j] != 1 || i == 0 || twoChains) {
622
+ // if (coordinates.atomIndex[j] != 1 || i == 0 || twoChains) {
623
+ //rewrite atom number
624
+ index = molBlocks[i].indexOf('V30', index) + 4;
625
+ indexEnd = molBlocks[i].indexOf(' ', index);
626
+ const atomNumber = parseInt(molBlocks[i].substring(index, indexEnd)) + natom;
627
+ molBlocks[i] = molBlocks[i].slice(0, index) + atomNumber + molBlocks[i].slice(indexEnd);
628
+
629
+ //rewrite coordinates
630
+ index = molBlocks[i].indexOf(' ', index) + 1;
631
+ index = molBlocks[i].indexOf(' ', index) + 1;
632
+ indexEnd = molBlocks[i].indexOf(' ', index);
633
+
634
+ const totalShift = true ? 0 : xShift - coordinates.x[0];
635
+ let coordinate = true ?
636
+ Math.round(10000*coordinates.x[j])/10000 :
637
+ Math.round(10000*(parseFloat(molBlocks[i].substring(index, indexEnd)) + totalShift))/10000;
638
+ molBlocks[i] = molBlocks[i].slice(0, index) + coordinate + molBlocks[i].slice(indexEnd);
639
+
640
+ index = molBlocks[i].indexOf(' ', index) + 1;
641
+ indexEnd = molBlocks[i].indexOf(' ', index);
642
+ coordinate = true ?
643
+ Math.round(10000*coordinates.y[j])/10000 :
644
+ Math.round(10000*(parseFloat(molBlocks[i].substring(index, indexEnd))))/10000;
645
+ molBlocks[i] = molBlocks[i].slice(0, index) + coordinate + molBlocks[i].slice(indexEnd);
646
+
647
+ index = molBlocks[i].indexOf('\n', index) + 1;
648
+ }
649
+
650
+ const indexAtomsEnd = molBlocks[i].indexOf('M V30 END ATOM');
651
+ atomBlock += molBlocks[i].substring(indexAtoms + 1, indexAtomsEnd);
652
+
653
+ let indexBonds = molBlocks[i].indexOf('M V30 BEGIN BOND'); // V3000 index for bonds
654
+ indexBonds = molBlocks[i].indexOf('\n', indexBonds);
655
+ index = indexBonds;
656
+ indexEnd = indexBonds;
657
+
658
+ for (let j = 0; j < numbers.nbond; j++) {
659
+ //rewrite bond number
660
+ index = molBlocks[i].indexOf('V30', index) + 4;
661
+ indexEnd = molBlocks[i].indexOf(' ', index);
662
+ const bondNumber = parseInt(molBlocks[i].substring(index, indexEnd)) + nbond;
663
+ molBlocks[i] = molBlocks[i].slice(0, index) + bondNumber + molBlocks[i].slice(indexEnd);
664
+
665
+ //rewrite atom pair in bond
666
+ index = molBlocks[i].indexOf(' ', index) + 1;
667
+ index = molBlocks[i].indexOf(' ', index) + 1;
668
+ indexEnd = molBlocks[i].indexOf(' ', index);
669
+ let atomNumber = parseInt(molBlocks[i].substring(index, indexEnd)) + natom;
670
+ molBlocks[i] = molBlocks[i].slice(0, index) + atomNumber + molBlocks[i].slice(indexEnd);
671
+ index = molBlocks[i].indexOf(' ', index) + 1;
672
+ indexEnd = Math.min(molBlocks[i].indexOf('\n', index), molBlocks[i].indexOf(' ', index));
673
+ atomNumber = parseInt(molBlocks[i].substring(index, indexEnd)) + natom;
674
+ molBlocks[i] = molBlocks[i].slice(0, index) + atomNumber + molBlocks[i].slice(indexEnd);
675
+
676
+ index = molBlocks[i].indexOf('\n', index) + 1;
677
+ }
678
+
679
+ const indexBondEnd = molBlocks[i].indexOf('M V30 END BOND');
680
+ bondBlock += molBlocks[i].substring(indexBonds + 1, indexBondEnd);
681
+
682
+ let indexCollection = molBlocks[i].indexOf('M V30 MDLV30/STEABS ATOMS=('); // V3000 index for collections
683
+
684
+ while (indexCollection != -1) {
685
+ indexCollection += 28;
686
+ const collectionEnd = molBlocks[i].indexOf(')', indexCollection);
687
+ const collectionEntries = molBlocks[i].substring(indexCollection, collectionEnd).split(' ').slice(1);
688
+ collectionEntries.forEach((e) => {
689
+ collection.push(parseInt(e) + natom);
690
+ });
691
+ indexCollection = collectionEnd;
692
+ indexCollection = molBlocks[i].indexOf('M V30 MDLV30/STEABS ATOMS=(', indexCollection);
693
+ }
694
+
695
+ natom += true ? numbers.natom : numbers.natom - 1;
696
+ nbond += numbers.nbond;
697
+ xShift += Math.max(...coordinates.x) + 1;//twoChains ? 0 : coordinates.x[numbers.natom - 1] - coordinates.x[0];
698
+ }
699
+
700
+ const entries = 4;
701
+ const collNumber = Math.ceil(collection.length / entries);
702
+
703
+ //if (oclRender) {
704
+ // collectionBlock += 'M V30 MDLV30/STEABS ATOMS=(' + collection.length;
705
+
706
+ // for (let j = 0; j < collection.length; j++)
707
+ // collectionBlock += ' ' + collection[j];
708
+
709
+ // collectionBlock += ')\n';
710
+ //} else {
711
+ collectionBlock += 'M V30 MDLV30/STEABS ATOMS=(' + collection.length + ' -\n';
712
+ for (let i = 0; i < collNumber; i++) {
713
+ collectionBlock += 'M V30 ';
714
+ const entriesCurrent = i + 1 == collNumber ? collection.length - (collNumber - 1)*entries : entries;
715
+ for (let j = 0; j < entriesCurrent; j++) {
716
+ collectionBlock += (j + 1 == entriesCurrent) ?
717
+ (i == collNumber - 1 ? collection[entries*i + j] + ')\n' : collection[entries*i + j] + ' -\n') :
718
+ collection[entries*i + j] + ' ';
719
+ }
720
+ }
721
+ //}
722
+
723
+ //generate file
724
+ true? natom : natom++;
725
+ macroMolBlock += 'M V30 COUNTS ' + natom + ' ' + nbond + ' 0 0 0\n';
726
+ macroMolBlock += 'M V30 BEGIN ATOM\n';
727
+ macroMolBlock += atomBlock;
728
+ macroMolBlock += 'M V30 END ATOM\n';
729
+ macroMolBlock += 'M V30 BEGIN BOND\n';
730
+ macroMolBlock += bondBlock;
731
+ macroMolBlock += 'M V30 END BOND\n';
732
+ if(useChirality){
733
+ macroMolBlock += 'M V30 BEGIN COLLECTION\n';
734
+ macroMolBlock += collectionBlock;
735
+ macroMolBlock += 'M V30 END COLLECTION\n';
736
+ } else
737
+ macroMolBlock = macroMolBlock.replace(/ CFG=\d/g, ' ');
738
+
739
+ macroMolBlock += 'M V30 END CTAB\n';
740
+ macroMolBlock += 'M END';
741
+
742
+ return macroMolBlock;
743
+ }
744
+
745
+ export function linkV3000(molBlocks: string[], useChirality: boolean = true) {
746
+ let macroMolBlock = '\nDatagrok macromolecule handler\n\n';
747
+ macroMolBlock += ' 0 0 0 0 0 0 999 V3000\n';
748
+ macroMolBlock += 'M V30 BEGIN CTAB\n';
749
+ let atomBlock = '';
750
+ let bondBlock = '';
751
+ let collectionBlock = '';
752
+ const collection: number [] = [];
753
+ let natom = 0;
754
+ let nbond = 0;
755
+ let xShift = 0;
756
+
757
+ for (let i = 0; i < molBlocks.length; i++) {
758
+ molBlocks[i] = molBlocks[i].replaceAll('(-\nM V30 ', '(')
759
+ .replaceAll('-\nM V30 ', '').replaceAll(' )', ')');
760
+ const numbers = extractAtomsBondsNumbersV3000(molBlocks[i]);
761
+ const coordinates = extractAtomDataV3000(molBlocks[i]);
762
+
763
+ let indexAtoms = molBlocks[i].indexOf('M V30 BEGIN ATOM'); // V3000 index for atoms coordinates
764
+ indexAtoms = molBlocks[i].indexOf('\n', indexAtoms);
765
+ let index = indexAtoms;
766
+ let indexEnd = indexAtoms;
767
+
768
+ for (let j = 0; j < numbers.natom; j++) {
769
+ if (coordinates.atomIndex[j] != 1 || i == 0) {
607
770
  //rewrite atom number
608
771
  index = molBlocks[i].indexOf('V30', index) + 4;
609
772
  indexEnd = molBlocks[i].indexOf(' ', index);
@@ -615,16 +778,14 @@ export function linkV3000(molBlocks: string[], twoChains: boolean = false, useCh
615
778
  index = molBlocks[i].indexOf(' ', index) + 1;
616
779
  indexEnd = molBlocks[i].indexOf(' ', index);
617
780
 
618
- const totalShift = twoChains ? 0 : xShift - coordinates.x[0];
619
- let coordinate = twoChains ?
620
- Math.round(10000*coordinates.x[j])/10000 :
781
+ const totalShift = xShift - coordinates.x[0];
782
+ let coordinate =
621
783
  Math.round(10000*(parseFloat(molBlocks[i].substring(index, indexEnd)) + totalShift))/10000;
622
784
  molBlocks[i] = molBlocks[i].slice(0, index) + coordinate + molBlocks[i].slice(indexEnd);
623
785
 
624
786
  index = molBlocks[i].indexOf(' ', index) + 1;
625
787
  indexEnd = molBlocks[i].indexOf(' ', index);
626
- coordinate = twoChains ?
627
- Math.round(10000*coordinates.y[j])/10000 :
788
+ coordinate =
628
789
  Math.round(10000*(parseFloat(molBlocks[i].substring(index, indexEnd))))/10000;
629
790
  molBlocks[i] = molBlocks[i].slice(0, index) + coordinate + molBlocks[i].slice(indexEnd);
630
791
 
@@ -681,9 +842,9 @@ export function linkV3000(molBlocks: string[], twoChains: boolean = false, useCh
681
842
  indexCollection = molBlocks[i].indexOf('M V30 MDLV30/STEABS ATOMS=(', indexCollection);
682
843
  }
683
844
 
684
- natom += twoChains ? numbers.natom : numbers.natom - 1;
845
+ natom += numbers.natom - 1;
685
846
  nbond += numbers.nbond;
686
- xShift += twoChains ? 0 : coordinates.x[numbers.natom - 1] - coordinates.x[0];
847
+ xShift += coordinates.x[numbers.natom - 1] - coordinates.x[0];
687
848
  }
688
849
 
689
850
  const entries = 4;
@@ -710,7 +871,7 @@ export function linkV3000(molBlocks: string[], twoChains: boolean = false, useCh
710
871
  //}
711
872
 
712
873
  //generate file
713
- twoChains? natom : natom++;
874
+ natom++;
714
875
  macroMolBlock += 'M V30 COUNTS ' + natom + ' ' + nbond + ' 0 0 0\n';
715
876
  macroMolBlock += 'M V30 BEGIN ATOM\n';
716
877
  macroMolBlock += atomBlock;
@@ -1,48 +1,67 @@
1
1
  import * as ui from 'datagrok-api/ui';
2
+ import {download} from '../helpers';
2
3
  import {sequenceToMolV3000} from '../structures-works/from-monomers';
3
- import {linkV3000} from '../structures-works/mol-transformations';
4
+ import {linkStrandsV3000} from '../structures-works/mol-transformations';
4
5
  import {getFormat} from '../structures-works/sequence-codes-tools';
5
6
 
6
- export function saveSdf(as: string, ss: string, oneEntity: boolean, useChirality: boolean, invertSS: boolean, invertAS: boolean) {
7
+ export function saveSdf(as: string, ss: string,
8
+ oneEntity: boolean, useChirality: boolean,
9
+ invertSS: boolean, invertAS: boolean,
10
+ as2: string | null = null, invertAS2: boolean | null) {
7
11
  const formatAs = getFormat(as);
8
12
  const formatSs = getFormat(ss);
13
+ let formatAs2: string | null = null;
14
+ let molAS2: string | null = null;
15
+
9
16
  const molSS = sequenceToMolV3000(ss, invertSS, false, formatSs!);
10
17
  const molAS = sequenceToMolV3000(as, invertAS, false, formatAs!);
18
+
19
+ if (as2 != null && as2 != '') {
20
+ formatAs2 = getFormat(as2!);
21
+ molAS2 = sequenceToMolV3000(as2, invertAS2!, false, formatAs2!);
22
+ }
23
+
11
24
  let result: string;
12
- if (oneEntity)
13
- result = linkV3000([molSS, molAS], true, useChirality) + '\n$$$$\n';
14
- else {
25
+ if (oneEntity) {
26
+ const antiStrands = molAS2 == null ? [molAS] : [molAS, molAS2];
27
+ result = linkStrandsV3000({senseStrands: [molSS], antiStrands: antiStrands}, useChirality) + '\n$$$$\n';
28
+
29
+ } else {
15
30
  result =
16
31
  molSS + '\n' +
17
32
  `> <Sequence>\nSense Strand\n$$$$\n` +
18
33
  molAS + '\n' +
19
34
  `> <Sequence>\nAnti Sense\n$$$$\n`;
20
- }
21
35
 
22
- const element = document.createElement('a');
23
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
24
- element.setAttribute('download', ss.replace(/\s/g, '') + '.sdf');
25
- element.click();
36
+ if (molAS2)
37
+ result += molAS2+ '\n' +
38
+ `> <Sequence>\nAnti Sense 2\n$$$$\n`;
39
+ }
40
+ download(ss.replace(/\s/g, '') + '.sdf', encodeURIComponent(result));
26
41
  }
27
42
 
28
43
  export function saveSenseAntiSense() {
29
44
  const moleculeSvgDiv = ui.block([]);
30
45
  const ssInput = ui.textInput('Sense Strand', '');
31
46
  const asInput = ui.textInput('Anti Sense', '');
47
+ const asInput2 = ui.textInput('Anti Sense 2', '');
32
48
  const straight = "5 prime -> 3 prime";
33
49
  const inverse = "3 prime -> 5 prime";
34
50
  let ssInverse = false;
35
51
  let asInverse = false;
52
+ let as2Inverse = false;
36
53
 
37
54
  const changeSense = ui.choiceInput('SS direction', straight, [straight, inverse]);
38
55
  changeSense.onChanged(() => {ssInverse = changeSense.value == inverse;});
39
56
  const changeAntiSense = ui.choiceInput('AS direction', straight, [straight, inverse]);
40
57
  changeAntiSense.onChanged(() => {asInverse = changeAntiSense.value == inverse;});
58
+ const changeAntiSense2 = ui.choiceInput('AS 2 direction', straight, [straight, inverse]);
59
+ changeAntiSense2.onChanged(() => {asInverse = changeAntiSense.value == inverse;});
41
60
 
42
61
  const saveOption = ui.switchInput('Save as one entity', true);
43
62
  const chirality = ui.switchInput('Use chiral', true);
44
63
  const saveBtn = ui.button('Save SDF', () =>
45
- saveSdf(asInput.value, ssInput.value, saveOption.value, chirality.value, ssInverse, asInverse));
64
+ saveSdf(asInput.value, ssInput.value, saveOption.value, chirality.value, ssInverse, asInverse, asInput2.value, as2Inverse));
46
65
 
47
66
  const saveSection = ui.panel([
48
67
  ui.div([
@@ -51,9 +70,11 @@ export function saveSenseAntiSense() {
51
70
  ui.divV([
52
71
  ssInput,
53
72
  asInput,
73
+ asInput2,
54
74
  ui.div([changeSense], {style: {width: '40'}}),
55
75
  changeSense,
56
76
  changeAntiSense,
77
+ changeAntiSense2,
57
78
  saveOption,
58
79
  chirality,
59
80
  ui.buttonsInput([saveBtn]),