@datagrok/bio 2.9.0 → 2.10.2

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.
@@ -2,12 +2,14 @@ import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
 
5
- import {_package} from '../package-test';
5
+ import wu from 'wu';
6
6
  import {category, test} from '@datagrok-libraries/utils/src/test';
7
7
  import {MonomerPlacer} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
8
8
  import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
9
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
10
 
11
+ import {_package} from '../package-test';
12
+
11
13
  category('renderers: monomerPlacer', () => {
12
14
  const tests = {
13
15
  splitter: {
@@ -34,21 +36,27 @@ category('renderers: monomerPlacer', () => {
34
36
  },
35
37
  splitterMsa: {
36
38
  /** For charWidth=7 and sepWidth=12, MSA
37
- * Array(10) [0, 26, 52, 78, 104, 130, 156, 175, 201, 227]
39
+ * Array(10) [5, 38, 71, 104, 137, 170, 203, 222, 255, 281]
38
40
  */
39
41
  csv: 'id,seq\n' +
40
- 'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' + //Array(10) [0, 26, 52, 78, 104, 130, 156, 175, 201, 227]
41
- 'id2,m1-mon2-m3-mon4-mon5-Num--MON8-N9\n' + //
42
- 'id3,mon1-M-mon3-mon4-mon5---MON8-N9\n', //
42
+ 'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' +
43
+ 'id2,m1-mon2-m3-mon4-mon5-Num--MON8-N9\n' +
44
+ 'id3,\n' + // empty
45
+ 'id4,mon1-M-mon3-mon4-mon5---MON8-N9\n',
43
46
  testList: [
44
47
  {src: {row: 0, x: -1}, tgt: {pos: null}},
45
48
  {src: {row: 1, x: 0}, tgt: {pos: null}},
46
49
  {src: {row: 1, x: 1}, tgt: {pos: null}},
47
- {src: {row: 1, x: 26}, tgt: {pos: 0}},
50
+ {src: {row: 1, x: 4}, tgt: {pos: null}},
51
+ {src: {row: 1, x: 5}, tgt: {pos: 0}},
52
+ {src: {row: 1, x: 37}, tgt: {pos: 0}},
53
+ {src: {row: 1, x: 38}, tgt: {pos: 1}},
48
54
  {src: {row: 1, x: 170}, tgt: {pos: 4}},
49
55
  {src: {row: 1, x: 200}, tgt: {pos: 5}},
50
- {src: {row: 2, x: 200}, tgt: {pos: 5}},
51
- {src: {row: 2, x: 203}, tgt: {pos: 5}},
56
+ {src: {row: 2, x: 20}, tgt: {pos: null}}, // empty value
57
+ {src: {row: 3, x: 170}, tgt: {pos: 4}},
58
+ {src: {row: 3, x: 200}, tgt: {pos: 5}},
59
+ {src: {row: 3, x: 282}, tgt: {pos: null}},
52
60
  ]
53
61
  },
54
62
  fastaMsa: {
@@ -58,6 +66,7 @@ category('renderers: monomerPlacer', () => {
58
66
  csv: `id,seq
59
67
  id1,QQYNIYPLT
60
68
  id2,QQWSSFPYT
69
+ id3,
61
70
  id3,QHIRE--LT
62
71
  `,
63
72
  testList: [
@@ -67,14 +76,15 @@ id3,QHIRE--LT
67
76
  {src: {row: 1, x: 19}, tgt: {pos: 0}},
68
77
  {src: {row: 1, x: 170}, tgt: {pos: 8}},
69
78
  {src: {row: 1, x: 171}, tgt: {pos: 8}},
70
- {src: {row: 2, x: 170}, tgt: {pos: 8}},
71
- {src: {row: 2, x: 181}, tgt: {pos: null}},
79
+ {src: {row: 2, x: 5}, tgt: {pos: null}}, // empty value
80
+ {src: {row: 3, x: 170}, tgt: {pos: 8}},
81
+ {src: {row: 3, x: 181}, tgt: {pos: null}},
72
82
  ]
73
83
  },
74
84
  };
75
85
 
76
86
  for (const [testName, testData] of Object.entries(tests)) {
77
- test(`getPosition_${testName}`, async () => {
87
+ test(`getPosition-${testName}`, async () => {
78
88
  const df: DG.DataFrame = DG.DataFrame.fromCsv(testData.csv);
79
89
  await grok.data.detectSemanticTypes(df);
80
90
  const seqCol: DG.Column = df.getCol('seq');
@@ -94,9 +104,12 @@ id3,QHIRE--LT
94
104
  });
95
105
 
96
106
  const testList = testData.testList;
107
+ // simulate rendering
108
+ for (let rowI: number = 0; rowI < seqCol.length; ++rowI)
109
+ colTemp.getCellMonomerLengths(rowI);
97
110
 
98
111
  const errorList: string[] = [];
99
- for (const test of testList) {
112
+ for (const [test, _testI] of wu.enumerate(testList)) {
100
113
  const res = {pos: colTemp.getPosition(test.src.row, test.src.x)};
101
114
  if (test.tgt.pos != res.pos) {
102
115
  errorList.push(`Test src ${JSON.stringify(test.src)} expected tgt ${JSON.stringify(test.tgt)},` +
@@ -26,13 +26,17 @@ category('Scoring', () => {
26
26
 
27
27
  test('Identity', async () => {
28
28
  const scoresCol = await sequenceIdentityScoring(table, seqCol, reference);
29
- for (let i = 0; i < scoresCol.length; i++)
30
- expectFloat(scoresCol.get(i)!, table.get(expectedIdentity, i), 0.01, `Wrong identity score for sequence at position ${i}`);
29
+ for (let i = 0; i < scoresCol.length; i++) {
30
+ expectFloat(scoresCol.get(i)!, table.get(expectedIdentity, i), 0.01,
31
+ `Wrong identity score for sequence at position ${i}`);
32
+ }
31
33
  });
32
34
 
33
35
  test('Similarity', async () => {
34
36
  const scoresCol = await sequenceSimilarityScoring(table, seqCol, reference);
35
- for (let i = 0; i < scoresCol.length; i++)
36
- expectFloat(scoresCol.get(i)!, table.get(expectedSimilarity, i), 0.01, `Wrong similarity score for sequence at position ${i}`);
37
+ for (let i = 0; i < scoresCol.length; i++) {
38
+ expectFloat(scoresCol.get(i)!, table.get(expectedSimilarity, i), 0.01,
39
+ `Wrong similarity score for sequence at position ${i}`);
40
+ }
37
41
  });
38
42
  });
@@ -0,0 +1,116 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as DG from 'datagrok-api/dg';
3
+
4
+ import {category, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
5
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
6
+ import {ALPHABET, NOTATION, TAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
+
8
+ category('UnitsHandler: getRegion', () => {
9
+ const data: {
10
+ [testName: string]: {
11
+ srcCsv: string,
12
+ startIdx: number | null,
13
+ endIdx: number | null,
14
+ tgtCsv: string,
15
+ units: NOTATION,
16
+ alphabet: ALPHABET | null, /* alphabet is not applicable for units 'helm' */
17
+
18
+ positionNames?: { tag: string | null, start: string | null, end: string | null }
19
+ }
20
+ } = {
21
+ 'fastaDna': {
22
+ srcCsv: `seq
23
+ ATTCGT
24
+ ACTGCTC
25
+ ATTCCGTA`,
26
+ startIdx: 2,
27
+ endIdx: 4,
28
+ tgtCsv: `seq
29
+ TCG
30
+ TGC
31
+ TCC`,
32
+ units: NOTATION.FASTA,
33
+ alphabet: ALPHABET.DNA,
34
+
35
+ positionNames: {tag: 'a, b, c, d, e, f, g, h', start: 'c', end: 'e'},
36
+ },
37
+ 'separatorPt': {
38
+ srcCsv: `seq
39
+ M-D-Y-K-E-T-L
40
+ M-I-E-V-F-L-F-G-I
41
+ M-M-`,
42
+ startIdx: 5,
43
+ endIdx: null,
44
+ tgtCsv: `seq
45
+ T-L--
46
+ L-F-G-I
47
+ ---`,
48
+ units: NOTATION.SEPARATOR,
49
+ alphabet: ALPHABET.PT,
50
+
51
+ positionNames: {tag: '1, 1A, 1B, 2, 3, 4, 4A, 4A, 4C', start: '4', end: null},
52
+ },
53
+ 'helm': {
54
+ srcCsv: `seq
55
+ PEPTIDE1{[meI].[hHis].[Aca].N.T.[dE].[Thr_PO3H2].[Aca].[D-Tyr_Et].[Tyr_ab-dehydroMe].[dV].E.N.[D-Orn]}$$$$
56
+ PEPTIDE1{[meI].[hHis].[Aca].[Cys_SEt].T.[dK].[Thr_PO3H2].[Aca].[Tyr_PO3H2].[D-Chg].[dV].[Phe_ab-dehydro]}$$$$
57
+ PEPTIDE1{[Lys_Boc].[hHis].[Aca].[Cys_SEt].T}$$$$`,
58
+ startIdx: 3,
59
+ endIdx: 6,
60
+ tgtCsv: `seq
61
+ PEPTIDE1{N.T.[dE].[Thr_PO3H2]}$$$$
62
+ PEPTIDE1{[Cys_SEt].T.[dK].[Thr_PO3H2]}$$$$
63
+ PEPTIDE1{[Cys_SEt].T.*.*}$$$$`,
64
+ units: NOTATION.HELM,
65
+ alphabet: null,
66
+
67
+ positionNames: {tag: null, start: '4', end: '7'}
68
+ }
69
+ };
70
+
71
+ for (const [testName, testData] of Object.entries(data)) {
72
+ test(`${testName}-idx`, async () => {
73
+ const srcDf = DG.DataFrame.fromCsv(testData.srcCsv);
74
+ const srcSeqCol = srcDf.getCol('seq');
75
+
76
+ const semType: string | null = await grok.functions.call('Bio:detectMacromolecule', {col: srcSeqCol});
77
+ if (semType) srcSeqCol.semType = semType;
78
+
79
+ const srcUh = UnitsHandler.getOrCreate(srcSeqCol);
80
+ const resSeqCol = srcUh.getRegion(testData.startIdx, testData.endIdx, 'regSeq');
81
+
82
+ const tgtDf = DG.DataFrame.fromCsv(testData.tgtCsv);
83
+ const tgtSeqCol = tgtDf.getCol('seq');
84
+
85
+ expect(srcSeqCol.getTag(DG.TAGS.UNITS), testData.units);
86
+ expect(resSeqCol.getTag(DG.TAGS.UNITS), testData.units);
87
+ expect(srcSeqCol.getTag(TAGS.alphabet), testData.alphabet);
88
+ expect(resSeqCol.getTag(TAGS.alphabet), testData.alphabet);
89
+ expectArray(resSeqCol.toList(), tgtSeqCol.toList());
90
+ });
91
+
92
+ if (testData.positionNames) {
93
+ test(`${testName}-positionNames`, async () => {
94
+ const srcDf = DG.DataFrame.fromCsv(testData.srcCsv);
95
+ const srcSeqCol = srcDf.getCol('seq');
96
+ if (testData.positionNames!.tag)
97
+ srcSeqCol.setTag(TAGS.positionNames, testData.positionNames!.tag);
98
+
99
+ const semType: string | null = await grok.functions.call('Bio:detectMacromolecule', {col: srcSeqCol});
100
+ if (semType) srcSeqCol.semType = semType;
101
+
102
+ const resSeqCol = await grok.functions.call('Bio:getRegion',
103
+ {sequence: srcSeqCol, start: testData.positionNames!.start, end: testData.positionNames!.end});
104
+
105
+ const tgtDf = DG.DataFrame.fromCsv(testData.tgtCsv);
106
+ const tgtSeqCol = tgtDf.getCol('seq');
107
+
108
+ expect(srcSeqCol.getTag(DG.TAGS.UNITS), testData.units);
109
+ expect(resSeqCol.getTag(DG.TAGS.UNITS), testData.units);
110
+ expect(srcSeqCol.getTag(TAGS.alphabet), testData.alphabet);
111
+ expect(resSeqCol.getTag(TAGS.alphabet), testData.alphabet);
112
+ expectArray(resSeqCol.toList(), tgtSeqCol.toList());
113
+ });
114
+ }
115
+ }
116
+ });
@@ -143,8 +143,11 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
143
143
  // Cell renderer settings
144
144
  const tempMonomerWidth: string | null = tableColTemp[tempTAGS.monomerWidth];
145
145
  const monomerWidth: string = (tempMonomerWidth != null) ? tempMonomerWidth : 'short';
146
- if (monomerWidth === 'short')
147
- maxLengthOfMonomer = tableColTemp[mmcrTemps.maxMonomerLength] ?? _package.properties.MaxMonomerLength;
146
+ if (monomerWidth === 'short') {
147
+ // Renderer can start to work before Bio package initialized, in that time _package.properties is null.
148
+ // TODO: Render function is available but package init method is not completed
149
+ maxLengthOfMonomer = tableColTemp[mmcrTemps.maxMonomerLength] ?? _package.properties?.MaxMonomerLength ?? 4;
150
+ }
148
151
 
149
152
 
150
153
  let seqColTemp: MonomerPlacer = tableCol.temp[tempTAGS.bioSeqCol];
@@ -206,36 +209,6 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
206
209
  ((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
207
210
  tempReferenceSequence : tempCurrentWord ?? '');
208
211
 
209
- // let maxLengthWords: { [pos: number]: number } = {};
210
- // if (tableCol.getTag(rndrTAGS.calculatedCellRender) !== splitLimit.toString()) {
211
- // let sampleCount = 0;
212
- // while (sampleCount < Math.min(tableCol.length, 100)) {
213
- // const rowIdx: number = sampleCount;
214
- // const column = tableCol.get(rowIdx);
215
- // const subParts: string[] = splitterFunc(column);
216
- // for (const [index, amino] of subParts.entries()) {
217
- // const textSize = monomerToShortFunction(amino, maxLengthOfMonomer).length * 7 + gapRenderer;
218
- // if (textSize > (maxLengthWords[index] ?? 0))
219
- // maxLengthWords[index] = textSize;
220
- // if (index > maxIndex) maxIndex = index;
221
- // }
222
- // sampleCount += 1;
223
- // }
224
- // const minLength = 3 * 7;
225
- // for (let i = 0; i <= maxIndex; i++) {
226
- // if (maxLengthWords[i] < minLength) maxLengthWords[i] = minLength;
227
- // const maxLengthWordSum: { [pos: number]: number } = {};
228
- // maxLengthWordSum[0] = maxLengthWords[0];
229
- // for (let i = 1; i <= maxIndex; i++) maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
230
- // colTemp[tempTAGS.bioSumMaxLengthWords] = maxLengthWordSum;
231
- // colTemp[tempTAGS.bioMaxIndex] = maxIndex;
232
- // colTemp[tempTAGS.bioMaxLengthWords] = maxLengthWords;
233
- // tableCol.setTag(rndrTAGS.calculatedCellRender, splitLimit.toString());
234
- // }
235
- // } else {
236
- // maxLengthWords = colTemp[tempTAGS.bioMaxLengthWords];
237
- // }
238
-
239
212
  const subParts: ISeqSplitted = splitterFunc(value);
240
213
  /* let x1 = x; */
241
214
  let color = undefinedColor;
@@ -4,7 +4,6 @@ import * as ui from 'datagrok-api/ui';
4
4
 
5
5
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
6
6
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
- import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
8
7
 
9
8
  import {_package} from '../package';
10
9
 
@@ -15,9 +14,9 @@ export function addCopyMenuUI(cell: DG.Cell, menu: DG.Menu): void {
15
14
 
16
15
  menu.group('Copy')
17
16
  .items(tgtNotationList, (tgtNotation) => {
18
- const nc = new NotationConverter(cell.column);
17
+ const ncUH = UnitsHandler.getOrCreate(cell.column);
19
18
  const separator = tgtNotation === NOTATION.SEPARATOR ? _package.properties.DefaultSeparator : undefined;
20
- const converter = nc.getConverter(tgtNotation as NOTATION, separator);
19
+ const converter = ncUH.getConverter(tgtNotation as NOTATION, separator);
21
20
  const tgtSeq = converter(cell.value);
22
21
 
23
22
  if (!navigator.clipboard) {
@@ -5,7 +5,6 @@ import * as grok from 'datagrok-api/grok';
5
5
  import $ from 'cash-dom';
6
6
  import {Subscription} from 'rxjs';
7
7
  import {NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
8
- import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
9
8
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
9
  import {expect} from '@datagrok-libraries/utils/src/test';
11
10
 
@@ -22,8 +21,8 @@ export function convert(col?: DG.Column): void {
22
21
  let tgtCol = col ?? grok.shell.t.columns.bySemType('Macromolecule')!;
23
22
  if (!tgtCol)
24
23
  throw new Error('No column with Macromolecule semantic type found');
25
- let converter = new NotationConverter(tgtCol);
26
- let currentNotation: NOTATION = converter.notation;
24
+ let converterUH = UnitsHandler.getOrCreate(tgtCol);
25
+ let currentNotation: NOTATION = converterUH.notation;
27
26
  const dialogHeader = ui.divText(
28
27
  'Current notation: ' + currentNotation,
29
28
  {
@@ -47,8 +46,8 @@ export function convert(col?: DG.Column): void {
47
46
  }
48
47
 
49
48
  tgtCol = newCol;
50
- converter = new NotationConverter(tgtCol);
51
- currentNotation = converter.notation;
49
+ converterUH = UnitsHandler.getOrCreate(tgtCol);
50
+ currentNotation = converterUH.notation;
52
51
  if (currentNotation === NOTATION.HELM)
53
52
  separatorInput.value = '/'; // helm monomers can have - in the name like D-aThr;
54
53
  dialogHeader.textContent = 'Current notation: ' + currentNotation;
@@ -117,8 +116,8 @@ export function convert(col?: DG.Column): void {
117
116
  * @param {string | null} separator Separator for SEPARATOR notation
118
117
  */
119
118
  export async function convertDo(srcCol: DG.Column, targetNotation: NOTATION, separator?: string): Promise<DG.Column> {
120
- const converter = new NotationConverter(srcCol);
121
- const newColumn = converter.convert(targetNotation, separator);
119
+ const converterUH = UnitsHandler.getOrCreate(srcCol);
120
+ const newColumn = converterUH.convert(targetNotation, separator);
122
121
  srcCol.dataFrame.columns.add(newColumn);
123
122
 
124
123
  // Call detector directly to escape some error on detectSemanticTypes
@@ -0,0 +1,261 @@
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
+
5
+ import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
6
+
7
+ export interface GetRegionParams {
8
+ table: DG.DataFrame,
9
+ sequence: DG.Column<string>,
10
+ start: string | null,
11
+ end: string | null,
12
+ /** Name for the column with sequence of the region */ name: string | null,
13
+ }
14
+
15
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
16
+ import {_package} from '../package';
17
+
18
+ export interface SeqRegion {
19
+ name: string,
20
+ description: string,
21
+ start: string,
22
+ end: string,
23
+ }
24
+
25
+ export class GetRegionFuncEditor {
26
+ inputs = new class {
27
+ table: DG.InputBase<DG.DataFrame | null>;
28
+ sequence: DG.InputBase<DG.Column | null>;
29
+ region: DG.InputBase<SeqRegion>;
30
+ start: DG.InputBase<string>;
31
+ end: DG.InputBase<string>;
32
+ name: DG.InputBase<string>;
33
+ }();
34
+
35
+ constructor(
36
+ private readonly call: DG.FuncCall
37
+ ) {
38
+ const getDesc = (paramName: string) => this.call.inputParams[paramName].property.description;
39
+
40
+ this.inputs.table = ui.tableInput('Table',
41
+ this.call.inputParams['table'].value ?? grok.shell.tv.dataFrame, undefined,
42
+ () => {});
43
+
44
+ const seqColValue = this.call.inputParams['sequence'].value ??
45
+ this.inputs.table.value!.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
46
+ const seqColOptions = {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE};
47
+ this.inputs.sequence = ui.columnInput('Sequence', grok.shell.tv.dataFrame, seqColValue,
48
+ this.sequenceInputChanged.bind(this), seqColOptions);
49
+ this.inputs.start = ui.choiceInput(
50
+ 'Start', undefined, [], this.startInputChanged.bind(this)) as unknown as DG.InputBase<string>;
51
+ this.inputs.end = ui.choiceInput(
52
+ 'End', undefined, [], this.endInputChanged.bind(this)) as unknown as DG.InputBase<string>;
53
+
54
+ this.inputs.region = ui.choiceInput<SeqRegion>('Region', null as unknown as SeqRegion, [],
55
+ this.regionInputChanged.bind(this)) as DG.InputBase<SeqRegion>;
56
+
57
+ this.inputs.name = ui.stringInput('Column name', this.getDefaultName(),
58
+ this.nameInputChanged.bind(this), {clearIcon: true});
59
+ this.inputs.name.onInput(this.nameInputInput.bind(this)); // To catch clear event
60
+
61
+ // tooltips
62
+ for (const paramName in this.call.inputParams) {
63
+ // @ts-ignore
64
+ ui.tooltip.bind(this.inputs[paramName].captionLabel, getDesc(paramName));
65
+ }
66
+
67
+ // initial
68
+ this.sequenceInputChanged();
69
+ }
70
+
71
+ private sequenceInputChanged(): void {
72
+ const seqCol = this.inputs.sequence.value;
73
+ const uh = seqCol ? UnitsHandler.getOrCreate(seqCol) : null;
74
+ this.updateRegionItems();
75
+ this.updateStartEndInputItems();
76
+ this.updateRegion(true);
77
+ this.updateNameInput();
78
+ }
79
+
80
+ private fixRegion: boolean = false;
81
+
82
+ private regionInputChanged(): void {
83
+ this.fixRegion = true;
84
+ try {
85
+ const regJsonStr = this.inputs.region.stringValue;
86
+ const reg: SeqRegion | null = regJsonStr ? JSON.parse(regJsonStr) as SeqRegion : null;
87
+
88
+ if (reg !== null) {
89
+ this.inputs.start.value = reg?.start;
90
+ this.inputs.end.value = reg?.end;
91
+ } else {
92
+ const uh = UnitsHandler.getOrCreate(this.inputs.sequence.value!);
93
+ this.inputs.start.value = uh.posList[0];
94
+ this.inputs.end.value = uh.posList[uh.posList.length - 1];
95
+ }
96
+ } finally {
97
+ this.fixRegion = false;
98
+ }
99
+ }
100
+
101
+ private startInputChanged(): void {
102
+ this.updateRegion(false);
103
+ this.updateNameInput();
104
+ }
105
+
106
+ private endInputChanged(): void {
107
+ this.updateRegion(false);
108
+ this.updateNameInput();
109
+ }
110
+
111
+ private nameInputChanged(): void {
112
+ if (!this.defaultNameUpdating)
113
+ this.defaultName = false;
114
+ }
115
+
116
+ private nameInputInput(): void {
117
+ if (!this.inputs.name.value) {
118
+ this.defaultName = true;
119
+ this.inputs.name.input.focus();
120
+ }
121
+ }
122
+
123
+ private updateStartEndInputItems(): void {
124
+ const seqCol = this.inputs.sequence.value;
125
+ const uh = seqCol ? UnitsHandler.getOrCreate(seqCol) : null;
126
+
127
+ const startSE = (this.inputs.start.input as HTMLSelectElement);
128
+ const endSE = (this.inputs.end.input as HTMLSelectElement);
129
+ for (let i = startSE.options.length - 1; i >= 0; --i) startSE.options.remove(i);
130
+ for (let i = endSE.options.length - 1; i >= 0; --i) endSE.options.remove(i);
131
+ for (const pos of uh?.posList ?? []) {
132
+ const startPosOE = document.createElement('option');
133
+ const endPosOE = document.createElement('option');
134
+ startPosOE.text = endPosOE.text = pos;
135
+ startPosOE.value = endPosOE.value = pos;
136
+ startSE.options.add(startPosOE);
137
+ endSE.options.add(endPosOE);
138
+ }
139
+ startSE.value = uh?.posList[0] ?? '';
140
+ endSE.value = uh?.posList[uh?.posList.length - 1] ?? '';
141
+ }
142
+
143
+ private updateRegionItems(): void {
144
+ const seqCol = this.inputs.sequence.value;
145
+ const regionsTagTxt: string | null = seqCol ? seqCol.getTag(bioTAGS.regions) : null;
146
+ const regionList: SeqRegion[] | null = regionsTagTxt ? JSON.parse(regionsTagTxt) : null;
147
+
148
+ const regionSE = (this.inputs.region.input as HTMLSelectElement);
149
+ for (let i = regionSE.options.length - 1; i >= 0; --i) regionSE.options.remove(i);
150
+
151
+ const nullOE = document.createElement('option');
152
+ nullOE.text = '';
153
+ nullOE.value = JSON.stringify(null);
154
+ regionSE.options.add(nullOE);
155
+
156
+ if (regionList != null) {
157
+ this.inputs.region.root.style.removeProperty('display');
158
+ for (const region of regionList) {
159
+ const regionOE = document.createElement('option');
160
+ regionOE.text = `${region.name}: ${region.start}-${region.end}`;
161
+ regionOE.value = JSON.stringify(region);
162
+ regionSE.options.add(regionOE);
163
+ }
164
+ } else {
165
+ this.inputs.region.root.style.display = 'none';
166
+ }
167
+ }
168
+
169
+ private updateRegion(reset: boolean): void {
170
+ const startPos: string = this.inputs.start.stringValue ?? '';
171
+ const endPos: string = this.inputs.end.stringValue ?? '';
172
+
173
+ if (!this.fixRegion) {
174
+ const regionSE = (this.inputs.region.input as HTMLSelectElement);
175
+ regionSE.selectedIndex = -1;
176
+ for (let i = regionSE.options.length - 1; i >= 0; --i) {
177
+ const regionOE = regionSE.options[i];
178
+ const reg: SeqRegion = JSON.parse(regionOE.value);
179
+ if (reg && startPos === reg.start && endPos === reg.end) regionSE.selectedIndex = i;
180
+ }
181
+ }
182
+ }
183
+
184
+ private defaultName: boolean = true;
185
+ private defaultNameUpdating: boolean = false;
186
+
187
+ private updateNameInput(): void {
188
+ this.defaultNameUpdating = true;
189
+ try {
190
+ if (this.defaultName) this.inputs.name.value = this.getDefaultName();
191
+ } finally {
192
+ this.defaultNameUpdating = false;
193
+ }
194
+ }
195
+
196
+ private getDefaultName(): string {
197
+ const regionJsonStr = this.inputs.region.stringValue;
198
+ const reg: SeqRegion | null = regionJsonStr ? JSON.parse(regionJsonStr) : null;
199
+
200
+ const seqCol: DG.Column<string> = this.inputs.sequence.value!;
201
+
202
+ const startPos: string = this.inputs.start.stringValue ?? '';
203
+ const endPos: string = this.inputs.end.stringValue ?? '';
204
+
205
+ return reg != null ? `${seqCol.name}(${reg.name}): ${reg.start}-${reg.end}` :
206
+ `${seqCol?.name}: (${startPos}-${endPos})`;
207
+ }
208
+
209
+ private getParams(): {} {
210
+ return {
211
+ table: this.inputs.table.value!,
212
+ sequence: this.inputs.sequence.value!,
213
+ start: this.getStart(),
214
+ end: this.getEnd(),
215
+ name: this.getName(),
216
+ };
217
+ }
218
+
219
+ private getStart(): string | null {
220
+ return this.inputs.start.stringValue;
221
+ }
222
+
223
+ private getEnd(): string | null {
224
+ return this.inputs.end.stringValue;
225
+ }
226
+
227
+ private getName(): string | null {
228
+ const str = this.inputs.name.stringValue;
229
+ return str == '' ? null : str;
230
+ }
231
+
232
+ // -- UI --
233
+
234
+ public dialog(): void {
235
+ const inputsForm = ui.inputs(Object.values(this.inputs), {style: {minWidth: '320px'}});
236
+ ui.dialog({title: 'Get Region'})
237
+ .add(inputsForm)
238
+ .onOK(async () => {
239
+ (async () => {
240
+ const callParams = this.getParams();
241
+ await this.call.func.prepare(callParams).call(true);
242
+ })()
243
+ .catch((err: any) => { _package.handleErrorUI(err); });
244
+ })
245
+ .show();
246
+ }
247
+
248
+ public widget(): DG.Widget {
249
+ const inputsForm = ui.inputs(Object.entries(this.inputs)
250
+ .filter(([inputName, input]) => !['table', 'sequence'].includes(inputName))
251
+ .map(([inputName, input]) => input));
252
+ const doBtn = ui.button('Get Region', () => {
253
+ (async () => {
254
+ const callParams = this.getParams();
255
+ await this.call.func.prepare(callParams).call(true);
256
+ })()
257
+ .catch((err: any) => { _package.handleErrorUI(err); });
258
+ });
259
+ return DG.Widget.fromRoot(ui.divV([inputsForm, ui.div(doBtn)]));
260
+ }
261
+ }
@@ -0,0 +1,65 @@
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
+
5
+ import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
6
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
7
+ import {getRegion} from '../package';
8
+ import {TaskBarProgressIndicator} from 'datagrok-api/dg';
9
+
10
+ export function getRegionUI(col: DG.Column<string>): void {
11
+ const uh = UnitsHandler.getOrCreate(col);
12
+
13
+ const nameInput = ui.stringInput('Name', '');
14
+ const startPositionInput = ui.choiceInput('Start Position', uh.posList[0], uh.posList,
15
+ () => { /* TODO: update name placeholder with getDefaultName() */ });
16
+ const endPositionInput = ui.choiceInput('End Position', uh.posList[uh.posList.length], uh.posList,
17
+ () => { /* TODO: update name placeholder with getDefaultName() */ });
18
+
19
+ const getDefaultName = (): string => {
20
+ return `${col.name}:${startPositionInput.value}-${endPositionInput.value}`;
21
+ };
22
+
23
+ ui.dialog({title: 'Get Region'}).add(ui.inputs([
24
+ nameInput,
25
+ startPositionInput,
26
+ endPositionInput,
27
+ ])).onOK(() => {
28
+ const pi = TaskBarProgressIndicator.create('Getting region...');
29
+ try {
30
+ const name: string = nameInput.value ?? getDefaultName();
31
+ const regCol = getRegionDo(col, name, startPositionInput.value, endPositionInput.value);
32
+ col.dataFrame.columns.add(regCol);
33
+ regCol.setTag(DG.TAGS.CELL_RENDERER, 'sequence');
34
+ } catch (err: any) {
35
+ grok.shell.error(err.toString());
36
+ } finally { pi.close(); }
37
+ });
38
+ }
39
+
40
+ /** {@link startPosName} and {@link endPosName} are according positionNames tag (or default ['1', '2',...]) */
41
+ export function getRegionDo(
42
+ col: DG.Column<string>, startPosName: string | null, endPosName: string | null, name: string | null
43
+ ): DG.Column<string> {
44
+ const uh = UnitsHandler.getOrCreate(col);
45
+
46
+ let startPosIdx: number | null = null;
47
+ let endPosIdx: number | null = null;
48
+
49
+ for (let posJ: number = 0; posJ < uh.posList.length; ++posJ) {
50
+ if (uh.posList[posJ] == startPosName) startPosIdx = posJ;
51
+ if (uh.posList[posJ] == endPosName) endPosIdx = posJ;
52
+ }
53
+ if (startPosIdx === null && startPosName !== null)
54
+ throw new Error(`Start position ${startPosName} not found.`);
55
+ if (endPosIdx === null && endPosName !== null)
56
+ throw new Error(`End position ${endPosName} not found.`);
57
+
58
+ if (uh.posList.length < endPosIdx!)
59
+ throw new Error(`End position ${endPosIdx} exceeds positions length`);
60
+
61
+ const regColName: string = !!name ? name : `${col.name}: (${startPosName ?? ''}-${endPosName ?? ''})`;
62
+
63
+ const regCol = uh.getRegion(startPosIdx, endPosIdx, regColName);
64
+ return regCol;
65
+ }