@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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.7.0",
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
  });
@@ -39,18 +39,28 @@ export async function getLibFileNameList(): Promise<string[]> {
39
39
  return res;
40
40
  }
41
41
 
42
- export async function getUserLibSettings(): Promise<LibSettings> {
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
- return res;
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
- await grok.dapi.userDataStorage.postValue(LIB_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
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
- let userStoragePromise: Promise<void> = Promise.resolve();
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
- userStoragePromise = userStoragePromise.then(async () => {
76
- if (libInput.value == true) {
77
- // Checked library remove from excluded list
78
- settings.exclude = settings.exclude.filter((l) => l != libFileName);
79
- } else {
80
- // Unchecked library add to excluded list
81
- if (!settings.exclude.includes(libFileName)) settings.exclude.push(libFileName);
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 libs: IMonomerLib[] = await Promise.all(libFileNameList
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
- console.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
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
- console.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
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
- console.debug('Bio: VdRegionsViewer.buildView() end');
330
+ _package.logger.debug('Bio: VdRegionsViewer.buildView() end');
326
331
  }
327
332
 
328
- private calcSize() {
329
- const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
333
+ private calcSizeRequested: boolean = false;
330
334
 
331
- const maxHeight: number = Math.min(logoHeight,
332
- Math.max(...this.logos.map((wlDict) =>
333
- Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
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
- for (let orderI = 0; orderI < this.logos.length; orderI++) {
337
- for (let chainI = 0; chainI < this.chains.length; chainI++) {
338
- const chain: string = this.chains[chainI];
339
- this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
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