@datagrok/helm 2.1.13 → 2.1.14

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,15 +2,21 @@ 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 {_package} from './package';
6
- import {findMonomers, parseHelm, getParts} from './utils';
5
+ import wu from 'wu';
6
+
7
7
  import {printLeftOrCentered} from '@datagrok-libraries/bio/src/utils/cell-renderer';
8
8
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
9
9
 
10
+ import {findMonomers, parseHelm} from './utils';
11
+ import {HelmMonomerPlacer} from './helm-monomer-placer';
12
+
10
13
  const enum tempTAGS {
11
14
  helmSumMaxLengthWords = 'helm-sum-maxLengthWords',
12
15
  helmMaxLengthWords = 'helm-maxLengthWords',
16
+
17
+ helmPlacer = 'bio-helmPlacer',
13
18
  }
19
+
14
20
  // Global flag is for replaceAll
15
21
  const helmGapStartRe = /\{(\*\.)+/g;
16
22
  const helmGapIntRe = /\.(\*\.)+/g;
@@ -37,52 +43,62 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
37
43
  try { tableCol = gridCell.tableColumn; } catch { }
38
44
  if (!tableCol) return;
39
45
 
40
- const colTemp: TempType[] = tableCol.temp ?? new Array<TempType>(tableCol.length);
41
- // Exit if no missed monomers (tags are not presented in colTemp)
42
- if (!colTemp || Object.keys(colTemp).length == 0) return;
46
+ const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
47
+ const [allParts, lengths, sumLengths] = helmPlacer.getCellAllPartsLengths(gridCell.tableRowIndex);
43
48
 
44
- const maxLengthWordsSum: { [pos: number]: number } = colTemp[tempTAGS.helmSumMaxLengthWords];
45
- const maxLengthWords: { [pos: number]: number } = colTemp[tempTAGS.helmMaxLengthWords];
46
-
47
- const maxIndex = Object.values(maxLengthWords).length - 1;
48
- const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCell.bounds.x);
49
+ const maxIndex = Object.values(lengths).length - 1;
50
+ const argsX = e.offsetX - gridCell.bounds.x;
49
51
  let left = 0;
50
52
  let right = maxIndex;
51
53
  let found = false;
52
- maxLengthWordsSum[maxIndex + 1] = argsX + 1;
54
+ let iterCount: number = 0;
55
+
53
56
  let mid = 0;
