@datagrok/bio 2.22.1 → 2.22.4
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 +55 -0
- 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 +10 -10
- package/scripts/embed.py +9 -3
- package/src/analysis/sequence-diversity-viewer.ts +85 -10
- 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 +24 -9
- package/src/tests/activity-cliffs-utils.ts +3 -2
- 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/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 +185 -176
- package/src/widgets/to-atomic-level-widget.ts +94 -23
- package/test-console-output-1.log +626 -627
- package/test-record-1.mp4 +0 -0
|
@@ -15,6 +15,7 @@ import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-h
|
|
|
15
15
|
import * as RxJs from 'rxjs';
|
|
16
16
|
import {filter} from 'rxjs/operators';
|
|
17
17
|
import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
|
|
18
|
+
import wu from 'wu';
|
|
18
19
|
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// OPTIMIZED VIEWPORT-AWARE CACHING WITH FORCE UPDATE SUPPORT
|
|
@@ -333,7 +334,13 @@ export function handleSequenceHeaderRendering() {
|
|
|
333
334
|
const seqCols = df.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
|
|
334
335
|
|
|
335
336
|
for (const seqCol of seqCols) {
|
|
336
|
-
|
|
337
|
+
let sh: ISeqHandler | null = null;
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
sh = _package.seqHelper.getSeqHandler(seqCol);
|
|
341
|
+
} catch (_e) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
337
344
|
if (!sh) continue;
|
|
338
345
|
if (sh.isHelm() || sh.alphabet === ALPHABET.UN) continue;
|
|
339
346
|
|
|
@@ -344,193 +351,195 @@ export function handleSequenceHeaderRendering() {
|
|
|
344
351
|
Array.from(grid.tableView.viewers).some((v) => v.type === 'Sequence Position Statistics');
|
|
345
352
|
|
|
346
353
|
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
354
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
355
|
+
if (isMSA) {
|
|
356
|
+
const ifNan = (a: number, els: number) => (Number.isNaN(a) ? els : a);
|
|
357
|
+
const getStart = () => ifNan(Math.max(Number.parseInt(seqCol.getTag(bioTAGS.positionShift) ?? '0'), 0), 0) + 1;
|
|
358
|
+
const getCurrent = () => ifNan(Number.parseInt(seqCol.getTag(bioTAGS.selectedPosition) ?? '-2'), -2);
|
|
359
|
+
const getFontSize = () => MonomerPlacer.getFontSettings(seqCol).fontWidth;
|
|
360
|
+
|
|
361
|
+
// Get maximum sequence length. since this scroller is only applicable to Single character monomeric sequences,
|
|
362
|
+
// we do not need to check every single sequence and split it, instead, max length will coorelate with length of the longest string
|
|
363
|
+
let pseudoMaxLenIndex = 0;
|
|
364
|
+
let pseudoMaxLength = 0;
|
|
365
|
+
const cats = seqCol.categories;
|
|
366
|
+
for (let i = 0; i < cats.length; i++) {
|
|
367
|
+
const seq = cats[i];
|
|
368
|
+
if (seq && seq.length > pseudoMaxLength) {
|
|
369
|
+
pseudoMaxLength = seq.length;
|
|
370
|
+
pseudoMaxLenIndex = i;
|
|
371
|
+
}
|
|
401
372
|
}
|
|
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');
|
|
373
|
+
const seq = cats[pseudoMaxLenIndex];
|
|
374
|
+
const split = sh.splitter(seq);
|
|
375
|
+
const maxSeqLen = split ? split.length : 30;
|
|
376
|
+
|
|
377
|
+
// Do not Skip if sequences are too short, rather, just don't render the tracks by default
|
|
378
|
+
|
|
379
|
+
const STRICT_THRESHOLDS = {
|
|
380
|
+
WITH_TITLE: 58, // BASE + TITLE_HEIGHT(16) + TRACK_GAP(4)
|
|
381
|
+
WITH_WEBLOGO: 107, // WITH_TITLE + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
|
|
382
|
+
WITH_BOTH: 156 // WITH_WEBLOGO + DEFAULT_TRACK_HEIGHT(45) + TRACK_GAP(4)
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
let initialHeaderHeight: number;
|
|
386
|
+
if (seqCol.length > 100_000 || maxSeqLen < 50) {
|
|
387
|
+
// Single sequence: just dotted cells
|
|
388
|
+
initialHeaderHeight = STRICT_THRESHOLDS.WITH_TITLE;
|
|
389
|
+
} else {
|
|
390
|
+
if (seqCol.length > 50_000)
|
|
391
|
+
initialHeaderHeight = STRICT_THRESHOLDS.WITH_WEBLOGO;
|
|
392
|
+
else
|
|
393
|
+
initialHeaderHeight = STRICT_THRESHOLDS.WITH_BOTH;
|
|
438
394
|
}
|
|
439
395
|
|
|
440
|
-
|
|
441
|
-
|
|
396
|
+
let webLogoTrackRef: LazyWebLogoTrack | null = null;
|
|
397
|
+
let conservationTrackRef: LazyConservationTrack | null = null;
|
|
398
|
+
const filterChangeSub = DG.debounce(
|
|
399
|
+
RxJs.merge(df.onFilterChanged, df.onDataChanged.pipe(filter((a) => a?.args?.column === seqCol))), 100
|
|
400
|
+
).subscribe(() => {
|
|
401
|
+
MSAViewportManager.clearAllCaches();
|
|
402
|
+
|
|
403
|
+
if (webLogoTrackRef) {
|
|
404
|
+
webLogoTrackRef.resetViewportTracking();
|
|
405
|
+
webLogoTrackRef.forceUpdate();
|
|
406
|
+
}
|
|
407
|
+
if (conservationTrackRef) {
|
|
408
|
+
conservationTrackRef.resetViewportTracking();
|
|
409
|
+
conservationTrackRef.forceUpdate();
|
|
410
|
+
}
|
|
411
|
+
setTimeout(() => {
|
|
412
|
+
if (!grid.isDetached)
|
|
413
|
+
grid.invalidate();
|
|
414
|
+
}, 50);
|
|
415
|
+
});
|
|
442
416
|
|
|
417
|
+
grid.sub(filterChangeSub);
|
|
443
418
|
|
|
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
|
-
});
|
|
419
|
+
const initializeHeaders = (monomerLib: IMonomerLib) => {
|
|
420
|
+
const tracks: { id: string, track: MSAHeaderTrack, priority: number }[] = [];
|
|
472
421
|
|
|
473
|
-
|
|
422
|
+
// Create lazy tracks only for MSA sequences
|
|
474
423
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
424
|
+
// OPTIMIZED: Pass seqHandler directly instead of column/splitter
|
|
425
|
+
const conservationTrack = new LazyConservationTrack(
|
|
426
|
+
sh,
|
|
427
|
+
maxSeqLen,
|
|
428
|
+
45, // DEFAULT_TRACK_HEIGHT
|
|
429
|
+
'default',
|
|
430
|
+
'Conservation'
|
|
431
|
+
);
|
|
432
|
+
conservationTrackRef = conservationTrack; // Store reference
|
|
433
|
+
tracks.push({id: 'conservation', track: conservationTrack, priority: 1});
|
|
434
|
+
|
|
435
|
+
// OPTIMIZED: Pass seqHandler directly
|
|
436
|
+
const webLogoTrack = new LazyWebLogoTrack(
|
|
437
|
+
sh,
|
|
438
|
+
maxSeqLen,
|
|
439
|
+
45, // DEFAULT_TRACK_HEIGHT
|
|
440
|
+
'WebLogo'
|
|
441
|
+
);
|
|
442
|
+
webLogoTrackRef = webLogoTrack; // Store reference
|
|
443
|
+
|
|
444
|
+
if (monomerLib) {
|
|
445
|
+
webLogoTrack.setMonomerLib(monomerLib);
|
|
446
|
+
webLogoTrack.setBiotype(sh.defaultBiotype || 'HELM_AA');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
webLogoTrack.setupDefaultTooltip();
|
|
450
|
+
tracks.push({id: 'weblogo', track: webLogoTrack, priority: 2});
|
|
451
|
+
|
|
452
|
+
// Create the scrolling header
|
|
453
|
+
const scroller = new MSAScrollingHeader({
|
|
454
|
+
canvas: grid.overlay,
|
|
455
|
+
headerHeight: initialHeaderHeight,
|
|
456
|
+
totalPositions: maxSeqLen + 1,
|
|
457
|
+
onPositionChange: (scrollerCur, scrollerRange) => {
|
|
458
|
+
setTimeout(() => {
|
|
459
|
+
const start = getStart();
|
|
460
|
+
const cur = getCurrent();
|
|
461
|
+
if (start !== scrollerRange.start)
|
|
462
|
+
seqCol.setTag(bioTAGS.positionShift, (scrollerRange.start - 1).toString());
|
|
463
|
+
|
|
464
|
+
if (cur !== scrollerCur) {
|
|
465
|
+
seqCol.setTag(bioTAGS.selectedPosition, (scrollerCur).toString());
|
|
466
|
+
if (scrollerCur >= 0 && !positionStatsViewerAddedOnce && grid.tableView && wu(grid.dataFrame?.columns.numerical).find((_c) => true)) {
|
|
467
|
+
positionStatsViewerAddedOnce = true;
|
|
468
|
+
const v = grid.tableView.addViewer('Sequence Position Statistics', {sequenceColumnName: seqCol.name});
|
|
469
|
+
grid.tableView.dockManager.dock(v, DG.DOCK_TYPE.DOWN, null, 'Sequence Position Statistics', 0.4);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
onHeaderHeightChange: (_newHeight) => {
|
|
475
|
+
// Update grid header height
|
|
476
|
+
if (grid.isDetached || _newHeight < STRICT_THRESHOLDS.WITH_TITLE) return;
|
|
477
|
+
setTimeout(() => grid.props.colHeaderHeight = _newHeight);
|
|
478
|
+
},
|
|
479
|
+
}, gCol);
|
|
479
480
|
|
|
480
|
-
|
|
481
|
+
scroller.setupTooltipHandling();
|
|
481
482
|
|
|
482
|
-
|
|
483
|
-
|
|
483
|
+
// Add tracks to scroller
|
|
484
|
+
tracks.forEach(({id, track}) => {
|
|
485
|
+
scroller.addTrack(id, track);
|
|
486
|
+
});
|
|
484
487
|
|
|
485
|
-
|
|
486
|
-
setTimeout(() => {
|
|
487
|
-
if (grid.isDetached) return;
|
|
488
|
-
gCol.width = 400;
|
|
489
|
-
}, 300);
|
|
490
|
-
}
|
|
488
|
+
scroller.setSelectionData(df, seqCol, sh);
|
|
491
489
|
|
|
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
|
-
|
|
490
|
+
if (maxSeqLen > 50) {
|
|
491
|
+
grid.props.colHeaderHeight = initialHeaderHeight;
|
|
492
|
+
|
|
493
|
+
// Set column width
|
|
494
|
+
setTimeout(() => {
|
|
495
|
+
if (grid.isDetached) return;
|
|
496
|
+
gCol.width = 400;
|
|
497
|
+
}, 300);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Handle cell rendering for MSA
|
|
501
|
+
grid.sub(grid.onCellRender.subscribe((e) => {
|
|
502
|
+
const cell = e.cell;
|
|
503
|
+
if (!cell || !cell.isColHeader || cell?.gridColumn?.name !== gCol?.name)
|
|
504
|
+
return;
|
|
505
|
+
|
|
506
|
+
const cellBounds = e.bounds;
|
|
507
|
+
if (!cellBounds) return;
|
|
508
|
+
|
|
509
|
+
// Set dynamic properties
|
|
510
|
+
scroller.headerHeight = cellBounds.height;
|
|
511
|
+
const font = getFontSize();
|
|
512
|
+
scroller.positionWidth = font + 8; // MSA always has padding
|
|
513
|
+
|
|
514
|
+
const start = getStart();
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
scroller.draw(
|
|
518
|
+
cellBounds.x,
|
|
519
|
+
cellBounds.y,
|
|
520
|
+
cellBounds.width,
|
|
521
|
+
cellBounds.height,
|
|
522
|
+
getCurrent(),
|
|
523
|
+
start,
|
|
524
|
+
e,
|
|
525
|
+
seqCol.name
|
|
526
|
+
);
|
|
527
|
+
}));
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// Initialize with monomer library for MSA sequences
|
|
531
|
+
getMonomerLibHelper()
|
|
532
|
+
.then((libHelper) => {
|
|
533
|
+
const monomerLib = libHelper.getMonomerLib();
|
|
534
|
+
initializeHeaders(monomerLib);
|
|
535
|
+
})
|
|
536
|
+
.catch((error) => {
|
|
537
|
+
grok.shell.warning(`Failed to initialize monomer library`);
|
|
538
|
+
console.error('Failed to initialize monomer library:', error);
|
|
539
|
+
});
|
|
540
|
+
} else {
|
|
541
|
+
// For non-MSA sequences, just use standard sequence rendering.
|
|
542
|
+
}
|
|
534
543
|
}
|
|
535
544
|
}, 1000);
|
|
536
545
|
};
|
|
@@ -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
|
}
|