@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.
- package/CHANGELOG.md +5 -0
- package/detectors.js +56 -1
- package/dist/package-test.js +3 -3
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +3 -3
- package/dist/package.js.map +1 -1
- package/package.json +6 -6
- package/scripts/embed.py +9 -3
- package/src/analysis/sequence-diversity-viewer.ts +86 -24
- package/src/analysis/sequence-search-base-viewer.ts +40 -8
- package/src/analysis/sequence-similarity-viewer.ts +47 -7
- package/src/analysis/sequence-space.ts +24 -4
- package/src/demo/bio05-helm-msa-sequence-space.ts +1 -1
- package/src/package-api.ts +417 -0
- package/src/package.g.ts +1 -0
- package/src/package.ts +14 -5
- package/src/tests/msa-tests.ts +1 -1
- package/src/tests/pepsea-tests.ts +3 -3
- package/src/tests/similarity-diversity-tests.ts +5 -5
- package/src/utils/context-menu.ts +7 -6
- package/src/utils/helm-to-molfile/converter/mol-wrapper.ts +1 -1
- package/src/utils/monomer-lib/library-file-manager/ui.ts +9 -1
- package/src/utils/multiple-sequence-alignment-ui.ts +20 -9
- package/src/utils/multiple-sequence-alignment.ts +22 -7
- package/src/utils/pepsea.ts +34 -18
- package/src/widgets/representations.ts +31 -58
- package/src/widgets/sequence-scrolling-widget.ts +184 -176
- package/src/widgets/to-atomic-level-widget.ts +94 -23
- package/test-console-output-1.log +620 -621
- package/test-record-1.mp4 +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
445
|
-
|
|
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
|
-
|
|
421
|
+
// Create lazy tracks only for MSA sequences
|
|
474
422
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
480
|
+
scroller.setupTooltipHandling();
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
// Add tracks to scroller
|
|
483
|
+
tracks.forEach(({id, track}) => {
|
|
484
|
+
scroller.addTrack(id, track);
|
|
485
|
+
});
|
|
484
486
|
|
|
485
|
-
|
|
486
|
-
setTimeout(() => {
|
|
487
|
-
if (grid.isDetached) return;
|
|
488
|
-
gCol.width = 400;
|
|
489
|
-
}, 300);
|
|
490
|
-
}
|
|
487
|
+
scroller.setSelectionData(df, seqCol, sh);
|
|
491
488
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
cellBounds.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
start
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
return
|
|
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
|
-
|
|
18
|
-
return
|
|
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
|
-
|
|
24
|
-
return
|
|
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
|
-
|
|
30
|
-
return
|
|
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
|
-
|
|
34
|
-
return
|
|
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
|
-
|
|
45
|
-
return
|
|
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
|
-
|
|
51
|
-
return
|
|
50
|
+
errorText = 'No structure generated';
|
|
51
|
+
return {errorText, mol: ''};
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
78
|
-
|
|
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
|
}
|