@datagrok/bio 2.4.48 → 2.4.50

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.
@@ -538,4 +538,4 @@ PEPTIDE1{aThr.hHis.Aca.Q.T.W.Q.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.Q.N.N.Phe_4Me}$$
538
538
  PEPTIDE1{meI.hHis.Aca.Q.T.W.Q.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.Bmt.Phe_4Me}$$$$,1.3272504220357146
539
539
  PEPTIDE1{meI.hHis.Aca.Q.T.W.Q.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.Thr_PO3H2.Phe_4Me}$$$$,2.25061208046269
540
540
  PEPTIDE1{meI.hHis.Aca.Q.T.W.Q.Aca.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.dV.Phe_4Me}$$$$,4.2426827257450315
541
- PEPTIDE1{meI.hHis.Hcy.Q.T.W.Q.Phe_4NH2.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.N.meK}$$$$,2.9379590568765788
541
+ PEPTIDE1{meI.hHis.Hcy.Q.T.W.Q.Phe_4NH2.D-Tyr_Et.Tyr_ab-dehydroMe.dV.E.N.N.meK}$$$$,2.9379590568765788
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.4.48",
8
+ "version": "2.4.50",
9
9
  "description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
10
10
  "repository": {
11
11
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@biowasm/aioli": "^3.1.0",
25
- "@datagrok-libraries/bio": "^5.32.5",
25
+ "@datagrok-libraries/bio": "^5.32.7",
26
26
  "@datagrok-libraries/chem-meta": "^1.0.1",
27
27
  "@datagrok-libraries/ml": "^6.3.39",
28
28
  "@datagrok-libraries/tutorials": "^1.3.2",
@@ -34,7 +34,6 @@
34
34
  "fastest-levenshtein": "^1.0.16",
35
35
  "openchemlib": "6.0.1",
36
36
  "rxjs": "^6.5.5",
37
- "source-map-loader": "^4.0.1",
38
37
  "style-loader": "^3.3.1",
39
38
  "wu": "latest"
40
39
  },
@@ -45,6 +44,7 @@
45
44
  "@typescript-eslint/parser": "latest",
46
45
  "eslint": "latest",
47
46
  "eslint-config-google": "latest",
47
+ "source-map-loader": "^4.0.1",
48
48
  "ts-loader": "^9.2.5",
49
49
  "typescript": "^5.0.4",
50
50
  "webpack": "^5.76.0",
