@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.
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +1 -1
- package/src/cell-renderer.ts +59 -50
- package/src/helm-monomer-placer.ts +78 -0
- package/src/package.ts +11 -4
- package/src/tests/findMonomers-tests.ts +3 -2
- package/src/tests/helm-tests.ts +1 -1
- package/src/utils.ts +12 -12
package/src/cell-renderer.ts
CHANGED
|
@@ -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
|
|
6
|
-
|
|
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
|
|
41
|
-
|
|
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
|
|
45
|
-
const
|
|
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
|
-
|
|
54
|
+
let iterCount: number = 0;
|
|
55
|
+
|
|
53
56
|
let mid = 0;
|
|
54
|
-
if (argsX >
|
|
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 >=
|
|
60
|
+
if (argsX >= sumLengths[mid] && argsX <= sumLengths[mid + 1]) {
|
|
58
61
|
left = mid;
|
|
59
62
|
found = true;
|
|
60
|
-
} else if (argsX <
|
|
63
|
+
} else if (argsX < sumLengths[mid]) {
|
|
61
64
|
right = mid - 1;
|
|
62
|
-
} else if (argsX >
|
|
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 >=
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
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 (
|
|
76
|
-
if (
|
|
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(
|
|
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
|
|
105
|
-
const
|
|
120
|
+
const missedColor = 'red';
|
|
121
|
+
const monomerColor: string = '#404040';
|
|
122
|
+
const frameColor: string = '#C0C0C0';
|
|
106
123
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
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
|
-
|
|
140
|
-
const allParts
|
|
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
|
-
|
|
143
|
-
const color =
|
|
144
|
-
|
|
145
|
-
|
|
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([
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
53
|
+
const monomerSymbolList: string[] = parseHelm(testHelmValue);
|
|
54
|
+
const resMissedSet: Set<string> = findMonomers(monomerSymbolList);
|
|
54
55
|
expectObject(resMissedSet, tgtMissedSet);
|
|
55
56
|
}
|
|
56
57
|
});
|
package/src/tests/helm-tests.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
}
|