54
- if (argsX > maxLengthWordsSum[0]) {
55
- while (!found) {
57
+ if (argsX > sumLengths[0]) {
58
+ while (!found && iterCount < sumLengths.length) {
56
59
  mid = Math.floor((right + left) / 2);
57
- if (argsX >= maxLengthWordsSum[mid] && argsX <= maxLengthWordsSum[mid + 1]) {
60
+ if (argsX >= sumLengths[mid] && argsX <= sumLengths[mid + 1]) {
58
61
  left = mid;
59
62
  found = true;
60
- } else if (argsX < maxLengthWordsSum[mid]) {
63
+ } else if (argsX < sumLengths[mid]) {
61
64
  right = mid - 1;
62
- } else if (argsX > maxLengthWordsSum[mid + 1]) {
65
+ } else if (argsX > sumLengths[mid + 1]) {
63
66
  left = mid + 1;
64
67
  }
65
68
  if (left == right)
66
69
  found = true;
70
+
71
+ iterCount++;
67
72
  }
68
73
  }
69
- left = (argsX >= maxLengthWordsSum[left]) ? left + 1 : left;
70
- const monomers = findMonomers(gridCell.cell.value);
71
- const s: string = gridCell.cell.value ?? '';
72
- const subParts: string[] = parseHelm(s);
73
- const allParts: string[] = getParts(subParts, s);
74
+ left = (argsX >= sumLengths[left]) ? left : left - 1; // correct left to between sumLengths
75
+
76
+ const seq: string = gridCell.cell.value;
77
+ const monomerList = parseHelm(seq);
78
+ const monomers = new Set<string>(monomerList);
79
+ const missedMonomers = findMonomers(monomerList);
80
+
74
81
  const tooltipMessage: HTMLElement[] = [];
75
- for (let partI = 0; partI < allParts.length; ++partI) {
76
- if (monomers.has(allParts[partI])) {
82
+ for (const [part, partI] of wu.enumerate(allParts)) {
83
+ if (missedMonomers.has(part)) {
77
84
  tooltipMessage[partI] = ui.divV([
78
85
  ui.divText(`Monomer ${allParts[partI]} not found.`),
79
86
  ui.divText('Open the Context Panel, then expand Manage Libraries')
80
87
  ]);
88
+ } else if (monomers.has(part)) {
89
+ const elList = [ui.div(part)];
90
+ const monomer = helmPlacer.getMonomer(part);
91
+ if (monomer) {
92
+ const options = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
93
+ const monomerSvg = grok.chem.svgMol(monomer.smiles, undefined, undefined, options);
94
+ elList.push(monomerSvg);
95
+ }
96
+ tooltipMessage[partI] = ui.divV(elList);
81
97
  }
82
98
  }
83
99
 
84
100
  (((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
85
- ui.tooltip.show(ui.div(tooltipMessage[left]), e.x + 16, e.y + 16) :
101
+ ui.tooltip.show(tooltipMessage[left], e.x + 16, e.y + 16) :
86
102
  ui.tooltip.hide();
87
103
  } catch (err: any) {
88
104
  const errMsg: string = errorToConsole(err);
@@ -101,12 +117,13 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
101
117
  if (!tableCol) return;
102
118
 
103
119
  const grid = gridCell.gridRow !== -1 ? gridCell.grid : undefined;
104
- const undefinedColor = 'rgb(100,100,100)';
105
- const grayColor = '#808080';
120
+ const missedColor = 'red';
121
+ const monomerColor: string = '#404040';
122
+ const frameColor: string = '#C0C0C0';
106
123
 
107
- const missedMonomers = findMonomers(gridCell.cell.value);
108
- const s: string = gridCell.cell.value ?? '';
109
- const subParts: string[] = parseHelm(s);
124
+ const seq = gridCell.cell.value;
125
+ const monomers: Set<string> = new Set<string>(parseHelm(seq));
126
+ const missedMonomers: Set<string> = findMonomers(seq);
110
127
 
111
128
  if (missedMonomers.size == 0) {
112
129
  const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
@@ -122,39 +139,31 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
122
139
  if (!grid) {
123
140
  const r = window.devicePixelRatio;
124
141
  h = 28;
125
- g.canvas.height = h*r;
142
+ g.canvas.height = h * r;
126
143
  g.canvas.style.height = `${h}px`;
127
144
  }
128
- const maxLengthWords: number[] = tableCol.temp[tempTAGS.helmMaxLengthWords] ?? [];
129
- if (subParts.length > maxLengthWords.length)
130
- maxLengthWords.push(...(new Array<number>(subParts.length - maxLengthWords.length).fill(-1)));
131
145
 
132
146
  w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
133
147
  g.save();
134
148
  g.beginPath();
135
149
  g.rect(x, y, w, h);
136
150
  g.clip();
151
+ g.transform(1, 0, 0, 1, x, y);
137
152
  g.font = '12px monospace';
138
153
  g.textBaseline = 'top';
139
- let x1 = x;
140
- const allParts: string[] = getParts(subParts, s);
154
+ const helmPlacer = HelmMonomerPlacer.getOrCreate(tableCol);
155
+ const [allParts, lengths, sumLengths] = helmPlacer.getCellAllPartsLengths(gridCell.tableRow.idx);
156
+
141
157
  for (let i = 0; i < allParts.length; ++i) {
142
- maxLengthWords[i] = Math.max(maxLengthWords[i], allParts[i].length * 7); /* What is 7, width of char ? */
143
- const color = missedMonomers.has(allParts[i]) ? 'red' : grayColor;
144
- g.fillStyle = undefinedColor;
145
- x1 = printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
158
+ const part: string = allParts[i];
159
+ const color: string =
160
+ part === '.' || part.endsWith('{') || part.startsWith('}') ? frameColor :
161
+ missedMonomers.has(part) ? missedColor :
162
+ monomers.has(part) ? monomerColor :
163
+ frameColor;
164
+ g.fillStyle = color;
165
+ printLeftOrCentered(sumLengths[i], 0, w, h, g, allParts[i], color, 0, true, 1.0);
146
166
  }
147
-
148
- const maxLengthWordSum: number[] = new Array<number>(maxLengthWords.length);
149
- maxLengthWordSum[0] = maxLengthWords[0];
150
- for (let partI = 1; partI < allParts.length; partI++)
151
- maxLengthWordSum[partI] = maxLengthWordSum[partI - 1] + maxLengthWords[partI];
152
-
153
- tableCol.temp = {
154
- [tempTAGS.helmSumMaxLengthWords]: maxLengthWordSum,
155
- [tempTAGS.helmMaxLengthWords]: maxLengthWords
156
- };
157
- return;
158
167
  }
159
168
  } finally {
160
169
  g.restore();
@@ -0,0 +1,78 @@
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 wu from 'wu';
6
+
7
+ import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
8
+
9
+ import {getParts, parseHelm} from './utils';
10
+ import {getMonomerLib} from './package';
11
+
12
+ export const enum Temps {
13
+ helmMonomerPlacer = 'bio-helmMonomerPlacer',
14
+ }
15
+
16
+ export class HelmMonomerPlacer {
17
+ private _allPartsList: string[][] = null;
18
+ private _lengthsList: number[][] = null;
19
+
20
+ private monomerLib: IMonomerLib;
21
+
22
+ public monomerCharWidth: number = 7;
23
+ public leftPadding: number = 5;
24
+
25
+ constructor(public readonly col: DG.Column<string>) {
26
+ this.col.dataFrame.onDataChanged.subscribe();
27
+ this.monomerLib = getMonomerLib();
28
+ }
29
+
30
+ /** @param rowIdx Row index of the table {@link DG.DataFrame}, HelmMonomerPlacer is {@link DG.Column} based */
31
+ public getCellAllPartsLengths(rowIdx: number): [string[], number[], number[]] {
32
+ if (this._allPartsList === null)
33
+ this._allPartsList = new Array<string[]>(this.col.length).fill(null);
34
+
35
+ if (this._lengthsList === null)
36
+ this._lengthsList = new Array<number[]>(this.col.length).fill(null);
37
+
38
+ const [allParts, lengths] = this.getCellMonomerLengthsForSeq(rowIdx);
39
+
40
+ const sumLengths: number[] = new Array<number>(lengths.length + 1);
41
+ sumLengths[0] = this.leftPadding; // padding
42
+ for (let pos: number = 1; pos < sumLengths.length; pos++)
43
+ sumLengths[pos] = sumLengths[pos - 1] + lengths[pos - 1];
44
+ return [allParts, lengths, sumLengths];
45
+ }
46
+
47
+ private getCellMonomerLengthsForSeq(rowIdx: number): [string[], number[]] {
48
+ const allParts: string[] = this._allPartsList[rowIdx] = this.getAllParts(rowIdx);
49
+ const lengths: number[] = this._lengthsList[rowIdx] = new Array<number>(allParts.length);
50
+
51
+ for (const [part, partI] of wu.enumerate(allParts)) {
52
+ const partWidth: number = part.length * this.monomerCharWidth;
53
+ lengths[partI] = partWidth;
54
+ }
55
+
56
+ return [allParts, lengths];
57
+ }
58
+
59
+ getAllParts(rowIdx: number): string[] {
60
+ const seq: string | null = this.col.get(rowIdx);
61
+ const subParts: string[] = parseHelm(seq);
62
+ return seq ? getParts(subParts, seq) : [];
63
+ }
64
+
65
+ getMonomer(monomerSymbol: any): Monomer | null {
66
+ let res: Monomer = null;
67
+ for (const polymerType of this.monomerLib.getPolymerTypes()) {
68
+ res = this.monomerLib.getMonomer(polymerType, monomerSymbol);
69
+ if (res) break;
70
+ }
71
+ return res ?? null;
72
+ }
73
+
74
+ public static getOrCreate(col: DG.Column<string>): HelmMonomerPlacer {
75
+ if (!(Temps.helmMonomerPlacer in col.temp)) col.temp[Temps.helmMonomerPlacer] = new HelmMonomerPlacer(col);
76
+ return col.temp[Temps.helmMonomerPlacer];
77
+ }
78
+ }
package/src/package.ts CHANGED
@@ -17,10 +17,13 @@ let monomerLib: IMonomerLib | null = null;
17
17
 
18
18
  //tags: init
19
19
  export async function initHelm(): Promise<void> {
20
- return Promise.all([new Promise((resolve, reject) => {
21
- // @ts-ignore
22
- dojo.ready(function() { resolve(null); });
23
- }), grok.functions.call('Bio:getBioLib')])
20
+ return Promise.all([
21
+ new Promise((resolve, reject) => {
22
+ // @ts-ignore
23
+ dojo.ready(function() { resolve(null); });
24
+ }),
25
+ grok.functions.call('Bio:getBioLib'),
26
+ ])
24
27
  .then(([_, lib]: [void, IMonomerLib]) => {
25
28
  monomerLib = lib;
26
29
  rewriteLibraries(); // initHelm()
@@ -51,6 +54,10 @@ export async function initHelm(): Promise<void> {
51
54
  });
52
55
  }
53
56
 
57
+ export function getMonomerLib(): IMonomerLib {
58
+ return monomerLib;
59
+ }
60
+
54
61
  function rewriteLibraries() {
55
62
  org.helm.webeditor.Monomers.clear();
56
63
  monomerLib.getPolymerTypes().forEach((polymerType) => {
@@ -4,7 +4,7 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {_package} from '../package-test';
6
6
  import {after, before, category, delay, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
7
- import {findMonomers} from '../utils';
7
+ import {findMonomers, parseHelm} from '../utils';
8
8
  import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
9
9
 
10
10
  const LIB_STORAGE_NAME = 'Libraries';
@@ -50,7 +50,8 @@ category('findMonomers', () => {
50
50
  }
51
51
 
52
52
  function _testFindMonomers(testHelmValue: string, tgtMissedSet: Set<string>): void {
53
- const resMissedSet: Set<string> = findMonomers(testHelmValue);
53
+ const monomerSymbolList: string[] = parseHelm(testHelmValue);
54
+ const resMissedSet: Set<string> = findMonomers(monomerSymbolList);
54
55
  expectObject(resMissedSet, tgtMissedSet);
55
56
  }
56
57
  });
@@ -2,11 +2,11 @@ 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 {_package} from '../package-test';
6
5
  import {after, before, category, delay, expect, test, expectArray} from '@datagrok-libraries/utils/src/test';
7
6
  //import {findMonomers, helmToFasta, helmToPeptide, helmToRNA, initHelm} from '../package';
8
7
  import {parseHelm} from '../utils';
9
8
 
9
+ import {_package} from '../package-test';
10
10
 
11
11
  category('Helm', () => {
12
12
  //These tests require webservice that is not present on test stand
package/src/utils.ts CHANGED
@@ -60,7 +60,7 @@ export function parseHelm(s: string) {
60
60
  const firstPiece = monomer.substring(0, monomer.indexOf('('));
61
61
  const thirdPiece = monomer.substring(monomer.lastIndexOf(')') + 1);
62
62
  const secondPiece = monomer.substring(firstPiece.length + 1, monomer.length - thirdPiece.length - 1);
63
- const elements =[firstPiece, secondPiece, thirdPiece];
63
+ const elements = [firstPiece, secondPiece, thirdPiece];
64
64
  for (const el of elements)
65
65
  ss.push(el);
66
66
  } else {
@@ -94,7 +94,8 @@ export function parseHelm(s: string) {
94
94
  // return new Set(helmPartList.filter((val) => !monomerNameList.includes(val)));
95
95
  // }
96
96
 
97
- export function findMonomers(helmString: string) {
97
+ /** Searches monomers of {@link helmString} for missed. */
98
+ export function findMonomers(monomerSymbolList: string[]): Set<string> {
98
99
  //@ts-ignore
99
100
  const types = Object.keys(org.helm.webeditor.monomerTypeList());
100
101
  const monomers: any = [];
@@ -107,8 +108,7 @@ export function findMonomers(helmString: string) {
107
108
  monomerNames.push(monomers[i][k].id);
108
109
  });
109
110
  }
110
- const splitString = parseHelm(helmString);
111
- return new Set(splitString.filter((val) => !monomerNames.includes(val)));
111
+ return new Set(monomerSymbolList.filter((val) => !monomerNames.includes(val)));
112
112
  }
113
113
 
114
114
  function split(s: string, sep: string) {
@@ -192,14 +192,14 @@ function unescape(s: string) {
192
192
 
193
193
  return s.replace(/[\\]./g, function(m) {
194
194
  switch (m) {
195
- case '\\r':
196
- return '\r';
197
- case '\\n':
198
- return '\n';
199
- case '\\t':
200
- return '\t';
201
- default:
202
- return m.substring(1);
195
+ case '\\r':
196
+ return '\r';
197
+ case '\\n':
198
+ return '\n';
199
+ case '\\t':
200
+ return '\t';
201
+ default:
202
+ return m.substring(1);
203
203
  }
204
204
  });
205
205
  }