@datagrok/bio 2.8.4 → 2.9.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.
@@ -14,8 +14,16 @@ import {
14
14
  } from '@datagrok-libraries/utils/src/test';
15
15
  import * as C from '../utils/constants';
16
16
  import {_package, getHelmMonomers} from '../package';
17
- import {TAGS as bioTAGS, splitterAsFasta, splitterAsHelm} from '@datagrok-libraries/bio/src/utils/macromolecule';
17
+ import {
18
+ TAGS as bioTAGS,
19
+ splitterAsFasta,
20
+ splitterAsHelm,
21
+ NOTATION
22
+ } from '@datagrok-libraries/bio/src/utils/macromolecule';
18
23
  import {splitToMonomersUI} from '../utils/split-to-monomers';
24
+ import {SEMTYPE} from 'datagrok-api/dg';
25
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
26
+ import {TAGS} from '../utils/constants';
19
27
 
20
28
 
21
29
  category('splitters', async () => {
@@ -35,6 +43,12 @@ category('splitters', async () => {
35
43
  ['M', 'MeI', 'Y', 'K', 'E', 'T', 'L', 'L', 'MeF', 'P',
36
44
  'K', 'T', 'D', 'F', 'P', 'M', 'R', 'G', 'G', 'L', 'MeA'],
37
45
  ],
46
+ fastaFromHelm: [
47
+ '[meI][Pip][dK][Thr_PO3H2][L-hArg(Et,Et)][D-Tyr_Et][Tyr_ab-dehydroMe][dV]EN[D-Orn][D-aThr][Phe_4Me]',
48
+ ['meI', 'Pip', 'dK', 'Thr_PO3H2', 'L-hArg(Et,Et)', 'D-Tyr_Et', 'Tyr_ab-dehydroMe', 'dV', 'E', 'N', 'D-Orn',
49
+ 'D-aThr', 'Phe_4Me'],
50
+ ],
51
+
38
52
  helm1: [
39
53
  'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.D-Orn.D-aThr.Phe_4Me}$$$',
40
54
  ['meI', 'hHis', 'Aca', 'N', 'T', 'dE', 'Thr_PO3H2', 'Aca', 'D-Tyr_Et',
@@ -68,6 +82,7 @@ category('splitters', async () => {
68
82
  };
69
83
 
70
84
  test('fastaMulti', async () => { await _testFastaSplitter(data.fastaMulti[0], data.fastaMulti[1]); });
85
+ test('fastaFromHelm', async () => { await _testFastaSplitter(data.fastaFromHelm[0], data.fastaFromHelm[1]); });
71
86
 
72
87
  test('helm1', async () => { await _testHelmSplitter(data.helm1[0], data.helm1[1]); });
73
88
  test('helm2', async () => { await _testHelmSplitter(data.helm2[0], data.helm2[1]); });
@@ -78,6 +93,7 @@ category('splitters', async () => {
78
93
  test('testHelm2', async () => { await _testHelmSplitter(data.testHelm2[0], data.testHelm2[1]); });
79
94
  test('testHelm3', async () => { await _testHelmSplitter(data.testHelm3[0], data.testHelm3[1]); });
80
95
 
96
+
81
97
  test('splitToMonomers', async () => {
82
98
  const df: DG.DataFrame = await grok.dapi.files.readCsv('System:AppData/Bio/samples/MSA.csv');
83
99
 
@@ -122,6 +138,16 @@ PEPTIDE1{hHis.Aca.Cys_SEt}$$$,5.72388
122
138
  throw new Error(msgs.join(' '));
123
139
  }
124
140
  });
141
+
142
+ // test('helmAsFasta', async () => {
143
+ // // The columns can't be empty for UnitsHandler
144
+ // /* eslint-disable max-len */
145
+ // const srcSeq = '[meI][Pip][dK][Thr_PO3H2][L-hArg(Et,Et)][D-Tyr_Et][Tyr_ab-dehydroMe][dV]EN[D-Orn][D-aThr][Phe_4Me]';
146
+ // const tgtSeqA = ['meI', 'Pip', 'dK', 'Thr_PO3H2', 'L-hArg(Et,Et)', 'D-Tyr_Et', 'Tyr_ab-dehydroMe', 'dV', 'E', 'N', 'D-Orn', 'D-aThr', 'Phe_4Me'];
147
+ // /* eslint-enable max-len */
148
+ // const resSeqA = splitterAsFasta(srcSeq);
149
+ // expectArray(resSeqA, tgtSeqA);
150
+ // });
125
151
  });
126
152
 
127
153
  export async function _testFastaSplitter(src: string, tgt: string[]) {
@@ -2,11 +2,18 @@ import * as grok from 'datagrok-api/grok';
2
2
  import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
+ import wu from 'wu';
6
+
5
7
  import {category, test, expect, expectArray} from '@datagrok-libraries/utils/src/test';
6
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
8
+ import {GapSymbols, UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
9
+ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
10
+ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
7
11
 
8
12
  category('UnitsHandler', () => {
9
- const data: { [testName: string]: { src: { csv: string }, tgt: { splitted: string[][] } } } = {
13
+ const fG = GapSymbols[NOTATION.FASTA];
14
+ const hG = GapSymbols[NOTATION.HELM];
15
+ const sG = GapSymbols[NOTATION.SEPARATOR];
16
+ const data: { [testName: string]: { src: { csv: string }, tgt: { splitted: (string[] | string)[] } } } = {
10
17
  fasta: {
11
18
  src: {
12
19
  csv: `seq
@@ -16,9 +23,9 @@ TTCAAC`
16
23
  },
17
24
  tgt: {
18
25
  splitted: [
19
- ['A', 'C', 'G', 'T', 'C'],
20
- ['C', 'A', 'G', 'T', 'G', 'T'],
21
- ['T', 'T', 'C', 'A', 'A', 'C']
26
+ 'ACGTC',
27
+ 'CAGTGT',
28
+ 'TTCAAC',
22
29
  ]
23
30
  }
24
31
  },
@@ -32,9 +39,9 @@ ACCGTACT`,
32
39
  tgt: {
33
40
  splitted: [
34
41
  //@formatter:off
35
- ['A', 'C', '' , 'G', 'T', '' , 'C', 'T'],
36
- ['C', 'A', 'C', '' , 'T', '' , 'G', 'T'],
37
- ['A', 'C', 'C', 'G', 'T', 'A', 'C', 'T'],
42
+ 'AC-GT-CT',
43
+ 'CAC-T-GT',
44
+ 'ACCGTACT',
38
45
  //@formatter:on
39
46
  ]
40
47
  }
@@ -65,8 +72,8 @@ rut12-rty-her2---wert`
65
72
  tgt: {
66
73
  splitted: [
67
74
  ['abc', 'dfgg', 'abc1', 'cfr3', 'rty', 'wert'],
68
- ['rut12', 'her2', 'rty', '', 'abc1', 'dfgg'],
69
- ['rut12', 'rty', 'her2', '', '', 'wert'],
75
+ ['rut12', 'her2', 'rty', sG, 'abc1', 'dfgg'],
76
+ ['rut12', 'rty', 'her2', sG, sG, 'wert'],
70
77
  ]
71
78
  }
72
79
  },
@@ -99,8 +106,8 @@ PEPTIDE1{meI.hHis.Aca.Cys_SEt.T.dK.Thr_PO3H2}$$$$`
99
106
  expect(col.semType, DG.SEMTYPE.MACROMOLECULE);
100
107
 
101
108
  const uh = UnitsHandler.getOrCreate(col);
102
- const splitted: string[][] = uh.splitted;
103
- expectArray(splitted, testData.tgt.splitted);
109
+ const resSplitted: ISeqSplitted[] = uh.splitted;
110
+ expectArray(resSplitted, testData.tgt.splitted);
104
111
  });
105
112
  }
106
113
  });
@@ -41,25 +41,25 @@ category('UnitsHandler', () => {
41
41
  });
42
42
 
43
43
  test('Seq-Fasta-units', async () => {
44
- const [_df, uh] = await loadCsvWithTag(seqDna, DG.TAGS.UNITS, NOTATION.FASTA);
44
+ const [_df, uh] = await loadCsvWithDetection(seqDna);
45
45
  expect(uh.notation, NOTATION.FASTA);
46
46
  expect(uh.isMsa(), false);
47
47
  });
48
48
 
49
49
  test('Seq-Fasta-MSA-units', async () => {
50
- const [_df, uh] = await loadCsvWithTag(seqDnaMsa, DG.TAGS.UNITS, NOTATION.FASTA);
50
+ const [_df, uh] = await loadCsvWithDetection(seqDnaMsa);
51
51
  expect(uh.notation, NOTATION.FASTA);
52
52
  expect(uh.isMsa(), true);
53
53
  });
54
54
 
55
55
  test('Seq-Helm', async () => {
56
- const [_df, uh] = await loadCsvWithTag(seqHelm, DG.TAGS.UNITS, NOTATION.HELM);
56
+ const [_df, uh] = await loadCsvWithDetection(seqHelm);
57
57
  expect(uh.notation, NOTATION.HELM);
58
58
  expect(uh.isHelm(), true);
59
59
  });
60
60
 
61
61
  test('Seq-UN', async () => {
62
- const [_df, uh] = await loadCsvWithTag(seqUn, DG.TAGS.UNITS, NOTATION.SEPARATOR);
62
+ const [_df, uh] = await loadCsvWithDetection(seqUn);
63
63
  expect(uh.notation, NOTATION.SEPARATOR);
64
64
  expect(uh.separator, '-');
65
65
  expect(uh.alphabet, ALPHABET.UN);
@@ -79,15 +79,15 @@ category('UnitsHandler', () => {
79
79
  return [df, uh];
80
80
  }
81
81
 
82
- async function loadCsvWithTag(csv: string, tag: string, value: string):
83
- Promise<[df: DG.DataFrame, uh: UnitsHandler]> {
84
- const df = DG.DataFrame.fromCsv(csv);
85
- const col = df.getCol('seq');
86
- col.setTag(tag, value);
87
- col.semType = DG.SEMTYPE.MACROMOLECULE;
88
- if (value === NOTATION.SEPARATOR)
89
- col.setTag(TAGS.separator, '-');
90
- const uh = UnitsHandler.getOrCreate(df.getCol('seq'));
91
- return [df, uh];
92
- }
82
+ // async function loadCsvWithTag(csv: string, tag: string, value: string):
83
+ // Promise<[df: DG.DataFrame, uh: UnitsHandler]> {
84
+ // const df = DG.DataFrame.fromCsv(csv);
85
+ // const col = df.getCol('seq');
86
+ // col.setTag(tag, value);
87
+ // col.semType = DG.SEMTYPE.MACROMOLECULE;
88
+ // if (value === NOTATION.SEPARATOR)
89
+ // col.setTag(TAGS.separator, '-');
90
+ // const uh = UnitsHandler.getOrCreate(df.getCol('seq'));
91
+ // return [df, uh];
92
+ // }
93
93
  });
@@ -2,13 +2,12 @@ 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, getBioLib} from '../package';
5
+ import wu from 'wu';
6
+
6
7
  import {printLeftOrCentered, DrawStyle} from '@datagrok-libraries/bio/src/utils/cell-renderer';
7
- import * as C from './constants';
8
8
  import {MonomerPlacer} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
9
9
  import {
10
10
  getPaletteByType,
11
- getSplitter,
12
11
  monomerToShort,
13
12
  MonomerToShortFunc,
14
13
  NOTATION,
@@ -18,8 +17,17 @@ import {
18
17
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
19
18
  import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
20
19
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
21
- import {Temps as mmcrTemps, Tags as mmcrTags,
22
- tempTAGS, rendererSettingsChangedState} from '../utils/cell-renderer-consts';
20
+
21
+ import {
22
+ Temps as mmcrTemps, Tags as mmcrTags,
23
+ tempTAGS, rendererSettingsChangedState
24
+ } from '../utils/cell-renderer-consts';
25
+ import * as C from './constants';
26
+
27
+ import {_package, getBioLib} from '../package';
28
+ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
29
+ import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
30
+
23
31
 
24
32
  type TempType = { [tagName: string]: any };
25
33
 
@@ -30,20 +38,21 @@ function getUpdatedWidth(grid: DG.Grid | null, g: CanvasRenderingContext2D, x: n
30
38
  return grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
31
39
  }
32
40
 
33
- export function processSequence(subParts: string[]): [string[], boolean] {
34
- const simplified = !subParts.some((amino, index) =>
41
+ export function processSequence(subParts: ISeqSplitted): [string[], boolean] {
42
+ const simplified = !wu.enumerate(subParts).some(([amino, index]) =>
35
43
  amino.length > 1 &&
36
44
  index != 0 &&
37
45
  index != subParts.length - 1);
38
46
 
39
47
  const text: string[] = [];
40
48
  const gap = simplified ? '' : ' ';
41
- subParts.forEach((amino: string, index) => {
49
+ for (const [amino, index] of wu.enumerate(subParts)) {
50
+ let aminoRes = amino;
42
51
  if (index < subParts.length)
43
- amino += `${amino ? '' : '-'}${gap}`;
52
+ aminoRes += `${amino ? '' : '-'}${gap}`;
44
53
 
45
- text.push(amino);
46
- });
54
+ text.push(aminoRes);
55
+ }
47
56
  return [text, simplified];
48
57
  }
49
58
 
@@ -188,11 +197,12 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
188
197
 
189
198
  const separator = tableCol.getTag(bioTAGS.separator) ?? '';
190
199
  const splitLimit = w / 5;
191
- const splitterFunc: SplitterFunc = getSplitter(units, separator, splitLimit);
200
+ const uh = UnitsHandler.getOrCreate(tableCol);
201
+ const splitterFunc: SplitterFunc = uh.getSplitter(splitLimit);
192
202
 
193
203
  const tempReferenceSequence: string | null = tableColTemp[tempTAGS.referenceSequence];
194
204
  const tempCurrentWord: string | null = tableColTemp[tempTAGS.currentWord];
195
- const referenceSequence: string[] = splitterFunc(
205
+ const referenceSequence: ISeqSplitted = splitterFunc(
196
206
  ((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
197
207
  tempReferenceSequence : tempCurrentWord ?? '');
198
208
 
@@ -226,7 +236,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
226
236
  // maxLengthWords = colTemp[tempTAGS.bioMaxLengthWords];
227
237
  // }
228
238
 
229
- const subParts: string[] = splitterFunc(value);
239
+ const subParts: ISeqSplitted = splitterFunc(value);
230
240
  /* let x1 = x; */
231
241
  let color = undefinedColor;
232
242
  let drawStyle = DrawStyle.classic;
@@ -234,7 +244,7 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
234
244
  if (aligned && aligned.includes('MSA') && units == NOTATION.SEPARATOR)
235
245
  drawStyle = DrawStyle.MSA;
236
246
 
237
- for (const [index, amino] of subParts.entries()) {
247
+ for (const [amino, index] of wu.enumerate(subParts)) {
238
248
  color = palette.get(amino);
239
249
  g.fillStyle = undefinedColor;
240
250
  const last = index === subParts.length - 1;
@@ -281,9 +291,10 @@ export class MacromoleculeDifferenceCellRenderer extends DG.GridCellRenderer {
281
291
  _cellStyle: DG.GridCellStyle): void {
282
292
  const grid = gridCell.grid;
283
293
  const cell = gridCell.cell;
294
+ const tableCol = gridCell.tableColumn as DG.Column<string>;
284
295
  const s: string = cell.value ?? '';
285
- const separator = gridCell.tableColumn!.tags[bioTAGS.separator];
286
- const units: string = gridCell.tableColumn!.tags[DG.TAGS.UNITS];
296
+ const separator = tableCol.tags[bioTAGS.separator];
297
+ const units: string = tableCol.tags[DG.TAGS.UNITS];
287
298
  w = getUpdatedWidth(grid, g, x, w);
288
299
  //TODO: can this be replaced/merged with splitSequence?
289
300
  const [s1, s2] = s.split('#');
@@ -300,14 +311,14 @@ export function drawMoleculeDifferenceOnCanvas(
300
311
  y: number,
301
312
  w: number,
302
313
  h: number,
303
- subParts1: string [],
304
- subParts2: string [],
314
+ subParts1: ISeqSplitted,
315
+ subParts2: ISeqSplitted,
305
316
  units: string,
306
317
  fullStringLength?: boolean,
307
318
  molDifferences?: { [key: number]: HTMLCanvasElement },
308
319
  ): void {
309
320
  if (subParts1.length !== subParts2.length) {
310
- const sequences: IComparedSequences = fillShorterSequence(subParts1, subParts2);
321
+ const sequences: IComparedSequences = fillShorterSequence(wu(subParts1).toArray(), wu(subParts2).toArray());
311
322
  subParts1 = sequences.subParts1;
312
323
  subParts2 = sequences.subParts2;
313
324
  }
@@ -16,16 +16,18 @@ const Tags = new class {
16
16
  const svgMolOptions = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
17
17
 
18
18
  export class MonomerTooltipHandler {
19
- private readonly grid: DG.Grid;
19
+ private readonly gridCol: DG.GridColumn;
20
20
 
21
- constructor(grid: DG.Grid) {
22
- this.grid = grid;
23
- this.grid.onCellTooltip(this.onCellTooltip.bind(this));
21
+ constructor(gridCol: DG.GridColumn) {
22
+ this.gridCol = gridCol;
23
+ this.gridCol.grid.onCellTooltip(this.onCellTooltip.bind(this));
24
24
  }
25
25
 
26
26
  private onCellTooltip(gridCell: DG.GridCell, x: number, y: number): any {
27
- if (gridCell.grid.dart != this.grid.dart || !gridCell.tableColumn || !gridCell.isTableCell ||
28
- gridCell.tableColumn.semType != 'Monomer') return false;
27
+ if (
28
+ gridCell.grid.dart != this.gridCol.grid.dart || gridCell.gridColumn.dart != this.gridCol.dart ||
29
+ !gridCell.tableColumn || !gridCell.isTableCell
30
+ ) return false;
29
31
 
30
32
  const alphabet = gridCell.tableColumn.getTag(bioTAGS.alphabet);
31
33
  const monomerName = gridCell.cell.value;
@@ -46,13 +48,11 @@ export class MonomerTooltipHandler {
46
48
  return true; // To prevent default tooltip behaviour
47
49
  }
48
50
 
49
- public static getOrCreate(grid: DG.Grid): MonomerTooltipHandler {
50
- const gridTemp: { [tempName: string]: any } = grid.dataFrame.temp;
51
- if (!(Tags.tooltipHandlerTemp in gridTemp)) {
52
- gridTemp[Tags.tooltipHandlerTemp] = new MonomerTooltipHandler(grid);
53
- grid.temp = gridTemp;
54
- }
55
- return gridTemp[Tags.tooltipHandlerTemp];
51
+ public static getOrCreate(gridCol: DG.GridColumn): MonomerTooltipHandler {
52
+ let res = gridCol.temp[Tags.tooltipHandlerTemp];
53
+ if (!res)
54
+ res = gridCol.temp[Tags.tooltipHandlerTemp] = new MonomerTooltipHandler(gridCol);
55
+ return res;
56
56
  }
57
57
  }
58
58
 
@@ -81,7 +81,7 @@ export class MonomerCellRenderer extends DG.GridCellRenderer {
81
81
  _cellStyle: DG.GridCellStyle
82
82
  ): void {
83
83
  if (gridCell.gridRow < 0) return;
84
- MonomerTooltipHandler.getOrCreate(gridCell.grid);
84
+ MonomerTooltipHandler.getOrCreate(gridCell.gridColumn);
85
85
 
86
86
 
87
87
  g.font = `12px monospace`;
@@ -107,7 +107,7 @@ export function wrapSequence(seq: string, splitter: SplitterFunc, lineWidth: num
107
107
  const seqLineList: string[] = [];
108
108
  while (seqPos < seqLength) {
109
109
  /* join sliced monomer into line */
110
- const seqLine: string[] = seqMonomerList.slice(seqPos, seqPos + lineWidth);
110
+ const seqLine: string[] = wu(seqMonomerList).slice(seqPos, seqPos + lineWidth).toArray();
111
111
  const seqLineTxt: string = seqLine.map((m) => m.length > 1 ? `[${m}]` : m).join('');
112
112
  seqLineList.push(seqLineTxt);
113
113
  seqPos += seqLine.length;
@@ -1,10 +1,13 @@
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
+
1
5
  import {delay} from '@datagrok-libraries/utils/src/test';
2
6
  import {checkInputColumnUI} from './check-input-column';
3
7
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
4
8
  import * as C from './constants';
5
9
  import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
6
- import * as grok from 'datagrok-api/grok';
7
- import * as DG from 'datagrok-api/dg';
10
+ import {SEM_TYPES} from './constants';
8
11
 
9
12
 
10
13
  export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<string>): Promise<DG.DataFrame> {
@@ -14,6 +17,7 @@ export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<s
14
17
  if (!checkInputColumnUI(seqCol, 'Sequence space')) return table;
15
18
 
16
19
  const tempDf = splitAlignedSequences(seqCol);
20
+ tempDf.name = 'splitToMonomers';
17
21
  const originalDf = seqCol.dataFrame;
18
22
  for (const tempCol of tempDf.columns) {
19
23
  // TODO: GROK-11212
@@ -21,9 +25,39 @@ export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<s
21
25
  tempCol.semType = C.SEM_TYPES.MONOMER;
22
26
  tempCol.setTag(bioTAGS.alphabet, seqCol.getTag(bioTAGS.alphabet));
23
27
  }
24
- // Create the new data frame to enable platform to setup cell renderers
25
- const newDf = originalDf.join(tempDf, [], [], undefined, undefined, DG.JOIN_TYPE.LEFT, false);
26
- grok.shell.addTableView(newDf);
27
28
 
28
- return newDf;
29
+ const colNameRe = /(\d+)(?: \((\d+)\))?/;
30
+ const generateNewColName = (srcName: string): string => {
31
+ colNameRe.lastIndex = 0;
32
+ const ma = srcName.match(colNameRe);
33
+ if (!ma) return srcName;
34
+ return `${ma[1]} (${parseInt(ma[2] ?? 0) + 1})`;
35
+ };
36
+
37
+ // if (tempDf.columns.length === 0) return;
38
+
39
+ for (let tempColI = 0; tempColI < tempDf.columns.length; tempColI++) {
40
+ const tempCol = tempDf.columns.byIndex(tempColI);
41
+ tempCol.semType = SEM_TYPES.MONOMER;
42
+ tempCol.setTag(bioTAGS.alphabet, seqCol.getTag(bioTAGS.alphabet));
43
+
44
+ const wdMax = 100;
45
+ let wdCount = 0;
46
+ while (originalDf.columns.byName(tempCol.name) && wdCount < wdMax) {
47
+ tempCol.name = generateNewColName(tempCol.name);
48
+ wdCount++;
49
+ }
50
+
51
+ originalDf.columns.add(tempCol);
52
+ }
53
+
54
+ // originalDf.join(tempDf, [], [], undefined, undefined, DG.JOIN_TYPE.LEFT, true);
55
+ await grok.data.detectSemanticTypes(originalDf);
56
+
57
+ for (let tempColI = 0; tempColI < tempDf.columns.length; tempColI++) {
58
+ const tempCol = tempDf.columns.byIndex(tempColI);
59
+ tempCol.setTag(DG.TAGS.CELL_RENDERER, 'Monomer');
60
+ }
61
+
62
+ return originalDf;
29
63
  }
@@ -1,13 +1,13 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
- export function getMacromoleculeColumn(): DG.Column | any {
5
- const col = grok.shell.t.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
6
- if (col === null) {
4
+ export function getMacromoleculeColumns(): DG.Column<any>[] | any {
5
+ const columns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
6
+ if (columns === null) {
7
7
  grok.shell.error('Current table does not contain macromolecules');
8
8
  return;
9
9
  }
10
- return col;
10
+ return columns;
11
11
  }
12
12
 
13
13
  export function updateDivInnerHTML(div: HTMLElement, content: string | Node): void {
@@ -42,6 +42,24 @@ const vrt = VdRegionType;
42
42
  // new VdRegion(vrt.FR, 'FR4', 'Heavy', 7, '118', null/*128*/),
43
43
  // ];
44
44
 
45
+ export enum PROPS_CATS {
46
+ STYLE = 'Style',
47
+ BEHAVIOR = 'Behavior',
48
+ LAYOUT = 'Layout',
49
+ DATA = 'Data',
50
+ }
51
+
52
+ export enum PROPS {
53
+ // -- Data --
54
+ skipEmptyPositions = 'skipEmptyPositions',
55
+ regionTypes = 'regionTypes',
56
+ chains = 'chains',
57
+
58
+ // -- Style --
59
+ positionWidth = 'positionWidth',
60
+ positionHeight = 'positionHeight',
61
+ }
62
+
45
63
  /** Viewer with tabs based on description of chain regions.
46
64
  * Used to define regions of an immunoglobulin LC.
47
65
  */
@@ -69,20 +87,24 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
69
87
  constructor() {
70
88
  super();
71
89
 
90
+ // -- Data --
91
+ this.skipEmptyPositions = this.bool(PROPS.skipEmptyPositions, false,
92
+ {category: PROPS_CATS.DATA});
93
+
72
94
  // To prevent ambiguous numbering scheme in MLB
73
- this.regionTypes = this.stringList('regionTypes', [vrt.CDR],
74
- {choices: Object.values(vrt).filter((t) => t != vrt.Unknown)}) as VdRegionType[];
75
- this.chains = this.stringList('chains', ['Heavy', 'Light'],
76
- {choices: ['Heavy', 'Light']});
77
- // this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
78
-
79
- this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
80
- this.positionWidth = this.float('positionWidth', 16, {
81
- editor: 'slider', min: 0, max: 64,
95
+ this.regionTypes = this.stringList(PROPS.regionTypes, [vrt.CDR], {
96
+ category: PROPS_CATS.DATA, choices: Object.values(vrt).filter((t) => t != vrt.Unknown)
97
+ }) as VdRegionType[];
98
+ this.chains = this.stringList(PROPS.chains, ['Heavy', 'Light'],
99
+ {category: PROPS_CATS.DATA, choices: ['Heavy', 'Light']});
100
+
101
+ // -- Layout --
102
+ this.positionWidth = this.float(PROPS.positionWidth, 16, {
103
+ category: PROPS_CATS.LAYOUT, editor: 'slider', min: 0, max: 64,
82
104
  description: 'Internal WebLogo viewers property width of position. A value of zero means autofit to the width.'
83
105
  });
84
- this.positionHeight = this.string('positionHeight', PositionHeight.Entropy,
85
- {choices: Object.keys(PositionHeight)}) as PositionHeight;
106
+ this.positionHeight = this.string(PROPS.positionHeight, PositionHeight.Entropy,
107
+ {category: PROPS_CATS.LAYOUT, choices: Object.keys(PositionHeight)}) as PositionHeight;
86
108
  }
87
109
 
88
110
  public async init() {
@@ -140,28 +162,35 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
140
162
  return;
141
163
  }
142
164
 
143
- if (property) {
144
- switch (property.name) {
145
- case 'regionTypes':
146
- break;
147
- case 'chains':
148
- break;
149
- case 'sequenceColumnNamePostfix':
150
- break;
151
- // for (let orderI = 0; orderI < this.logos.length; orderI++) {
152
- // for (let chainI = 0; chainI < this.chains.length; chainI++) {
153
- // const chain: string = this.chains[chainI];
154
- // this.logos[orderI][chain].setOptions({skipEmptyPositions: this.skipEmptyPositions});
155
- // }
156
- // }
157
- // this.calcSize();
158
- }
165
+ switch (property.name) {
166
+ case PROPS.regionTypes:
167
+ case PROPS.chains:
168
+ this.setData(this.dataFrame, this.regions);
169
+ break;
159
170
  }
160
171
 
161
172
  switch (property.name) {
162
- case 'skipEmptyPositions':
163
- case 'positionWidth':
164
- case 'positionHeight':
173
+ case PROPS.skipEmptyPositions:
174
+ for (let orderI = 0; orderI < this.logos.length; ++orderI) {
175
+ for (const chain of this.chains)
176
+ this.logos[orderI][chain].setOptions({[wlPROPS.skipEmptyPositions]: this.skipEmptyPositions});
177
+ }
178
+ this.calcSize();
179
+ break;
180
+
181
+ case PROPS.positionWidth:
182
+ this.calcSize();
183
+ break;
184
+
185
+ case PROPS.positionHeight:
186
+ for (let orderI = 0; orderI < this.logos.length; ++orderI) {
187
+ for (const chain of this.chains)
188
+ this.logos[orderI][chain].setOptions({[wlPROPS.positionWidth]: this.positionWidth});
189
+ }
190
+ this.calcSize();
191
+ break;
192
+
193
+ default:
165
194
  this.setData(this.dataFrame, this.regions); // onPropertyChanged
166
195
  break;
167
196
  }
@@ -265,8 +294,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
265
294
  this.logos = new Array(orderList.length);
266
295
  for (let orderI = 0; orderI < orderList.length; ++orderI)
267
296
  this.logos[orderI] = {};
268
- for (const [orderI, chain, wl] of logoList)
297
+ for (const [orderI, chain, wl] of logoList) {
269
298
  this.logos[orderI][chain] = wl;
299
+ this.viewSubs.push(wl.onFreqsCalculated.subscribe(() => { this.calcSize(); }));
300
+ }
270
301
 
271
302
  // ui.tableFromMap()
272
303
  // DG.HtmlTable.create()
@@ -336,35 +367,41 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
336
367
  private calcSize() {
337
368
  _package.logger.debug(`Bio: VdRegionsViewer.calcSize(), start`);
338
369
  const calcSizeInt = (): void => {
339
- const dpr: number = window.devicePixelRatio;
340
- const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
341
-
342
- const maxHeight: number = Math.min(logoHeight,
343
- Math.max(...this.logos.map((wlDict) =>
344
- Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
345
- );
370
+ // Postponed calcSizeInt can result call after the viewer has been closed (on tests)
371
+ if (!this.host) return;
346
372
 
373
+ const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
347
374
  let totalPos: number = 0;
348
375
  for (let orderI = 0; orderI < this.logos.length; orderI++) {
349
- for (const chain of this.chains)
350
- this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
376
+ for (const chain of this.chains) {
377
+ const wl = this.logos[orderI][chain];
378
+ wl.root.style.height = `${logoHeight}px`;
379
+ }
351
380
 
352
381
  totalPos += Math.max(...this.chains.map((chain) => this.logos[orderI][chain].Length));
353
382
  }
354
383
 
355
- if (this.positionWidth === 0 && this.logos.length > 0 && totalPos > 0) {
356
- const leftPad = 22/* Chain label */;
357
- const rightPad = 6 + 6 + 1;
358
- const logoMargin = 8 + 1;
359
- const fitPositionWidth =
360
- (this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos * dpr;
361
-
362
- for (let orderI = 0; orderI < this.logos.length; orderI++) {
363
- for (let chainI = 0; chainI < this.chains.length; chainI++) {
364
- const chain: string = this.chains[chainI];
365
- this.logos[orderI][chain].setOptions({positionWidth: fitPositionWidth});
384
+ if (this.positionWidth === 0) {
385
+ if (this.logos.length > 0 && totalPos > 0) {
386
+ const leftPad = 22/* Chain label */;
387
+ const rightPad = 6 + 6 + 1;
388
+ const logoMargin = 8 + 1;
389
+ const fitPositionWidth =
390
+ (this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos;
391
+
392
+ for (let orderI = 0; orderI < this.logos.length; orderI++) {
393
+ for (const chain of this.chains) {
394
+ const wl = this.logos[orderI][chain];
395
+ wl.setOptions({[wlPROPS.positionWidth]: fitPositionWidth});
396
+ wl.root.style.width = `${fitPositionWidth * wl.Length}px`;
397
+ }
366
398
  }
367
399
  }
400
+ } else {
401
+ for (let orderI = 0; orderI < this.logos.length; orderI++) {
402
+ for (const chain of this.chains)
403
+ this.logos[orderI][chain].setOptions({[wlPROPS.positionWidth]: this.positionWidth});
404
+ }
368
405
  }
369
406
 
370
407
  if (this.positionWidth === 0)