@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.0.11",
4
+ "version": "1.0.13",
5
5
  "author": {
6
6
  "name": "Vadym Kovadlo",
7
7
  "email": "vkovadlo@datagrok.ai"
@@ -13,7 +13,7 @@
13
13
  "directory": "packages/SequenceTranslator"
14
14
  },
15
15
  "dependencies": {
16
- "@datagrok-libraries/utils": "^1.11.1",
16
+ "@datagrok-libraries/utils": "^1.15.5",
17
17
  "@types/react": "^18.0.15",
18
18
  "datagrok-api": "^1.7.2",
19
19
  "datagrok-tools": "^4.1.2",
@@ -43,27 +43,35 @@ it('TEST', async () => {
43
43
  return new Promise<object>((resolve, reject) => {
44
44
  (<any>window).grok.functions.eval(targetPackage + ':test()').then((df: any) => {
45
45
  const cStatus = df.columns.byName('success');
46
+ const cSkipped = df.columns.byName('skipped');
46
47
  const cMessage = df.columns.byName('result');
47
48
  const cCat = df.columns.byName('category');
48
49
  const cName = df.columns.byName('name');
49
50
  const cTime = df.columns.byName('ms');
50
51
  let failed = false;
52
+ let skipReport = '';
51
53
  let passReport = '';
52
54
  let failReport = '';
53
55
  for (let i = 0; i < df.rowCount; i++) {
54
56
  if (cStatus.get(i)) {
55
- passReport += `Test result : Success : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
57
+ if (cSkipped.get(i)) {
58
+ skipReport += `Test result : Skipped : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
59
+ } else {
60
+ passReport += `Test result : Success : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
61
+ }
56
62
  } else {
57
63
  failed = true;
58
64
  failReport += `Test result : Failed : ${cTime.get(i)} : ${targetPackage}.${cCat.get(i)}.${cName.get(i)} : ${cMessage.get(i)}\n`;
59
65
  }
60
66
  }
61
- resolve({failReport, passReport, failed});
67
+ resolve({failReport, skipReport, passReport, failed});
62
68
  }).catch((e: any) => reject(e));
63
69
  });
64
70
  }, targetPackage);
65
71
  // @ts-ignore
66
72
  console.log(r.passReport);
67
73
  // @ts-ignore
74
+ console.log(r.skipReport);
75
+ // @ts-ignore
68
76
  expect(r.failed).checkOutput(false, r.failReport);
69
- }, 3600000);
77
+ }, 7200000);
File without changes
File without changes
@@ -0,0 +1,37 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+ import {sortByStringLengthInDescendingOrder} from '../helpers';
3
+ import {MODIFICATIONS} from '../structures-works/map';
4
+
5
+ export function saltMass(
6
+ saltNames: string[], molWeightCol: DG.Column, equivalentsCol: DG.Column, i: number, saltCol: DG.Column,
7
+ ): number {
8
+ const saltRowIndex = saltNames.indexOf(saltCol.get(i));
9
+ return (
10
+ saltRowIndex == -1 || molWeightCol.get(saltRowIndex) == DG.FLOAT_NULL || equivalentsCol.get(i) == DG.INT_NULL
11
+ ) ?
12
+ DG.FLOAT_NULL :
13
+ molWeightCol.get(saltRowIndex) * equivalentsCol.get(i);
14
+ }
15
+
16
+ export function saltMolWeigth(saltNamesList: string[], saltCol: DG.Column, molWeightCol: DG.Column, i: number): number {
17
+ const saltRowIndex = saltNamesList.indexOf(saltCol.get(i));
18
+ return (saltRowIndex == -1) ? DG.FLOAT_NULL : molWeightCol.get(saltRowIndex);
19
+ }
20
+
21
+ export function batchMolWeight(compoundMolWeightCol: DG.Column, saltMassCol: DG.Column, i: number): number {
22
+ return (compoundMolWeightCol.getString(i) == '' || saltMassCol.getString(i) == '') ?
23
+ DG.FLOAT_NULL :
24
+ compoundMolWeightCol.get(i) + saltMassCol.get(i);
25
+ }
26
+
27
+ export function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
28
+ const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
29
+ let weight = 0;
30
+ let i = 0;
31
+ while (i < sequence.length) {
32
+ const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
33
+ weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
34
+ i += matchedCode.length;
35
+ }
36
+ return weight - 61.97;
37
+ }
@@ -0,0 +1,37 @@
1
+ export const SEQUENCE_TYPES = {
2
+ SENSE_STRAND: 'SS',
3
+ ANTISENSE_STRAND: 'AS',
4
+ DUPLEX: 'Duplex',
5
+ TRIPLEX: 'Triplex',
6
+ DIMER: 'Dimer',
7
+ };
8
+
9
+ export const COL_NAMES = {
10
+ CHEMISTRY: 'Chemistry',
11
+ NUMBER: 'Number',
12
+ TYPE: 'Type',
13
+ CHEMISTRY_NAME: 'Chemistry Name',
14
+ INTERNAL_COMPOUND_ID: 'Internal compound ID',
15
+ IDP: 'IDP',
16
+ SEQUENCE: 'Sequence',
17
+ COMPOUND_NAME: 'Compound Name',
18
+ COMPOUND_COMMENTS: 'Compound Comments',
19
+ SALT: 'Salt',
20
+ EQUIVALENTS: 'Equivalents',
21
+ PURITY: 'Purity',
22
+ COMPOUND_MOL_WEIGHT: 'Cpd MW',
23
+ SALT_MOL_WEIGHT: 'Salt MW',
24
+ SALT_MASS: 'Salt mass',
25
+ BATCH_MOL_WEIGHT: 'Batch MW',
26
+ SOURCE: 'Source',
27
+ ICD: 'ICD',
28
+ OWNER: 'Owner',
29
+ };
30
+
31
+ export const GENERATED_COL_NAMES = [
32
+ COL_NAMES.COMPOUND_NAME,
33
+ COL_NAMES.COMPOUND_COMMENTS,
34
+ COL_NAMES.COMPOUND_MOL_WEIGHT,
35
+ COL_NAMES.SALT_MASS,
36
+ COL_NAMES.BATCH_MOL_WEIGHT,
37
+ ];
@@ -3,90 +3,59 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
  import {siRnaBioSpringToGcrs, siRnaAxolabsToGcrs, gcrsToNucleotides, asoGapmersBioSpringToGcrs, gcrsToMermade12,
5
5
  siRnaNucleotidesToGcrs} from '../structures-works/converters';
6
- import {map, COL_NAMES, MODIFICATIONS} from '../structures-works/map';
6
+ import {weightsObj, SYNTHESIZERS} from '../structures-works/map';
7
+ import {SEQUENCE_TYPES, COL_NAMES, GENERATED_COL_NAMES} from './constants';
8
+ import {saltMass, saltMolWeigth, molecularWeight, batchMolWeight} from './calculations';
7
9
  import {isValidSequence} from '../structures-works/sequence-codes-tools';
8
10
  import {sequenceToMolV3000} from '../structures-works/from-monomers';
9
- import {linkV3000} from '../structures-works/mol-transformations';
11
+ import {linkStrandsV3000} from '../structures-works/mol-transformations';
12
+ import {stringify, download, removeEmptyRows, differenceOfTwoArrays} from '../helpers';
10
13
 
11
- import {SALTS_CSV} from '../salts';
12
- import {USERS_CSV} from '../users';
13
- import {ICDS} from '../ICDs';
14
- import {SOURCES} from '../sources';
15
- import {IDPS} from '../IDPs';
14
+ import {SALTS_CSV} from './salts';
15
+ import {USERS_CSV} from './users';
16
+ import {ICDS} from './ICDs';
17
+ import {SOURCES} from './sources';
18
+ import {IDPS} from './IDPs';
16
19
 
17
20
 
18
- function sortByStringLengthInDescendingOrder(array: string[]): string[] {
19
- return array.sort(function(a, b) {return b.length - a.length;});
21
+ function parseStrandsFromDuplexCell(s: string): {SS: string, AS: string} {
22
+ const arr = s.slice(3).split('\r\nAS ');
23
+ return {SS: arr[0], AS: arr[1]};
20
24
  }
21
25
 
22
- function stringify(items: string[]): string {
23
- return '["' + items.join('", "') + '"]';
26
+ function parseStrandsFromTriplexOrDimerCell(s: string): {SS: string, AS1: string, AS2: string} {
27
+ const arr1 = s.slice(3).split('\r\nAS1 ');
28
+ const arr2 = arr1[1].split('\r\nAS2 ');
29
+ return {SS: arr1[0], AS1: arr2[0], AS2: arr2[1]};
24
30
  }
25
31
 
26
- function saltMass(saltNames: string[], molWeightCol: DG.Column, equivalentsCol: DG.Column, i: number,
27
- saltCol: DG.Column) {
28
- const saltRowIndex = saltNames.indexOf(saltCol.get(i));
29
- return (
30
- saltRowIndex == -1 || molWeightCol.get(saltRowIndex) == DG.FLOAT_NULL || equivalentsCol.get(i) == DG.INT_NULL) ?
31
- DG.FLOAT_NULL :
32
- molWeightCol.get(saltRowIndex) * equivalentsCol.get(i);
33
- }
34
-
35
- function saltMolWeigth(saltNamesList: string[], saltCol: DG.Column, molWeightCol: DG.Column, i: number) {
36
- const saltRowIndex = saltNamesList.indexOf(saltCol.get(i));
37
- return (saltRowIndex == -1) ? DG.FLOAT_NULL : molWeightCol.get(saltRowIndex);
38
- }
39
-
40
- function batchMolWeight(compoundMolWeightCol: DG.Column, saltMassCol: DG.Column, i: number) {
41
- return (compoundMolWeightCol.getString(i) == '' || saltMassCol.getString(i) == '') ?
42
- DG.FLOAT_NULL :
43
- compoundMolWeightCol.get(i) + saltMassCol.get(i);
44
- }
45
-
46
- function molecularWeight(sequence: string, weightsObj: {[index: string]: number}): number {
47
- const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj)).concat(Object.keys(MODIFICATIONS));
48
- let weight = 0;
49
- let i = 0;
50
- while (i < sequence.length) {
51
- const matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
52
- weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
53
- i += matchedCode.length;
32
+ async function saveTableAsSdFile(table: DG.DataFrame) {
33
+ if (GENERATED_COL_NAMES.some((colName) => !table.columns.contains(colName))) {
34
+ const absentColNames = differenceOfTwoArrays(GENERATED_COL_NAMES, table.columns.names()).join(`', '`);
35
+ grok.shell.warning(`File saved without columns '${absentColNames}'`);
54
36
  }
55
- return weight - 61.97;
56
- }
57
37
 
58
- function parseStrandsFromDuplexCell(s: string): string[] {
59
- return s.slice(3).split('\r\nAS ');
60
- }
38
+ const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
39
+ const typeCol = table.getCol(COL_NAMES.TYPE);
61
40
 
62
- async function saveTableAsSdFile(table: DG.DataFrame) {
63
- if (!table.columns.contains('Compound Name')) {
64
- grok.shell.warning(
65
- 'File saved without columns \'' +
66
- [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
67
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''),
68
- );
69
- }
70
- const structureColumn = table.getCol(COL_NAMES.SEQUENCE);
71
- const typeColumn = table.getCol(COL_NAMES.TYPE);
72
41
  let result = '';
73
42
  for (let i = 0; i < table.rowCount; i++) {
74
- const format = 'Janssen GCRS Codes'; //getFormat(structureColumn.get(i))!;
75
- if (typeColumn.get(i) == 'Duplex') {
76
- const array = parseStrandsFromDuplexCell(structureColumn.get(i));
77
- const as = sequenceToMolV3000(array[1], true, true, format) +
78
- '\n' + `> <Sequence>\nAnti Sense\n\n`;
79
- const ss = sequenceToMolV3000(array[0], false, true, format) +
80
- '\n' + `> <Sequence>\nSense Strand\n\n`;
81
- result += linkV3000([ss, as], true, true) + '\n\n';
82
- } else if (typeColumn.get(i) == 'SS') {
83
- const molSS = sequenceToMolV3000(structureColumn.get(i), false, true, format) +
84
- '\n' + `> <Sequence>\nSense Strand\n\n`;
85
- result += molSS;
86
- } else if (typeColumn.get(i) == 'AS') {
87
- const molAS = sequenceToMolV3000(structureColumn.get(i), true, true, format) +
88
- '\n' + `> <Sequence>\nAnti Sense\n\n`;
89
- result += molAS;
43
+ const format = SYNTHESIZERS.GCRS; //getFormat(sequenceCol.get(i))!;
44
+ if (typeCol.get(i) == SEQUENCE_TYPES.SENSE_STRAND)
45
+ result += `${sequenceToMolV3000(sequenceCol.get(i), false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
46
+ else if (typeCol.get(i) == SEQUENCE_TYPES.ANTISENSE_STRAND)
47
+ result += `${sequenceToMolV3000(sequenceCol.get(i), true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
48
+ else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
49
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
50
+ const as = `${sequenceToMolV3000(obj.AS, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
51
+ const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
52
+ result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as]}, true)}\n\n`;
53
+ } else if ([SEQUENCE_TYPES.TRIPLEX, SEQUENCE_TYPES.DIMER].includes(typeCol.get(i))) {
54
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
55
+ const as1 = `${sequenceToMolV3000(obj.AS1, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
56
+ const as2 = `${sequenceToMolV3000(obj.AS2, true, true, format)}\n> <Sequence>\nAnti Sense\n\n`;
57
+ const ss = `${sequenceToMolV3000(obj.SS, false, true, format)}\n> <Sequence>\nSense Strand\n\n`;
58
+ result += `${linkStrandsV3000({senseStrands: [ss], antiStrands: [as1, as2]}, true)}\n\n`;
90
59
  }
91
60
 
92
61
  for (const col of table.columns) {
@@ -95,17 +64,16 @@ async function saveTableAsSdFile(table: DG.DataFrame) {
95
64
  }
96
65
  result += '$$$$\n';
97
66
  }
98
- const element = document.createElement('a');
99
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
100
- element.setAttribute('download', table.name + '.sdf');
101
- element.click();
67
+ download(`${table.name}.sdf`, encodeURIComponent(result));
102
68
  }
103
69
 
104
70
  export function autostartOligoSdFileSubscription() {
105
71
  grok.events.onViewAdded.subscribe((v: any) => {
106
- if (v.type == 'TableView') {
72
+ if (v.type == DG.VIEW_TYPE.TABLE_VIEW) {
107
73
  if (v.dataFrame.columns.contains(COL_NAMES.TYPE))
108
74
  oligoSdFile(v.dataFrame);
75
+
76
+ // Should be removed after fixing bug https://github.com/datagrok-ai/public/issues/808
109
77
  grok.events.onContextMenu.subscribe((args) => {
110
78
  const seqCol = args.args.context.table.currentCol; // /^[fsACGUacgu]{6,}$/
111
79
  if (DG.Detector.sampleCategories(seqCol,
@@ -166,56 +134,56 @@ export function oligoSdFile(table: DG.DataFrame) {
166
134
  const sequenceCol = table.getCol(COL_NAMES.SEQUENCE);
167
135
  const saltCol = table.getCol(COL_NAMES.SALT);
168
136
  const equivalentsCol = table.getCol(COL_NAMES.EQUIVALENTS);
169
- const typeColumn = table.getCol(COL_NAMES.TYPE);
137
+ const typeCol = table.getCol(COL_NAMES.TYPE);
170
138
  const chemistryNameCol = table.getCol(COL_NAMES.CHEMISTRY_NAME);
171
139
 
172
140
  const molWeightCol = saltsDf.getCol('MOLWEIGHT');
173
141
  const saltNamesList = saltsDf.getCol('DISPLAY').toList();
174
142
 
143
+ let newDf: DG.DataFrame;
144
+ let addColumnsPressed = false;
145
+
175
146
  function addColumns(t: DG.DataFrame) {
176
- if (t.columns.contains(COL_NAMES.COMPOUND_NAME))
147
+ if (GENERATED_COL_NAMES.some((colName) => t.columns.contains(colName)))
177
148
  return grok.shell.error('Columns already exist');
178
149
 
179
- for (let i = t.rowCount - 1; i > -1; i--) {
180
- if (sequenceCol.get(i) == '')
181
- t.rows.removeAt(i, 1, false);
182
- }
150
+ t = removeEmptyRows(t, sequenceCol);
183
151
 
184
152
  t.columns.addNewString(COL_NAMES.COMPOUND_NAME).init((i: number) => {
185
- return (typeColumn.get(i) == 'Duplex') ? chemistryNameCol.get(i) : sequenceCol.get(i);
153
+ return ([SEQUENCE_TYPES.DUPLEX, SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) ?
154
+ chemistryNameCol.get(i) :
155
+ sequenceCol.get(i);
186
156
  });
187
157
 
188
158
  t.columns.addNewString(COL_NAMES.COMPOUND_COMMENTS).init((i: number) => {
189
- if (typeColumn.get(i) == 'Duplex') {
190
- const arr = parseStrandsFromDuplexCell(sequenceCol.get(i));
191
- return chemistryNameCol.get(i) + '; duplex of SS: ' + arr[0] + ' and AS: ' + arr[1];
159
+ if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i)))
160
+ return sequenceCol.get(i);
161
+ else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
162
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
163
+ return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS: ${obj.AS}`;
164
+ } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
165
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
166
+ return `${chemistryNameCol.get(i)}; duplex of SS: ${obj.SS} and AS1: ${obj.AS1} and AS2: ${obj.AS2}`;
192
167
  }
193
- return sequenceCol.get(i);
194
168
  });
195
169
 
196
- const weightsObj: {[code: string]: number} = {};
197
- for (const synthesizer of Object.keys(map)) {
198
- for (const technology of Object.keys(map[synthesizer])) {
199
- for (const code of Object.keys(map[synthesizer][technology]))
200
- weightsObj[code] = map[synthesizer][technology][code].weight!;
201
- }
202
- }
203
- for (const [key, value] of Object.entries(MODIFICATIONS))
204
- weightsObj[key] = value.molecularWeight;
205
-
206
- t.columns.addNewFloat(COL_NAMES.CPD_MW).init((i: number) => {
207
- if (typeColumn.get(i) == 'Duplex') {
208
- const arr = parseStrandsFromDuplexCell(sequenceCol.get(i));
209
- return (
210
- isValidSequence(arr[0], null).indexOfFirstNotValidChar == -1 &&
211
- isValidSequence(arr[1], null).indexOfFirstNotValidChar == -1
212
- ) ?
213
- molecularWeight(arr[0], weightsObj) + molecularWeight(arr[1], weightsObj) :
170
+ t.columns.addNewFloat(COL_NAMES.COMPOUND_MOL_WEIGHT).init((i: number) => {
171
+ if ([SEQUENCE_TYPES.SENSE_STRAND, SEQUENCE_TYPES.ANTISENSE_STRAND].includes(typeCol.get(i))) {
172
+ return (isValidSequence(sequenceCol.get(i), null).indexOfFirstNotValidChar == -1) ?
173
+ molecularWeight(sequenceCol.get(i), weightsObj) :
174
+ DG.FLOAT_NULL;
175
+ } else if (typeCol.get(i) == SEQUENCE_TYPES.DUPLEX) {
176
+ const obj = parseStrandsFromDuplexCell(sequenceCol.get(i));
177
+ return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
178
+ molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS, weightsObj) :
179
+ DG.FLOAT_NULL;
180
+ } else if ([SEQUENCE_TYPES.DIMER, SEQUENCE_TYPES.TRIPLEX].includes(typeCol.get(i))) {
181
+ const obj = parseStrandsFromTriplexOrDimerCell(sequenceCol.get(i));
182
+ return (Object.values(obj).every((seq) => isValidSequence(seq, null).indexOfFirstNotValidChar == -1)) ?
183
+ molecularWeight(obj.SS, weightsObj) + molecularWeight(obj.AS1, weightsObj) +
184
+ molecularWeight(obj.AS2, weightsObj) :
214
185
  DG.FLOAT_NULL;
215
186
  }
216
- return (isValidSequence(sequenceCol.get(i), null).indexOfFirstNotValidChar == -1) ?
217
- molecularWeight(sequenceCol.get(i), weightsObj) :
218
- DG.FLOAT_NULL;
219
187
  });
220
188
 
221
189
  t.columns.addNewFloat(COL_NAMES.SALT_MASS).init((i: number) =>
@@ -224,15 +192,24 @@ export function oligoSdFile(table: DG.DataFrame) {
224
192
  t.columns.addNewFloat(COL_NAMES.SALT_MOL_WEIGHT).init((i: number) =>
225
193
  saltMolWeigth(saltNamesList, saltCol, molWeightCol, i));
226
194
 
227
- t.columns.addNewFloat(COL_NAMES.BATCH_MW).init((i: number) =>
228
- batchMolWeight(t.getCol(COL_NAMES.CPD_MW), t.getCol(COL_NAMES.SALT_MASS), i));
195
+ const compoundMolWeightCol = t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT);
196
+ const saltMassCol = t.getCol(COL_NAMES.SALT_MASS);
197
+ t.columns.addNewFloat(COL_NAMES.BATCH_MOL_WEIGHT).init((i: number) =>
198
+ batchMolWeight(compoundMolWeightCol, saltMassCol, i));
229
199
 
200
+ grok.shell.getTableView(table.name).grid.columns.setOrder(Object.values(COL_NAMES));
230
201
  addColumnsPressed = true;
231
202
  return newDf = t;
232
203
  }
233
204
 
234
- let newDf: DG.DataFrame;
235
- let addColumnsPressed = false;
205
+ function updateCalculatedColumns(t: DG.DataFrame, i: number): void {
206
+ const smValue = saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol);
207
+ t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
208
+ const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
209
+ t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
210
+ const bmw = batchMolWeight(t.getCol(COL_NAMES.COMPOUND_MOL_WEIGHT), t.getCol(COL_NAMES.SALT_MASS), i);
211
+ t.getCol(COL_NAMES.BATCH_MOL_WEIGHT).set(i, bmw, false);
212
+ }
236
213
 
237
214
  const d = ui.div([
238
215
  ui.icons.edit(() => {
@@ -240,18 +217,15 @@ export function oligoSdFile(table: DG.DataFrame) {
240
217
  if (table.getCol(COL_NAMES.IDP).type != DG.COLUMN_TYPE.STRING)
241
218
  table.changeColumnType(COL_NAMES.IDP, DG.COLUMN_TYPE.STRING);
242
219
  d.append(
243
- ui.link('Add Columns', () => {
244
- addColumns(table);
245
- view.grid.columns.setOrder(Object.values(COL_NAMES));
246
- }, 'Add columns: \'' + [COL_NAMES.COMPOUND_NAME, COL_NAMES.COMPOUND_COMMENTS, COL_NAMES.CPD_MW,
247
- COL_NAMES.SALT_MASS, COL_NAMES.BATCH_MW].join('\', \''), '',
248
- ),
249
- ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table)),
220
+ ui.divH([
221
+ ui.icons.add(() => addColumns(table), `Add columns: '${GENERATED_COL_NAMES.join(`', '`)}'`),
222
+ ui.icons.save(() => saveTableAsSdFile(addColumnsPressed ? newDf : table), 'Save SD file'),
223
+ ]),
250
224
  );
251
225
 
252
226
  const view = grok.shell.getTableView(table.name);
253
-
254
- view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, '["AS", "SS", "Duplex"]');
227
+ view.grid.setOptions({rowHeight: 45});
228
+ view.dataFrame.getCol(COL_NAMES.TYPE).setTag(DG.TAGS.CHOICES, stringify(Object.values(SEQUENCE_TYPES)));
255
229
  view.dataFrame.getCol(COL_NAMES.OWNER).setTag(DG.TAGS.CHOICES, stringify(usersDf.columns.byIndex(0).toList()));
256
230
  view.dataFrame.getCol(COL_NAMES.SALT).setTag(DG.TAGS.CHOICES, stringify(saltsDf.columns.byIndex(0).toList()));
257
231
  view.dataFrame.getCol(COL_NAMES.SOURCE).setTag(DG.TAGS.CHOICES, stringify(sourcesDf.columns.byIndex(0).toList()));
@@ -275,15 +249,6 @@ export function oligoSdFile(table: DG.DataFrame) {
275
249
  if ([COL_NAMES.SALT, COL_NAMES.EQUIVALENTS, COL_NAMES.SALT_MOL_WEIGHT].includes(colName))
276
250
  updateCalculatedColumns(view.dataFrame, view.dataFrame.currentRowIdx);
277
251
  });
278
-
279
- function updateCalculatedColumns(t: DG.DataFrame, i: number): void {
280
- const smValue = saltMass(saltNamesList, molWeightCol, equivalentsCol, i, saltCol);
281
- t.getCol(COL_NAMES.SALT_MASS).set(i, smValue, false);
282
- const smwValue = saltMolWeigth(saltNamesList, saltCol, molWeightCol, i);
283
- t.getCol(COL_NAMES.SALT_MOL_WEIGHT).set(i, smwValue, false);
284
- const bmw = batchMolWeight(t.getCol(COL_NAMES.CPD_MW), t.getCol(COL_NAMES.SALT_MASS), i);
285
- t.getCol(COL_NAMES.BATCH_MW).set(i, bmw, false);
286
- }
287
252
  }),
288
253
  ]);
289
254
  grok.shell.v.setRibbonPanels([[d]]);
File without changes
File without changes
File without changes
@@ -131,17 +131,20 @@ export function defineAxolabsPattern() {
131
131
  asBases[i] = ui.choiceInput('', asBases[i].value, baseChoices, (v: string) => {
132
132
  if (!enumerateModifications.includes(v)) {
133
133
  enumerateModifications.push(v);
134
- isEnumerateModificationsDiv.append(ui.boolInput(v, true, (boolV: boolean) => {
135
- if (boolV) {
136
- if (!enumerateModifications.includes(v))
137
- enumerateModifications.push(v);
138
- } else {
139
- const index = enumerateModifications.indexOf(v, 0);
140
- if (index > -1)
141
- enumerateModifications.splice(index, 1);
142
- }
143
- updateSvgScheme();
144
- }).root);
134
+ isEnumerateModificationsDiv.append(
135
+ ui.divText('', {style: {width: '25px'}}),
136
+ ui.boolInput(v, true, (boolV: boolean) => {
137
+ if (boolV) {
138
+ if (!enumerateModifications.includes(v))
139
+ enumerateModifications.push(v);
140
+ } else {
141
+ const index = enumerateModifications.indexOf(v, 0);
142
+ if (index > -1)
143
+ enumerateModifications.splice(index, 1);
144
+ }
145
+ updateSvgScheme();
146
+ }).root,
147
+ );
145
148
  }
146
149
  updateAsModification();
147
150
  updateSvgScheme();
@@ -174,17 +177,20 @@ export function defineAxolabsPattern() {
174
177
  ssBases[i] = ui.choiceInput('', ssBases[i].value, baseChoices, (v: string) => {
175
178
  if (!enumerateModifications.includes(v)) {
176
179
  enumerateModifications.push(v);
177
- isEnumerateModificationsDiv.append(ui.boolInput(v, true, (boolV: boolean) => {
178
- if (boolV) {
179
- if (!enumerateModifications.includes(v))
180
- enumerateModifications.push(v);
181
- } else {
182
- const index = enumerateModifications.indexOf(v, 0);
183
- if (index > -1)
184
- enumerateModifications.splice(index, 1);
185
- }
186
- updateSvgScheme();
187
- }).root);
180
+ isEnumerateModificationsDiv.append(
181
+ ui.divText('', {style: {width: '25px'}}),
182
+ ui.boolInput(v, true, (boolV: boolean) => {
183
+ if (boolV) {
184
+ if (!enumerateModifications.includes(v))
185
+ enumerateModifications.push(v);
186
+ } else {
187
+ const index = enumerateModifications.indexOf(v, 0);
188
+ if (index > -1)
189
+ enumerateModifications.splice(index, 1);
190
+ }
191
+ updateSvgScheme();
192
+ }).root,
193
+ );
188
194
  }
189
195
  updateSsModification();
190
196
  updateSvgScheme();
@@ -499,18 +505,20 @@ export function defineAxolabsPattern() {
499
505
  const loadPatternDiv = ui.div([]);
500
506
  const asModificationDiv = ui.div([]);
501
507
  const firstAsPtoDiv = ui.div([]);
502
- const isEnumerateModificationsDiv = ui.divH([ui.boolInput(defaultBase, true, (v: boolean) => {
503
- if (v) {
504
- if (!enumerateModifications.includes(defaultBase))
505
- enumerateModifications.push(defaultBase);
506
- } else {
507
- const index = enumerateModifications.indexOf(defaultBase, 0);
508
- if (index > -1)
509
- enumerateModifications.splice(index, 1);
510
- }
511
- updateSvgScheme();
512
- updateOutputExamples();
513
- }).root]);
508
+ const isEnumerateModificationsDiv = ui.divH([
509
+ ui.boolInput(defaultBase, true, (v: boolean) => {
510
+ if (v) {
511
+ if (!enumerateModifications.includes(defaultBase))
512
+ enumerateModifications.push(defaultBase);
513
+ } else {
514
+ const index = enumerateModifications.indexOf(defaultBase, 0);
515
+ if (index > -1)
516
+ enumerateModifications.splice(index, 1);
517
+ }
518
+ updateSvgScheme();
519
+ updateOutputExamples();
520
+ }).root,
521
+ ]);
514
522
 
515
523
  let ssBases = Array(defaultSequenceLength).fill(ui.choiceInput('', defaultBase, baseChoices));
516
524
  let asBases = Array(defaultSequenceLength).fill(ui.choiceInput('', defaultBase, baseChoices));
@@ -35,14 +35,8 @@ function getTextWidth(text: string, font: number): number {
35
35
  return 2 * context.measureText(text).width;
36
36
  }
37
37
 
38
- function getTextInsideCircle(
39
- bases: string[], index: number, nucleotideCounter: number,
40
- numberOfNucleotides: number, enumerateModifications: string[]): string {
41
- return ((bases[index].slice(-3) == '(o)') || !enumerateModifications.includes(bases[index])) ?
42
- '' :
43
- (['A', 'G', 'C', 'U', 'T'].includes(bases[index])) ?
44
- bases[index] :
45
- '';//String(numberOfNucleotides - nucleotideCounter);
38
+ function getTextInsideCircle(bases: string[], index: number): string {
39
+ return (bases[index].slice(-3) == '(o)' || !['A', 'G', 'C', 'U', 'T'].includes(bases[index])) ? '' : bases[index];
46
40
  }
47
41
 
48
42
  function getFontColorVisibleOnBackground(rgbString: string): string {
@@ -160,14 +154,15 @@ export function drawAxolabsPattern(
160
154
  Math.max(xOfSsRightModifications, xOfAsRightModifications) +
161
155
  widthOfLeftModification + baseDiameter * (Math.max(ssRightOverhangs, asRightOverhangs));
162
156
  const yOfTitle = baseRadius;
163
- const yOfNumbers = 2 * baseRadius;
157
+ const yOfSsNumbers = 2 * baseRadius;
158
+ const yOfAsNumbers = 8.5 * baseRadius;
164
159
  const yOfSsTexts = 4 * baseRadius;
165
160
  const yOfAsTexts = 7 * baseRadius;
166
161
  const yOfComment = asExists ? 11 * baseRadius : 8.5 * baseRadius;
167
162
  const yOfSsCircles = 3.5 * baseRadius;
168
163
  const yOfAsCircles = 6.5 * baseRadius;
169
- const yOfCirclesInLegends = asExists ? 9 * baseRadius : 6 * baseRadius;
170
- const yOfTextLegend = asExists ? 9.5 * baseRadius - 3 : yOfAsCircles - 3;
164
+ const yOfCirclesInLegends = asExists ? 9.5 * baseRadius : 6 * baseRadius;
165
+ const yOfTextLegend = asExists ? 10 * baseRadius - 3 : yOfAsCircles - 3;
171
166
 
172
167
  const image = svg.render(width, height);
173
168
 
@@ -196,11 +191,13 @@ export function drawAxolabsPattern(
196
191
  getShiftToAlignNumberInsideCircle(ssBases, ssBases.length - i, numberOfSsNucleotides - nucleotideCounter);
197
192
  if (ssBases[i].slice(-3) != '(o)')
198
193
  nucleotideCounter--;
194
+ const n = (ssBases[i].slice(-3) != '(o)' && enumerateModifications.includes(ssBases[i])) ?
195
+ String(numberOfSsNucleotides - nucleotideCounter) : '';
199
196
  image.append(
200
- svg.text(String(numberOfSsNucleotides - nucleotideCounter), xOfNumbers, yOfNumbers, legendFontSize, fontColor),
197
+ svg.text(n, xOfNumbers, yOfSsNumbers, legendFontSize, fontColor),
201
198
  svg.circle(getXOfBaseCircles(i, ssRightOverhangs), yOfSsCircles, baseRadius, getBaseColor(ssBases[i])),
202
- svg.text(getTextInsideCircle(ssBases, i, nucleotideCounter, numberOfSsNucleotides, enumerateModifications),
203
- xOfNumbers, yOfSsTexts, baseFontSize, getFontColorVisibleOnBackground(axolabsMap[ssBases[i]]['color'])),
199
+ svg.text(getTextInsideCircle(ssBases, i), xOfNumbers, yOfSsTexts, baseFontSize,
200
+ getFontColorVisibleOnBackground(getBaseColor(ssBases[i]))),
204
201
  ssPtoStatuses[i] ?
205
202
  svg.star(getXOfBaseCircles(i, ssRightOverhangs) + baseRadius, yOfSsTexts + psLinkageRadius, psLinkageColor) :
206
203
  '',
@@ -222,13 +219,16 @@ export function drawAxolabsPattern(
222
219
  for (let i = asBases.length - 1; i > -1; i--) {
223
220
  if (asBases[i].slice(-3) != '(o)')
224
221
  nucleotideCounter--;
222
+ const xOfNumbers = getXOfBaseCircles(i, asRightOverhangs) +
223
+ getShiftToAlignNumberInsideCircle(asBases, i, nucleotideCounter + 1);
224
+ const n = (asBases[i].slice(-3) != '(o)' && enumerateModifications.includes(asBases[i])) ?
225
+ String(nucleotideCounter + 1) : '';
225
226
  image.append(
227
+ svg.text(n, xOfNumbers, yOfAsNumbers, legendFontSize, fontColor),
226
228
  svg.circle(getXOfBaseCircles(i, asRightOverhangs), yOfAsCircles, baseRadius, getBaseColor(asBases[i])),
227
- svg.text(getTextInsideCircle(
228
- asBases, i, numberOfAsNucleotides - nucleotideCounter - 1, numberOfAsNucleotides, enumerateModifications),
229
- getXOfBaseCircles(i, asRightOverhangs) +
230
- getShiftToAlignNumberInsideCircle(asBases, i, nucleotideCounter + 1),
231
- yOfAsTexts, baseFontSize, getFontColorVisibleOnBackground(axolabsMap[asBases[i]]['color'])),
229
+ svg.text(getTextInsideCircle(asBases, i),
230
+ getXOfBaseCircles(i, asRightOverhangs) + getShiftToAlignNumberInsideCircle(asBases, i, nucleotideCounter + 1),
231
+ yOfAsTexts, baseFontSize, getFontColorVisibleOnBackground(getBaseColor(asBases[i]))),
232
232
  asPtoStatuses[i] ? svg.star(getXOfBaseCircles(i, asRightOverhangs) +
233
233
  baseRadius, yOfAsTexts + psLinkageRadius, psLinkageColor) : '',
234
234
  );