@datagrok/bio 2.10.22 → 2.10.23

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.10.22",
8
+ "version": "2.10.23",
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",
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "@biowasm/aioli": "^3.1.0",
37
- "@datagrok-libraries/bio": "^5.38.11",
37
+ "@datagrok-libraries/bio": "^5.38.12",
38
38
  "@datagrok-libraries/chem-meta": "^1.0.1",
39
39
  "@datagrok-libraries/ml": "^6.3.49",
40
40
  "@datagrok-libraries/tutorials": "^1.3.6",
package/src/package.ts CHANGED
@@ -385,7 +385,7 @@ export async function getRegionTopMenu(
385
385
  //input: object options {optional: true}
386
386
  //output: viewer result
387
387
  //editor: Bio:SeqActivityCliffsEditor
388
- export async function activityCliffs(df: DG.DataFrame, macroMolecule: DG.Column, activities: DG.Column,
388
+ export async function activityCliffs(df: DG.DataFrame, macroMolecule: DG.Column<string>, activities: DG.Column,
389
389
  similarity: number, methodName: DimReductionMethods, options?: (IUMAPOptions | ITSNEOptions) & Options,
390
390
  ): Promise<DG.Viewer | undefined> {
391
391
  if (!checkInputColumnUI(macroMolecule, 'Activity Cliffs'))
@@ -495,8 +495,8 @@ export async function sequenceSpaceTopMenu(
495
495
  embedYCol = table.columns.byName(embedColsNames[1]);
496
496
  }
497
497
 
498
- embedXCol.init((i) => embeddings[i][0]);
499
- embedYCol.init((i) => embeddings[i][1]);
498
+ embedXCol.init((i) => embeddings[i] ? embeddings[i][0] : undefined);
499
+ embedYCol.init((i) => embeddings[i] ? embeddings[i][1] : undefined);
500
500
  const progress = (_nEpoch / epochsLength * 100);
501
501
  pg.update(progress, `Running sequence space ... ${progress.toFixed(0)}%`);
502
502
  }
@@ -613,7 +613,7 @@ export async function sequenceSpaceTopMenu(
613
613
  sp = (v as DG.TableView).scatterPlot({x: embedColsNames[0], y: embedColsNames[1], title: 'Sequence space'});
614
614
  }
615
615
  } */
616
- };
616
+ }
617
617
 
618
618
  //top-menu: Bio | Convert | To Atomic Level...
619
619
  //name: To Atomic Level
@@ -1,7 +1,7 @@
1
1
  import * as grok from 'datagrok-api/grok';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
 
4
- import {category, expect, expectArray, test, awaitCheck, delay} from '@datagrok-libraries/utils/src/test';
4
+ import {category, expect, expectArray, test, testEvent} from '@datagrok-libraries/utils/src/test';
5
5
  import {ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
6
6
  import {
7
7
  countForMonomerAtPosition,
@@ -30,9 +30,9 @@ ATC-G-TTGC--
30
30
  seqCol.setTag(bioTAGS.aligned, 'SEQ.MSA');
31
31
 
32
32
  const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo')) as WebLogoViewer;
33
- tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
34
-
35
- await awaitCheck(() => wlViewer.Length > 0, 'WebLogo.Length is zero', 100);
33
+ await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
34
+ tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
35
+ }, 200);
36
36
  const positions: PI[] = wlViewer['positions'];
37
37
 
38
38
  const resAllDf1: PI[] = [
@@ -82,9 +82,9 @@ ATC-G-TTGC--
82
82
  df.filter.fireChanged();
83
83
  const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo',
84
84
  {'shrinkEmptyTail': true})) as WebLogoViewer;
85
- tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
86
-
87
- await awaitCheck(() => wlViewer.Length > 0, 'WebLogo.Length is zero', 100);
85
+ await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
86
+ tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
87
+ }, 200);
88
88
  const positions: PI[] = wlViewer['positions'];
89
89
 
