@datagrok/helm 2.1.34 → 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/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
- let monomerLib: IMonomerLib | null = null;
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
- //@ts-ignore
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 monTypeList: string[] = monomerLib!.getPolymerTypes();
79
- const msgStr: string = 'Monomer lib updated:<br />' + (
80
- monTypeList.length == 0 ? 'empty' : monTypeList.map((monType) => {
81
- return `${monType} ${monomerLib!.getMonomerSymbolsByType(monType).length}`;
82
- }).join('<br />'));
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
- grok.shell.info(msgStr);
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 absent! <br/>` +
170
- `Please, upload the monomer library! <br/>` +
171
- `<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
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
- // @ts-ignore
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, 'HELM');
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 {getUserLibSettings, setUserLibSettings, setUserLibSettingsForTests} from '@datagrok-libraries/bio/src/monomer-works/lib-settings';
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.fromList(DG.COLUMN_TYPE.STRING, 'seq', [seq]);
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 {category, expect, test, awaitCheck, delay} from '@datagrok-libraries/utils/src/test';
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 function _testMissedInLib() {
16
- const df: DG.DataFrame = await grok.dapi.files.readCsv('System:AppData/Helm/tests/sample_HELM-missedInLib.csv');
17
- const helmCol: DG.Column<string> = df.getCol('HELM');
18
-
19
- const tv: DG.TableView = grok.shell.addTableView(df);
20
- await awaitCheck(() => {
21
- return $(tv.root).find('.d4-grid canvas').length > 0;
22
- }, 'Table view canvas not found', 100);
23
-
24
- expect(helmCol.semType, DG.SEMTYPE.MACROMOLECULE);
25
- expect(helmCol.getTag(DG.TAGS.UNITS), NOTATION.HELM);
26
- expect(helmCol.getTag(DG.TAGS.CELL_RENDERER), 'helm');
27
-
28
- const cellRendererErrorJson: string = helmCol.getTag(helmTAGS.cellRendererRenderError);
29
- if (!!cellRendererErrorJson) {
30
- const cellRendererError: any = JSON.parse(cellRendererErrorJson);
31
- const err = new Error(cellRendererError['message']);
32
- err.stack = cellRendererError['stack'];
33
- throw err;
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,3 @@
1
+ declare module 'dojo' {
2
+ export function ready(callback: () => void): void;
3
+ }
@@ -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
+ }