@datagrok/bio 2.7.0 → 2.7.1
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/tests/monomer-libraries-tests.ts +14 -1
- package/src/utils/monomer-lib.ts +32 -22
- package/src/viewers/vd-regions-viewer.ts +55 -15
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.7.
|
|
8
|
+
"version": "2.7.1",
|
|
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",
|
|
@@ -5,7 +5,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
5
5
|
import {test, after, before, category, expect} from '@datagrok-libraries/utils/src/test';
|
|
6
6
|
|
|
7
7
|
import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
8
|
-
import {LIB_STORAGE_NAME} from '../utils/monomer-lib';
|
|
8
|
+
import {getLibFileNameList, getUserLibSettings, LIB_STORAGE_NAME, setUserLibSetting} from '../utils/monomer-lib';
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
category('monomerLibraries', () => {
|
|
@@ -31,4 +31,17 @@ category('monomerLibraries', () => {
|
|
|
31
31
|
const currentMonomerLib = monomerLibHelper.getBioLib();
|
|
32
32
|
expect(currentMonomerLib.getPolymerTypes().length > 0, true);
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
test('empty', async () => {
|
|
36
|
+
// exclude all monomer libraries for empty set
|
|
37
|
+
const libSettings = await getUserLibSettings();
|
|
38
|
+
const libFnList = await getLibFileNameList();
|
|
39
|
+
libSettings.exclude = libFnList;
|
|
40
|
+
await setUserLibSetting(libSettings);
|
|
41
|
+
|
|
42
|
+
await monomerLibHelper.loadLibraries(true);
|
|
43
|
+
const currentMonomerLib = monomerLibHelper.getBioLib();
|
|
44
|
+
expect(currentMonomerLib.getPolymerTypes().length === 0, true);
|
|
45
|
+
const monomerOfTypesList = currentMonomerLib.getMonomerMolsByPolymerType('PEPTIDE');
|
|
46
|
+
});
|
|
34
47
|
});
|
package/src/utils/monomer-lib.ts
CHANGED
|
@@ -39,18 +39,28 @@ export async function getLibFileNameList(): Promise<string[]> {
|
|
|
39
39
|
return res;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
const resStr: string = await grok.dapi.userDataStorage.getValue(LIB_STORAGE_NAME, 'Settings', true);
|
|
44
|
-
const res: LibSettings = resStr ? JSON.parse(resStr) : {exclude: []};
|
|
45
|
-
|
|
46
|
-
// Fix empty object returned in case there is no settings stored for user
|
|
47
|
-
res.exclude = res.exclude instanceof Array ? res.exclude : [];
|
|
42
|
+
let userLibSettingsPromise: Promise<void> = Promise.resolve();
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
export async function getUserLibSettings(): Promise<LibSettings> {
|
|
45
|
+
let res: LibSettings;
|
|
46
|
+
userLibSettingsPromise = userLibSettingsPromise.then(async () => {
|
|
47
|
+
const resStr: string = await grok.dapi.userDataStorage.getValue(LIB_STORAGE_NAME, 'Settings', true);
|
|
48
|
+
res = resStr ? JSON.parse(resStr) : {exclude: []};
|
|
49
|
+
|
|
50
|
+
// Fix empty object returned in case there is no settings stored for user
|
|
51
|
+
res.exclude = res.exclude instanceof Array ? res.exclude : [];
|
|
52
|
+
console.debug(`Bio: getUserLibSettings()\n${JSON.stringify(res, undefined, 2)}`);
|
|
53
|
+
});
|
|
54
|
+
await userLibSettingsPromise;
|
|
55
|
+
return res!;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export async function setUserLibSetting(value: LibSettings): Promise<void> {
|
|
53
|
-
|
|
59
|
+
userLibSettingsPromise = userLibSettingsPromise.then(async () => {
|
|
60
|
+
console.debug(`Bio: setUserLibSettings()\n${JSON.stringify(value, undefined, 2)}`);
|
|
61
|
+
await grok.dapi.userDataStorage.postValue(LIB_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
|
|
62
|
+
});
|
|
63
|
+
await userLibSettingsPromise;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
export async function manageFiles() {
|
|
@@ -67,20 +77,19 @@ export async function getLibraryPanelUI(): Promise<DG.Widget> {
|
|
|
67
77
|
const inputsForm: HTMLDivElement = ui.inputs([]);
|
|
68
78
|
const libFileNameList: string[] = await getLibFileNameList();
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
const settings = await getUserLibSettings();
|
|
81
|
+
|
|
71
82
|
for (const libFileName of libFileNameList) {
|
|
72
|
-
const settings = await getUserLibSettings();
|
|
73
83
|
const libInput: DG.InputBase<boolean | null> = ui.boolInput(libFileName, !settings.exclude.includes(libFileName),
|
|
74
84
|
() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
await setUserLibSetting(settings);
|
|
85
|
+
if (libInput.value == true) {
|
|
86
|
+
// Checked library remove from excluded list
|
|
87
|
+
settings.exclude = settings.exclude.filter((l) => l != libFileName);
|
|
88
|
+
} else {
|
|
89
|
+
// Unchecked library add to excluded list
|
|
90
|
+
if (!settings.exclude.includes(libFileName)) settings.exclude.push(libFileName);
|
|
91
|
+
}
|
|
92
|
+
setUserLibSetting(settings).then(async () => {
|
|
84
93
|
await MonomerLibHelper.instance.loadLibraries(true); // from libraryPanel()
|
|
85
94
|
grok.shell.info('Monomer library user settings saved.');
|
|
86
95
|
});
|
|
@@ -115,7 +124,7 @@ export class MonomerLib implements IMonomerLib {
|
|
|
115
124
|
getMonomerMolsByPolymerType(polymerType: string): { [monomerSymbol: string]: string } {
|
|
116
125
|
const res: { [monomerSymbol: string]: string } = {};
|
|
117
126
|
|
|
118
|
-
Object.keys(this._monomers[polymerType]).forEach((monomerSymbol) => {
|
|
127
|
+
Object.keys(this._monomers[polymerType] ?? {}).forEach((monomerSymbol) => {
|
|
119
128
|
res[monomerSymbol] = this._monomers[polymerType][monomerSymbol].molfile;
|
|
120
129
|
});
|
|
121
130
|
|
|
@@ -193,8 +202,9 @@ export class MonomerLibHelper implements IMonomerLibHelper {
|
|
|
193
202
|
getLibFileNameList(),
|
|
194
203
|
getUserLibSettings(),
|
|
195
204
|
]);
|
|
196
|
-
const
|
|
197
|
-
.filter((libFileName) => !settings.exclude.includes(libFileName))
|
|
205
|
+
const filteredLibFnList = libFileNameList
|
|
206
|
+
.filter((libFileName) => !settings.exclude.includes(libFileName));
|
|
207
|
+
const libs: IMonomerLib[] = await Promise.all(filteredLibFnList
|
|
198
208
|
.map((libFileName) => {
|
|
199
209
|
//TODO handle whether files are in place
|
|
200
210
|
return this.readLibrary(LIB_PATH, libFileName).catch((err: any) => {
|
|
@@ -62,6 +62,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
62
62
|
// public sequenceColumnNamePostfix: string;
|
|
63
63
|
|
|
64
64
|
public skipEmptyPositions: boolean;
|
|
65
|
+
/* A value of zero means autofit to the width. */
|
|
65
66
|
public positionWidth: number;
|
|
66
67
|
public positionHeight: PositionHeight;
|
|
67
68
|
|
|
@@ -76,7 +77,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
76
77
|
// this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
|
|
77
78
|
|
|
78
79
|
this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
|
|
79
|
-
this.positionWidth = this.float('positionWidth', 16
|
|
80
|
+
this.positionWidth = this.float('positionWidth', 16, {
|
|
81
|
+
editor: 'slider', min: 0, max: 64,
|
|
82
|
+
description: 'Internal WebLogo viewers property width of position. A value of zero means autofit to the width.'
|
|
83
|
+
});
|
|
80
84
|
this.positionHeight = this.string('positionHeight', PositionHeight.Entropy,
|
|
81
85
|
{choices: Object.keys(PositionHeight)}) as PositionHeight;
|
|
82
86
|
}
|
|
@@ -105,7 +109,6 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
105
109
|
|
|
106
110
|
// this.mlbView.dockManager.dock(this.regionsFg.root, DG.DOCK_TYPE.LEFT, rootNode, 'Filter regions', 0.2);
|
|
107
111
|
|
|
108
|
-
this.subs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
109
112
|
this.subs.push(fromEvent<MouseEvent>(this.root, 'mousemove').subscribe(this.rootOnMouseMove.bind(this)));
|
|
110
113
|
|
|
111
114
|
// await this.buildView('init'); // init
|
|
@@ -213,7 +216,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
213
216
|
|
|
214
217
|
private async destroyView(purpose: string): Promise<void> {
|
|
215
218
|
// TODO: Unsubscribe from and remove all view elements
|
|
216
|
-
|
|
219
|
+
_package.logger.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
|
|
217
220
|
`purpose = '${purpose}'`);
|
|
218
221
|
if (this.filterSourceInput) {
|
|
219
222
|
//
|
|
@@ -232,7 +235,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
private async buildView(purpose: string): Promise<void> {
|
|
235
|
-
|
|
238
|
+
_package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
|
|
236
239
|
|
|
237
240
|
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
238
241
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
@@ -252,6 +255,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
252
255
|
positionWidth: this.positionWidth,
|
|
253
256
|
positionHeight: this.positionHeight,
|
|
254
257
|
}) as WebLogoViewer;
|
|
258
|
+
wl.onSizeChanged.subscribe(() => { this.calcSize(); });
|
|
255
259
|
return [orderI, chain, wl];
|
|
256
260
|
})());
|
|
257
261
|
}
|
|
@@ -321,23 +325,59 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
321
325
|
this.root.style.overflowX = 'auto';
|
|
322
326
|
|
|
323
327
|
this.calcSize();
|
|
328
|
+
this.viewSubs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
324
329
|
|
|
325
|
-
|
|
330
|
+
_package.logger.debug('Bio: VdRegionsViewer.buildView() end');
|
|
326
331
|
}
|
|
327
332
|
|
|
328
|
-
private
|
|
329
|
-
const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
|
|
333
|
+
private calcSizeRequested: boolean = false;
|
|
330
334
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
private calcSize() {
|
|
336
|
+
_package.logger.debug(`Bio: VdRegionsViewer.calcSize(), start`);
|
|
337
|
+
const calcSizeInt = (): void => {
|
|
338
|
+
const dpr: number = window.devicePixelRatio;
|
|
339
|
+
const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
|
|
340
|
+
|
|
341
|
+
const maxHeight: number = Math.min(logoHeight,
|
|
342
|
+
Math.max(...this.logos.map((wlDict) =>
|
|
343
|
+
Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
let totalPos: number = 0;
|
|
347
|
+
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
348
|
+
for (const chain of this.chains)
|
|
349
|
+
this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
|
|
350
|
+
|
|
351
|
+
totalPos += Math.max(...this.chains.map((chain) => this.logos[orderI][chain].Length));
|
|
352
|
+
}
|
|
335
353
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
354
|
+
if (this.positionWidth === 0 && this.logos.length > 0 && totalPos > 0) {
|
|
355
|
+
const leftPad = 22/* Chain label */;
|
|
356
|
+
const rightPad = 6 + 6 + 1;
|
|
357
|
+
const logoMargin = 8 + 1;
|
|
358
|
+
const fitPositionWidth =
|
|
359
|
+
(this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos * dpr;
|
|
360
|
+
|
|
361
|
+
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
362
|
+
for (let chainI = 0; chainI < this.chains.length; chainI++) {
|
|
363
|
+
const chain: string = this.chains[chainI];
|
|
364
|
+
this.logos[orderI][chain].setOptions({positionWidth: fitPositionWidth});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
340
367
|
}
|
|
368
|
+
|
|
369
|
+
if (this.positionWidth === 0)
|
|
370
|
+
this.host!.style.setProperty('overflow-x', 'hidden', 'important');
|
|
371
|
+
else
|
|
372
|
+
this.host!.style.removeProperty('overflow-x');
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (!this.calcSizeRequested) {
|
|
376
|
+
this.calcSizeRequested = true;
|
|
377
|
+
window.setTimeout(() => {
|
|
378
|
+
calcSizeInt();
|
|
379
|
+
this.calcSizeRequested = false;
|
|
380
|
+
}, 0 /* next event cycle */);
|
|
341
381
|
}
|
|
342
382
|
}
|
|
343
383
|
|