@@ -13,7 +13,7 @@ export async function getMonomericMols(
13
13
  const uh = UnitsHandler.getOrCreate(mcol);
14
14
  let molV3000Array;
15
15
  monomersDict ??= new Map();
16
- const monomers = uh.units === NOTATION.HELM ?
16
+ const monomers = uh.isHelm() ?
17
17
  getHelmMonomers(mcol) : Object.keys(uh.stats.freq).filter((it) => it !== '');
18
18
 
19
19
  for (let i = 0; i < monomers.length; i++) {
@@ -21,7 +21,7 @@ export async function getMonomericMols(
21
21
  monomersDict.set(monomers[i], `${monomersDict.size + 1}`);
22
22
  }
23
23
 
24
- if (uh.units === NOTATION.HELM) {
24
+ if (uh.isHelm()) {
25
25
  molV3000Array = await grok.functions.call('HELM:getMolFiles', {col: mcol});
26
26
  molV3000Array = changeV2000ToV3000(molV3000Array, monomersDict, pattern);
27
27
  } else {
@@ -13,6 +13,7 @@ import './tests/splitters-test'; //Unhandled exceptions.exceptions : Cannot read
13
13
 
14
14
  import './tests/monomer-libraries-tests';
15
15
  import './tests/renderers-test';
16
+ import './tests/renderers-monomer-placer';
16
17
  import './tests/converters-test';
17
18
  import './tests/fasta-handler-test';
18
19
  import './tests/fasta-export-tests';
@@ -0,0 +1,109 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as DG from 'datagrok-api/dg';
3
+ import * as ui from 'datagrok-api/ui';
4
+
5
+ import {_package} from '../package-test';
6
+ import {after, before, category, delay, expect, test} from '@datagrok-libraries/utils/src/test';
7
+ import {MonomerPlacer} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
8
+ import {monomerToShort} from '@datagrok-libraries/bio/src/utils/macromolecule';
9
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
+
11
+ category('renderers: monomerPlacer', () => {
12
+ const tests = {
13
+ splitter: {
14
+ /**
15
+ 0 = Array(10) [0, 26, 45, 71, 97, 123, 142, 161, 187, 213],
16
+ 1 = Array(9) [0, 26, 52, 78, 104, 130, 156, 182, 208],
17
+ 2 = Array(8) [0, 26, 45, 71, 97, 123, 149, 175],
18
+ * */
19
+ csv: 'id,seq\n' +
20
+ 'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' + //Array(10) [0, 26, 52, 78, 104, 130, 156, 175, 201, 227]
21
+ 'id2,m1-mon2-m3-mon4-mon5-Num-MON8-N9\n' + //
22
+ 'id3,mon1-M-mon3-mon4-mon5-MON8-N9\n', //
23
+ testList: [
24
+ {src: {row: 0, x: -1}, tgt: {pos: null}},
25
+ {src: {row: 1, x: 0}, tgt: {pos: 0}},
26
+ {src: {row: 1, x: 1}, tgt: {pos: 0}},
27
+ {src: {row: 1, x: 26}, tgt: {pos: 1}},
28
+ {src: {row: 1, x: 170}, tgt: {pos: 6}},
29
+ {src: {row: 1, x: 208}, tgt: {pos: null}},
30
+ {src: {row: 2, x: 170}, tgt: {pos: 6}},
31
+ {src: {row: 2, x: 175}, tgt: {pos: null}},
32
+ ]
33
+ },
34
+ splitterMsa: {
35
+ /** For charWidth=7 and sepWidth=12, MSA
36
+ * Array(10) [0, 26, 52, 78, 104, 130, 156, 175, 201, 227]
37
+ */
38
+ csv: 'id,seq\n' +
39
+ 'id1,m1-M-m3-mon4-mon5-N-T-MON8-N9\n' + //Array(10) [0, 26, 52, 78, 104, 130, 156, 175, 201, 227]
40
+ 'id2,m1-mon2-m3-mon4-mon5-Num--MON8-N9\n' + //
41
+ 'id3,mon1-M-mon3-mon4-mon5---MON8-N9\n', //
42
+ testList: [
43
+ {src: {row: 0, x: -1}, tgt: {pos: null}},
44
+ {src: {row: 1, x: 0}, tgt: {pos: 0}},
45
+ {src: {row: 1, x: 1}, tgt: {pos: 0}},
46
+ {src: {row: 1, x: 26}, tgt: {pos: 1}},
47
+ {src: {row: 1, x: 170}, tgt: {pos: 6}},
48
+ {src: {row: 1, x: 227}, tgt: {pos: null}},
49
+ {src: {row: 2, x: 220}, tgt: {pos: 8}},
50
+ {src: {row: 2, x: 227}, tgt: {pos: null}},
51
+ ]
52
+ },
53
+ fastaMsa: {
54
+ /** For charWidth=7 and sepWidth=12, MSA
55
+ * Array(10) [0, 19, 38, 57, 76, 95, 114, 133, 152, 171]
56
+ */
57
+ csv: `id,seq
58
+ id1,QQYNIYPLT
59
+ id2,QQWSSFPYT
60
+ id3,QHIRE--LT
61
+ `,
62
+ testList: [
63
+ {src: {row: 1, x: -1}, tgt: {pos: null}},
64
+ {src: {row: 1, x: 0}, tgt: {pos: 0}},
65
+ {src: {row: 1, x: 1}, tgt: {pos: 0}},
66
+ {src: {row: 1, x: 19}, tgt: {pos: 1}},
67
+ {src: {row: 1, x: 170}, tgt: {pos: 8}},
68
+ {src: {row: 1, x: 171}, tgt: {pos: null}},
69
+ {src: {row: 2, x: 170}, tgt: {pos: 8}},
70
+ {src: {row: 2, x: 171}, tgt: {pos: null}},
71
+ ]
72
+ },
73
+ };
74
+
75
+ for (const [testName, testData] of Object.entries(tests)) {
76
+ test(`getPosition_${testName}`, async () => {
77
+ const df: DG.DataFrame = DG.DataFrame.fromCsv(testData.csv);
78
+ await grok.data.detectSemanticTypes(df);
79
+ const seqCol: DG.Column = df.getCol('seq');
80
+
81
+ const monLength: number = 1;
82
+ const charWidth: number = 7;
83
+ const sepWidth: number = 12;
84
+ const colTemp: MonomerPlacer = new MonomerPlacer(null, seqCol, () => {
85
+ const uh = UnitsHandler.getOrCreate(seqCol);
86
+ return {
87
+ unitsHandler: uh,
88
+ monomerCharWidth: charWidth,
89
+ separatorWidth: sepWidth,
90
+ monomerToShort: monomerToShort,
91
+ monomerLengthLimit: monLength,
92
+ };
93
+ });
94
+
95
+ const testList = testData.testList;
96
+
97
+ const errorList: string[] = [];
98
+ for (const test of testList) {
99
+ const res = {pos: colTemp.getPosition(test.src.row, test.src.x)};
100
+ if (test.tgt.pos != res.pos) {
101
+ errorList.push(`Test src ${JSON.stringify(test.src)} expected tgt ${JSON.stringify(test.tgt)},` +
102
+ ` but get ${JSON.stringify({res})}`);
103
+ }
104
+ }
105
+ if (errorList.length > 0)
106
+ throw new Error('Test failed error(s):\n' + errorList.join(', n'));
107
+ });
108
+ }
109
+ });
@@ -108,8 +108,11 @@ category('renderers', () => {
108
108
  async function _rendererMacromoleculeDifference() {
109
109
  const seqDiffCol: DG.Column = DG.Column.fromStrings('SequencesDiff',
110
110
  ['meI/hHis/Aca/N/T/dK/Thr_PO3H2/Aca#D-Tyr_Et/Tyr_ab-dehydroMe/meN/E/N/dV']);
111
- seqDiffCol.tags[DG.TAGS.UNITS] = NOTATION.SEPARATOR;
112
- seqDiffCol.tags[bioTAGS.separator] = '/';
111
+ seqDiffCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
112
+ seqDiffCol.setTag(bioTAGS.separator, '/');
113
+ seqDiffCol.setTag(bioTAGS.aligned, 'SEQ');
114
+ seqDiffCol.setTag(bioTAGS.alphabet, 'UN');
115
+ seqDiffCol.setTag(bioTAGS.alphabetIsMultichar, 'true');
113
116
  seqDiffCol.semType = C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;
114
117
  const df = DG.DataFrame.fromColumns([seqDiffCol]);
115
118
 
@@ -195,8 +198,11 @@ category('renderers', () => {
195
198
  /**/
196
199
  const seqDiffCol: DG.Column = DG.Column.fromStrings('SequencesDiff',
197
200
  ['meI/hHis/Aca/N/T/dK/Thr_PO3H2/Aca#D-Tyr_Et/Tyr_ab-dehydroMe/meN/E/N/dV']);
198
- seqDiffCol.tags[DG.TAGS.UNITS] = NOTATION.SEPARATOR;
199
- seqDiffCol.tags[bioTAGS.separator] = '/';
201
+ seqDiffCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
202
+ seqDiffCol.setTag(bioTAGS.separator, '/');
203
+ seqDiffCol.setTag(bioTAGS.aligned, 'SEQ');
204
+ seqDiffCol.setTag(bioTAGS.alphabet, 'UN');
205
+ seqDiffCol.setTag(bioTAGS.alphabetIsMultichar, 'true');
200
206
  seqDiffCol.semType = C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;
201
207
  const df = DG.DataFrame.fromColumns([seqDiffCol]);
202
208
  const tv = grok.shell.addTableView(df);
@@ -217,8 +223,11 @@ category('renderers', () => {
217
223
  async function _setRendererManually() {
218
224
  const seqDiffCol: DG.Column = DG.Column.fromStrings('SequencesDiff',
219
225
  ['meI/hHis/Aca/N/T/dK/Thr_PO3H2/Aca#D-Tyr_Et/Tyr_ab-dehydroMe/meN/E/N/dV']);
220
- seqDiffCol.tags[DG.TAGS.UNITS] = NOTATION.SEPARATOR;
221
- seqDiffCol.tags[bioTAGS.separator] = '/';
226
+ seqDiffCol.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
227
+ seqDiffCol.setTag(bioTAGS.separator, '/');
228
+ seqDiffCol.setTag(bioTAGS.aligned, 'SEQ');
229
+ seqDiffCol.setTag(bioTAGS.alphabet, 'UN');
230
+ seqDiffCol.setTag(bioTAGS.alphabetIsMultichar, 'true');
222
231
  seqDiffCol.semType = DG.SEMTYPE.MACROMOLECULE;
223
232
  const tgtCellRenderer = 'MacromoleculeDifference';
224
233
  seqDiffCol.setTag(DG.TAGS.CELL_RENDERER, tgtCellRenderer);
@@ -23,21 +23,12 @@ export function generateLongSequence(): DG.Column[] {
23
23
  return columns;
24
24
  }
25
25
 
26
- export function setTagsMacromolecule(col: DG.Column) {
27
- col.semType = DG.SEMTYPE.MACROMOLECULE;
28
- col.setTag(DG.TAGS.UNITS, NOTATION.SEPARATOR);
29
- col.setTag(bioTAGS.aligned, ALIGNMENT.SEQ_MSA);
30
- col.setTag(bioTAGS.alphabet, ALPHABET.UN);
31
- col.setTag(bioTAGS.separator, '/');
32
- return col;
33
- }
34
-
35
26
  export function performanceTest(generateFunc: () => DG.Column[], testName: string) {
36
27
  const columns = generateFunc();
37
28
  const df: DG.DataFrame = DG.DataFrame.fromColumns(columns);
29
+ grok.data.detectSemanticTypes(df);
38
30
  const startTime: number = Date.now();
39
31
  const col: DG.Column = df.columns.byName('MSA');
40
- setTagsMacromolecule(col);
41
32
  grok.shell.addTableView(df);
42
33
 
43
34
  const endTime: number = Date.now();
@@ -2,37 +2,45 @@ 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
6
  import {printLeftOrCentered, DrawStyle} from '@datagrok-libraries/bio/src/utils/cell-renderer';
7
+ import * as C from './constants';
8
+ import {MonomerPlacer} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
6
9
  import {
7
10
  ALIGNMENT, ALPHABET,
8
11
  getPaletteByType,
9
12
  getSplitter,
13
+ getSplitterForColumn,
10
14
  monomerToShort,
15
+ MonomerToShortFunc,
11
16
  NOTATION,
12
17
  SplitterFunc,
13
18
  TAGS as bioTAGS,
14
19
  } from '@datagrok-libraries/bio/src/utils/macromolecule';
15
20
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
16
21
  import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
22
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
17
23
  import {MonomerWorks} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
18
24
  import {Tags as mmcrTags, Temps as mmcrTemps} from '../utils/cell-renderer-consts';
19
-
20
- import * as C from './constants';
21
- import {_package} from '../package';
25
+ import { HELM_POLYMER_TYPE } from '@datagrok-libraries/bio/src/utils/const';
26
+ import { MonomerLib } from './monomer-lib';
27
+ import { IMonomerLib } from '@datagrok-libraries/bio/src/types';
22
28
 
23
29
  const enum tempTAGS {
24
30
  referenceSequence = 'reference-sequence',
25
31
  currentWord = 'current-word',
26
32
  monomerWidth = 'monomer-width',
27
- bioSumMaxLengthWords = 'bio-sum-maxLengthWords',
28
- bioMaxIndex = 'bio-maxIndex',
29
- bioMaxLengthWords = 'bio-maxLengthWords',
33
+ bioSeqCol = 'bio-seqCol',
34
+ }
35
+
36
+ const enum rndrTAGS {
37
+ calculatedCellRender = '.calculatedCellRender',
30
38
  }
31
39
 
32
40
  type TempType = { [tagName: string]: any };
33
41
 
34
42
  const undefinedColor = 'rgb(100,100,100)';
35
- const monomerToShortFunction: (amino: string, maxLengthOfMonomer: number) => string = monomerToShort;
43
+ const monomerToShortFunction: MonomerToShortFunc = monomerToShort;
36
44
 
37
45
  function getUpdatedWidth(grid: DG.Grid | null, g: CanvasRenderingContext2D, x: number, w: number): number {
38
46
  return grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
@@ -55,8 +63,9 @@ export function processSequence(subParts: string[]): [string[], boolean] {
55
63
  return [text, simplified];
56
64
  }
57
65
 
58
-
59
66
  export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
67
+ private padding: number = 5;
68
+
60
69
  get name(): string { return 'sequence'; }
61
70
 
62
71
  get cellType(): string { return 'sequence'; }
@@ -72,39 +81,39 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
72
81
  }
73
82
 
74
83
  onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
75
- if (gridCell.cell.column.getTag(bioTAGS.aligned) !== ALIGNMENT.SEQ_MSA)
76
- return;
77
-
78
- const colTemp: TempType = gridCell.cell.column.temp;
79
- const maxLengthWordsSum = colTemp[tempTAGS.bioSumMaxLengthWords];
80
- const maxIndex = colTemp[tempTAGS.bioMaxIndex];
81
- const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCell.bounds.x);
82
- let left = 0;
83
- let right = maxIndex;
84
- let found = false;
85
- maxLengthWordsSum[maxIndex + 1] = argsX + 1;
86
- let mid = 0;
87
- if (argsX > maxLengthWordsSum[0]) {
88
- while (!found) {
89
- mid = Math.floor((right + left) / 2);
90
- if (argsX >= maxLengthWordsSum[mid] && argsX <= maxLengthWordsSum[mid + 1]) {
91
- left = mid;
92
- found = true;
93
- } else if (argsX < maxLengthWordsSum[mid]) {
94
- right = mid - 1;
95
- } else if (argsX > maxLengthWordsSum[mid + 1]) {
96
- left = mid + 1;
97
- }
98
- if (left == right)
99
- found = true;
84
+ // if (gridCell.cell.column.getTag(bioTAGS.aligned) !== ALIGNMENT.SEQ_MSA)
85
+ // return;
86
+
87
+ const tableCol: DG.Column = gridCell.cell.column;
88
+ const tableColTemp: TempType = tableCol.temp;
89
+ const seqColTemp: MonomerPlacer = tableCol.temp[tempTAGS.bioSeqCol];
90
+ if (!seqColTemp) return; // Can do nothing without precalculated data
91
+
92
+ const gridCellBounds: DG.Rect = gridCell.bounds;
93
+ // const value: any = gridCell.cell.value;
94
+ //
95
+ // const maxLengthWords: number[] = seqColTemp.getCellMonomerLengths(gridCell.tableRowIndex!);
96
+ // const maxLengthWordsSum: number[] = new Array<number>(maxLengthWords.length).fill(0);
97
+ // for (let posI: number = 1; posI < maxLengthWords.length; posI++)
98
+ // maxLengthWordsSum[posI] = maxLengthWordsSum[posI - 1] + maxLengthWords[posI];
99
+ // const maxIndex = maxLengthWords.length;
100
+ const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCellBounds.x);
101
+ const left: number | null = seqColTemp.getPosition(gridCell.tableRowIndex!, argsX);
102
+
103
+ const seqMonList: string[] = seqColTemp.getSeqMonList(gridCell.tableRowIndex!);
104
+ if (left !== null && left < seqMonList.length) {
105
+ const monomerSymbol: string = seqMonList[left];
106
+ const tooltipElements: HTMLElement[] = [ui.div(monomerSymbol)];
107
+ const monomer = seqColTemp.getMonomer(monomerSymbol);
108
+ if(monomer) {
109
+ const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
110
+ const monomerSVG = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
111
+ tooltipElements.push(monomerSVG);
100
112
  }
113
+ ui.tooltip.show(ui.divV(tooltipElements), e.x + 16, e.y + 16);
114
+ } else {
115
+ ui.tooltip.hide();
101
116
  }
102
- left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
103
- const separator = gridCell.cell.column.getTag('separator') ?? '';
104
- const splitterFunc: SplitterFunc = getSplitter('separator', separator);
105
- const subParts: string[] = splitterFunc(gridCell.cell.value);
106
- (((subParts[left]?.length ?? 0) > 0)) ?
107
- ui.tooltip.show(ui.div(subParts[left]), e.x + 16, e.y + 16) : ui.tooltip.hide();
108
117
  }
109
118
 
110
119
  /**
@@ -120,102 +129,130 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
120
129
  * @memberof AlignedSequenceCellRenderer
121
130
  */
122
131
  render(
123
- g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number,
124
- gridCell: DG.GridCell, _cellStyle: DG.GridCellStyle,
125
- ) {
126
- const grid = gridCell.gridRow !== -1 ? gridCell.grid : null;
127
- const cell = gridCell.cell;
128
- const paletteType = gridCell.cell.column.getTag(bioTAGS.alphabet);
129
- const minDistanceRenderer = 50;
130
- w = getUpdatedWidth(grid, g, x, w);
131
- g.save();
132
- g.beginPath();
133
- g.rect(x, y, w, h);
134
- g.clip();
135
- g.font = '12px monospace';
136
- g.textBaseline = 'top';
137
-
138
- //TODO: can this be replaced/merged with splitSequence?
139
- const units = gridCell.cell.column.getTag(DG.TAGS.UNITS);
140
- const aligned: string = gridCell.cell.column.getTag(bioTAGS.aligned);
141
-
142
- const palette = getPaletteByType(paletteType);
143
-
144
- const separator = gridCell.cell.column.getTag(bioTAGS.separator) ?? '';
145
- const splitLimit = w / 5;
146
- const splitterFunc: SplitterFunc = getSplitter(units, separator, splitLimit);
132
+ g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, gridCell: DG.GridCell,
133
+ cellStyle: DG.GridCellStyle
134
+ ): void {
135
+ let gapRenderer = 5;
136
+ let maxLengthOfMonomer = 8;
147
137
 
148
138
  // TODO: Store temp data to GridColumn
149
139
  // Now the renderer requires data frame table Column underlying GridColumn
150
- const colTemp: TempType = gridCell.cell.column.temp;
151
-
152
- const tempReferenceSequence: string | null = colTemp[tempTAGS.referenceSequence];
153
- const tempCurrentWord: string | null = colTemp[tempTAGS.currentWord];
154
- const tempMonomerWidth: string | null = colTemp[tempTAGS.monomerWidth];
155
- const referenceSequence: string[] = splitterFunc(
156
- ((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
157
- tempReferenceSequence : tempCurrentWord ?? '');
158
- const monomerWidth: string = tempMonomerWidth ?? 'short';
159
-
160
- let gapRenderer = 5;
161
- let maxIndex = 0;
162
- let maxLengthOfMonomer: number = 8;
140
+ const view = gridCell.grid.view;
141
+ const tableCol: DG.Column = gridCell.cell.column;
142
+ const tableColTemp: TempType = tableCol.temp;
163
143
 
144
+ // Cell renderer settings
145
+ const tempMonomerWidth: string | null = tableColTemp[tempTAGS.monomerWidth];
146
+ const monomerWidth: string = (tempMonomerWidth != null) ? tempMonomerWidth : 'short';
164
147
  if (monomerWidth === 'short') {
165
- gapRenderer = 12;
166
- maxLengthOfMonomer = colTemp[mmcrTemps.maxMonomerLength] ?? _package.properties.maxMonomerLength;
148
+ maxLengthOfMonomer = tableColTemp[mmcrTemps.maxMonomerLength] ?? _package.properties.maxMonomerLength;
167
149
  }
168
150
 
169
- let maxLengthWords: any = {};
170
- if (gridCell.cell.column.getTag(mmcrTags.calculated) !== splitLimit.toString()) {
171
- let samples = 0;
172
- while (samples < Math.min(gridCell.cell.column.length, 100)) {
173
- const column = gridCell.cell.column.get(samples);
174
- const subParts: string[] = splitterFunc(column);
175
- subParts.forEach((amino, index) => {
176
- const textSize = monomerToShortFunction(amino, maxLengthOfMonomer).length * 7 + gapRenderer;
177
- if (textSize > (maxLengthWords[index] ?? 0))
178
- maxLengthWords[index] = textSize;
179
- if (index > maxIndex) maxIndex = index;
151
+ let seqColTemp: MonomerPlacer = tableCol.temp[tempTAGS.bioSeqCol];
152
+ if (!seqColTemp) {
153
+ seqColTemp = new MonomerPlacer(view, tableCol,
154
+ () => {
155
+ const uh = UnitsHandler.getOrCreate(tableCol);
156
+ return {
157
+ unitsHandler: uh,
158
+ monomerCharWidth: 7, separatorWidth: !uh.isMsa() ? gapRenderer : 8,
159
+ monomerToShort: monomerToShortFunction, monomerLengthLimit: maxLengthOfMonomer,
160
+ monomerLib: getBioLib()
161
+ };
180
162
  });
181
- samples += 1;
182
- }
183
- const minLength = 3 * 7;
184
- for (let i = 0; i <= maxIndex; i++) {
185
- if (maxLengthWords[i] < minLength) maxLengthWords[i] = minLength;
186
- const maxLengthWordSum: any = {};
187
- maxLengthWordSum[0] = maxLengthWords[0];
188
- for (let i = 1; i <= maxIndex; i++) maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
189
- colTemp[tempTAGS.bioSumMaxLengthWords] = maxLengthWordSum;
190
- colTemp[tempTAGS.bioMaxIndex] = maxIndex;
191
- colTemp[tempTAGS.bioMaxLengthWords] = maxLengthWords;
192
- gridCell.cell.column.setTag(mmcrTags.calculated, splitLimit.toString());
193
- }
194
- } else {
195
- maxLengthWords = colTemp[tempTAGS.bioMaxLengthWords];
196
163
  }
197
164
 
198
- const subParts: string[] = splitterFunc(cell.value);
199
- let x1 = x;
200
- let color = undefinedColor;
201
- let drawStyle = DrawStyle.classic;
202
-
203
-
204
- if (aligned && aligned.includes('MSA') && units == NOTATION.SEPARATOR)
205
- drawStyle = DrawStyle.MSA;
165
+ const [maxLengthWords, maxLengthWordsSum]: [number[], number[]] =
166
+ seqColTemp.getCellMonomerLengths(gridCell.tableRowIndex!);
167
+ const maxIndex = maxLengthWords.length;
206
168
 
207
- subParts.every((amino, index) => {
208
- color = palette.get(amino);
209
- g.fillStyle = undefinedColor;
210
- const last = index === subParts.length - 1;
211
- x1 = printLeftOrCentered(x1, y, w, h,
212
- g, amino, color, 0, true, 1.0, separator, last, drawStyle,
213
- maxLengthWords, index, gridCell, referenceSequence, maxLengthOfMonomer);
214
- return minDistanceRenderer <= w;
215
- });
169
+ // Store updated seqColTemp to the col temp
170
+ if (seqColTemp.updated) tableColTemp[tempTAGS.bioSeqCol] = seqColTemp;
216
171
 
217
- g.restore();
218
- return;
172
+ g.save();
173
+ try {
174
+ const grid = gridCell.gridRow !== -1 ? gridCell.grid : null;
175
+ const value: any = gridCell.cell.value;
176
+ const paletteType = tableCol.getTag(bioTAGS.alphabet);
177
+ const minDistanceRenderer = 50;
178
+ w = getUpdatedWidth(grid, g, x, w);
179
+ g.beginPath();
180
+ g.rect(x + this.padding, y + this.padding, w - this.padding - 1, h - this.padding * 2);
181
+ g.clip();
182
+ g.font = '12px monospace';
183
+ g.textBaseline = 'top';
184
+
185
+ //TODO: can this be replaced/merged with splitSequence?
186
+ const units = tableCol.getTag(DG.TAGS.UNITS);
187
+ const aligned: string = tableCol.getTag(bioTAGS.aligned);
188
+
189
+ const palette = getPaletteByType(paletteType);
190
+
191
+ const separator = tableCol.getTag(bioTAGS.separator) ?? '';
192
+ const splitLimit = w / 5;
193
+ const splitterFunc: SplitterFunc = getSplitter(units, separator, splitLimit);
194
+
195
+ const tempReferenceSequence: string | null = tableColTemp[tempTAGS.referenceSequence];
196
+ const tempCurrentWord: string | null = tableColTemp[tempTAGS.currentWord];
197
+ const referenceSequence: string[] = splitterFunc(
198
+ ((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
199
+ tempReferenceSequence : tempCurrentWord ?? '');
200
+
201
+ // let maxLengthWords: { [pos: number]: number } = {};
202
+ // if (tableCol.getTag(rndrTAGS.calculatedCellRender) !== splitLimit.toString()) {
203
+ // let sampleCount = 0;
204
+ // while (sampleCount < Math.min(tableCol.length, 100)) {
205
+ // const rowIdx: number = sampleCount;
206
+ // const column = tableCol.get(rowIdx);
207
+ // const subParts: string[] = splitterFunc(column);
208
+ // for (const [index, amino] of subParts.entries()) {
209
+ // const textSize = monomerToShortFunction(amino, maxLengthOfMonomer).length * 7 + gapRenderer;
210
+ // if (textSize > (maxLengthWords[index] ?? 0))
211
+ // maxLengthWords[index] = textSize;
212
+ // if (index > maxIndex) maxIndex = index;
213
+ // }
214
+ // sampleCount += 1;
215
+ // }
216
+ // const minLength = 3 * 7;
217
+ // for (let i = 0; i <= maxIndex; i++) {
218
+ // if (maxLengthWords[i] < minLength) maxLengthWords[i] = minLength;
219
+ // const maxLengthWordSum: { [pos: number]: number } = {};
220
+ // maxLengthWordSum[0] = maxLengthWords[0];
221
+ // for (let i = 1; i <= maxIndex; i++) maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
222
+ // colTemp[tempTAGS.bioSumMaxLengthWords] = maxLengthWordSum;
223
+ // colTemp[tempTAGS.bioMaxIndex] = maxIndex;
224
+ // colTemp[tempTAGS.bioMaxLengthWords] = maxLengthWords;
225
+ // tableCol.setTag(rndrTAGS.calculatedCellRender, splitLimit.toString());
226
+ // }
227
+ // } else {
228
+ // maxLengthWords = colTemp[tempTAGS.bioMaxLengthWords];
229
+ // }
230
+
231
+ const subParts: string[] = splitterFunc(value);
232
+ /* let x1 = x; */
233
+ let color = undefinedColor;
234
+ let drawStyle = DrawStyle.classic;
235
+
236
+ if (aligned && aligned.includes('MSA') && units == NOTATION.SEPARATOR)
237
+ drawStyle = DrawStyle.MSA;
238
+
239
+ for (const [index, amino] of subParts.entries()) {
240
+ color = palette.get(amino);
241
+ g.fillStyle = undefinedColor;
242
+ const last = index === subParts.length - 1;
243
+ /*x1 = */
244
+ printLeftOrCentered(x + this.padding, y, w, h,
245
+ g, amino, color, 0, true, 1.0, separator, last, drawStyle,
246
+ maxLengthWordsSum, index, gridCell, referenceSequence, maxLengthOfMonomer);
247
+ if (minDistanceRenderer > w) break;
248
+ }
249
+ } catch (err: any) {
250
+ const errMsg: string = err instanceof Error ? err.message : !!err ? err.toString() : 'Error \'undefined\'';
251
+ _package.logger.error(`Bio: MacromoleculeSequenceCellRenderer.render() error: ${errMsg}`);
252
+ //throw err; // Do not throw to prevent disabling renderer
253
+ } finally {
254
+ g.restore();
255
+ }
219
256
  }
220
257
  }
221
258
 
@@ -4,8 +4,10 @@ import * as grok from 'datagrok-api/grok';
4
4
 
5
5
  import $ from 'cash-dom';
6
6
  import {Subscription} from 'rxjs';
7
- import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
+ import {NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
8
8
  import {NotationConverter} from '@datagrok-libraries/bio/src/utils/notation-converter';
9
+ import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
10
+ import {expect} from '@datagrok-libraries/utils/src/test';
9
11
 
10
12
 
11
13
  let convertDialog: DG.Dialog | null = null;
@@ -127,6 +129,8 @@ export async function convertDo(
127
129
  newColumn.semType = semType;
128
130
 
129
131
  // call to calculate 'cell.renderer' tag
132
+ const newUH = UnitsHandler.getOrCreate(newColumn);
133
+ expect(newUH.isMsa(), true);
130
134
  await grok.data.detectSemanticTypes(srcCol.dataFrame);
131
135
 
132
136
  return newColumn;