90
90
  const resAllDf1: PI[] = [
@@ -120,9 +120,9 @@ ATC-G-TTGC--
120
120
 
121
121
  const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo',
122
122
  {'skipEmptyPositions': true})) as WebLogoViewer;
123
- tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
124
-
125
- await awaitCheck(() => wlViewer.Length > 0, 'WebLogo.Length is zero');
123
+ await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
124
+ tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
125
+ }, 200);
126
126
  const resPosList: PI[] = wlViewer['positions'];
127
127
 
128
128
  const tgtPosList: PI[] = [
@@ -156,9 +156,9 @@ ATC-G-TTGC--
156
156
  endPositionName: '7',
157
157
  skipEmptyPositions: true,
158
158
  })) as WebLogoViewer;
159
- tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
160
-
161
- await awaitCheck(() => wlViewer.Length > 0, 'WebLogo.Length is zero', 100);
159
+ await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
160
+ tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
161
+ }, 200);
162
162
  const resPosList: PI[] = wlViewer['positions'];
163
163
  const tgtPosList: PI[] = [
164
164
  new PI(2, '3', {'C': new PMI(5)}),
@@ -29,7 +29,7 @@ category('detectorsBenchmark', () => {
29
29
  });
30
30
 
31
31
  test('fastaDnaLong1e6Few50', async () => {
32
- await detectMacromoleculeBenchmark(15, NOTATION.FASTA, ALPHABET.DNA, 1E6, 50);
32
+ await detectMacromoleculeBenchmark(20, NOTATION.FASTA, ALPHABET.DNA, 1E6, 50);
33
33
  });
34
34
 
35
35
  // -- separator --
@@ -49,7 +49,7 @@ category('detectorsBenchmark', () => {
49
49
  async function detectMacromoleculeBenchmark(
50
50
  maxET: number, notation: NOTATION, alphabet: ALPHABET, length: number, count: number, separator?: string,
51
51
  ): Promise<number> {
52
- return await benchmark<DG.FuncCall, DG.Column>(10,
52
+ return await benchmark<DG.FuncCall, DG.Column>(maxET,
53
53
  (): DG.FuncCall => {
54
54
  const col: DG.Column = generate(notation, [...getAlphabet(alphabet)], length, count, separator);
55
55
  const funcCall: DG.FuncCall = detectFunc.prepare({col: col});
@@ -182,7 +182,10 @@ MWRSWY-CKHP`;
182
182
  const df: DG.DataFrame = await readFile(samples[key]);
183
183
  // await grok.data.detectSemanticTypes(df);
184
184
  return df;
185
- })();
185
+ })().catch((err: any) => {
186
+ delete _samplesDfs[key];
187
+ throw err;
188
+ });
186
189
  }
187
190
  return _samplesDfs[key];
188
191
  };
@@ -27,6 +27,7 @@ import * as C from './constants';
27
27
  import {_package, getBioLib} from '../package';
28
28
  import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
29
29
  import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
30
+ import {errInfo} from './err-info';
30
31
 
31
32
 
32
33
  type TempType = { [tagName: string]: any };
@@ -136,7 +137,12 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
136
137
 
137
138
  // TODO: Store temp data to GridColumn
138
139
  // Now the renderer requires data frame table Column underlying GridColumn
139
- const grid = gridCell.grid;
140
+ let grid: DG.Grid | undefined = undefined;
141
+ try { grid = gridCell.grid; } catch (err: any) {
142
+ grid = undefined;
143
+ const [errMsg, errStack] = errInfo(err);
144
+ _package.logger.error(errMsg, undefined, errStack);
145
+ }
140
146
  const tableCol: DG.Column = gridCell.cell.column;
141
147
  if (!grid || !tableCol) return;
142
148
 
@@ -0,0 +1,28 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+
5
+ export function errMsg(err: any): string {
6
+ if (typeof err === 'string' || err instanceof String)
7
+ return err as string;
8
+ else if (err.constructor.name === 'StateError')
9
+ return err['message'];
10
+ else if (err.constructor.name === 'StateError' && '$thrownJsError' in err)
11
+ return errMsg(err['$thrownJsError']);
12
+ else if (err instanceof Error)
13
+ return (err as Error).message;
14
+ else
15
+ return err.toString();
16
+ }
17
+
18
+ export function errStack(err: any): string | undefined {
19
+ if (err instanceof Error)
20
+ return err.stack;
21
+ else if (err.constructor.name === 'StateError' && '$thrownJsError' in err)
22
+ return errStack(err['$thrownJsError']);
23
+ return undefined;
24
+ }
25
+
26
+ export function errInfo(err: any): [string, string | undefined] {
27
+ return [errMsg(err), errStack(err)];
28
+ }
@@ -152,10 +152,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
152
152
  }
153
153
 
154
154
  override detach() {
155
- if (this.setDataInProgress) return;
156
155
  const superDetach = super.detach.bind(this);
157
156
  this.detachPromise = this.detachPromise.then(async () => { // detach
158
157
  await this.viewPromise;
158
+ if (this.setDataInProgress) return; // check setDataInProgress synced
159
159
  if (this.viewed) {
160
160
  await this.destroyView('detach');
161
161
  this.viewed = false;
@@ -166,7 +166,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
166
166
 
167
167
  override onTableAttached() {
168
168
  super.onTableAttached();
169
- this.setData(this.dataFrame, this.regions);
169
+ this.setData(this.regions);
170
170
  }
171
171
 
172
172
  public override onPropertyChanged(property: DG.Property | null): void {
@@ -180,7 +180,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
180
180
  switch (property.name) {
181
181
  case PROPS.regionTypes:
182
182
  case PROPS.chains:
183
- this.setData(this.dataFrame, this.regions);
183
+ this.setData(this.regions);
184
184
  break;
185
185
  }
186
186
 
@@ -211,42 +211,66 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
211
211
  break;
212
212
 
213
213
  default:
214
- this.setData(this.dataFrame, this.regions); // onPropertyChanged
214
+ this.setData(this.regions); // onPropertyChanged
215
215
  break;
216
216
  }
217
217
  }
218
218
 
219
219
  // -- Data --
220
220
 
221
+ // private static viewerCount = 0;
222
+ // private viewerId: number = ++VdRegionsViewer.viewerCount;
223
+ // private setDataInCount: number = 0;
224
+
221
225
  // TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
222
- public setData(mlbDf: DG.DataFrame, regions: VdRegion[]) {
223
- if (!this.setDataInProgress) this.setDataInProgress = true; else return;
224
- _package.logger.debug('Bio: VdRegionsViewer.setData()');
226
+ public setData(regions: VdRegion[]) {
227
+ // const setDataInId = ++this.setDataInCount;
228
+ _package.logger.debug('Bio: VdRegionsViewer.setData(), in, ' +
229
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
230
+ `regions.length = ${regions.length}`
231
+ );
225
232
 
226
233
  this.viewPromise = this.viewPromise.then(async () => { // setData
227
- if (this.viewed) {
228
- await this.destroyView('setData');
229
- this.viewed = false;
230
- }
231
- }).then(async () => {
232
- await this.detachPromise;
233
- // Wait whether this.dataFrame assigning has called detach() before continue set data and build view
234
-
235
- // -- Data --
236
- this.regions = regions;
237
- if (this.dataFrame.dart !== mlbDf.dart) this.dataFrame = mlbDf; // causes detach and onTableAttached
238
- }).then(async () => {
239
- if (!this.viewed) {
240
- await this.buildView('setData');
241
- this.viewed = true;
234
+ // _package.logger.debug('Bio: VdRegionsViewer.setData(), in sync, ' +
235
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
236
+ // `regions.length = ${regions.length}`);
237
+ if (!this.setDataInProgress) this.setDataInProgress = true; else return; // check setDataInProgress synced
238
+ // _package.logger.debug('Bio: VdRegionsViewer.setData(), start, ' +
239
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
240
+ // `regions.length = ${regions.length}`);
241
+ try {
242
+ if (this.viewed) {
243
+ // _package.logger.debug('Bio: VdRegionsViewer.setData(), destroyView, ' +
244
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
245
+ // `regions.length = ${regions.length}`);
246
+ await this.destroyView('setData');
247
+ this.viewed = false;
248
+ }
249
+
250
+ await this.detachPromise;
251
+ // Wait whether this.dataFrame assigning has called detach() before continue set data and build view
252
+
253
+ // -- Data --
254
+ this.regions = regions;
255
+
256
+ if (!this.viewed) {
257
+ // _package.logger.debug('Bio: VdRegionsViewer.setData(), buildView, ' +
258
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
259
+ // `regions.length = ${regions.length}`);
260
+ await this.buildView('setData');
261
+ this.viewed = true;
262
+ }
263
+ } catch (err: any) {
264
+ const errMsg = err instanceof Error ? err.message : err.toString();
265
+ const stack = err instanceof Error ? err.stack : undefined;
266
+ grok.shell.error(errMsg);
267
+ _package.logger.error(errMsg, undefined, stack);
268
+ } finally {
269
+ // _package.logger.debug('Bio: VdRegionsViewer.setData(), finally, ' +
270
+ // `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
271
+ // `regions.length = ${regions.length}`);
272
+ this.setDataInProgress = false;
242
273
  }
243
- }).catch((err: any) => {
244
- const errMsg = err instanceof Error ? err.message : err.toString();
245
- const stack = err instanceof Error ? err.stack : undefined;
246
- grok.shell.error(errMsg);
247
- _package.logger.error(errMsg, undefined, stack);
248
- }).finally(() => {
249
- this.setDataInProgress = false;
250
274
  });
251
275
  }
252
276
 
@@ -266,7 +290,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
266
290
  private async destroyView(purpose: string): Promise<void> {
267
291
  // TODO: Unsubscribe from and remove all view elements
268
292
  _package.logger.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
269
- `purpose = '${purpose}'`);
293
+ `purpose = '${purpose}', this.regions.length = ${this.regions.length}`);
270
294
  if (this.filterSourceInput) {
271
295
  //
272
296
  ui.empty(this.filterSourceInput.root);
@@ -284,7 +308,8 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
284
308
  }
285
309
 
286
310
  private async buildView(purpose: string): Promise<void> {
287
- _package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` + `purpose = '${purpose}'`);
311
+ _package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` +
312
+ `purpose = '${purpose}', this.regions.length = ${this.regions.length}`);
288
313
 
289
314
  const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
290
315
  const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
@@ -18,6 +18,8 @@ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
18
18
  import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
19
19
  import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
20
20
 
21
+ import {errInfo} from '../utils/err-info';
22
+
21
23
  import {_package} from '../package';
22
24
 
23
25
  declare global {
@@ -403,25 +405,30 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
403
405
  // -- Data --
404
406
 
405
407
  setData(): void {
406
- if (!this.setDataInProgress) this.setDataInProgress = true; else return;
407
- _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.setData() `);
408
-
408
+ _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.setData(), in`);
409
409
  this.viewPromise = this.viewPromise.then(async () => { // setData
410
- if (this.viewed) {
411
- await this.destroyView();
412
- this.viewed = false;
413
- }
414
- }).then(async () => {
415
- await this.detachPromise;
416
-
417
- this.updateSeqCol();
418
- }).then(async () => {
419
- if (!this.viewed) {
420
- await this.buildView();
421
- this.viewed = true;
410
+ if (!this.setDataInProgress) this.setDataInProgress = true; else return; // check setDataInProgress synced
411
+ try {
412
+ if (this.viewed) {
413
+ this.renderRequestSub.unsubscribe();
414
+ await this.destroyView();
415
+ this.viewed = false;
416
+ }
417
+
418
+ await this.detachPromise;
419
+ this.updateSeqCol();
420
+
421
+ if (!this.viewed) {
422
+ await this.buildView(); //requests rendering
423
+ this.viewed = true;
424
+ }
425
+ } catch (err: any) {
426
+ const [errMsg, errStack] = errInfo(err);
427
+ grok.shell.error(errMsg);
428
+ _package.logger.error(errMsg, undefined, errStack);
429
+ } finally {
430
+ this.setDataInProgress = false;
422
431
  }
423
- }).finally(() => {
424
- this.setDataInProgress = false;
425
432
  });
426
433
  }
427
434
 
@@ -450,6 +457,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
450
457
  const dataFrameTxt: string = this.dataFrame ? 'data' : 'null';
451
458
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView( dataFrame = ${dataFrameTxt} ) start`);
452
459
  const dpr = window.devicePixelRatio;
460
+ this.viewSubs.push(DG.debounce(this.renderRequest)
461
+ .subscribe(this.renderRequestOnDebounce.bind(this)));
453
462
 
454
463
  this.helpUrl = '/help/visualize/viewers/web-logo.md';
455
464
 
@@ -508,7 +517,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
508
517
  private rootOnSizeChanged(): void {
509
518
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.rootOnSizeChanged(), start `);
510
519
 
511
- this.render(WlRenderLevel.Layout, 'rootOnSizeChanged').then(() => {});
520
+ this.render(WlRenderLevel.Layout, 'rootOnSizeChanged');
512
521
  }
513
522
 
514
523
  /** Assigns {@link seqCol} and {@link cp} based on {@link sequenceColumnName} and calls {@link render}().
@@ -571,7 +580,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
571
580
  this.positionNames.includes(this.endPositionName)) ?
572
581
  this.positionNames.indexOf(this.endPositionName) : (maxLength - 1);
573
582
 
574
- this.render(WlRenderLevel.Freqs, 'updatePositions').then(() => {});
583
+ this.render(WlRenderLevel.Freqs, 'updatePositions');
575
584
  }
576
585
 
577
586
  private getFilter(): DG.BitSet {
@@ -801,11 +810,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
801
810
  case PROPS.verticalAlignment:
802
811
  case PROPS.positionMargin:
803
812
  case PROPS.positionMarginState:
804
- this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`).then(() => {});
813
+ this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
805
814
  break;
806
815
 
807
816
  case PROPS.backgroundColor:
808
- this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`).then(() => {});
817
+ this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
809
818
  break;
810
819
  }
811
820
  }
@@ -822,12 +831,12 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
822
831
 
823
832
  /** Remove all handlers when table is a detach */
824
833
  public override async detach() {
825
- if (this.setDataInProgress) return;
826
834
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.detach(), `);
827
835
 
828
836
  const superDetach = super.detach.bind(this);
829
837
  this.detachPromise = this.detachPromise.then(async () => { // detach
830
838
  await this.viewPromise;
839
+ if (this.setDataInProgress) return; // check setDataInProgress synced
831
840
  if (this.viewed) {
832
841
  await this.destroyView();
833
842
  this.viewed = false;
@@ -888,12 +897,24 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
888
897
  }
889
898
 
890
899
  /** default value of RecalcLevel.Freqs is for recalc from the scratch at the beginning */
891
- private renderLevelRequested: WlRenderLevel = WlRenderLevel.Freqs;
900
+ private requestedRenderLevel: WlRenderLevel = WlRenderLevel.Freqs;
901
+ private readonly renderRequest: Subject<WlRenderLevel> = new Subject<WlRenderLevel>();
902
+ private renderRequestSub: Unsubscribable;
892
903
 
893
904
  /** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
894
- render(recalcLevel: WlRenderLevel, reason: string): Promise<void> {
905
+ render(renderLevel: WlRenderLevel, reason: string): void {
895
906
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>` +
896
- `.render( recalcLevelVal=${recalcLevel}, reason='${reason}' )`);
907
+ `.render( recalcLevelVal=${renderLevel}, reason='${reason}' )`);
908
+ this.requestedRenderLevel = Math.max(this.requestedRenderLevel, renderLevel);
909
+ this.renderRequest.next(this.requestedRenderLevel);
910
+ }
911
+
912
+ /** Render WebLogo sensitive to changes in params of rendering
913
+ *@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
914
+ */
915
+ protected async renderInt(renderLevel: WlRenderLevel): Promise<void> {
916
+ _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( renderLevel=${renderLevel} ), ` +
917
+ `start `);
897
918
 
898
919
  /** Calculate freqs of monomers */
899
920
  const calculateFreqsInt = (): void => {
@@ -960,85 +981,70 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
960
981
  this._onLayoutCalculated.next();
961
982
  };
962
983
 
963
- /** Render WebLogo sensitive to changes in params of rendering
964
- *@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
965
- */
966
- const renderInt = (recalcLevel: WlRenderLevel) => {
967
- _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), ` +
968
- `start `);
969
- if (this.msgHost) {
970
- if (this.seqCol && !this.cp) {
971
- this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
972
- this.msgHost!.style.display = '';
973
- } else {
974
- this.msgHost!.style.display = 'none';
975
- }
984
+ if (this.msgHost) {
985
+ if (this.seqCol && !this.cp) {
986
+ this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
987
+ this.msgHost!.style.display = '';
988
+ } else {
989
+ this.msgHost!.style.display = 'none';
976
990
  }
991
+ }
977
992
 
978
- if (!this.seqCol || !this.dataFrame || !this.cp || this.host == null || this.slider == null)
979
- return;
993
+ if (!this.seqCol || !this.dataFrame || !this.cp || this.host == null || this.slider == null)
994
+ return;
980
995
 
981
- const dpr: number = window.devicePixelRatio;
982
- /** 0 is for no position labels */
983
- const positionLabelsHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT : 0;
984
- if (recalcLevel >= WlRenderLevel.Freqs) calculateFreqsInt();
985
- this.calcLayout(dpr); // after _skipEmptyPositions
986
- if (this.positions.length === 0 || this.startPosition === -1 || this.endPosition === -1) return;
987
- if (recalcLevel >= WlRenderLevel.Layout) calculateLayoutInt(window.devicePixelRatio, positionLabelsHeight);
988
-
989
- const g = this.canvas.getContext('2d');
990
- if (!g) return;
991
-
992
- const length: number = this.Length;
993
- g.resetTransform();
994
- g.fillStyle = intToHtmlA(this.backgroundColor);
995
- g.fillRect(0, 0, this.canvas.width, this.canvas.height);
996
- g.textBaseline = this.textBaseline;
997
-
998
- //#region Plot positionNames
999
- const positionFontSize = 10 * dpr;
1000
- g.resetTransform();
1001
- g.fillStyle = 'black';
1002
- g.textAlign = 'center';
1003
- g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
1004
- const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
1005
- const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
1006
- (this._positionWidth * dpr - 2) / posNameMaxWidth;
1007
-
1008
- if (positionLabelsHeight > 0) {
1009
- renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
1010
- this.positions, this.slider.min, this.slider.max);
1011
- }
1012
- //#endregion Plot positionNames
1013
- const fontStyle = '16px Roboto, Roboto Local, sans-serif';
1014
- // Hacks to scale uppercase characters to target rectangle
1015
- const uppercaseLetterAscent = 0.25;
1016
- const uppercaseLetterHeight = 12.2;
1017
- for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
1018
- this.positions[jPos].render(g, (m) => { return this.unitsHandler!.isGap(m); },
1019
- fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
1020
- /* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
1021
- }
996
+ const dpr: number = window.devicePixelRatio;
997
+ /** 0 is for no position labels */
998
+ const positionLabelsHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT : 0;
999
+ if (renderLevel >= WlRenderLevel.Freqs) calculateFreqsInt();
1000
+ this.calcLayout(dpr); // after _skipEmptyPositions
1001
+ if (this.positions.length === 0 || this.startPosition === -1 || this.endPosition === -1) return;
1002
+ if (renderLevel >= WlRenderLevel.Layout) calculateLayoutInt(window.devicePixelRatio, positionLabelsHeight);
1022
1003
 
1023
- _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end`);
1024
- };
1004
+ const g = this.canvas.getContext('2d');
1005
+ if (!g) return;
1025
1006
 
1026
- this.renderLevelRequested = Math.max(this.renderLevelRequested, recalcLevel);
1027
- return new Promise<void>((resolve, reject) => {
1028
- window.setTimeout(() => {
1029
- try {
1030
- if (this.renderLevelRequested > WlRenderLevel.None) {
1031
- try {
1032
- renderInt(this.renderLevelRequested);
1033
- } finally {
1034
- this.renderLevelRequested = WlRenderLevel.None;
1035
- }
1036
- }
1037
- } catch (err: any) {
1038
- reject(err);
1039
- }
1040
- }, 0 /* next event cycle */);
1041
- });
1007
+ const length: number = this.Length;
1008
+ g.resetTransform();
1009
+ g.fillStyle = intToHtmlA(this.backgroundColor);
1010
+ g.fillRect(0, 0, this.canvas.width, this.canvas.height);
1011
+ g.textBaseline = this.textBaseline;
1012
+
1013
+ //#region Plot positionNames
1014
+ const positionFontSize = 10 * dpr;
1015
+ g.resetTransform();
1016
+ g.fillStyle = 'black';
1017
+ g.textAlign = 'center';
1018
+ g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
1019
+ const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
1020
+ const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
1021
+ (this._positionWidth * dpr - 2) / posNameMaxWidth;
1022
+
1023
+ if (positionLabelsHeight > 0) {
1024
+ renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
1025
+ this.positions, this.slider.min, this.slider.max);
1026
+ }
1027
+ //#endregion Plot positionNames
1028
+ const fontStyle = '16px Roboto, Roboto Local, sans-serif';
1029
+ // Hacks to scale uppercase characters to target rectangle
1030
+ const uppercaseLetterAscent = 0.25;
1031
+ const uppercaseLetterHeight = 12.2;
1032
+ for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
1033
+ this.positions[jPos].render(g, (m) => { return this.unitsHandler!.isGap(m); },
1034
+ fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
1035
+ /* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
1036
+ }
1037
+
1038
+ _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${renderLevel} ), end`);
1039
+ }
1040
+
1041
+ private renderRequestOnDebounce(renderLevel: WlRenderLevel): void {
1042
+ this.requestedRenderLevel = WlRenderLevel.None;
1043
+ this.renderInt(renderLevel)
1044
+ .catch((err: any) => {
1045
+ const [errMsg, errStack] = errInfo(err);
1046
+ _package.logger.error(errMsg, undefined, errStack);
1047
+ });
1042
1048
  }
