@datagrok/helm 2.1.33 → 2.2.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.
- package/.eslintrc.json +1 -1
- package/CHANGELOG.md +23 -0
- 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 +6 -6
- package/src/cell-renderer.ts +17 -153
- package/src/helm-helper.ts +9 -10
- package/src/helm-monomer-placer.ts +18 -66
- package/src/helm-web-editor.ts +5 -2
- package/src/package-test.ts +1 -0
- package/src/package-utils.ts +17 -0
- package/src/package.ts +75 -25
- package/src/tests/helm-service-tests.ts +97 -0
- package/src/tests/properties-widget-tests.ts +4 -2
- package/src/tests/renderers-tests.ts +97 -22
- package/src/types/dojo.ts +3 -0
- package/src/utils/dummy-monomer.ts +69 -0
- package/src/utils/get-hovered.ts +115 -0
- package/src/utils/helm-grid-cell-renderer.ts +201 -0
- package/src/utils/helm-service.ts +157 -0
- package/src/utils/index.ts +7 -7
- package/webpack.config.js +7 -0
package/src/package.ts
CHANGED
|
@@ -3,6 +3,14 @@ import * as grok from 'datagrok-api/grok';
|
|
|
3
3
|
import * as ui from 'datagrok-api/ui';
|
|
4
4
|
import * as DG from 'datagrok-api/dg';
|
|
5
5
|
|
|
6
|
+
// import '@datagrok-libraries/bio/src/types/dojo';
|
|
7
|
+
// import * as dojo from 'DOJO';
|
|
8
|
+
import '@datagrok-libraries/bio/src/types/helm';
|
|
9
|
+
import * as scil from 'scil';
|
|
10
|
+
import * as org from 'org';
|
|
11
|
+
import '@datagrok-libraries/bio/src/types/jsdraw2';
|
|
12
|
+
import * as JSDraw2 from 'JSDraw2';
|
|
13
|
+
|
|
6
14
|
import $ from 'cash-dom';
|
|
7
15
|
|
|
8
16
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
@@ -10,16 +18,19 @@ import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
|
10
18
|
import {GapOriginals, SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
11
19
|
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types';
|
|
12
20
|
import {IHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
21
|
+
import {HelmServiceBase} from '@datagrok-libraries/bio/src/viewers/helm-service';
|
|
13
22
|
|
|
14
23
|
import {findMonomers, parseHelm} from './utils';
|
|
15
24
|
import {HelmCellRenderer} from './cell-renderer';
|
|
16
25
|
import {HelmHelper} from './helm-helper';
|
|
17
26
|
import {getPropertiesWidget} from './widgets/properties-widget';
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
import {HelmGridCellRenderer, HelmGridCellRendererBack} from './utils/helm-grid-cell-renderer';
|
|
28
|
+
import {_getHelmService} from './package-utils';
|
|
20
29
|
|
|
21
30
|
import {WebEditorMonomer, RGROUP_CAP_GROUP_NAME, RGROUP_LABEL, SMILES} from './constants';
|
|
22
31
|
|
|
32
|
+
let monomerLib: IMonomerLib | null = null;
|
|
33
|
+
|
|
23
34
|
export const _package = new DG.Package();
|
|
24
35
|
|
|
25
36
|
function initHelmPatchDojo(): void {
|
|
@@ -56,7 +67,8 @@ function initHelmPatchDojo(): void {
|
|
|
56
67
|
|
|
57
68
|
//tags: init
|
|
58
69
|
export async function initHelm(): Promise<void> {
|
|
59
|
-
|
|
70
|
+
const logPrefix: string = 'Helm: initHelm()';
|
|
71
|
+
_package.logger.debug(`${logPrefix}, start`);
|
|
60
72
|
org.helm.webeditor.kCaseSensitive = true; // GROK-13880
|
|
61
73
|
|
|
62
74
|
return Promise.all([
|
|
@@ -69,19 +81,22 @@ export async function initHelm(): Promise<void> {
|
|
|
69
81
|
grok.functions.call('Bio:getBioLib'),
|
|
70
82
|
])
|
|
71
83
|
.then(([_, lib]: [unknown, IMonomerLib]) => {
|
|
84
|
+
_package.logger.debug(`${logPrefix}, then(), lib loaded`);
|
|
72
85
|
monomerLib = lib;
|
|
73
86
|
rewriteLibraries(); // initHelm()
|
|
74
87
|
monomerLib.onChanged.subscribe((_) => {
|
|
75
88
|
try {
|
|
89
|
+
const logPrefixInt = `${logPrefix} monomerLib.onChanged()`;
|
|
90
|
+
_package.logger.debug(`${logPrefixInt}, start, org,helm.webeditor.Monomers updating`);
|
|
76
91
|
rewriteLibraries(); // initHelm()
|
|
77
92
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
const libSummary: string = monomerLib!.getSummary()
|
|
94
|
+
.replaceAll('\n', '<br />\n')
|
|
95
|
+
.replaceAll(',', ' ');
|
|
96
|
+
const libMsg: string = `Monomer lib updated:<br /> ${libSummary}'`;
|
|
97
|
+
grok.shell.info(libMsg);
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
_package.logger.debug(`${logPrefixInt}, end, org.helm.webeditor.Monomers completed`);
|
|
85
100
|
} catch (err: any) {
|
|
86
101
|
const errMsg = errorToConsole(err);
|
|
87
102
|
console.error('Helm: initHelm monomerLib.onChanged() error:\n' + errMsg);
|
|
@@ -104,7 +119,6 @@ export function getMonomerLib(): IMonomerLib {
|
|
|
104
119
|
|
|
105
120
|
/** Fills org.helm.webeditor.Monomers dictionary for WebEditor */
|
|
106
121
|
function rewriteLibraries() {
|
|
107
|
-
// @ts-ignore
|
|
108
122
|
org.helm.webeditor.Monomers.clear();
|
|
109
123
|
monomerLib!.getPolymerTypes().forEach((polymerType) => {
|
|
110
124
|
const monomerSymbols = monomerLib!.getMonomerSymbolsByType(polymerType);
|
|
@@ -136,7 +150,6 @@ function rewriteLibraries() {
|
|
|
136
150
|
isBroken = true;
|
|
137
151
|
|
|
138
152
|
if (!isBroken) {
|
|
139
|
-
// @ts-ignore
|
|
140
153
|
org.helm.webeditor.Monomers.addOneMonomer(webEditorMonomer);
|
|
141
154
|
}
|
|
142
155
|
});
|
|
@@ -147,13 +160,20 @@ function rewriteLibraries() {
|
|
|
147
160
|
if (grid) grid.invalidate();
|
|
148
161
|
}
|
|
149
162
|
|
|
163
|
+
//name: getHelmService
|
|
164
|
+
//output: object result
|
|
165
|
+
export function getHelmService(): HelmServiceBase {
|
|
166
|
+
return _getHelmService();
|
|
167
|
+
}
|
|
168
|
+
|
|
150
169
|
//name: helmCellRenderer
|
|
151
170
|
//tags: cellRenderer
|
|
152
171
|
//meta.cellType: helm
|
|
153
172
|
//meta.columnTags: quality=Macromolecule, units=helm
|
|
154
173
|
//output: grid_cell_renderer result
|
|
155
174
|
export function helmCellRenderer(): HelmCellRenderer {
|
|
156
|
-
return new HelmCellRenderer();
|
|
175
|
+
// return new HelmCellRenderer(); // old
|
|
176
|
+
return new HelmGridCellRenderer(); // new
|
|
157
177
|
}
|
|
158
178
|
|
|
159
179
|
function checkMonomersAndOpenWebEditor(cell: DG.Cell, value?: string, units?: string) {
|
|
@@ -166,9 +186,9 @@ function checkMonomersAndOpenWebEditor(cell: DG.Cell, value?: string, units?: st
|
|
|
166
186
|
grok.shell.warning(`WebEditor doesn't support Helm with gaps '${GapOriginals[NOTATION.HELM]}'.`);
|
|
167
187
|
else {
|
|
168
188
|
grok.shell.warning(
|
|
169
|
-
`Monomers ${Array.from(missedMonomerSet).map((m) => `'${m}'`).join(', ')} are
|
|
170
|
-
`Please
|
|
171
|
-
`<a href="https://datagrok.ai/help/domains/bio
|
|
189
|
+
`Monomers ${Array.from(missedMonomerSet).map((m) => `'${m}'`).join(', ')} are not found. <br/>` +
|
|
190
|
+
`Please specify the monomer library to use. <br/>` +
|
|
191
|
+
`<a href="https://datagrok.ai/help/datagrok/solutions/domains/bio/#manage-monomer-libraries" target="_blank">Learn more</a>`);
|
|
172
192
|
}
|
|
173
193
|
}
|
|
174
194
|
|
|
@@ -186,7 +206,7 @@ export function editMoleculeCell(cell: DG.GridCell): void {
|
|
|
186
206
|
//input: string mol { semType: Macromolecule }
|
|
187
207
|
export function openEditor(mol: string): void {
|
|
188
208
|
const df = grok.shell.tv.grid.dataFrame;
|
|
189
|
-
const col = df.columns.bySemType('Macromolecule')
|
|
209
|
+
const col = df.columns.bySemType('Macromolecule')! as DG.Column<string>;
|
|
190
210
|
const colSh = SeqHandler.forColumn(col);
|
|
191
211
|
const colUnits = col.getTag(DG.TAGS.UNITS);
|
|
192
212
|
if (colUnits === NOTATION.HELM)
|
|
@@ -208,13 +228,11 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
|
|
|
208
228
|
const view = ui.div();
|
|
209
229
|
// const df = grok.shell.tv.grid.dataFrame;
|
|
210
230
|
// const col = df.columns.bySemType('Macromolecule')!;
|
|
211
|
-
const col = cell.column
|
|
231
|
+
const col = cell.column as DG.Column<string>;
|
|
212
232
|
const sh = SeqHandler.forColumn(col);
|
|
213
233
|
const rowIdx = cell.rowIndex;
|
|
214
|
-
// @ts-ignore
|
|
215
234
|
org.helm.webeditor.MolViewer.molscale = 0.8;
|
|
216
|
-
|
|
217
|
-
const app = new scil.helm.App(view, {
|
|
235
|
+
const app = new org.helm.webeditor.App(view, {
|
|
218
236
|
showabout: false,
|
|
219
237
|
mexfontsize: '90%',
|
|
220
238
|
mexrnapinontab: true,
|
|
@@ -227,12 +245,9 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
|
|
|
227
245
|
const sizes = app.calculateSizes();
|
|
228
246
|
app.canvas.resize(sizes.rightwidth - 100, sizes.topheight - 210);
|
|
229
247
|
let s = {width: sizes.rightwidth - 100 + 'px', height: sizes.bottomheight + 'px'};
|
|
230
|
-
//@ts-ignore
|
|
231
248
|
scil.apply(app.sequence.style, s);
|
|
232
|
-
//@ts-ignore
|
|
233
249
|
scil.apply(app.notation.style, s);
|
|
234
250
|
s = {width: sizes.rightwidth + 'px', height: (sizes.bottomheight + app.toolbarheight) + 'px'};
|
|
235
|
-
//@ts-ignore
|
|
236
251
|
scil.apply(app.properties.parent.style, s);
|
|
237
252
|
app.structureview.resize(sizes.rightwidth, sizes.bottomheight + app.toolbarheight);
|
|
238
253
|
app.mex.resize(sizes.topheight - 80);
|
|
@@ -240,9 +255,8 @@ function webEditor(cell: DG.Cell, value?: string, units?: string) {
|
|
|
240
255
|
if (!!cell && units === undefined)
|
|
241
256
|
app.canvas.helm.setSequence(cell.value, 'HELM');
|
|
242
257
|
else
|
|
243
|
-
app.canvas.helm.setSequence(value
|
|
258
|
+
app.canvas.helm.setSequence(value!, 'HELM');
|
|
244
259
|
}, 200);
|
|
245
|
-
//@ts-ignore
|
|
246
260
|
ui.dialog({showHeader: false, showFooter: true})
|
|
247
261
|
.add(view)
|
|
248
262
|
.onOK(() => {
|
|
@@ -312,3 +326,39 @@ export function getMolfiles(col: DG.Column): DG.Column {
|
|
|
312
326
|
export async function getHelmHelper(): Promise<IHelmHelper> {
|
|
313
327
|
return HelmHelper.getInstance();
|
|
314
328
|
}
|
|
329
|
+
|
|
330
|
+
import {testEvent} from '@datagrok-libraries/utils/src/test';
|
|
331
|
+
import {CellRendererBackAsyncBase} from '@datagrok-libraries/bio/src/utils/cell-renderer-async-base';
|
|
332
|
+
|
|
333
|
+
//name: measureCellRenderer
|
|
334
|
+
export async function measureCellRenderer(): Promise<void> {
|
|
335
|
+
const grid = grok.shell.tv.grid;
|
|
336
|
+
const gridCol = grid.columns.byName('sequence')!;
|
|
337
|
+
const back = gridCol.temp['rendererBack'] as HelmGridCellRendererBack;
|
|
338
|
+
|
|
339
|
+
let etSum: number = 0;
|
|
340
|
+
let etCount: number = 0;
|
|
341
|
+
for (let i = 0; i < 20; ++i) {
|
|
342
|
+
const t1 = window.performance.now();
|
|
343
|
+
let t2: number;
|
|
344
|
+
if (!back.cacheEnabled) {
|
|
345
|
+
await testEvent(back.onRendered, () => {
|
|
346
|
+
t2 = window.performance.now();
|
|
347
|
+
_package.logger.info(`measureCellRenderer() cache disabled , ET: ${t2 - t1} ms`);
|
|
348
|
+
}, () => {
|
|
349
|
+
back.invalidate(); // grid.invalidate();
|
|
350
|
+
}, 5000);
|
|
351
|
+
} else {
|
|
352
|
+
await testEvent(grid.onAfterDrawContent, () => {
|
|
353
|
+
t2 = window.performance.now();
|
|
354
|
+
_package.logger.info(`measureCellRenderer() cache enabled, ET: ${t2! - t1} ms`);
|
|
355
|
+
}, () => {
|
|
356
|
+
grid.invalidate();
|
|
357
|
+
}, 5000);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
etSum += (t2! - t1);
|
|
361
|
+
etCount++;
|
|
362
|
+
}
|
|
363
|
+
_package.logger.info(`measureCellRenderer(), avg ET: ${etSum / etCount} ms`);
|
|
364
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 {Subject} from 'rxjs';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
category, expect, test, awaitCheck, delay, testEvent, before, after
|
|
9
|
+
} from '@datagrok-libraries/utils/src/test';
|
|
10
|
+
import {
|
|
11
|
+
getHelmService, HelmServiceBase, HelmAux, HelmProps
|
|
12
|
+
} from '@datagrok-libraries/bio/src/viewers/helm-service';
|
|
13
|
+
import {RenderTask} from '@datagrok-libraries/bio/src/utils/cell-renderer-async-base';
|
|
14
|
+
|
|
15
|
+
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
16
|
+
import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
17
|
+
import {
|
|
18
|
+
getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests
|
|
19
|
+
} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
20
|
+
|
|
21
|
+
import {_package} from '../package-test';
|
|
22
|
+
|
|
23
|
+
category('HelmService', () => {
|
|
24
|
+
let monomerLibHelper: IMonomerLibHelper;
|
|
25
|
+
/** Backup actual user's monomer libraries settings */
|
|
26
|
+
let userLibSettings: UserLibSettings;
|
|
27
|
+
|
|
28
|
+
let helmSvc: HelmServiceBase;
|
|
29
|
+
|
|
30
|
+
before(async () => {
|
|
31
|
+
const logPrefix = `Helm: tests: HelmService: before`;
|
|
32
|
+
_package.logger.debug(`${logPrefix}, start`);
|
|
33
|
+
try {
|
|
34
|
+
monomerLibHelper = await getMonomerLibHelper();
|
|
35
|
+
userLibSettings = await getUserLibSettings();
|
|
36
|
+
|
|
37
|
+
await delay(1000); // for loading monomer libraries files
|
|
38
|
+
// Test 'helm' requires default monomer library loaded
|
|
39
|
+
await setUserLibSettingsForTests();
|
|
40
|
+
await monomerLibHelper.loadLibraries(true); // load default libraries
|
|
41
|
+
const libSummary = monomerLibHelper.getBioLib().getSummary();
|
|
42
|
+
|
|
43
|
+
helmSvc = await getHelmService();
|
|
44
|
+
await helmSvc.reset();
|
|
45
|
+
|
|
46
|
+
_package.logger.debug(`${logPrefix}, end`);
|
|
47
|
+
} finally {
|
|
48
|
+
_package.logger.debug(`${logPrefix}, fina`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
after(async () => {
|
|
53
|
+
const logPrefix = `Helm: tests: HelmService: after`;
|
|
54
|
+
_package.logger.debug(`${logPrefix}, start`);
|
|
55
|
+
|
|
56
|
+
await helmSvc.reset();
|
|
57
|
+
|
|
58
|
+
await setUserLibSettings(userLibSettings);
|
|
59
|
+
await monomerLibHelper.loadLibraries(true); // load user settings libraries
|
|
60
|
+
_package.logger.debug(`${logPrefix}, end`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('helm', async () => {
|
|
64
|
+
const logPrefix: string = `Helm: tests: HelmService: helm`;
|
|
65
|
+
_package.logger.debug(`${logPrefix}, start`);
|
|
66
|
+
const helmStr: string = 'PEPTIDE1{I.H.A.N.T.Thr_PO3H2.Aca.D-Tyr_Et}$$$$';
|
|
67
|
+
let consumerId: number | null = null;
|
|
68
|
+
let taskAux: HelmAux | null = null;
|
|
69
|
+
const event = new Subject<void>();
|
|
70
|
+
await testEvent(event,
|
|
71
|
+
() => {
|
|
72
|
+
},
|
|
73
|
+
() => {
|
|
74
|
+
const task: RenderTask<HelmProps, HelmAux> = {
|
|
75
|
+
name: 'test1',
|
|
76
|
+
props: {
|
|
77
|
+
helm: helmStr,
|
|
78
|
+
backColor: DG.Color.fromHtml('#FFFFFF'),
|
|
79
|
+
width: 300, height: 100,
|
|
80
|
+
},
|
|
81
|
+
onAfterRender: (canvas: HTMLCanvasElement, aux: HelmAux) => {
|
|
82
|
+
taskAux = aux;
|
|
83
|
+
event.next();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
helmSvc.reset();
|
|
87
|
+
consumerId = helmSvc.render(consumerId, task, 0);
|
|
88
|
+
}, 7000);
|
|
89
|
+
expect(taskAux !== null, true, 'Helm task aux is null');
|
|
90
|
+
expect(taskAux!.mol.atoms.length > 0, true, 'Polymer mol atoms empty');
|
|
91
|
+
expect(taskAux!.mol.atoms.length, 8);
|
|
92
|
+
expect(taskAux!.mol.bonds.length, 7);
|
|
93
|
+
expect(helmSvc.errorCount, 0, 'There was errors in NglGlService.');
|
|
94
|
+
expect(consumerId !== null, true, 'consumerId not assigned');
|
|
95
|
+
_package.logger.debug(`${logPrefix}, end`);
|
|
96
|
+
}, {timeout: 10000});
|
|
97
|
+
});
|
|
@@ -5,7 +5,9 @@ import * as grok from 'datagrok-api/grok';
|
|
|
5
5
|
import {after, before, category, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
|
|
6
6
|
import {ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
7
|
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests
|
|
10
|
+
} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
9
11
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
10
12
|
|
|
11
13
|
import {getPropertiesDict} from '../widgets/properties-widget';
|
|
@@ -88,7 +90,7 @@ category('properties-widget', () => {
|
|
|
88
90
|
function testPropertiesDict(
|
|
89
91
|
seq: string, units: NOTATION, separator: string | undefined, alphabet: ALPHABET | undefined, expPropDict: {}
|
|
90
92
|
) {
|
|
91
|
-
const col = DG.Column.
|
|
93
|
+
const col = DG.Column.fromStrings('seq', [seq]) as DG.Column<string>;
|
|
92
94
|
col.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
93
95
|
col.setTag(DG.TAGS.UNITS, units);
|
|
94
96
|
if (separator) col.setTag(bioTAGS.separator, separator);
|
|
@@ -3,33 +3,108 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
5
|
import $ from 'cash-dom';
|
|
6
|
+
import {fromEvent} from 'rxjs';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
after, before, category, test, expect, awaitCheck, delay, testEvent
|
|
10
|
+
} from '@datagrok-libraries/utils/src/test';
|
|
8
11
|
import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
12
|
+
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
13
|
+
import {UserLibSettings} from '@datagrok-libraries/bio/src/monomer-works/types';
|
|
14
|
+
import {
|
|
15
|
+
getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests
|
|
16
|
+
} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
|
|
17
|
+
|
|
18
|
+
import {awaitGrid} from './utils';
|
|
9
19
|
import {TAGS as helmTAGS} from '../constants';
|
|
10
20
|
|
|
21
|
+
import {_package} from '../package-test';
|
|
22
|
+
|
|
11
23
|
category('renderers', () => {
|
|
24
|
+
let monomerLibHelper: IMonomerLibHelper;
|
|
25
|
+
/** Backup actual user's monomer libraries settings */
|
|
26
|
+
let userLibSettings: UserLibSettings;
|
|
27
|
+
|
|
28
|
+
before(async () => {
|
|
29
|
+
monomerLibHelper = await getMonomerLibHelper();
|
|
30
|
+
userLibSettings = await getUserLibSettings();
|
|
31
|
+
|
|
32
|
+
// Test 'helm' requires default monomer library loaded
|
|
33
|
+
await setUserLibSettingsForTests();
|
|
34
|
+
await monomerLibHelper.loadLibraries(true); // load default libraries
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
after(async () => {
|
|
38
|
+
// UserDataStorage.put() replaces existing data
|
|
39
|
+
await setUserLibSettings(userLibSettings);
|
|
40
|
+
await monomerLibHelper.loadLibraries(true); // load user settings libraries
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
|
|
12
44
|
test('missedInLib', async () => { await _testMissedInLib(); });
|
|
13
|
-
});
|
|
14
45
|
|
|
15
|
-
async
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
test('scatterPlotTooltip', async () => { await _testScatterPlotTooltip(); });
|
|
47
|
+
|
|
48
|
+
async function _testMissedInLib() {
|
|
49
|
+
const df: DG.DataFrame = await grok.dapi.files.readCsv('System:AppData/Helm/tests/sample_HELM-missedInLib.csv');
|
|
50
|
+
const helmCol: DG.Column<string> = df.getCol('HELM');
|
|
51
|
+
|
|
52
|
+
const tv: DG.TableView = grok.shell.addTableView(df);
|
|
53
|
+
await awaitCheck(() => {
|
|
54
|
+
return $(tv.root).find('.d4-grid canvas').length > 0;
|
|
55
|
+
}, 'Table view canvas not found', 100);
|
|
56
|
+
|
|
57
|
+
expect(helmCol.semType, DG.SEMTYPE.MACROMOLECULE);
|
|
58
|
+
expect(helmCol.getTag(DG.TAGS.UNITS), NOTATION.HELM);
|
|
59
|
+
expect(helmCol.getTag(DG.TAGS.CELL_RENDERER), 'helm');
|
|
60
|
+
|
|
61
|
+
const cellRendererErrorJson: string = helmCol.getTag(helmTAGS.cellRendererRenderError);
|
|
62
|
+
if (!!cellRendererErrorJson) {
|
|
63
|
+
const cellRendererError: any = JSON.parse(cellRendererErrorJson);
|
|
64
|
+
const err = new Error(cellRendererError['message']);
|
|
65
|
+
err.stack = cellRendererError['stack'];
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const helmCoordsCsv = `seq,x,y
|
|
71
|
+
PEPTIDE1{I.H.A.N.T.Thr_PO3H2.Aca.D-Tyr_Et}$$$$,0,0
|
|
72
|
+
RNA1{r(A)p.r(C)p.r(G)p.r(U)p}$$$$,1,0
|
|
73
|
+
RNA1{d(A)p.d(C)p.d(G)p.d(U)p}|PEPTIDE1{I.H.A.N.T.Thr_PO3H2}$$$$,0,1
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
async function _testScatterPlotTooltip(): Promise<void> {
|
|
77
|
+
const df = DG.DataFrame.fromCsv(helmCoordsCsv);
|
|
78
|
+
df.currentRowIdx = 0;
|
|
79
|
+
const view = grok.shell.addTableView(df);
|
|
80
|
+
const sp: DG.ScatterPlotViewer = df.plot.scatter({x: 'x', y: 'y'});
|
|
81
|
+
view.dockManager.dock(sp, DG.DOCK_TYPE.RIGHT, null);
|
|
82
|
+
await Promise.all([
|
|
83
|
+
testEvent(sp.onAfterDrawScene, () => {}, () => { sp.invalidateCanvas(); }, 1000),
|
|
84
|
+
awaitGrid(view.grid, 500)
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const spBcr = sp.root.getBoundingClientRect();
|
|
88
|
+
const wp = sp.worldToScreen(1, 0);
|
|
89
|
+
const ev = new MouseEvent('mousemove', {
|
|
90
|
+
cancelable: true, bubbles: true, view: window, button: 0,
|
|
91
|
+
clientX: spBcr.left + wp.x, clientY: spBcr.top + wp.y
|
|
92
|
+
});
|
|
93
|
+
const spCanvas = $(sp.root).find('canvas').get()[0] as HTMLCanvasElement;
|
|
94
|
+
await testEvent(fromEvent(spCanvas, 'mousemove'), () => {
|
|
95
|
+
_package.logger.debug(`Test: event, currentRowIdx=${df.currentRowIdx}`);
|
|
96
|
+
expect($(ui.tooltip.root).find('div table.d4-row-tooltip-table tr td canvas').length, 1);
|
|
97
|
+
expect(sp.hitTest(wp.x, wp.y), 1);
|
|
98
|
+
}, () => {
|
|
99
|
+
spCanvas.dispatchEvent(ev);
|
|
100
|
+
}, 500);
|
|
101
|
+
// TODO: Any error occurred become 'Cannot read properties of null (reading 'get$columns')' because of scatter plot
|
|
102
|
+
//await testEvent(sp.onAfterDrawScene, () => {}, () => { sp.invalidateCanvas(); }, 200);
|
|
103
|
+
await awaitGrid(view.grid, 500);
|
|
34
104
|
}
|
|
35
|
-
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
import {_package} from '../package';
|
|
5
|
+
|
|
6
|
+
/* eslint-disable camelcase */
|
|
7
|
+
export class DummyWebEditorMonomer {
|
|
8
|
+
public readonly backgroundcolor: string = '#FF4444';
|
|
9
|
+
public readonly linecolor: string = '#800000';
|
|
10
|
+
public readonly textcolor: string = '#FFFFFF';
|
|
11
|
+
|
|
12
|
+
// no effect
|
|
13
|
+
// public readonly na: string = 'NA';
|
|
14
|
+
// public readonly nature: string = 'NA';
|
|
15
|
+
|
|
16
|
+
/** R-Group index os single digit only is allowed in Pistoia code */
|
|
17
|
+
public readonly at: { [rg: string]: string } = {
|
|
18
|
+
R1: 'H', R2: 'H', R3: 'H', R4: 'H', R5: 'H', R6: 'H', R7: 'H', R8: 'H', R9: 'H'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
public readonly biotype: string,
|
|
23
|
+
/** symbol, elem */ public readonly id: string,
|
|
24
|
+
/** name */ public readonly n: string = 'missing',
|
|
25
|
+
/** molfile*/ public readonly m?: string,
|
|
26
|
+
) {
|
|
27
|
+
if (!this.id)
|
|
28
|
+
throw new Error('Not supported undefined [id].');
|
|
29
|
+
|
|
30
|
+
return new Proxy(this, {
|
|
31
|
+
get: (target: any, prop: string | symbol, _receiver: any) => {
|
|
32
|
+
const logPrefix = `${this.toLog()}.proxy.get( '${String(prop)}' )`;
|
|
33
|
+
switch (prop) {
|
|
34
|
+
case 'id':
|
|
35
|
+
case 'at':
|
|
36
|
+
case 'na':
|
|
37
|
+
case 'nature':
|
|
38
|
+
case 'backgroundcolor':
|
|
39
|
+
case 'linecolor':
|
|
40
|
+
case 'textcolor': {
|
|
41
|
+
// these attributes are known and handled
|
|
42
|
+
return target[prop];
|
|
43
|
+
}
|
|
44
|
+
case 'rs': {
|
|
45
|
+
/* R-Group count (?), 2 is default for monomer "?" */
|
|
46
|
+
throw new Error('Not supported get "rs" attribute.');
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
// Unexpected attribute requested
|
|
50
|
+
_package.logger.warning(`${logPrefix}`);
|
|
51
|
+
return target[prop];
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
set: (target, prop, value: any, _receiver: any): boolean => {
|
|
55
|
+
throw new Error(`Not supported set '${String(prop)}' attribute, any.`);
|
|
56
|
+
},
|
|
57
|
+
apply: (target: any, thisArg: any, argArray: any[]): any | undefined => {
|
|
58
|
+
throw new Error(`Not supported call (apply) '${thisArg.toString()}' method, any.`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private static objCounter: number = -1;
|
|
64
|
+
private readonly objId: number = ++DummyWebEditorMonomer.objCounter;
|
|
65
|
+
|
|
66
|
+
protected toLog(): string {
|
|
67
|
+
return `Helm: DummyWebEditorMonomer<${this.objId}>`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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 * as org from 'org';
|
|
6
|
+
import * as JSDraw2 from 'JSDraw2';
|
|
7
|
+
|
|
8
|
+
import {IMonomerLib} from '@datagrok-libraries/bio/src/types/index';
|
|
9
|
+
|
|
10
|
+
import {HelmMonomerPlacer, ISeqMonomer} from '../helm-monomer-placer';
|
|
11
|
+
|
|
12
|
+
export function getHoveredMonomerFromEditorMol(
|
|
13
|
+
argsX: number, argsY: number, gridCell: DG.GridCell, mol: JSDraw2.IEditorMol
|
|
14
|
+
): ISeqMonomer | null {
|
|
15
|
+
let hoveredSeqMonomer: ISeqMonomer | null = null;
|
|
16
|
+
|
|
17
|
+
/** @return {[number, number]} [atom, distance] */
|
|
18
|
+
function getNearest(excluded: (number | undefined)[]): [number | undefined, number | undefined] {
|
|
19
|
+
let atom: number | undefined = undefined;
|
|
20
|
+
let distance: number | undefined = undefined;
|
|
21
|
+
for (let atomI = 0; atomI < mol.atoms.length; ++atomI) {
|
|
22
|
+
if (!excluded.includes(atomI)) {
|
|
23
|
+
const aX: number = mol.atoms[atomI].p.x;
|
|
24
|
+
const aY: number = mol.atoms[atomI].p.y;
|
|
25
|
+
const distanceToAtomI: number = Math.sqrt((argsX - aX) ** 2 + (argsY - aY) ** 2);
|
|
26
|
+
if (distance === undefined || distance > distanceToAtomI) {
|
|
27
|
+
atom = atomI;
|
|
28
|
+
distance = distanceToAtomI;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [atom, distance];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const [firstAtomI, firstDistance] = getNearest([]);
|
|
36
|
+
const [secondAtomI, secondDistance] = getNearest([firstAtomI]);
|
|
37
|
+
|
|
38
|
+
if (firstAtomI !== undefined && firstDistance !== undefined) {
|
|
39
|
+
const firstAtom = mol.atoms[firstAtomI];
|
|
40
|
+
const firstSeqMonomer = getSeqMonomerFromHelmAtom(firstAtom);
|
|
41
|
+
if (secondAtomI !== undefined && secondDistance !== undefined) {
|
|
42
|
+
if (firstDistance < secondDistance * 0.45)
|
|
43
|
+
hoveredSeqMonomer = firstSeqMonomer;
|
|
44
|
+
} else {
|
|
45
|
+
if (firstDistance < 0.35 * gridCell.bounds.height)
|
|
46
|
+
hoveredSeqMonomer = firstSeqMonomer;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return hoveredSeqMonomer;
|
|
50
|
+
}
|
|
51
|
+
|
|
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(allParts[0], allParts[left], helmPlacer.monomerLib);
|
|
84
|
+
return hoveredSeqMonomer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getSeqMonomerFromHelmAtom(atom: JSDraw2.IEditorMolAtom): ISeqMonomer {
|
|
88
|
+
let polymerType: string | undefined = undefined;
|
|
89
|
+
switch (atom.bio.type) {
|
|
90
|
+
case 'HELM_BASE':
|
|
91
|
+
case 'HELM_SUGAR': // r - ribose, d - deoxyribose
|
|
92
|
+
case 'HELM_LINKER': // p - phosphate
|
|
93
|
+
polymerType = 'RNA';
|
|
94
|
+
break;
|
|
95
|
+
case 'HELM_AA':
|
|
96
|
+
polymerType = 'PEPTIDE';
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
polymerType = 'PEPTIDE';
|
|
100
|
+
}
|
|
101
|
+
return {symbol: atom.elem, polymerType: polymerType};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getSeqMonomerFromHelm(
|
|
105
|
+
helmPrefix: string, symbol: string, monomerLib: IMonomerLib
|
|
106
|
+
): ISeqMonomer {
|
|
107
|
+
let resSeqMonomer: ISeqMonomer | undefined = undefined;
|
|
108
|
+
for (const polymerType of monomerLib.getPolymerTypes()) {
|
|
109
|
+
if (helmPrefix.startsWith(polymerType))
|
|
110
|
+
resSeqMonomer = {symbol: symbol, polymerType: polymerType};
|
|
111
|
+
}
|
|
112
|
+
if (!resSeqMonomer)
|
|
113
|
+
throw new Error(`Monomer not found for symbol = '${symbol}' and helmPrefix = '${helmPrefix}'.`);
|
|
114
|
+
return resSeqMonomer;
|
|
115
|
+
}
|