@datagrok/helm 2.5.5 → 2.5.7
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/CHANGELOG.md +15 -0
- package/dist/package-dojo.js +1 -1
- package/dist/package-dojo.js.map +1 -1
- 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 +4 -4
- package/src/global.d.ts +13 -0
- package/src/helm-helper.ts +2 -0
- package/src/package-test.ts +0 -1
- package/src/package-utils.ts +6 -9
- package/src/package.ts +2 -4
- package/src/tests/helm-helper-tests.ts +15 -0
- package/src/tests/parse-helm-tests.ts +45 -4
- package/src/utils/get-hovered.ts +0 -37
- package/src/utils/helm-grid-cell-renderer.ts +44 -27
- package/src/utils/helm-service.ts +9 -4
- package/src/helm-monomer-placer.ts +0 -98
- package/src/tests/get-all-parts-tests.ts +0 -39
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagrok/helm",
|
|
3
3
|
"friendlyName": "Helm",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Aleksandr Tanas",
|
|
7
7
|
"email": "atanas@datagrok.ai"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"css/helm.css"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@datagrok-libraries/bio": "^5.45.
|
|
19
|
+
"@datagrok-libraries/bio": "^5.45.5",
|
|
20
20
|
"@datagrok-libraries/chem-meta": "^1.2.7",
|
|
21
21
|
"@datagrok-libraries/utils": "^4.3.6",
|
|
22
22
|
"cash-dom": "^8.1.1",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@datagrok-libraries/helm-web-editor": "^1.1.12",
|
|
31
31
|
"@datagrok-libraries/js-draw-lite": "^0.0.9",
|
|
32
|
-
"@datagrok/bio": "^2.16.
|
|
32
|
+
"@datagrok/bio": "^2.16.6",
|
|
33
33
|
"@datagrok/chem": "^1.12.3",
|
|
34
34
|
"@types/node": "17.0.45",
|
|
35
35
|
"@types/webpack-env": "^1.18.5",
|
|
@@ -79,4 +79,4 @@
|
|
|
79
79
|
"directory": "packages/Helm"
|
|
80
80
|
},
|
|
81
81
|
"category": "Bioinformatics"
|
|
82
|
-
}
|
|
82
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as grokNamespace from 'datagrok-api/grok';
|
|
2
|
+
import * as uiNamespace from 'datagrok-api/ui';
|
|
3
|
+
import * as DGNamespace from 'datagrok-api/dg';
|
|
4
|
+
import * as rxjsNamespace from 'rxjs';
|
|
5
|
+
import $Namespace from 'cash-dom';
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
const grok: typeof grokNamespace;
|
|
9
|
+
const ui: typeof uiNamespace;
|
|
10
|
+
const DG: typeof DGNamespace;
|
|
11
|
+
const rjxs: typeof rxjsNamespace;
|
|
12
|
+
const $: typeof $Namespace;
|
|
13
|
+
}
|
package/src/helm-helper.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {IHelmWebEditor} from '@datagrok-libraries/bio/src/helm/types';
|
|
20
20
|
import {IMonomerLib, IMonomerLibBase, IMonomerLinkData, IMonomerSetPlaceholder} from '@datagrok-libraries/bio/src/types/index';
|
|
21
21
|
import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
22
|
+
import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
|
|
22
23
|
|
|
23
24
|
import {HelmWebEditor} from './helm-web-editor';
|
|
24
25
|
import {OrgHelmModule, ScilModule} from './types';
|
|
@@ -39,6 +40,7 @@ export class HelmHelper implements IHelmHelper {
|
|
|
39
40
|
private static instanceCount: number = 0;
|
|
40
41
|
|
|
41
42
|
constructor(
|
|
43
|
+
public readonly seqHelper: ISeqHelper,
|
|
42
44
|
private readonly logger: ILogger
|
|
43
45
|
) {
|
|
44
46
|
// Watchdog for singleton
|
package/src/package-test.ts
CHANGED
|
@@ -14,7 +14,6 @@ import './tests/helm-service-tests';
|
|
|
14
14
|
import './tests/renderers-tests';
|
|
15
15
|
import './tests/get-molfiles-tests';
|
|
16
16
|
import './tests/properties-widget-tests';
|
|
17
|
-
import './tests/get-all-parts-tests';
|
|
18
17
|
import './tests/get-monomer-tests';
|
|
19
18
|
import './tests/parse-helm-tests';
|
|
20
19
|
import './tests/helm-web-editor-tests';
|
package/src/package-utils.ts
CHANGED
|
@@ -59,6 +59,7 @@ export async function initHelmLoadAndPatchDojo(): Promise<void> {
|
|
|
59
59
|
try {
|
|
60
60
|
const requireBackup = window.require;
|
|
61
61
|
|
|
62
|
+
// Dojo modules required by JDraw2 and HELMWebEditor
|
|
62
63
|
// 'dojo.window','dojo.io.script','dojo.io.iframe','dojo.dom',
|
|
63
64
|
// 'dojox.gfx','dojox.gfx.svg','dojox.gfx.shape','dojox.charting'
|
|
64
65
|
const dojoTargetList: { name: string, checker: () => boolean }[] = [
|
|
@@ -206,13 +207,6 @@ export const helmJsonReplacer = (key: string, value: any): any => {
|
|
|
206
207
|
export class HelmPackage extends DG.Package {
|
|
207
208
|
public alertOriginal: ((s: string) => void) | null = null;
|
|
208
209
|
|
|
209
|
-
private _seqHelper: ISeqHelper;
|
|
210
|
-
public get seqHelper(): ISeqHelper {
|
|
211
|
-
if (!this._seqHelper)
|
|
212
|
-
throw new Error('Package Helm .seqHelper is not initialized');
|
|
213
|
-
return this._seqHelper;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
210
|
private _helmHelper: IHelmHelper;
|
|
217
211
|
public get helmHelper(): IHelmHelper {
|
|
218
212
|
if (!this._helmHelper)
|
|
@@ -220,6 +214,10 @@ export class HelmPackage extends DG.Package {
|
|
|
220
214
|
return this._helmHelper;
|
|
221
215
|
};
|
|
222
216
|
|
|
217
|
+
public get seqHelper(): ISeqHelper {
|
|
218
|
+
return this._helmHelper.seqHelper;
|
|
219
|
+
}
|
|
220
|
+
|
|
223
221
|
public _libHelper!: IMonomerLibHelper;
|
|
224
222
|
|
|
225
223
|
constructor(opts: { debug: boolean } = {debug: false}) {
|
|
@@ -327,8 +325,7 @@ export class HelmPackage extends DG.Package {
|
|
|
327
325
|
private _initialized: boolean = false;
|
|
328
326
|
|
|
329
327
|
/** Requires both Bio & HELMWebEditor initialized */
|
|
330
|
-
completeInit(
|
|
331
|
-
this._seqHelper = seqHelper;
|
|
328
|
+
completeInit(helmHelper: IHelmHelper, libHelper: IMonomerLibHelper): void {
|
|
332
329
|
this._helmHelper = helmHelper;
|
|
333
330
|
this._libHelper = libHelper;
|
|
334
331
|
|
package/src/package.ts
CHANGED
|
@@ -51,15 +51,13 @@ async function initHelmInt(): Promise<void> {
|
|
|
51
51
|
_package.logger.debug(`${logPrefix}, start`);
|
|
52
52
|
|
|
53
53
|
try {
|
|
54
|
-
const helmHelper: IHelmHelper = new HelmHelper(_package.logger);
|
|
55
54
|
const [_, seqHelper, libHelper] = await Promise.all([
|
|
56
55
|
_package.initHELMWebEditor(),
|
|
57
56
|
getSeqHelper(),
|
|
58
57
|
getMonomerLibHelper(),
|
|
59
58
|
]);
|
|
60
|
-
|
|
61
|
-
_package.
|
|
62
|
-
_package.completeInit(seqHelper, helmHelper, libHelper);
|
|
59
|
+
const helmHelper: IHelmHelper = new HelmHelper(seqHelper, _package.logger);
|
|
60
|
+
_package.completeInit(helmHelper, libHelper);
|
|
63
61
|
} catch (err: any) {
|
|
64
62
|
const [errMsg, errStack] = errInfo(err);
|
|
65
63
|
// const errMsg: string = err instanceof Error ? err.message : !!err ? err.toString() : 'Exception \'undefined\'';
|
|
@@ -6,6 +6,9 @@ import * as DG from 'datagrok-api/dg';
|
|
|
6
6
|
import {after, before, category, delay, expect, expectArray, test, testEvent} from '@datagrok-libraries/utils/src/test';
|
|
7
7
|
import {HelmNotSupportedError, HelmNotSupportedErrorType, IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
8
8
|
import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
9
|
+
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
10
|
+
import {getUserLibSettings, setUserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
11
|
+
import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
9
12
|
/* eslint-enable max-len */
|
|
10
13
|
|
|
11
14
|
import {_package} from '../package-test';
|
|
@@ -15,9 +18,21 @@ type TestTgtType = { helm: string | null, map: [number, number][], errType?: str
|
|
|
15
18
|
|
|
16
19
|
category('HelmHelper: removeGaps', () => {
|
|
17
20
|
let helmHelper: IHelmHelper;
|
|
21
|
+
let libHelper: IMonomerLibHelper;
|
|
22
|
+
let userLibSettings: UserLibSettings;
|
|
18
23
|
|
|
19
24
|
before(async () => {
|
|
20
25
|
helmHelper = await getHelmHelper();
|
|
26
|
+
libHelper = await getMonomerLibHelper();
|
|
27
|
+
|
|
28
|
+
userLibSettings = await getUserLibSettings();
|
|
29
|
+
// Cysteine has different R-groups in other libraries
|
|
30
|
+
await libHelper.loadMonomerLibForTests();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
after(async () => {
|
|
34
|
+
await setUserLibSettings(userLibSettings);
|
|
35
|
+
await libHelper.loadMonomerLib(true);
|
|
21
36
|
});
|
|
22
37
|
|
|
23
38
|
const tests: { [testName: string]: { src: TestSrcType, tgt: TestTgtType } } = {
|
|
@@ -10,6 +10,7 @@ import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
|
10
10
|
import {
|
|
11
11
|
getUserLibSettings, setUserLibSettings
|
|
12
12
|
} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
13
|
+
import {IHelmHelper, getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
13
14
|
|
|
14
15
|
import {JSDraw2Module} from '../types';
|
|
15
16
|
import {initHelmMainPackage} from './utils';
|
|
@@ -37,10 +38,28 @@ category('parseHelm', () => {
|
|
|
37
38
|
},
|
|
38
39
|
'RNA': {
|
|
39
40
|
src: 'RNA1{[dhp]([m1A])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
40
|
-
tgt: {
|
|
41
|
-
|
|
41
|
+
tgt: {
|
|
42
|
+
atomCount: 12, bondCount: 11,
|
|
43
|
+
helm: 'RNA1{[dhp]([m1A])p.r(T)p.r(T)p.r(G)p}$$$$V2.0',
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
'PT-with-gap': {
|
|
47
|
+
src: 'PEPTIDE1{[meY].*.C.R.N.P.C.T}$$$$V2.0',
|
|
48
|
+
tgt: {
|
|
49
|
+
atomCount: 8, bondCount: 7,
|
|
50
|
+
helm: 'PEPTIDE1{[meY].*.C.R.N.P.C.T}$$$$V2.0',
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
'PT-with-gap-at-connection': {
|
|
54
|
+
src: 'PEPTIDE1{[meY].*.C.R.N.P.C.T}$PEPTIDE1,PEPTIDE1,2:R3-7:R3$$$V2.0',
|
|
55
|
+
tgt: {
|
|
56
|
+
atomCount: 8, bondCount: 8,
|
|
57
|
+
helm: 'PEPTIDE1{[meY].*.C.R.N.P.C.T}$PEPTIDE1,PEPTIDE1,2:R3-7:R3$$$V2.0',
|
|
58
|
+
}
|
|
59
|
+
},
|
|
42
60
|
};
|
|
43
61
|
|
|
62
|
+
let helmHelper: IHelmHelper;
|
|
44
63
|
let libHelper: IMonomerLibHelper;
|
|
45
64
|
/** Backup actual user's monomer libraries settings */
|
|
46
65
|
let userLibSettings: UserLibSettings;
|
|
@@ -48,8 +67,9 @@ category('parseHelm', () => {
|
|
|
48
67
|
before(async () => {
|
|
49
68
|
await initHelmMainPackage();
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
helmHelper = await getHelmHelper();
|
|
52
71
|
|
|
72
|
+
libHelper = await getMonomerLibHelper();
|
|
53
73
|
userLibSettings = await getUserLibSettings();
|
|
54
74
|
|
|
55
75
|
// parseHelm is dependent on monomers RGroups available, test requires default monomer library
|
|
@@ -67,6 +87,12 @@ category('parseHelm', () => {
|
|
|
67
87
|
});
|
|
68
88
|
}
|
|
69
89
|
|
|
90
|
+
for (const [testName, {src, tgt}] of Object.entries(testData)) {
|
|
91
|
+
test(`HelmHelper-${testName}`, async () => {
|
|
92
|
+
_testParseHelmWithHelmHelper(src, tgt, helmHelper);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
for (const [testName, {src, tgt}] of Object.entries(testData)) {
|
|
71
97
|
test(`woDOM-${testName}`, async () => {
|
|
72
98
|
_testParseHelmWithoutDOM(src, tgt);
|
|
@@ -89,6 +115,20 @@ function _testParseHelmWithEditor(src: string, tgt: TestTgtType): void {
|
|
|
89
115
|
expect(m.bonds.length, tgt.bondCount);
|
|
90
116
|
}
|
|
91
117
|
|
|
118
|
+
function _testParseHelmWithHelmHelper(src: string, tgt: TestTgtType, helmHelper: IHelmHelper): void {
|
|
119
|
+
const io = org.helm.webeditor.IO;
|
|
120
|
+
|
|
121
|
+
const origin = new JSDraw2.Point(0, 0);
|
|
122
|
+
const resMol = helmHelper.parse(src, origin);
|
|
123
|
+
|
|
124
|
+
const resHelm = io.getHelm(resMol);
|
|
125
|
+
expect(resMol.atoms.length, tgt.atomCount);
|
|
126
|
+
expect(resMol.bonds.length, tgt.bondCount);
|
|
127
|
+
expect(resHelm, tgt.helm);
|
|
128
|
+
expect(resMol.atoms.every((a) => !a.elem.startsWith('[') && !a.elem.endsWith(']')), true,
|
|
129
|
+
'Atoms should not contain square braces.');
|
|
130
|
+
}
|
|
131
|
+
|
|
92
132
|
function _testParseHelmWithoutDOM(src: string, tgt: TestTgtType): void {
|
|
93
133
|
const io = org.helm.webeditor.IO;
|
|
94
134
|
|
|
@@ -103,5 +143,6 @@ function _testParseHelmWithoutDOM(src: string, tgt: TestTgtType): void {
|
|
|
103
143
|
expect(m.atoms.length, tgt.atomCount);
|
|
104
144
|
expect(m.bonds.length, tgt.bondCount);
|
|
105
145
|
expect(resHelm, tgt.helm);
|
|
106
|
-
expect(m.atoms.every((a) => !a.elem.startsWith('[') && !a.elem.endsWith(']')), true,
|
|
146
|
+
expect(m.atoms.every((a) => !a.elem.startsWith('[') && !a.elem.endsWith(']')), true,
|
|
147
|
+
'Atoms should not contain square braces.');
|
|
107
148
|
}
|
package/src/utils/get-hovered.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/m
|
|
|
9
9
|
import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
10
10
|
import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
11
11
|
|
|
12
|
-
import {HelmMonomerPlacer} from '../helm-monomer-placer';
|
|
13
|
-
|
|
14
12
|
export function getHoveredMonomerFromEditorMol(
|
|
15
13
|
argsX: number, argsY: number, mol: HelmMol, cellHeight?: number
|
|
16
14
|
): HelmAtom | null {
|
|
@@ -49,41 +47,6 @@ export function getHoveredMonomerFromEditorMol(
|
|
|
49
47
|
return resAtom;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
export function getHoveredMonomerFallback(
|
|
53
|
-
argsX: number, _argsY: number, gridCell: DG.GridCell, helmPlacer: HelmMonomerPlacer
|
|
54
|
-
): ISeqMonomer | null {
|
|
55
|
-
let hoveredSeqMonomer: ISeqMonomer | null = null;
|
|
56
|
-
const [allParts, lengths, sumLengths] = helmPlacer.getCellAllPartsLengths(gridCell.tableRowIndex!);
|
|
57
|
-
const maxIndex = Object.values(lengths).length - 1;
|
|
58
|
-
let left = 0;
|
|
59
|
-
let right = maxIndex;
|
|
60
|
-
let found = false;
|
|
61
|
-
let iterCount: number = 0;
|
|
62
|
-
|
|
63
|
-
let mid = 0;
|
|
64
|
-
if (argsX > sumLengths[0]) {
|
|
65
|
-
while (!found && iterCount < sumLengths.length) {
|
|
66
|
-
mid = Math.floor((right + left) / 2);
|
|
67
|
-
if (argsX >= sumLengths[mid] && argsX <= sumLengths[mid + 1]) {
|
|
68
|
-
left = mid;
|
|
69
|
-
found = true;
|
|
70
|
-
} else if (argsX < sumLengths[mid])
|
|
71
|
-
right = mid - 1;
|
|
72
|
-
else if (argsX > sumLengths[mid + 1])
|
|
73
|
-
left = mid + 1;
|
|
74
|
-
|
|
75
|
-
if (left == right)
|
|
76
|
-
found = true;
|
|
77
|
-
|
|
78
|
-
iterCount++;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
left = (argsX >= sumLengths[left]) ? left : left - 1; // correct left to between sumLengths
|
|
82
|
-
if (left >= 0)
|
|
83
|
-
hoveredSeqMonomer = getSeqMonomerFromHelm(left, allParts[left], allParts[0]);
|
|
84
|
-
return hoveredSeqMonomer;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
50
|
export function getSeqMonomerFromHelmAtom(atom: HelmAtom): ISeqMonomer {
|
|
88
51
|
const canonicalSymbol = atom.elem === GapOriginals[NOTATION.HELM] ? GAP_SYMBOL : atom.elem;
|
|
89
52
|
return {position: parseInt(atom.bio!.continuousId as string) - 1, symbol: canonicalSymbol, biotype: atom.bio!.type};
|
|
@@ -94,48 +94,62 @@ export class HelmGridCellRendererBack extends CellRendererBackAsyncBase<HelmProp
|
|
|
94
94
|
protected override renderCellImageData(
|
|
95
95
|
gridCtx: CanvasRenderingContext2D, gridCellBounds: DG.Rect, gridCell: DG.GridCell, cellImageData: ImageData,
|
|
96
96
|
): boolean {
|
|
97
|
+
// super.renderCellImageData(gridCtx, gridCellBounds, gridCell, cellImageData);
|
|
98
|
+
// return true;
|
|
99
|
+
|
|
100
|
+
const gcb = gridCellBounds;
|
|
97
101
|
const dpr = window.devicePixelRatio;
|
|
98
102
|
if (gridCell.tableRowIndex === null) return false;
|
|
99
103
|
const aux = this._auxList[gridCell.tableRowIndex];
|
|
100
104
|
if (!aux) return true;
|
|
101
105
|
|
|
106
|
+
const [cellWidth, cellHeight] = [gcb.width * dpr - 2, gcb.height * dpr - 2];
|
|
107
|
+
const cellDScale = Math.min(0.95 * cellWidth / aux.dBox.width, 0.95 * cellHeight / aux.dBox.height);
|
|
108
|
+
const [cellDWidth, cellDHeight] = [cellDScale * aux.dBox.width, cellDScale * aux.dBox.height];
|
|
109
|
+
const cellDBox = new DG.Rect((cellWidth - cellDWidth) / 2, (cellHeight - cellDHeight) / 2, cellDWidth, cellDHeight);
|
|
110
|
+
|
|
111
|
+
|
|
102
112
|
// Draw cell image data to scale it with drawImage() while transform()
|
|
103
113
|
const cellCanvas = ui.canvas(cellImageData.width, cellImageData.height);
|
|
104
114
|
const cellCtx = cellCanvas.getContext('2d')!;
|
|
105
115
|
cellCtx.putImageData(cellImageData, 0, 0);
|
|
106
116
|
|
|
107
|
-
// Get bbox canvas
|
|
108
|
-
const bBoxImageData = cellCtx.getImageData(aux.bBox.x, aux.bBox.y, aux.bBox.width, aux.bBox.height);
|
|
109
|
-
const bBoxCanvas = ui.canvas(aux.bBox.width, aux.bBox.height);
|
|
110
|
-
const bBoxCtx = bBoxCanvas.getContext('2d')!;
|
|
111
|
-
bBoxCtx.putImageData(bBoxImageData, 0, 0);
|
|
112
|
-
|
|
117
|
+
// // Get bbox canvas
|
|
118
|
+
// const bBoxImageData = cellCtx.getImageData(aux.bBox.x, aux.bBox.y, aux.bBox.width, aux.bBox.height);
|
|
119
|
+
// const bBoxCanvas = ui.canvas(aux.bBox.width, aux.bBox.height);
|
|
120
|
+
// const bBoxCtx = bBoxCanvas.getContext('2d')!;
|
|
121
|
+
// bBoxCtx.putImageData(bBoxImageData, 0, 0);
|
|
122
|
+
//
|
|
113
123
|
const fitCanvasWidth = gridCellBounds.width * dpr - 2;
|
|
114
124
|
const fitCanvasHeight = gridCellBounds.height * dpr - 2;
|
|
115
|
-
|
|
116
|
-
const bBoxRatio = Math.max(aux.bBox.width / aux.cBox.width, aux.bBox.height / aux.cBox.height);
|
|
117
|
-
const bBoxFullWidth = aux.bBox.width / bBoxRatio;
|
|
118
|
-
const bBoxFullHeight = aux.bBox.height / bBoxRatio;
|
|
119
|
-
|
|
120
|
-
const fitScale = Math.min(fitCanvasWidth / bBoxFullWidth, fitCanvasHeight / bBoxFullHeight);
|
|
121
|
-
|
|
122
|
-
// Relative shift of bbox center to cbox center
|
|
123
|
-
const bBoxShiftHR = (aux.bBox.left + aux.bBox.width / 2 - aux.cBox.width / 2) / aux.cBox.width;
|
|
124
|
-
const bBoxShiftVR = (aux.bBox.top + aux.bBox.height / 2 - aux.cBox.height / 2) / aux.cBox.height;
|
|
125
|
-
|
|
126
|
-
const bBoxFitLeft = fitCanvasWidth / 2 - bBoxCanvas.width * fitScale * (1 - 2 * bBoxShiftHR) / 2;
|
|
127
|
-
const bBoxFitTop = fitCanvasHeight / 2 - bBoxCanvas.height * fitScale * (1 - 2 * bBoxShiftVR) / 2;
|
|
128
|
-
|
|
125
|
+
//
|
|
126
|
+
// const bBoxRatio = Math.max(aux.bBox.width / aux.cBox.width, aux.bBox.height / aux.cBox.height);
|
|
127
|
+
// const bBoxFullWidth = aux.bBox.width / bBoxRatio;
|
|
128
|
+
// const bBoxFullHeight = aux.bBox.height / bBoxRatio;
|
|
129
|
+
//
|
|
130
|
+
// const fitScale = Math.min(fitCanvasWidth / bBoxFullWidth, fitCanvasHeight / bBoxFullHeight);
|
|
131
|
+
//
|
|
132
|
+
// // Relative shift of bbox center to cbox center
|
|
133
|
+
// const bBoxShiftHR = (aux.bBox.left + aux.bBox.width / 2 - aux.cBox.width / 2) / aux.cBox.width;
|
|
134
|
+
// const bBoxShiftVR = (aux.bBox.top + aux.bBox.height / 2 - aux.cBox.height / 2) / aux.cBox.height;
|
|
135
|
+
//
|
|
136
|
+
// const bBoxFitLeft = fitCanvasWidth / 2 - bBoxCanvas.width * fitScale * (1 - 2 * bBoxShiftHR) / 2;
|
|
137
|
+
// const bBoxFitTop = fitCanvasHeight / 2 - bBoxCanvas.height * fitScale * (1 - 2 * bBoxShiftVR) / 2;
|
|
138
|
+
//
|
|
129
139
|
const fitCanvas = ui.canvas(fitCanvasWidth, fitCanvasHeight);
|
|
130
140
|
const fitCtx = fitCanvas.getContext('2d')!;
|
|
131
141
|
// fitCtx.fillStyle = '#FFFFA0';
|
|
132
142
|
// fitCtx.fillRect(0, 0, fitCanvasWidth, fitCanvasHeight);
|
|
133
|
-
fitCtx.transform(fitScale, 0, 0, fitScale, bBoxFitLeft, bBoxFitTop);
|
|
134
|
-
fitCtx.drawImage(bBoxCanvas, 0, 0); // draw with scale transform
|
|
143
|
+
// fitCtx.transform(fitScale, 0, 0, fitScale, bBoxFitLeft, bBoxFitTop);
|
|
144
|
+
// fitCtx.drawImage(bBoxCanvas, 0, 0); // draw with scale transform
|
|
145
|
+
fitCtx.drawImage(cellCanvas,
|
|
146
|
+
aux.dBox.x, aux.dBox.y, aux.dBox.width, aux.dBox.height,
|
|
147
|
+
cellDBox.x, cellDBox.y, cellDBox.width, cellDBox.height);
|
|
135
148
|
|
|
136
149
|
const fitCanvasData = fitCtx.getImageData(0, 0, fitCanvasWidth, fitCanvasHeight);
|
|
137
150
|
this.renderOnGrid(gridCtx, gridCellBounds, gridCell, fitCanvasData);
|
|
138
|
-
|
|
151
|
+
|
|
152
|
+
return aux.cBox.width != cellWidth || aux.cBox.height != cellHeight; // request rendering
|
|
139
153
|
}
|
|
140
154
|
|
|
141
155
|
onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
|
|
@@ -147,15 +161,18 @@ export class HelmGridCellRendererBack extends CellRendererBackAsyncBase<HelmProp
|
|
|
147
161
|
const dpr = window.devicePixelRatio;
|
|
148
162
|
|
|
149
163
|
/** {@link gridCell}.bounds */ const gcb = gridCell.bounds;
|
|
150
|
-
const
|
|
151
|
-
const
|
|
164
|
+
const cX = (e.offsetX - gcb.x) * dpr;
|
|
165
|
+
const cY = (e.offsetY - gcb.y) * dpr;
|
|
166
|
+
|
|
167
|
+
const bX = aux.bBox.x + (cX - aux.dBox.x) * aux.bBox.width / aux.dBox.width;
|
|
168
|
+
const bY = aux.bBox.y + (cY - aux.dBox.y) * aux.bBox.height / aux.dBox.height;
|
|
152
169
|
|
|
153
|
-
const editorMol: Mol<HelmType> | null = aux.mol;
|
|
170
|
+
const editorMol: Mol<HelmType> | null = aux.mol; // in bBox world
|
|
154
171
|
if (!editorMol) {
|
|
155
172
|
this.logger.warning(`${logPrefix}, editorMol of the cell not found.`);
|
|
156
173
|
return; // The gridCell is not rendered yet
|
|
157
174
|
}
|
|
158
|
-
const hoveredAtom = getHoveredMonomerFromEditorMol(
|
|
175
|
+
const hoveredAtom = getHoveredMonomerFromEditorMol(bX, bY, editorMol, gridCell.bounds.height);
|
|
159
176
|
|
|
160
177
|
let seqMonomer: ISeqMonomer | null = null;
|
|
161
178
|
if (hoveredAtom) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as grok from 'datagrok-api/grok';
|
|
2
1
|
import * as ui from 'datagrok-api/ui';
|
|
3
2
|
import * as DG from 'datagrok-api/dg';
|
|
4
3
|
|
|
@@ -89,15 +88,19 @@ export class HelmService extends HelmServiceBase {
|
|
|
89
88
|
if (!helmStr)
|
|
90
89
|
editor.reset();
|
|
91
90
|
|
|
92
|
-
const
|
|
91
|
+
const svgEl = editor.div.children[0] as SVGSVGElement;
|
|
92
|
+
const bBox = svgEl.getBBox();
|
|
93
|
+
const [cellWidth, cellHeight] = [task.props.width, task.props.height];
|
|
94
|
+
const bScale = Math.min(cellWidth * 0.95 / bBox.width, cellHeight * 0.95 / bBox.height);
|
|
95
|
+
const [drawWidth, drawHeight] = [bBox.width * bScale, bBox.height * bScale];
|
|
93
96
|
const aux: HelmAux = {
|
|
94
97
|
mol: editor.m.clone(false),
|
|
95
98
|
bBox: new DG.Rect(bBox.x, bBox.y, bBox.width + 2, bBox.height + 2) /* adjust for clipping right/bottom */,
|
|
99
|
+
dBox: new DG.Rect((cellWidth - drawWidth) / 2, (cellHeight - drawHeight) / 2, drawWidth, drawHeight),
|
|
96
100
|
cBox: new DG.Rect(0, 0, task.props.width, task.props.height),
|
|
97
101
|
};
|
|
98
102
|
const lET = window.performance.now();
|
|
99
103
|
|
|
100
|
-
const svgEl = $(this.hostDiv).find('svg').get(0) as unknown as SVGSVGElement;
|
|
101
104
|
const dpr = window.devicePixelRatio;
|
|
102
105
|
|
|
103
106
|
const rST = window.performance.now();
|
|
@@ -135,7 +138,9 @@ export class HelmService extends HelmServiceBase {
|
|
|
135
138
|
if (!this.image || !g) return false;
|
|
136
139
|
// g.fillStyle = '#C0C0FF';
|
|
137
140
|
// g.fillRect(0, 0, canvas.width, canvas.height);
|
|
138
|
-
g.drawImage(this.image,
|
|
141
|
+
g.drawImage(this.image,
|
|
142
|
+
aux.bBox.x, aux.bBox.y, aux.bBox.width, aux.bBox.height,
|
|
143
|
+
aux.dBox.x, aux.dBox.y, aux.dBox.width, aux.dBox.height);
|
|
139
144
|
task.onAfterRender(canvas, aux);
|
|
140
145
|
} finally {
|
|
141
146
|
canvas.remove();
|
|
@@ -1,98 +0,0 @@
|
|
|
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 {HelmType, Mol, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
8
|
-
|
|
9
|
-
import {CellRendererBackBase} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
|
|
10
|
-
|
|
11
|
-
import {getParts, parseHelm} from './utils';
|
|
12
|
-
|
|
13
|
-
import {_package} from './package';
|
|
14
|
-
|
|
15
|
-
export class HelmMonomerPlacer extends CellRendererBackBase<string> {
|
|
16
|
-
private _allPartsList: (string[] | null)[];
|
|
17
|
-
private _lengthsList: (number[] | null)[];
|
|
18
|
-
private _editorMolList: (Mol<HelmType> | null)[];
|
|
19
|
-
|
|
20
|
-
public monomerCharWidth: number = 7;
|
|
21
|
-
public leftPadding: number = 5;
|
|
22
|
-
public monomerTextSizeMap: { [p: string]: TextMetrics } = {};
|
|
23
|
-
|
|
24
|
-
public constructor(
|
|
25
|
-
gridCol: DG.GridColumn | null,
|
|
26
|
-
tableCol: DG.Column<string>
|
|
27
|
-
) {
|
|
28
|
-
super(gridCol, tableCol, _package.logger);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number,
|
|
32
|
-
gridCell: DG.GridCell, cellStyle: DG.GridCellStyle
|
|
33
|
-
) {
|
|
34
|
-
throw new Error('Not implemented');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected override reset(): void {
|
|
38
|
-
this._allPartsList = new Array<string[] | null>(this.tableCol.length).fill(null);
|
|
39
|
-
this._lengthsList = new Array<number[] | null>(this.tableCol.length).fill(null);
|
|
40
|
-
this._editorMolList = new Array<Mol<HelmType> | null>(this.tableCol.length).fill(null);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Skips cell for the fallback rendering */
|
|
44
|
-
public skipCell(rowIdx: number): void {
|
|
45
|
-
this._allPartsList[rowIdx] = [];
|
|
46
|
-
this._lengthsList[rowIdx] = [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** @param rowIdx Row index of the table {@link DG.DataFrame}, HelmMonomerPlacer is {@link DG.Column} based */
|
|
50
|
-
public getCellAllPartsLengths(rowIdx: number): [string[], number[], number[]] {
|
|
51
|
-
const [allParts, lengths] = this.getCellMonomerLengthsForSeq(rowIdx);
|
|
52
|
-
|
|
53
|
-
const sumLengths: number[] = new Array<number>(lengths.length + 1);
|
|
54
|
-
sumLengths[0] = this.leftPadding; // padding
|
|
55
|
-
for (let pos: number = 1; pos < sumLengths.length; pos++)
|
|
56
|
-
sumLengths[pos] = sumLengths[pos - 1] + lengths[pos - 1];
|
|
57
|
-
return [allParts, lengths, sumLengths];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private getCellMonomerLengthsForSeq(rowIdx: number): [string[], number[]] {
|
|
61
|
-
let allParts: string[] | null = this._allPartsList![rowIdx];
|
|
62
|
-
if (allParts === null) {
|
|
63
|
-
const seq = this.tableCol.get(rowIdx);
|
|
64
|
-
allParts = this._allPartsList![rowIdx] = getAllParts(seq);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let lengths: number[] | null = this._lengthsList![rowIdx];
|
|
68
|
-
if (lengths === null) {
|
|
69
|
-
lengths = this._lengthsList![rowIdx] = new Array<number>(allParts.length);
|
|
70
|
-
for (const [part, partI] of wu.enumerate(allParts)) {
|
|
71
|
-
const partWidth: number = part.length * this.monomerCharWidth;
|
|
72
|
-
lengths[partI] = partWidth;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return [allParts, lengths];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// -- Handle events --
|
|
80
|
-
|
|
81
|
-
private monomerLibOnChanged(_value: any): void {
|
|
82
|
-
this.dirty = true;
|
|
83
|
-
this.invalidateGrid();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public setEditorMol(tableRowIndex: number, editorMol: Mol<HelmType>) {
|
|
87
|
-
this._editorMolList![tableRowIndex] = editorMol.clone(false);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public getEditorMol(tableRowIndex: number): Mol<HelmType> | null {
|
|
91
|
-
return this._editorMolList![tableRowIndex];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function getAllParts(seq: string | null): string[] {
|
|
96
|
-
const monomerList: string[] = !!seq ? parseHelm(seq) : [];
|
|
97
|
-
return !!seq ? getParts(monomerList, seq) : [];
|
|
98
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
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 {after, before, category, delay, expect, test, expectArray} from '@datagrok-libraries/utils/src/test';
|
|
6
|
-
|
|
7
|
-
import {getAllParts} from '../helm-monomer-placer';
|
|
8
|
-
import {initHelmMainPackage} from './utils';
|
|
9
|
-
|
|
10
|
-
category('getAllParts', () => {
|
|
11
|
-
/** All parts of Helm string are required for fallback rendering. */
|
|
12
|
-
|
|
13
|
-
const tests: { [testName: string]: { tgt: string[]; seq: string } } = {
|
|
14
|
-
'peptideSimpleSingleChar': {
|
|
15
|
-
seq: 'PEPTIDE1{L.V.A}$$$$',
|
|
16
|
-
tgt: ['PEPTIDE1{', 'L', '.', 'V', '.', 'A', '}$$$$'],
|
|
17
|
-
},
|
|
18
|
-
'peptideSimpleMultiChar': {
|
|
19
|
-
seq: 'PEPTIDE1{[Ac].V.A}$$$$',
|
|
20
|
-
/* doubt joining square brackets to separator or notation is correct */
|
|
21
|
-
tgt: ['PEPTIDE1{[', 'Ac', '].', 'V', '.', 'A', '}$$$$'],
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
before(async () => {
|
|
26
|
-
await initHelmMainPackage();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
for (const [testName, testData] of Object.entries(tests)) {
|
|
30
|
-
test(`${testName}`, async () => {
|
|
31
|
-
await _testAllParts(testData.seq, testData.tgt);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
async function _testAllParts(seq: string | null, tgt: string[]): Promise<void> {
|
|
37
|
-
const res: string[] = getAllParts(seq);
|
|
38
|
-
expectArray(res, tgt);
|
|
39
|
-
}
|