@datagrok/bio 2.26.7 → 2.26.8
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/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +1 -1
- package/src/package.ts +1 -1
- package/src/utils/cell-renderer.ts +2 -2
- package/src/utils/monomer-cell-renderer.ts +1 -1
- package/src/widgets/monomer-info-widget.ts +90 -50
- package/test-console-output-1.log +567 -651
- package/test-record-1.mp4 +0 -0
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Davit Rizhinashvili",
|
|
6
6
|
"email": "drizhinashvili@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.26.
|
|
8
|
+
"version": "2.26.8",
|
|
9
9
|
"description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
package/src/package.ts
CHANGED
|
@@ -390,7 +390,7 @@ export class PackageFunctions {
|
|
|
390
390
|
})
|
|
391
391
|
static monomerInfoPanel(
|
|
392
392
|
@grok.decorators.param({options: {semType: 'Monomer'}}) monomerSv: DG.SemanticValue): DG.Widget {
|
|
393
|
-
return getMonomerInfoWidget(monomerSv
|
|
393
|
+
return getMonomerInfoWidget(monomerSv, _package.monomerLib);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
@grok.decorators.func({
|
|
@@ -225,8 +225,8 @@ export class MacromoleculeDifferenceCellRendererBack extends CellRendererWithMon
|
|
|
225
225
|
const splitter = this.tableCol.temp[SeqTemps.notationProvider]?.separatorSplitter ?? this.tableCol.temp[SeqTemps.notationProvider]?.splitter ?? getSplitter(units, separator);
|
|
226
226
|
const s1SS = splitter(s1);
|
|
227
227
|
const s2SS = splitter(s2);
|
|
228
|
-
const subParts1 = wu.count(0).take(s1SS.length).map((posIdx) => s1SS.
|
|
229
|
-
const subParts2 = wu.count(0).take(s2SS.length).map((posIdx) => s2SS.
|
|
228
|
+
const subParts1 = wu.count(0).take(s1SS.length).map((posIdx) => s1SS.getOriginal(posIdx)).toArray();
|
|
229
|
+
const subParts2 = wu.count(0).take(s2SS.length).map((posIdx) => s2SS.getOriginal(posIdx)).toArray();
|
|
230
230
|
const alphabet = this.tableCol.getTag(bioTAGS.alphabet);
|
|
231
231
|
const biotype = alphabet === ALPHABET.RNA || alphabet === ALPHABET.DNA ? HelmTypes.NUCLEOTIDE : HelmTypes.AA;
|
|
232
232
|
drawMoleculeDifferenceOnCanvas(g, x, y, w, h, subParts1, subParts2, biotype, this.monomerLib, undefined, undefined);
|
|
@@ -154,7 +154,7 @@ export class MonomerCellRendererBack extends CellRendererWithMonomerLibBackBase
|
|
|
154
154
|
const actBioType: HelmType = this.getHelmType(gridCell, biotype);
|
|
155
155
|
return this.monomerLib!.getTooltip(actBioType, s);
|
|
156
156
|
});
|
|
157
|
-
const tooltipEl = ui.divH(tooltipEls, {style: {alignItems: 'top'}});
|
|
157
|
+
const tooltipEl = ui.divH(tooltipEls, {style: {alignItems: 'top', flexWrap: 'wrap', gap: '4px'}});
|
|
158
158
|
// tooltip max width is 600px, so we need to shrink the canvases a bit if needed. by default, it is 250px
|
|
159
159
|
const canvases = Array.from(tooltipEl.querySelectorAll('canvas'));
|
|
160
160
|
if (canvases.length > 2) {
|
|
@@ -5,14 +5,11 @@ import * as DG from 'datagrok-api/dg';
|
|
|
5
5
|
|
|
6
6
|
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/monomer-library';
|
|
7
7
|
import {HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
8
|
+
import {MONOMER_MOTIF_SPLITTER, MONOMER_CANONICALIZER_TEMP} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
9
|
+
import {IMonomerCanonicalizer} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
8
10
|
|
|
9
11
|
import {getCorrectedSmiles, capSmiles} from '../utils/monomer-lib/monomer-manager/monomer-manager';
|
|
10
12
|
|
|
11
|
-
/** Finds a monomer in the library across all polymer types. */
|
|
12
|
-
function findMonomer(monomerLib: IMonomerLib, symbol: string): Monomer | null {
|
|
13
|
-
return monomerLib.getMonomer(null, symbol);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
13
|
/** Caps the monomer (replaces R-groups with cap atoms) and returns capped SMILES. */
|
|
17
14
|
function getCappedSmiles(monomer: Monomer): string | null {
|
|
18
15
|
try {
|
|
@@ -22,58 +19,101 @@ function getCappedSmiles(monomer: Monomer): string | null {
|
|
|
22
19
|
return null;
|
|
23
20
|
}
|
|
24
21
|
|
|
22
|
+
/** Renders a single-monomer details pane content. */
|
|
23
|
+
function renderMonomerDetails(monomer: Monomer): HTMLElement {
|
|
24
|
+
const map: {[key: string]: any} = {
|
|
25
|
+
'Symbol': monomer[REQ.SYMBOL],
|
|
26
|
+
'Name': monomer[REQ.NAME],
|
|
27
|
+
'Polymer Type': monomer[REQ.POLYMER_TYPE],
|
|
28
|
+
'Monomer Type': monomer[REQ.MONOMER_TYPE],
|
|
29
|
+
};
|
|
30
|
+
if (monomer[REQ.AUTHOR])
|
|
31
|
+
map['Author'] = monomer[REQ.AUTHOR];
|
|
32
|
+
if (monomer.naturalAnalog)
|
|
33
|
+
map['Natural Analog'] = monomer.naturalAnalog;
|
|
34
|
+
if (monomer.lib?.source) {
|
|
35
|
+
let source = monomer.lib.source;
|
|
36
|
+
if (source.endsWith('.json'))
|
|
37
|
+
source = source.substring(0, source.length - 5);
|
|
38
|
+
map['Library'] = source;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Structure
|
|
42
|
+
if (monomer.molfile)
|
|
43
|
+
map['Structure'] = grok.chem.drawMolecule(monomer.molfile, 150, 150);
|
|
44
|
+
else if (monomer.smiles)
|
|
45
|
+
map['Structure'] = grok.chem.drawMolecule(monomer.smiles, 150, 150);
|
|
46
|
+
|
|
47
|
+
// R-Groups
|
|
48
|
+
const rgroups = monomer[REQ.RGROUPS];
|
|
49
|
+
if (rgroups && rgroups.length > 0) {
|
|
50
|
+
for (const rg of rgroups)
|
|
51
|
+
map[rg[RGP.LABEL] ?? '?'] = rg[RGP.ALTERNATE_ID] ?? '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return ui.tableFromMap(map);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Renders the molecule info panel pane content for a single monomer. */
|
|
58
|
+
function renderMoleculePane(monomer: Monomer): HTMLElement {
|
|
59
|
+
const cappedMol = getCappedSmiles(monomer);
|
|
60
|
+
if (!cappedMol)
|
|
61
|
+
return ui.divText('No molecular structure available');
|
|
62
|
+
|
|
63
|
+
const molSv = DG.SemanticValue.fromValueType(cappedMol, DG.SEMTYPE.MOLECULE);
|
|
64
|
+
return ui.panels.infoPanel(molSv).root;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Tries to get the canonicalizer from the SemanticValue's cell column. */
|
|
68
|
+
function getCanonicalizer(sv: DG.SemanticValue): IMonomerCanonicalizer | null {
|
|
69
|
+
try {
|
|
70
|
+
const col = sv.cell?.column;
|
|
71
|
+
if (col)
|
|
72
|
+
return col.temp[MONOMER_CANONICALIZER_TEMP] ?? null;
|
|
73
|
+
} catch { /* no cell context */ }
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
25
77
|
/** Creates a widget for the monomer info panel shown in the context panel. */
|
|
26
|
-
export function getMonomerInfoWidget(
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
29
|
-
return new DG.Widget(ui.divText(
|
|
78
|
+
export function getMonomerInfoWidget(sv: DG.SemanticValue, monomerLib: IMonomerLib): DG.Widget {
|
|
79
|
+
const rawValue = sv.value as string;
|
|
80
|
+
if (!rawValue)
|
|
81
|
+
return new DG.Widget(ui.divText('No monomer value.'));
|
|
30
82
|
|
|
31
|
-
|
|
83
|
+
// Canonicalize if a canonicalizer is available from the column context
|
|
84
|
+
const canonicalizer = getCanonicalizer(sv);
|
|
85
|
+
const canonicalized = canonicalizer ? canonicalizer.canonicalize(rawValue) : rawValue;
|
|
32
86
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const map: {[key: string]: any} = {
|
|
36
|
-
'Symbol': monomer[REQ.SYMBOL],
|
|
37
|
-
'Name': monomer[REQ.NAME],
|
|
38
|
-
'Polymer Type': monomer[REQ.POLYMER_TYPE],
|
|
39
|
-
'Monomer Type': monomer[REQ.MONOMER_TYPE],
|
|
40
|
-
};
|
|
41
|
-
if (monomer[REQ.AUTHOR])
|
|
42
|
-
map['Author'] = monomer[REQ.AUTHOR];
|
|
43
|
-
if (monomer.naturalAnalog)
|
|
44
|
-
map['Natural Analog'] = monomer.naturalAnalog;
|
|
45
|
-
if (monomer.lib?.source) {
|
|
46
|
-
let source = monomer.lib.source;
|
|
47
|
-
if (source.endsWith('.json'))
|
|
48
|
-
source = source.substring(0, source.length - 5);
|
|
49
|
-
map['Library'] = source;
|
|
50
|
-
}
|
|
87
|
+
// Split by motif splitter — a cell value may contain multiple monomers
|
|
88
|
+
const symbols = canonicalized.split(MONOMER_MOTIF_SPLITTER).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
else if (monomer.smiles)
|
|
56
|
-
map['Structure'] = grok.chem.drawMolecule(monomer.smiles, 150, 150);
|
|
57
|
-
|
|
58
|
-
// R-Groups
|
|
59
|
-
const rgroups = monomer[REQ.RGROUPS];
|
|
60
|
-
if (rgroups && rgroups.length > 0) {
|
|
61
|
-
for (const rg of rgroups)
|
|
62
|
-
map[rg[RGP.LABEL] ?? '?'] = rg[RGP.ALTERNATE_ID] ?? '';
|
|
63
|
-
}
|
|
90
|
+
// Resolve monomers from library
|
|
91
|
+
const monomers: {symbol: string; monomer: Monomer | null}[] =
|
|
92
|
+
symbols.map((s) => ({symbol: s, monomer: monomerLib.getMonomer(null, s)}));
|
|
64
93
|
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
const found = monomers.filter((m) => m.monomer !== null);
|
|
95
|
+
if (found.length === 0)
|
|
96
|
+
return new DG.Widget(ui.divText(`Monomer '${rawValue}' not found in the library.`));
|
|
67
97
|
|
|
68
|
-
|
|
69
|
-
acc.addPane('Molecule', () => {
|
|
70
|
-
const cappedMol = getCappedSmiles(monomer);
|
|
71
|
-
if (!cappedMol)
|
|
72
|
-
return ui.divText('No molecular structure available');
|
|
98
|
+
const acc = ui.accordion('Monomer');
|
|
73
99
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
if (found.length === 1) {
|
|
101
|
+
// Single monomer — flat panes
|
|
102
|
+
const m = found[0].monomer!;
|
|
103
|
+
acc.addPane('Details', () => renderMonomerDetails(m), true);
|
|
104
|
+
acc.addPane('Molecule', () => renderMoleculePane(m), true);
|
|
105
|
+
} else {
|
|
106
|
+
// Multiple monomers — one pane per monomer
|
|
107
|
+
for (const {symbol, monomer} of found) {
|
|
108
|
+
acc.addPane(symbol, () => {
|
|
109
|
+
return ui.divV([
|
|
110
|
+
renderMonomerDetails(monomer!),
|
|
111
|
+
ui.element('hr'),
|
|
112
|
+
renderMoleculePane(monomer!),
|
|
113
|
+
]);
|
|
114
|
+
}, true);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
77
117
|
|
|
78
118
|
return new DG.Widget(acc.root);
|
|
79
119
|
}
|