@datagrok/bio 2.15.7 → 2.15.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/CHANGELOG.md +13 -0
- package/dist/package-test.js +2 -2
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/package.json +4 -4
- package/src/analysis/sequence-activity-cliffs.ts +10 -5
- package/src/analysis/sequence-similarity-viewer.ts +1 -1
- package/src/package.ts +1 -1
- package/src/tests/WebLogo-positions-test.ts +1 -1
- package/src/tests/splitters-test.ts +23 -6
- package/src/utils/cell-renderer.ts +101 -111
- package/src/utils/monomer-cell-renderer-base.ts +30 -0
- package/src/utils/monomer-cell-renderer.ts +74 -54
- package/src/utils/monomer-lib/lib-manager.ts +1 -1
- package/src/utils/monomer-lib/monomer-colors.ts +33 -30
- package/src/utils/monomer-lib/monomer-lib-base.ts +62 -10
- package/src/utils/monomer-lib/monomer-lib.ts +7 -50
- package/src/utils/monomer-lib/web-editor-monomer-dummy.ts +11 -4
- package/src/utils/monomer-lib/web-editor-monomer-of-library.ts +19 -12
- package/src/viewers/web-logo-viewer.ts +40 -27
- package/src/widgets/composition-analysis-widget.ts +12 -17
|
@@ -7,19 +7,21 @@ import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
|
|
|
7
7
|
*/
|
|
8
8
|
export const naturalMonomerColors = {
|
|
9
9
|
[HelmTypes.BASE]: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// Chromatogram palette // HELMWebEditor monomerColors
|
|
11
|
+
A: "green", // "#A0A0FF",
|
|
12
|
+
G: "black", // "#FF7070",
|
|
13
|
+
T: "red", // "#A0FFA0",
|
|
14
|
+
C: "blue", // "#FF8C4B",
|
|
15
|
+
U: "red", // "#FF8080"
|
|
15
16
|
},
|
|
16
17
|
|
|
17
18
|
[HelmTypes.NUCLEOTIDE]: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// Chromatogram palette // HELMWebEditor monomerColors
|
|
20
|
+
A: "green", // "#A0A0FF",
|
|
21
|
+
G: "black", // "#FF7070",
|
|
22
|
+
T: "red", // "#A0FFA0",
|
|
23
|
+
C: "blue", // "#FF8C4B",
|
|
24
|
+
U: "red", // "#FF8080"
|
|
23
25
|
},
|
|
24
26
|
|
|
25
27
|
[HelmTypes.LINKER]: {
|
|
@@ -34,26 +36,27 @@ export const naturalMonomerColors = {
|
|
|
34
36
|
},
|
|
35
37
|
|
|
36
38
|
[HelmTypes.AA]: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
// GrokGroups palette // HELMWebEditor monomerColors
|
|
40
|
+
A: "rgb(44,160,44)", // "#C8C8C8",
|
|
41
|
+
R: "rgb(23,190,207)", // "#145AFF",
|
|
42
|
+
N: "rgb(235,137,70)", // "#00DCDC",
|
|
43
|
+
D: "rgb(31,119,180)", // "#E60A0A",
|
|
44
|
+
C: "rgb(188,189,34)", // "#E6E600",
|
|
45
|
+
E: "rgb(31, 120, 150)", // "#00DCDC",
|
|
46
|
+
Q: "rgb(205, 111, 71)", // "#E60A0A",
|
|
47
|
+
G: "rgb(214,39,40)", // "#EBEBEB",
|
|
48
|
+
H: "rgb(158,218,229)", // "#8282D2",
|
|
49
|
+
I: "rgb(23,103,57)", // "#0F820F",
|
|
50
|
+
L: "rgb(30,110,96)", // "#0F820F",
|
|
51
|
+
K: "rgb(108, 218, 229)", //"#145AFF",
|
|
52
|
+
M: "rgb(60,131,95)", // "#E6E600",
|
|
53
|
+
F: "rgb(24,110,79)", // "#3232AA",
|
|
54
|
+
P: "rgb(255,152,150)", // "#DC9682",
|
|
55
|
+
S: "rgb(255,187,120)", // "#FA9600",
|
|
56
|
+
T: "rgb(245,167,100)", // "#FA9600",
|
|
57
|
+
W: "rgb(182, 223, 138)", // "#B45AB4",
|
|
58
|
+
Y: "rgb(152,223,138)", // "#3232AA",
|
|
59
|
+
V: "rgb(74,160,74)", // "#0F820F",
|
|
57
60
|
},
|
|
58
61
|
|
|
59
62
|
[HelmTypes.CHEM]: {
|
|
@@ -3,6 +3,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
5
|
import wu from 'wu';
|
|
6
|
+
import {Observable, Subject} from 'rxjs';
|
|
6
7
|
|
|
7
8
|
import {IMonomerLibBase, Monomer, RGroup} from '@datagrok-libraries/bio/src/types/index';
|
|
8
9
|
import {HelmAtom, HelmType, IWebEditorMonomer, MonomerType, PolymerType} from '@datagrok-libraries/bio/src/helm/types';
|
|
@@ -10,13 +11,12 @@ import {getMonomerHandleArgs} from '@datagrok-libraries/bio/src/helm/helm-helper
|
|
|
10
11
|
import {helmTypeToPolymerType} from '@datagrok-libraries/bio/src/monomer-works/monomer-works';
|
|
11
12
|
import {HelmTypes, PolymerTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
12
13
|
import {HELM_REQUIRED_FIELD as REQ, HELM_RGROUP_FIELDS as RGP} from '@datagrok-libraries/bio/src/utils/const';
|
|
13
|
-
import {GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
14
|
+
import {GAP_SYMBOL, GapOriginals, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
14
15
|
|
|
15
16
|
import {AmbiguousWebEditorMonomer, GapWebEditorMonomer, MissingWebEditorMonomer} from './web-editor-monomer-dummy';
|
|
16
17
|
import {LibraryWebEditorMonomer} from './web-editor-monomer-of-library';
|
|
17
18
|
|
|
18
19
|
import {_package} from '../../package';
|
|
19
|
-
import {Observable, Subject} from 'rxjs';
|
|
20
20
|
|
|
21
21
|
const monomerRe = /[\w()]+/;
|
|
22
22
|
//** Do not mess with monomer symbol with parenthesis enclosed in square brackets */
|
|
@@ -25,13 +25,20 @@ const ambMonomerRe = RegExp(String.raw`\(${monomerRe}(,${monomerRe})+\)`);
|
|
|
25
25
|
export type MonomerLibDataType = { [polymerType: string]: { [monomerSymbol: string]: Monomer } };
|
|
26
26
|
|
|
27
27
|
export class MonomerLibBase implements IMonomerLibBase {
|
|
28
|
+
protected _isEmpty: boolean;
|
|
29
|
+
get isEmpty(): boolean { return this._isEmpty; }
|
|
30
|
+
|
|
28
31
|
protected _onChanged = new Subject<any>();
|
|
29
32
|
|
|
30
33
|
get onChanged(): Observable<any> { return this._onChanged; }
|
|
31
34
|
|
|
35
|
+
|
|
32
36
|
constructor(
|
|
33
37
|
protected _monomers: MonomerLibDataType,
|
|
34
|
-
) {
|
|
38
|
+
) {
|
|
39
|
+
this._isEmpty = !this._monomers || Object.keys(this._monomers).length === 0 ||
|
|
40
|
+
Object.entries(this._monomers).every(([_, ptMonomers]) => Object.keys(ptMonomers).length === 0);
|
|
41
|
+
}
|
|
35
42
|
|
|
36
43
|
/** Creates missing {@link Monomer} */
|
|
37
44
|
addMissingMonomer(polymerType: PolymerType, monomerSymbol: string): Monomer {
|
|
@@ -40,7 +47,7 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
40
47
|
mSet = this._monomers[polymerType] = {};
|
|
41
48
|
|
|
42
49
|
let monomerName: string = monomerSymbol;
|
|
43
|
-
if (monomerSymbol === GapOriginals[NOTATION.HELM])
|
|
50
|
+
if (monomerSymbol == GAP_SYMBOL || monomerSymbol === GapOriginals[NOTATION.HELM] /* usage from HELMWebEditor */)
|
|
44
51
|
monomerName = 'Gap';
|
|
45
52
|
else if (polymerType === PolymerTypes.PEPTIDE && monomerSymbol === 'X')
|
|
46
53
|
monomerName = 'Any';
|
|
@@ -120,17 +127,17 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
120
127
|
/** Get or create {@link org,helm.WebEditorMonomer} */
|
|
121
128
|
let resWem: IWebEditorMonomer | null = m.wem ?? null;
|
|
122
129
|
if (!resWem) {
|
|
123
|
-
if (elem === '*')
|
|
124
|
-
resWem = m.wem = new GapWebEditorMonomer(biotype
|
|
130
|
+
if (elem === GAP_SYMBOL || elem == '*' /* usage from HELMWebEditor */)
|
|
131
|
+
resWem = m.wem = new GapWebEditorMonomer(biotype);
|
|
125
132
|
else if (
|
|
126
|
-
(biotype ===
|
|
127
|
-
(biotype ===
|
|
128
|
-
(biotype ===
|
|
133
|
+
(biotype === HelmTypes.NUCLEOTIDE && elem === 'N') ||
|
|
134
|
+
(biotype === HelmTypes.AA && elem === 'X') ||
|
|
135
|
+
(biotype === HelmTypes.CHEM && false) || // TODO: Ambiguous monomer for CHEM
|
|
129
136
|
ambMonomerRe.test(elem) // e.g. (A,R,_)
|
|
130
137
|
)
|
|
131
138
|
resWem = m.wem = new AmbiguousWebEditorMonomer(biotype, elem);
|
|
132
139
|
else if (!m.lib)
|
|
133
|
-
resWem = m.wem = new MissingWebEditorMonomer(biotype, elem);
|
|
140
|
+
resWem = m.wem = new MissingWebEditorMonomer(biotype, elem, this.isEmpty);
|
|
134
141
|
|
|
135
142
|
if (!resWem)
|
|
136
143
|
resWem = m.wem = LibraryWebEditorMonomer.fromMonomer(biotype, m, this);
|
|
@@ -139,6 +146,51 @@ export class MonomerLibBase implements IMonomerLibBase {
|
|
|
139
146
|
return resWem!;
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
getTooltip(biotype: HelmType, monomerSymbol: string): HTMLElement {
|
|
150
|
+
const polymerType = helmTypeToPolymerType(biotype);
|
|
151
|
+
const res = ui.div([], {classes: 'ui-form ui-tooltip'});
|
|
152
|
+
const monomer = this.getMonomer(polymerType, monomerSymbol);
|
|
153
|
+
if (monomer) {
|
|
154
|
+
// Symbol & Name
|
|
155
|
+
const symbol = monomer[REQ.SYMBOL];
|
|
156
|
+
const _name = monomer[REQ.NAME];
|
|
157
|
+
const wem = this.getWebEditorMonomer(biotype, monomerSymbol)!;
|
|
158
|
+
const htmlColor = wem.backgroundcolor;
|
|
159
|
+
res.append(ui.divH([
|
|
160
|
+
ui.div([symbol], {style: {fontWeight: 'bolder', textWrap: 'nowrap', marginRight: '6px', color: htmlColor}}),
|
|
161
|
+
ui.div([monomer.name])
|
|
162
|
+
], {style: {display: 'flex', flexDirection: 'row', justifyContent: 'left'}}));
|
|
163
|
+
|
|
164
|
+
// Structure
|
|
165
|
+
const chemOptions = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
|
|
166
|
+
let structureEl: HTMLElement;
|
|
167
|
+
if (monomer.molfile)
|
|
168
|
+
structureEl = grok.chem.svgMol(monomer.molfile, undefined, undefined, chemOptions);
|
|
169
|
+
else if (monomer.smiles) {
|
|
170
|
+
structureEl = ui.divV([
|
|
171
|
+
grok.chem.svgMol(monomer.smiles, undefined, undefined, chemOptions),
|
|
172
|
+
ui.divText('from smiles', {style: {fontSize: 'smaller'}}),
|
|
173
|
+
]);
|
|
174
|
+
} else {
|
|
175
|
+
// Unable to get monomer's structure
|
|
176
|
+
structureEl = ui.divText('No structure', {style: {margin: '6px'}});
|
|
177
|
+
}
|
|
178
|
+
res.append(ui.div(structureEl,
|
|
179
|
+
{style: {display: 'flex', flexDirection: 'row', justifyContent: 'center', margin: '6px'}}));
|
|
180
|
+
|
|
181
|
+
// Source
|
|
182
|
+
if (monomer.symbol != GAP_SYMBOL)
|
|
183
|
+
res.append(ui.divText(monomer.lib?.source ?? 'Missed in libraries'));
|
|
184
|
+
} else {
|
|
185
|
+
res.append(ui.divV([
|
|
186
|
+
ui.divText(`Monomer '${monomerSymbol}' of type '${polymerType}' not found.`),
|
|
187
|
+
ui.divText('Open the Context Panel, then expand Manage Libraries'),
|
|
188
|
+
]));
|
|
189
|
+
}
|
|
190
|
+
return res;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
142
194
|
getRS(smiles: string): { [r: string]: string } {
|
|
143
195
|
const newS = smiles.match(/(?<=\[)[^\][]*(?=])/gm);
|
|
144
196
|
const res: { [name: string]: string } = {};
|
|
@@ -163,22 +163,20 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
|
|
|
163
163
|
this._monomers[type][monomerSymbol] = lib.getMonomer(type, monomerSymbol)!;
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
public update(lib: IMonomerLib): void {
|
|
169
|
-
this._updateLibInt(lib);
|
|
170
|
-
this._onChanged.next();
|
|
166
|
+
this._isEmpty = this.isEmpty && lib.isEmpty;
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
|
|
174
|
-
if (reload)
|
|
170
|
+
if (reload) {
|
|
175
171
|
this._monomers = {};
|
|
172
|
+
this._isEmpty = true;
|
|
173
|
+
}
|
|
176
174
|
this._duplicateMonomers = {}; // Reset duplicates
|
|
177
175
|
for (const lib of libList)
|
|
178
176
|
if (!lib.error) this._updateLibInt(lib);
|
|
179
177
|
if (Object.entries(this.duplicateMonomers).length > 0) {
|
|
180
178
|
getUserLibSettings().then((settings) => {
|
|
181
|
-
this.
|
|
179
|
+
this.assignDuplicatePreferences(settings);
|
|
182
180
|
});
|
|
183
181
|
} else
|
|
184
182
|
this._duplicatesHandled = true;
|
|
@@ -186,8 +184,8 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
|
|
|
186
184
|
this._onChanged.next();
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
/** Checks
|
|
190
|
-
|
|
187
|
+
/** Checks weather all duplicated monomers have set preferences in user settings. overwrites those which have. */
|
|
188
|
+
assignDuplicatePreferences(userSettings: UserLibSettings): boolean {
|
|
191
189
|
let res = true;
|
|
192
190
|
for (const polymerType in this.duplicateMonomers) {
|
|
193
191
|
for (const monomerSymbol in this.duplicateMonomers[polymerType]) {
|
|
@@ -243,47 +241,6 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
|
|
|
243
241
|
return resStr;
|
|
244
242
|
}
|
|
245
243
|
|
|
246
|
-
getTooltip(biotype: HelmType, monomerSymbol: string): HTMLElement {
|
|
247
|
-
const polymerType = helmTypeToPolymerType(biotype);
|
|
248
|
-
const res = ui.div([], {classes: 'ui-form ui-tooltip'});
|
|
249
|
-
const monomer = this.getMonomer(polymerType, monomerSymbol);
|
|
250
|
-
if (monomer) {
|
|
251
|
-
// Symbol & Name
|
|
252
|
-
const symbol = monomer[REQ.SYMBOL];
|
|
253
|
-
const _name = monomer[REQ.NAME];
|
|
254
|
-
res.append(ui.divH([
|
|
255
|
-
ui.div([symbol], {style: {fontWeight: 'bolder', textWrap: 'nowrap', marginRight: '6px'}}),
|
|
256
|
-
ui.div([monomer.name])
|
|
257
|
-
], {style: {display: 'flex', flexDirection: 'row', justifyContent: 'left'}}));
|
|
258
|
-
|
|
259
|
-
// Structure
|
|
260
|
-
const chemOptions = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
|
|
261
|
-
let structureEl: HTMLElement;
|
|
262
|
-
if (monomer.molfile)
|
|
263
|
-
structureEl = grok.chem.svgMol(monomer.molfile, undefined, undefined, chemOptions);
|
|
264
|
-
else if (monomer.smiles) {
|
|
265
|
-
structureEl = ui.divV([
|
|
266
|
-
grok.chem.svgMol(monomer.smiles, undefined, undefined, chemOptions),
|
|
267
|
-
ui.divText('from smiles', {style: {fontSize: 'smaller'}}),
|
|
268
|
-
]);
|
|
269
|
-
} else {
|
|
270
|
-
// Unable to get monomer's structure
|
|
271
|
-
structureEl = ui.divText('No structure', {style: {margin: '6px'}});
|
|
272
|
-
}
|
|
273
|
-
res.append(ui.div(structureEl,
|
|
274
|
-
{style: {display: 'flex', flexDirection: 'row', justifyContent: 'center', margin: '6px'}}));
|
|
275
|
-
|
|
276
|
-
// Source
|
|
277
|
-
res.append(ui.divText(monomer.lib?.source ?? 'Missed in libraries'));
|
|
278
|
-
} else {
|
|
279
|
-
res.append(ui.divV([
|
|
280
|
-
ui.divText(`Monomer '${monomerSymbol}' of type '${polymerType}' not found.`),
|
|
281
|
-
ui.divText('Open the Context Panel, then expand Manage Libraries'),
|
|
282
|
-
]));
|
|
283
|
-
}
|
|
284
|
-
return res;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
244
|
override(data: MonomerLibData): IMonomerLibBase {
|
|
288
245
|
return new OverriddenMonomerLib(data, this);
|
|
289
246
|
}
|
|
@@ -3,7 +3,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
PolymerType, MonomerType,
|
|
7
7
|
IWebEditorMonomer, WebEditorRGroups
|
|
8
8
|
} from '@datagrok-libraries/bio/src/helm/types';
|
|
9
9
|
|
|
@@ -85,8 +85,9 @@ export class GapWebEditorMonomer extends WebEditorMonomerDummy {
|
|
|
85
85
|
public readonly linecolor: string = '#808080';
|
|
86
86
|
public readonly textcolor: string = '#808080';
|
|
87
87
|
|
|
88
|
-
constructor(biotype: string
|
|
89
|
-
|
|
88
|
+
constructor(biotype: string) {
|
|
89
|
+
// monomer symbol is used to build pseudo molfile for Helm, symbol can not be empty for molfile
|
|
90
|
+
super(biotype, '*', 'gap');
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -105,8 +106,14 @@ export class MissingWebEditorMonomer extends WebEditorMonomerDummy {
|
|
|
105
106
|
public readonly linecolor: string = '#800000';
|
|
106
107
|
public readonly textcolor: string = '#FFFFFF';
|
|
107
108
|
|
|
108
|
-
constructor(biotype: string, id: string) {
|
|
109
|
+
constructor(biotype: string, id: string, isLibEmpty: boolean) {
|
|
109
110
|
super(biotype, id, 'missing');
|
|
111
|
+
|
|
112
|
+
if (isLibEmpty) {
|
|
113
|
+
this.backgroundcolor = '#C0C0C0';
|
|
114
|
+
this.linecolor = '#404040';
|
|
115
|
+
this.textcolor = '#404040';
|
|
116
|
+
}
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
119
|
|
|
@@ -43,7 +43,8 @@ export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
|
43
43
|
at = monomerLib.getRS(smiles);
|
|
44
44
|
} else if (!monomer.lib) {
|
|
45
45
|
// missing
|
|
46
|
-
|
|
46
|
+
throw new Error('Unexpected missing monomer without .lib');
|
|
47
|
+
// return new MissingWebEditorMonomer(biotype, symbol);
|
|
47
48
|
} else {
|
|
48
49
|
// broken
|
|
49
50
|
return new BrokenWebEditorMonomer(biotype, symbol);
|
|
@@ -58,7 +59,7 @@ export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
|
58
59
|
monomer[REQ.MONOMER_TYPE],
|
|
59
60
|
at);
|
|
60
61
|
|
|
61
|
-
const colors = getMonomerColors(biotype, monomer);
|
|
62
|
+
const colors = getMonomerColors(biotype, monomer, monomerLib);
|
|
62
63
|
if (colors) {
|
|
63
64
|
res.textcolor = colors?.textcolor;
|
|
64
65
|
res.linecolor = colors?.linecolor;
|
|
@@ -69,7 +70,7 @@ export class LibraryWebEditorMonomer implements IWebEditorMonomer {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
function getMonomerColors(biotype: HelmType, monomer: Monomer): IMonomerColors | null {
|
|
73
|
+
function getMonomerColors(biotype: HelmType, monomer: Monomer, monomerLib?: IMonomerLibBase): IMonomerColors | null {
|
|
73
74
|
const currentMonomerSchema = 'default';
|
|
74
75
|
let monomerSchema: string = currentMonomerSchema;
|
|
75
76
|
|
|
@@ -77,23 +78,29 @@ function getMonomerColors(biotype: HelmType, monomer: Monomer): IMonomerColors |
|
|
|
77
78
|
if (monomer.meta && monomer.meta.colors) {
|
|
78
79
|
const monomerColors: { [colorSchemaName: string]: any } = monomer.meta.colors;
|
|
79
80
|
if (!(currentMonomerSchema in monomerColors)) monomerSchema = 'default';
|
|
80
|
-
|
|
81
|
+
res = monomerColors[monomerSchema];
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
if (!res) {
|
|
84
85
|
const biotypeColors: { [symbol: string]: string } | undefined = naturalMonomerColors[biotype];
|
|
85
|
-
const nColor = biotypeColors?.[monomer.symbol];
|
|
86
|
-
if (nColor)
|
|
87
|
-
|
|
86
|
+
const nColor: string = biotypeColors?.[monomer.symbol];
|
|
87
|
+
if (nColor) {
|
|
88
|
+
const nTextColor = DG.Color.toHtml(DG.Color.getContrastColor(DG.Color.fromHtml(nColor)));
|
|
89
|
+
res = {textColor: nTextColor, lineColor: '#202020', backgroundColor: nColor};
|
|
90
|
+
}
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
const
|
|
91
|
-
if (!res &&
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
93
|
+
const naSymbol: string | undefined = monomer[OPT.NATURAL_ANALOG];
|
|
94
|
+
if (!res && naSymbol) {
|
|
95
|
+
const polymerType = monomer[REQ.POLYMER_TYPE];
|
|
96
|
+
const naMonomer = monomerLib?.getMonomer(polymerType, naSymbol);
|
|
97
|
+
if (naMonomer)
|
|
98
|
+
return getMonomerColors(biotype, naMonomer);
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
if (!res)
|
|
102
|
+
res = {textColor: "#202020", lineColor: "#202020", backgroundColor: "#A0A0A0"};
|
|
103
|
+
|
|
97
104
|
return !res ? null : {
|
|
98
105
|
textcolor: res.text ?? res.textColor,
|
|
99
106
|
linecolor: res.line ?? res.lineColor,
|
|
@@ -7,9 +7,8 @@ import wu from 'wu';
|
|
|
7
7
|
import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
|
|
8
8
|
|
|
9
9
|
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
10
|
-
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
11
10
|
import {
|
|
12
|
-
monomerToShort,
|
|
11
|
+
monomerToShort, pickUpSeqCol, TAGS as bioTAGS, positionSeparator, ALPHABET
|
|
13
12
|
} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
14
13
|
import {
|
|
15
14
|
FilterSources, HorizontalAlignments, IWebLogoViewer, PositionHeight, PositionMarginStates,
|
|
@@ -21,11 +20,14 @@ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/type
|
|
|
21
20
|
import {testEvent} from '@datagrok-libraries/utils/src/test';
|
|
22
21
|
import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
|
|
23
22
|
import {GAP_SYMBOL} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
23
|
+
import {IMonomerLibBase} from '@datagrok-libraries/bio/src/types/index';
|
|
24
|
+
import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
|
|
25
|
+
import {undefinedColor} from '@datagrok-libraries/bio/src/utils/cell-renderer-monomer-placer';
|
|
24
26
|
|
|
25
27
|
import {AggFunc, getAgg} from '../utils/agg';
|
|
26
28
|
import {buildCompositionTable} from '../widgets/composition-analysis-widget';
|
|
27
29
|
|
|
28
|
-
import {_package} from '../package';
|
|
30
|
+
import {_package, getMonomerLibHelper} from '../package';
|
|
29
31
|
|
|
30
32
|
declare global {
|
|
31
33
|
interface HTMLCanvasElement {
|
|
@@ -199,7 +201,8 @@ export class PositionInfo {
|
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
render(g: CanvasRenderingContext2D,
|
|
202
|
-
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number,
|
|
204
|
+
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number,
|
|
205
|
+
biotype: HelmType, monomerLib: IMonomerLibBase | null
|
|
203
206
|
) {
|
|
204
207
|
for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
|
|
205
208
|
if (monomer !== GAP_SYMBOL) {
|
|
@@ -207,11 +210,17 @@ export class PositionInfo {
|
|
|
207
210
|
const b = pmInfo.bounds!;
|
|
208
211
|
const left = b.left;
|
|
209
212
|
|
|
213
|
+
let color: string = undefinedColor;
|
|
214
|
+
if (monomerLib) {
|
|
215
|
+
const wem = monomerLib.getWebEditorMonomer(biotype, monomer)!;
|
|
216
|
+
color = wem.backgroundcolor!;
|
|
217
|
+
}
|
|
218
|
+
|
|
210
219
|
g.resetTransform();
|
|
211
220
|
g.strokeStyle = 'lightgray';
|
|
212
221
|
g.lineWidth = 1;
|
|
213
222
|
g.rect(left, b.top, b.width, b.height);
|
|
214
|
-
g.fillStyle =
|
|
223
|
+
g.fillStyle = color;
|
|
215
224
|
g.textAlign = 'left';
|
|
216
225
|
g.font = fontStyle;
|
|
217
226
|
//g.fillRect(b.left, b.top, b.width, b.height);
|
|
@@ -233,13 +242,13 @@ export class PositionInfo {
|
|
|
233
242
|
return !!findRes ? findRes[0] : undefined;
|
|
234
243
|
}
|
|
235
244
|
|
|
236
|
-
buildCompositionTable(
|
|
245
|
+
buildCompositionTable(biotype: HelmType, monomerLib: IMonomerLibBase): HTMLTableElement {
|
|
237
246
|
if ('-' in this._freqs)
|
|
238
247
|
throw new Error(`Unexpected monomer symbol '-'.`);
|
|
239
|
-
return buildCompositionTable(
|
|
248
|
+
return buildCompositionTable(
|
|
240
249
|
Object.assign({}, ...Object.entries(this._freqs)
|
|
241
|
-
.map(([m, pmi]) => ({[m]: pmi.rowCount})))
|
|
242
|
-
|
|
250
|
+
.map(([m, pmi]) => ({[m]: pmi.rowCount}))),
|
|
251
|
+
biotype, monomerLib);
|
|
243
252
|
}
|
|
244
253
|
}
|
|
245
254
|
|
|
@@ -304,9 +313,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
304
313
|
private seqHandler: SeqHandler | null;
|
|
305
314
|
private initialized: boolean = false;
|
|
306
315
|
|
|
307
|
-
|
|
308
|
-
protected palette: SeqPalette | null = null;
|
|
309
|
-
|
|
316
|
+
private monomerLib: IMonomerLibBase | null = null;
|
|
310
317
|
private host?: HTMLDivElement;
|
|
311
318
|
private msgHost?: HTMLElement;
|
|
312
319
|
private canvas: HTMLCanvasElement;
|
|
@@ -446,6 +453,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
446
453
|
this.canvas.classList.value = 'bio-wl-canvas';
|
|
447
454
|
this.canvas.style.width = '100%';
|
|
448
455
|
|
|
456
|
+
getMonomerLibHelper().then((libHelper) => {
|
|
457
|
+
this.monomerLib = libHelper.getMonomerLib();
|
|
458
|
+
this.render(WlRenderLevel.Render, 'monomerLib');
|
|
459
|
+
this.subs.push(this.monomerLib.onChanged.subscribe(() => {
|
|
460
|
+
this.render(WlRenderLevel.Render, 'monomerLib changed');
|
|
461
|
+
}));
|
|
462
|
+
});
|
|
463
|
+
|
|
449
464
|
/* this.root.style.background = '#FFEEDD'; */
|
|
450
465
|
this.viewSyncer = new PromiseSyncer(_package.logger);
|
|
451
466
|
}
|
|
@@ -583,7 +598,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
583
598
|
// Set valueColumnNameProp.choices has no effect
|
|
584
599
|
}
|
|
585
600
|
|
|
586
|
-
/** Assigns {@link seqCol}
|
|
601
|
+
/** Assigns {@link seqCol} based on {@link sequenceColumnName} and calls {@link render}(). */
|
|
587
602
|
private updateSeqCol(): void {
|
|
588
603
|
if (this.dataFrame) {
|
|
589
604
|
this.seqCol = this.sequenceColumnName ? this.dataFrame.col(this.sequenceColumnName) : null;
|
|
@@ -595,7 +610,6 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
595
610
|
try {
|
|
596
611
|
this.seqHandler = SeqHandler.forColumn(this.seqCol);
|
|
597
612
|
|
|
598
|
-
this.palette = pickUpPalette(this.seqCol);
|
|
599
613
|
this.render(WlRenderLevel.Freqs, 'updateSeqCol()');
|
|
600
614
|
this.error = null;
|
|
601
615
|
} catch (err: any) {
|
|
@@ -609,7 +623,6 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
609
623
|
this.positionLabels = [];
|
|
610
624
|
this.startPosition = -1;
|
|
611
625
|
this.endPosition = -1;
|
|
612
|
-
this.palette = null;
|
|
613
626
|
}
|
|
614
627
|
}
|
|
615
628
|
}
|
|
@@ -1085,15 +1098,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1085
1098
|
this._onLayoutCalculated.next();
|
|
1086
1099
|
};
|
|
1087
1100
|
|
|
1088
|
-
if (this.msgHost)
|
|
1089
|
-
|
|
1090
|
-
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
1091
|
-
this.msgHost!.style.display = '';
|
|
1092
|
-
} else
|
|
1093
|
-
this.msgHost!.style.display = 'none';
|
|
1094
|
-
}
|
|
1101
|
+
if (this.msgHost)
|
|
1102
|
+
this.msgHost!.style.display = 'none';
|
|
1095
1103
|
|
|
1096
|
-
if (!this.seqCol || !this.dataFrame ||
|
|
1104
|
+
if (!this.seqCol || !this.dataFrame || this.host == null || this.slider == null)
|
|
1097
1105
|
return;
|
|
1098
1106
|
|
|
1099
1107
|
const dpr: number = window.devicePixelRatio;
|
|
@@ -1133,8 +1141,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1133
1141
|
// Hacks to scale uppercase characters to target rectangle
|
|
1134
1142
|
const uppercaseLetterAscent = 0.25;
|
|
1135
1143
|
const uppercaseLetterHeight = 12.2;
|
|
1144
|
+
const sh = SeqHandler.forColumn(this.seqCol);
|
|
1145
|
+
const biotype = sh.defaultBiotype;
|
|
1136
1146
|
for (let jPos = firstPos; jPos <= lastPos; jPos++)
|
|
1137
|
-
this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, this.
|
|
1147
|
+
this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, biotype, this.monomerLib);
|
|
1138
1148
|
} finally {
|
|
1139
1149
|
g.restore();
|
|
1140
1150
|
}
|
|
@@ -1216,12 +1226,15 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1216
1226
|
const [pi, monomer] = this.getMonomer(cursorP, dpr);
|
|
1217
1227
|
const positionLabelHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT * dpr : 0;
|
|
1218
1228
|
|
|
1219
|
-
if (pi !== null && monomer === null && 0 <= cursorP.y && cursorP.y <= positionLabelHeight) {
|
|
1229
|
+
if (pi !== null && monomer === null && 0 <= cursorP.y && cursorP.y <= positionLabelHeight && this.monomerLib) {
|
|
1220
1230
|
// Position tooltip
|
|
1221
1231
|
|
|
1222
1232
|
const tooltipRows = [ui.divText(`Position ${pi.label}`)];
|
|
1223
|
-
if (this.valueAggrType === DG.AGG.TOTAL_COUNT)
|
|
1224
|
-
|
|
1233
|
+
if (this.valueAggrType === DG.AGG.TOTAL_COUNT) {
|
|
1234
|
+
const sh = SeqHandler.forColumn(this.seqCol!);
|
|
1235
|
+
const biotype = sh.defaultBiotype;
|
|
1236
|
+
tooltipRows.push(pi.buildCompositionTable(biotype, this.monomerLib));
|
|
1237
|
+
}
|
|
1225
1238
|
const tooltipEl = ui.divV(tooltipRows);
|
|
1226
1239
|
ui.tooltip.show(tooltipEl, args.x + 16, args.y + 16);
|
|
1227
1240
|
} else if (pi !== null && monomer && this.dataFrame && this.seqCol && this.seqHandler) {
|
|
@@ -7,27 +7,19 @@ import wu from 'wu';
|
|
|
7
7
|
import {TAGS as bioTAGS, ALPHABET, getPaletteByType} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
8
8
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
9
9
|
import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
|
|
10
|
-
import '../../css/composition-analysis.css';
|
|
11
10
|
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
12
11
|
import {GAP_SYMBOL} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
|
|
12
|
+
import {IMonomerLibBase} from '@datagrok-libraries/bio/src/types';
|
|
13
|
+
import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
|
|
14
|
+
import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
|
|
13
15
|
|
|
16
|
+
import '../../css/composition-analysis.css';
|
|
14
17
|
|
|
15
|
-
export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
18
|
+
export function getCompositionAnalysisWidget(val: DG.SemanticValue, monomerLib: IMonomerLibBase): DG.Widget {
|
|
16
19
|
const host = ui.div();
|
|
17
20
|
host.classList.add('macromolecule-cell-comp-analysis-host');
|
|
18
21
|
const alphabet = val.cell.column.tags[bioTAGS.alphabet];
|
|
19
|
-
|
|
20
|
-
switch (alphabet) {
|
|
21
|
-
case ALPHABET.DNA:
|
|
22
|
-
case ALPHABET.RNA:
|
|
23
|
-
palette = getPaletteByType(ALPHABET.DNA);
|
|
24
|
-
break;
|
|
25
|
-
case ALPHABET.PT:
|
|
26
|
-
palette = getPaletteByType(ALPHABET.PT);
|
|
27
|
-
break;
|
|
28
|
-
default:
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
22
|
+
const biotype = alphabet === ALPHABET.DNA || alphabet === ALPHABET.RNA ? HelmTypes.NUCLEOTIDE : HelmTypes.AA;
|
|
31
23
|
|
|
32
24
|
const counts: { [m: string]: number } = {};
|
|
33
25
|
const sh = SeqHandler.forColumn(val.cell.column as DG.Column<string>);
|
|
@@ -38,7 +30,7 @@ export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
|
38
30
|
const count = counts[cm] || 0;
|
|
39
31
|
counts[cm] = count + 1;
|
|
40
32
|
});
|
|
41
|
-
const table = buildCompositionTable(
|
|
33
|
+
const table = buildCompositionTable(counts, biotype, monomerLib);
|
|
42
34
|
Array.from(table.rows).forEach((row) => {
|
|
43
35
|
const barCol = (row.getElementsByClassName('macromolecule-cell-comp-analysis-bar')[0] as HTMLDivElement)
|
|
44
36
|
.style.backgroundColor;
|
|
@@ -49,7 +41,9 @@ export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
|
49
41
|
return new DG.Widget(host);
|
|
50
42
|
}
|
|
51
43
|
|
|
52
|
-
export function buildCompositionTable(
|
|
44
|
+
export function buildCompositionTable(
|
|
45
|
+
counts: { [m: string]: number }, biotype: HelmType, monomerLib: IMonomerLibBase
|
|
46
|
+
): HTMLTableElement {
|
|
53
47
|
let sumValue: number = 0;
|
|
54
48
|
let maxValue: number | null = null;
|
|
55
49
|
for (const value of Object.values(counts)) {
|
|
@@ -61,7 +55,8 @@ export function buildCompositionTable(palette: SeqPalette, counts: { [m: string]
|
|
|
61
55
|
.sort((a, b) => b[1] - a[1])
|
|
62
56
|
.map(([cm, value]) => {
|
|
63
57
|
const ratio = value / sumValue;
|
|
64
|
-
const
|
|
58
|
+
const wem = monomerLib.getWebEditorMonomer(biotype, cm)!;
|
|
59
|
+
const color = wem.backgroundcolor!;
|
|
65
60
|
const barDiv = ui.div('', {classes: 'macromolecule-cell-comp-analysis-bar'});
|
|
66
61
|
barDiv.style.width = `${50 * ratio / maxRatio}px`;
|
|
67
62
|
barDiv.style.backgroundColor = color;
|