1043
1049
 
1044
1050
  private _lastWidth: number;
@@ -1062,7 +1068,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1062
1068
  };
1063
1069
  _package.logger.debug(
1064
1070
  `Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged( ${JSON.stringify(val)} ), start`);
1065
- this.render(WlRenderLevel.Layout, 'sliderOnValuesChanged').then(() => {});
1071
+ this.render(WlRenderLevel.Layout, 'sliderOnValuesChanged');
1066
1072
  } catch (err: any) {
1067
1073
  const errMsg = errorToConsole(err);
1068
1074
  _package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged() error:\n` + errMsg);
@@ -1075,7 +1081,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1075
1081
  try {
1076
1082
  this.updatePositions();
1077
1083
  if (this.filterSource === FilterSources.Filtered)
1078
- this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged').then(() => {});
1084
+ this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged');
1079
1085
  } catch (err: any) {
1080
1086
  const errMsg = errorToConsole(err);
1081
1087
  _package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n` + errMsg);
@@ -1087,7 +1093,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1087
1093
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()`);
1088
1094
  try {
1089
1095
  if (this.filterSource === FilterSources.Selected)
1090
- this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged').then(() => {});
1096
+ this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged');
1091
1097
  } catch (err: any) {
1092
1098
  const errMsg = errorToConsole(err);
1093
1099
  _package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n` + errMsg);