@datagrok/bio 2.22.0 → 2.22.3

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.
@@ -333,7 +333,13 @@ export function handleSequenceHeaderRendering() {
333
333
  const seqCols = df.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
334
334
 
335
335
  for (const seqCol of seqCols) {
336
- const sh = _package.seqHelper.getSeqHandler(seqCol);
336
+ let sh: ISeqHandler | null = null;
337
+
338
+ try {
339
+ sh = _package.seqHelper.getSeqHandler(seqCol);
340
+ } catch (_e) {
341
+ continue;
342
+ }
337
343
  if (!sh) continue;
338
344
  if (sh.isHelm() || sh.alphabet === ALPHABET.UN) continue;
339
345
 
@@ -344,193 +350,195 @@ export function handleSequenceHeaderRendering() {
344
350
  Array.from(grid.tableView.viewers).some((v) => v.type === 'Sequence Position Statistics');
345
351
 
346
352
  const isMSA = sh.isMsa();
347
- const ifNan = (a: number, els: number) => (Number.isNaN(a) ? els : a);
348
- const getStart = () => ifNan(Math.max(Number.parseInt(seqCol.getTag(bioTAGS.positionShift) ?? '0'), 0), 0) + 1;
349
- const getCurrent = () => ifNan(Number.parseInt(seqCol.getTag(bioTAGS.selectedPosition) ?? '-2'), -2);
350
- const getFontSize = () => MonomerPlacer.getFontSettings(seqCol).fontWidth;
351
-
352
- // Get maximum sequence length. since this scroller is only applicable to Single character monomeric sequences,
353
- // we do not need to check every single sequence and split it, instead, max length will coorelate with length of the longest string
354
- let pseudoMaxLenIndex = 0;
355
- let pseudoMaxLength = 0;
356
- const cats = seqCol.categories;
357
- for (let i = 0; i < cats.length; i++) {
358
- const seq = cats[i];
359
- if (seq && seq.length > pseudoMaxLength) {
360
- pseudoMaxLength = seq.length;
361
- pseudoMaxLenIndex = i;
362
- }
363
- }
364
- const seq = cats[pseudoMaxLenIndex];
365
- const split = sh.splitter(seq);
366
- const maxSeqLen = split ? split.length : 30;
367
-
368
- // Do not Skip if sequences are too short, rather, just don't render the tracks by default
369
-
370
- const STRICT_THRESHOLDS = {
371
- WITH_TITLE: 58, // BASE + TITLE_HEIGHT(16) + TRACK_GAP(4)
372
- WITH_WEBLOGO: 107, // WITH_TITLE + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
373
- WITH_BOTH: 156 // WITH_WEBLOGO + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
374
- };
375
-
376
- let initialHeaderHeight: number;
377
- if (seqCol.length > 100_000 || maxSeqLen < 50) {
378
- // Single sequence: just dotted cells
379
- initialHeaderHeight = STRICT_THRESHOLDS.WITH_TITLE;
380
- } else {
381
- if (seqCol.length > 50_000)
382
- initialHeaderHeight = STRICT_THRESHOLDS.WITH_WEBLOGO;
383
- else
384
- initialHeaderHeight = STRICT_THRESHOLDS.WITH_BOTH;
385
- }
386
353
 
387
- let webLogoTrackRef: LazyWebLogoTrack | null = null;
388
- let conservationTrackRef: LazyConservationTrack | null = null;
389
- const filterChangeSub = DG.debounce(
390
- RxJs.merge(df.onFilterChanged, df.onDataChanged.pipe(filter((a) => a?.args?.column === seqCol))), 100
391
- ).subscribe(() => {
392
- MSAViewportManager.clearAllCaches();
393
-
394
- if (webLogoTrackRef) {
395
- webLogoTrackRef.resetViewportTracking();
396
- webLogoTrackRef.forceUpdate();
397
- }
398
- if (conservationTrackRef) {
399
- conservationTrackRef.resetViewportTracking();
400
- conservationTrackRef.forceUpdate();
354
+ if (isMSA) {
355
+ const ifNan = (a: number, els: number) => (Number.isNaN(a) ? els : a);
356
+ const getStart = () => ifNan(Math.max(Number.parseInt(seqCol.getTag(bioTAGS.positionShift) ?? '0'), 0), 0) + 1;
357
+ const getCurrent = () => ifNan(Number.parseInt(seqCol.getTag(bioTAGS.selectedPosition) ?? '-2'), -2);
358
+ const getFontSize = () => MonomerPlacer.getFontSettings(seqCol).fontWidth;
359
+
360
+ // Get maximum sequence length. since this scroller is only applicable to Single character monomeric sequences,
361
+ // we do not need to check every single sequence and split it, instead, max length will coorelate with length of the longest string
362
+ let pseudoMaxLenIndex = 0;
363
+ let pseudoMaxLength = 0;
364
+ const cats = seqCol.categories;
365
+ for (let i = 0; i < cats.length; i++) {
366
+ const seq = cats[i];
367
+ if (seq && seq.length > pseudoMaxLength) {
368
+ pseudoMaxLength = seq.length;
369
+ pseudoMaxLenIndex = i;
370
+ }
401
371
  }
402
- setTimeout(() => {
403
- if (!grid.isDetached)
404
- grid.invalidate();
405
- }, 50);
406
- });
407
-
408
- grid.sub(filterChangeSub);
409
-
410
- const initializeHeaders = (monomerLib: IMonomerLib) => {
411
- const tracks: { id: string, track: MSAHeaderTrack, priority: number }[] = [];
412
-
413
- // Create lazy tracks only if we have multiple sequences
414
-
415
- // OPTIMIZED: Pass seqHandler directly instead of column/splitter
416
- const conservationTrack = new LazyConservationTrack(
417
- sh,
418
- maxSeqLen,
419
- 45, // DEFAULT_TRACK_HEIGHT
420
- 'default',
421
- 'Conservation'
422
- );
423
- conservationTrackRef = conservationTrack; // Store reference
424
- tracks.push({id: 'conservation', track: conservationTrack, priority: 1});
425
-
426
- // OPTIMIZED: Pass seqHandler directly
427
- const webLogoTrack = new LazyWebLogoTrack(
428
- sh,
429
- maxSeqLen,
430
- 45, // DEFAULT_TRACK_HEIGHT
431
- 'WebLogo'
432
- );
433
- webLogoTrackRef = webLogoTrack; // Store reference
434
-
435
- if (monomerLib) {
436
- webLogoTrack.setMonomerLib(monomerLib);
437
- webLogoTrack.setBiotype(sh.defaultBiotype || 'HELM_AA');
372
+ const seq = cats[pseudoMaxLenIndex];
373
+ const split = sh.splitter(seq);
374
+ const maxSeqLen = split ? split.length : 30;
375
+
376
+ // Do not Skip if sequences are too short, rather, just don't render the tracks by default
377
+
378
+ const STRICT_THRESHOLDS = {
379
+ WITH_TITLE: 58, // BASE + TITLE_HEIGHT(16) + TRACK_GAP(4)
380
+ WITH_WEBLOGO: 107, // WITH_TITLE + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
381
+ WITH_BOTH: 156 // WITH_WEBLOGO + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
382
+ };
383
+
384
+ let initialHeaderHeight: number;
385
+ if (seqCol.length > 100_000 || maxSeqLen < 50) {
386
+ // Single sequence: just dotted cells
387
+ initialHeaderHeight = STRICT_THRESHOLDS.WITH_TITLE;
388
+ } else {
389
+ if (seqCol.length > 50_000)
390
+ initialHeaderHeight = STRICT_THRESHOLDS.WITH_WEBLOGO;
391
+ else
392
+ initialHeaderHeight = STRICT_THRESHOLDS.WITH_BOTH;
438
393
  }
439
394
 
440
- webLogoTrack.setupDefaultTooltip();
441
- tracks.push({id: 'weblogo', track: webLogoTrack, priority: 2});
395
+ let webLogoTrackRef: LazyWebLogoTrack | null = null;
396
+ let conservationTrackRef: LazyConservationTrack | null = null;
397
+ const filterChangeSub = DG.debounce(
398
+ RxJs.merge(df.onFilterChanged, df.onDataChanged.pipe(filter((a) => a?.args?.column === seqCol))), 100
399
+ ).subscribe(() => {
400
+ MSAViewportManager.clearAllCaches();
401
+
402
+ if (webLogoTrackRef) {
403
+ webLogoTrackRef.resetViewportTracking();
404
+ webLogoTrackRef.forceUpdate();
405
+ }
406
+ if (conservationTrackRef) {
407
+ conservationTrackRef.resetViewportTracking();
408
+ conservationTrackRef.forceUpdate();
409
+ }
410
+ setTimeout(() => {
411
+ if (!grid.isDetached)
412
+ grid.invalidate();
413
+ }, 50);
414
+ });
442
415
 
416
+ grid.sub(filterChangeSub);
443
417
 
444
- // Create the scrolling header
445
- const scroller = new MSAScrollingHeader({
446
- canvas: grid.overlay,
447
- headerHeight: initialHeaderHeight,
448
- totalPositions: maxSeqLen + 1,
449
- onPositionChange: (scrollerCur, scrollerRange) => {
450
- setTimeout(() => {
451
- const start = getStart();
452
- const cur = getCurrent();
453
- if (start !== scrollerRange.start)
454
- seqCol.setTag(bioTAGS.positionShift, (scrollerRange.start - 1).toString());
455
-
456
- if (cur !== scrollerCur) {
457
- seqCol.setTag(bioTAGS.selectedPosition, (scrollerCur).toString());
458
- if (scrollerCur >= 0 && !positionStatsViewerAddedOnce && grid.tableView) {
459
- positionStatsViewerAddedOnce = true;
460
- const v = grid.tableView.addViewer('Sequence Position Statistics', {sequenceColumnName: seqCol.name});
461
- grid.tableView.dockManager.dock(v, DG.DOCK_TYPE.DOWN, null, 'Sequence Position Statistics', 0.4);
462
- }
463
- }
464
- });
465
- },
466
- onHeaderHeightChange: (_newHeight) => {
467
- // Update grid header height
468
- if (grid.isDetached || _newHeight < STRICT_THRESHOLDS.WITH_TITLE) return;
469
- setTimeout(() => grid.props.colHeaderHeight = _newHeight);
470
- },
471
- });
418
+ const initializeHeaders = (monomerLib: IMonomerLib) => {
419
+ const tracks: { id: string, track: MSAHeaderTrack, priority: number }[] = [];
472
420
 
473
- scroller.setupTooltipHandling();
421
+ // Create lazy tracks only for MSA sequences
474
422
 
475
- // Add tracks to scroller
476
- tracks.forEach(({id, track}) => {
477
- scroller.addTrack(id, track);
478
- });
423
+ // OPTIMIZED: Pass seqHandler directly instead of column/splitter
424
+ const conservationTrack = new LazyConservationTrack(
425
+ sh,
426
+ maxSeqLen,
427
+ 45, // DEFAULT_TRACK_HEIGHT
428
+ 'default',
429
+ 'Conservation'
430
+ );
431
+ conservationTrackRef = conservationTrack; // Store reference
432
+ tracks.push({id: 'conservation', track: conservationTrack, priority: 1});
433
+
434
+ // OPTIMIZED: Pass seqHandler directly
435
+ const webLogoTrack = new LazyWebLogoTrack(
436
+ sh,
437
+ maxSeqLen,
438
+ 45, // DEFAULT_TRACK_HEIGHT
439
+ 'WebLogo'
440
+ );
441
+ webLogoTrackRef = webLogoTrack; // Store reference
442
+
443
+ if (monomerLib) {
444
+ webLogoTrack.setMonomerLib(monomerLib);
445
+ webLogoTrack.setBiotype(sh.defaultBiotype || 'HELM_AA');
446
+ }
447
+
448
+ webLogoTrack.setupDefaultTooltip();
449
+ tracks.push({id: 'weblogo', track: webLogoTrack, priority: 2});
450
+
451
+ // Create the scrolling header
452
+ const scroller = new MSAScrollingHeader({
453
+ canvas: grid.overlay,
454
+ headerHeight: initialHeaderHeight,
455
+ totalPositions: maxSeqLen + 1,
456
+ onPositionChange: (scrollerCur, scrollerRange) => {
457
+ setTimeout(() => {
458
+ const start = getStart();
459
+ const cur = getCurrent();
460
+ if (start !== scrollerRange.start)
461
+ seqCol.setTag(bioTAGS.positionShift, (scrollerRange.start - 1).toString());
462
+
463
+ if (cur !== scrollerCur) {
464
+ seqCol.setTag(bioTAGS.selectedPosition, (scrollerCur).toString());
465
+ if (scrollerCur >= 0 && !positionStatsViewerAddedOnce && grid.tableView) {
466
+ positionStatsViewerAddedOnce = true;
467
+ const v = grid.tableView.addViewer('Sequence Position Statistics', {sequenceColumnName: seqCol.name});
468
+ grid.tableView.dockManager.dock(v, DG.DOCK_TYPE.DOWN, null, 'Sequence Position Statistics', 0.4);
469
+ }
470
+ }
471
+ });
472
+ },
473
+ onHeaderHeightChange: (_newHeight) => {
474
+ // Update grid header height
475
+ if (grid.isDetached || _newHeight < STRICT_THRESHOLDS.WITH_TITLE) return;
476
+ setTimeout(() => grid.props.colHeaderHeight = _newHeight);
477
+ },
478
+ });
479
479
 
480
- scroller.setSelectionData(df, seqCol, sh);
480
+ scroller.setupTooltipHandling();
481
481
 
482
- if (maxSeqLen > 50) {
483
- grid.props.colHeaderHeight = initialHeaderHeight;
482
+ // Add tracks to scroller
483
+ tracks.forEach(({id, track}) => {
484
+ scroller.addTrack(id, track);
485
+ });
484
486
 
485
- // Set column width
486
- setTimeout(() => {
487
- if (grid.isDetached) return;
488
- gCol.width = 400;
489
- }, 300);
490
- }
487
+ scroller.setSelectionData(df, seqCol, sh);
491
488
 
492
- // Handle cell rendering
493
- grid.sub(grid.onCellRender.subscribe((e) => {
494
- const cell = e.cell;
495
- if (!cell || !cell.isColHeader || cell?.gridColumn?.name !== gCol?.name)
496
- return;
497
-
498
-
499
- const cellBounds = e.bounds;
500
- if (!cellBounds) return;
501
-
502
- // Set dynamic properties
503
- scroller.headerHeight = cellBounds.height;
504
- const font = getFontSize();
505
- scroller.positionWidth = font + (isMSA ? 8 : 0);
506
-
507
- const start = getStart();
508
- const startPadding = isMSA ? 0 : 4;
509
-
510
- scroller.draw(
511
- cellBounds.x + startPadding,
512
- cellBounds.y,
513
- cellBounds.width - startPadding,
514
- cellBounds.height,
515
- getCurrent(),
516
- start,
517
- e,
518
- seqCol.name
519
- );
520
- }));
521
- };
522
-
523
- // Initialize with monomer library
524
- getMonomerLibHelper()
525
- .then((libHelper) => {
526
- const monomerLib = libHelper.getMonomerLib();
527
- initializeHeaders(monomerLib);
528
- })
529
- .catch((error) => {
530
- grok.shell.warning(`Failed to initialize monomer library`);
531
- //initializeHeaders();
532
- console.error('Failed to initialize monomer library:', error);
533
- });
489
+ if (maxSeqLen > 50) {
490
+ grid.props.colHeaderHeight = initialHeaderHeight;
491
+
492
+ // Set column width
493
+ setTimeout(() => {
494
+ if (grid.isDetached) return;
495
+ gCol.width = 400;
496
+ }, 300);
497
+ }
498
+
499
+ // Handle cell rendering for MSA
500
+ grid.sub(grid.onCellRender.subscribe((e) => {
501
+ const cell = e.cell;
502
+ if (!cell || !cell.isColHeader || cell?.gridColumn?.name !== gCol?.name)
503
+ return;
504
+
505
+ const cellBounds = e.bounds;
506
+ if (!cellBounds) return;
507
+
508
+ // Set dynamic properties
509
+ scroller.headerHeight = cellBounds.height;
510
+ const font = getFontSize();
511
+ scroller.positionWidth = font + 8; // MSA always has padding
512
+
513
+ const start = getStart();
514
+ const startPadding = 0; // MSA doesn't need extra padding
515
+
516
+ scroller.draw(
517
+ cellBounds.x + startPadding,
518
+ cellBounds.y,
519
+ cellBounds.width - startPadding,
520
+ cellBounds.height,
521
+ getCurrent(),
522
+ start,
523
+ e,
524
+ seqCol.name
525
+ );
526
+ }));
527
+ };
528
+
529
+ // Initialize with monomer library for MSA sequences
530
+ getMonomerLibHelper()
531
+ .then((libHelper) => {
532
+ const monomerLib = libHelper.getMonomerLib();
533
+ initializeHeaders(monomerLib);
534
+ })
535
+ .catch((error) => {
536
+ grok.shell.warning(`Failed to initialize monomer library`);
537
+ console.error('Failed to initialize monomer library:', error);
538
+ });
539
+ } else {
540
+ // For non-MSA sequences, just use standard sequence rendering.
541
+ }
534
542
  }
535
543
  }, 1000);
536
544
  };
@@ -1,37 +1,37 @@
1
1
  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
+ import * as OCL from 'openchemlib/full';
4
5
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
5
6
  import {_package, getSeqHelper, toAtomicLevel} from '../package';
6
7
 
7
8
 
8
- export async function toAtomicLevelWidget(sequence: DG.SemanticValue): Promise<DG.Widget> {
9
- const errorDiv = ui.divText('');
10
- const errorWidget = DG.Widget.fromRoot(errorDiv);
9
+ export async function toAtomicLevelSingle(sequence: DG.SemanticValue): Promise<{mol: string, errorText: string}> {
10
+ let errorText = '';
11
11
  try {
12
12
  if (!sequence || !sequence.value) {
13
- errorDiv.innerText = 'No sequence provided';
14
- return errorWidget;
13
+ errorText = 'No sequence provided';
14
+ return {errorText, mol: ''};
15
15
  }
16
16
  if (!sequence.cell || !sequence.cell.dart || !sequence.cell.dataFrame || !sequence.cell.column) {
17
- errorDiv.innerText = 'Atomic level conversion requeires a sequence column';
18
- return errorWidget;
17
+ errorText = 'Atomic level conversion requeires a sequence column';
18
+ return {errorText, mol: ''};
19
19
  }
20
20
  const supportedUnits: string[] = [NOTATION.FASTA, NOTATION.SEPARATOR, NOTATION.HELM];
21
21
  //todo: add support for custom notations
22
22
  if (!supportedUnits.includes(sequence.cell.column.meta.units?.toLowerCase() ?? '')) {
23
- errorDiv.innerText = 'Unsupported sequence notation. please use Bio | Polytool | Convert';
24
- return errorWidget;
23
+ errorText = 'Unsupported sequence notation. please use Bio | Polytool | Convert';
24
+ return {errorText, mol: ''};
25
25
  }
26
26
  const seqHelper = await getSeqHelper();
27
27
  const seqSh = seqHelper.getSeqHandler(sequence.cell.column);
28
28
  if (!seqSh) {
29
- errorDiv.innerText = 'No sequence handler found';
30
- return errorWidget;
29
+ errorText = 'No sequence handler found';
30
+ return {errorText, mol: ''};
31
31
  }
32
32
  if ((seqSh.getSplitted(sequence.cell.rowIndex, 50)?.length ?? 100) > 40) {
33
- errorDiv.innerText = 'Maximum number of monomers is 40';
34
- return errorWidget;
33
+ errorText = 'Maximum number of monomers is 40';
34
+ return {errorText, mol: ''};
35
35
  }
36
36
  const singleValCol = DG.Column.fromStrings('singleVal', [sequence.value]);
37
37
  const sDf = DG.DataFrame.fromColumns([singleValCol]);
@@ -41,30 +41,43 @@ export async function toAtomicLevelWidget(sequence: DG.SemanticValue): Promise<D
41
41
  });
42
42
  await toAtomicLevel(sDf, singleValCol, sequence.cell.column.meta.units === NOTATION.HELM, false);
43
43
  if (sDf.columns.length < 2) {
44
- errorDiv.innerText = 'No structure generated';
45
- return errorWidget;
44
+ errorText = 'No structure generated';
45
+ return {errorText, mol: ''};
46
46
  }
47
47
  const molCol = sDf.columns.byIndex(1);
48
48
  const molfile = molCol.get(0);
49
49
  if (!molfile) {
50
- errorDiv.innerText = 'No structure generated';
51
- return errorWidget;
50
+ errorText = 'No structure generated';
51
+ return {errorText, mol: ''};
52
52
  }
53
- molCol.semType = DG.SEMTYPE.MOLECULE;
54
- const molSemanticValue = DG.SemanticValue.fromTableCell(sDf.cell(0, molCol.name));
53
+ return {errorText: '', mol: molfile as string};
54
+ } catch (e) {
55
+ _package.logger.error(e);
56
+ }
57
+
58
+ errorText = 'No Structure generated';
59
+ return {errorText, mol: ''};
60
+ }
61
+
62
+ export async function toAtomicLevelWidget(sequence: DG.SemanticValue): Promise<DG.Widget> {
63
+ const res = await toAtomicLevelSingle(sequence);
64
+ if (res.errorText || !res.mol)
65
+ return DG.Widget.fromRoot(ui.divText(res.errorText ?? 'No structure generated'));
66
+ try {
67
+ const molSemanticValue = DG.SemanticValue.fromValueType(res.mol, DG.SEMTYPE.MOLECULE);
55
68
  const panel = ui.panels.infoPanel(molSemanticValue);
56
69
  let molPanel: DG.Widget | null = null;
57
70
  if (panel)
58
71
  molPanel = DG.Widget.fromRoot(panel.root);
59
72
 
60
73
 
61
- const root = grok.chem.drawMolecule(molfile, 300, 300, false);
74
+ const root = grok.chem.drawMolecule(res.mol, 300, 300, false);
62
75
  root.style.cursor = 'pointer';
63
76
  ui.tooltip.bind(root, 'Click to expand');
64
77
  root.onclick = () => {
65
78
  const width = window.innerWidth - 200;
66
79
  const height = window.innerHeight - 200;
67
- const bigMol = grok.chem.drawMolecule(molfile, width, height, false);
80
+ const bigMol = grok.chem.drawMolecule(res.mol, width, height, false);
68
81
  ui.dialog({title: 'Molecule'}).add(bigMol).showModal(true);
69
82
  };
70
83
  if (molPanel)
@@ -73,7 +86,65 @@ export async function toAtomicLevelWidget(sequence: DG.SemanticValue): Promise<D
73
86
  } catch (e) {
74
87
  _package.logger.error(e);
75
88
  }
89
+ return DG.Widget.fromRoot(ui.divText('No structure generated'));
90
+ }
91
+
92
+ /**
93
+ * 3D representation widget of macromolecule.
94
+ *
95
+ * @export
96
+ * @return {Promise<DG.Widget>} Widget.
97
+ */
98
+ export async function molecular3DStructureWidget(
99
+ sequence: DG.SemanticValue
100
+ ): Promise<DG.Widget> {
101
+ const pi = DG.TaskBarProgressIndicator.create('Creating 3D view');
102
+ let widgetHost;
103
+ let molBlock3D = '';
104
+ try {
105
+ // make sure biostructure viewer package is loaded.
106
+ await DG.Func.find({name: 'getPdbHelper'})[0]?.apply({});
107
+ try {
108
+ const result = await toAtomicLevelSingle(sequence);//await getMacroMol(atomicCodes!);
109
+ if (result.errorText || !result.mol) {
110
+ widgetHost = ui.divText(result.errorText ?? 'No structure generated');
111
+ pi.close();
112
+ return new DG.Widget(widgetHost);
113
+ }
114
+ const molBlock2D = result.mol;
115
+ molBlock3D = (await grok.functions.call('Bio:Embed', {molecule: molBlock2D})) as unknown as string;
116
+ // rdfkit sometimes fails to convert molv3 to molv2, so we try to convert it via the OCL
117
+ const OCLMol = OCL.Molecule.fromMolfile(molBlock3D);
118
+ if (OCLMol)
119
+ molBlock3D = OCLMol.toMolfile();
120
+ else
121
+ console.warn('Failed to convert molv3 to molv2');
122
+
123
+ //molBlock3D = grok.chem.convert(molBlock3D, grok.chem.Notation.Unknown, grok.chem.Notation.MolBlock);
124
+ } catch (e) {
125
+ console.warn(e);
126
+ }
76
127
 
77
- errorDiv.innerText = 'No Structure generated';
78
- return errorWidget;
128
+ try {
129
+ molBlock3D = molBlock3D.replaceAll('\\n', '\n');
130
+ const stringBlob = new Blob([molBlock3D], {type: 'text/plain'});
131
+ const nglHost = ui.div([], {classes: 'd4-ngl-viewer', id: 'ngl-3d-host'});
132
+ nglHost.style.setProperty('height', '100%', 'important');
133
+ //@ts-ignore
134
+ const stage = new NGL.Stage(nglHost, {backgroundColor: 'white'});
135
+ //@ts-ignore
136
+ stage.loadFile(stringBlob, {ext: 'sdf'}).then(function(comp: NGL.StructureComponent) {
137
+ stage.setSize(300, 300);
138
+ comp.addRepresentation('ball+stick');
139
+ comp.autoView();
140
+ });
141
+ widgetHost = ui.div([nglHost], {style: {aspectRatio: '1'}});
142
+ } catch (e) {
143
+ widgetHost = ui.divText('Couldn\'t get 3D structure');
144
+ }
145
+ } catch (e) {
146
+ widgetHost = ui.divText('Couldn\'t get 3D structure');
147
+ }
148
+ pi.close();
149
+ return new DG.Widget(widgetHost);
79
150
  }