@datagrok/bio 2.8.3 → 2.8.6

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.
@@ -8,25 +8,15 @@ import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
8
8
  import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
9
9
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
10
10
  import {
11
- monomerToShort,
12
- pickUpPalette,
13
- pickUpSeqCol,
14
- TAGS as bioTAGS,
11
+ monomerToShort, pickUpPalette, pickUpSeqCol, TAGS as bioTAGS
15
12
  } from '@datagrok-libraries/bio/src/utils/macromolecule';
16
13
  import {
17
- FilterSources,
18
- HorizontalAlignments,
19
- IWebLogoViewer,
20
- PositionHeight,
21
- PositionMarginStates,
22
- positionSeparator,
23
- TAGS as wlTAGS,
24
- VerticalAlignments,
25
- WebLogoProps,
26
- WebLogoPropsDefault,
14
+ FilterSources, HorizontalAlignments, IWebLogoViewer, PositionHeight, PositionMarginStates, positionSeparator,
15
+ TAGS as wlTAGS, VerticalAlignments, WebLogoProps, WebLogoPropsDefault
27
16
  } from '@datagrok-libraries/bio/src/viewers/web-logo';
28
17
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
29
18
  import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
19
+ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
30
20
 
31
21
  import {_package} from '../package';
32
22
 
@@ -153,45 +143,14 @@ export class PositionInfo {
153
143
  }
154
144
 
155
145
  calcScreen(
156
- jPos: number, absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
157
- positionWidthWithMargin: number, positionWidth: number, r: number, axisHeight: number
146
+ posIdx: number, firstVisiblePosIdx: number,
147
+ absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
148
+ positionWidthWithMargin: number, positionWidth: number, dpr: number, axisHeight: number
158
149
  ): void {
159
- const dpr = window.devicePixelRatio;
160
- // const rowCount = this.positions[jPos].rowCount;
161
- // const alphabetSize = this.getAlphabetSize();
162
- // if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
163
- // grok.shell.error('WebLogo: alphabet is undefined.');
164
- //
165
- // const alphabetSizeLog = Math.log2(alphabetSize);
166
- // const maxHeight = (this.positionHeight == PositionHeight.Entropy) ?
167
- // (absoluteMaxHeight * (alphabetSizeLog - (this.positions[jPos].sumForHeightCalc)) / alphabetSizeLog) :
168
- // absoluteMaxHeight;
169
- //
170
- // let y: number = this.axisHeight * r + (absoluteMaxHeight - maxHeight - 1);
171
- //
172
- // const entries = Object.entries(freq).sort((a, b) => {
173
- // if (a[0] !== '-' && b[0] !== '-')
174
- // return b[1].count - a[1].count;
175
- // else if (a[0] === '-' && b[0] === '-')
176
- // return 0;
177
- // else if (a[0] === '-')
178
- // return -1;
179
- // else /* (b[0] === '-') */
180
- // return +1;
181
- // });
182
- // for (const entry of entries) {
183
- // const pmInfo: PositionMonomerInfo = entry[1];
184
- // // const m: string = entry[0];
185
- // const h: number = maxHeight * pmInfo.count / rowCount;
186
- //
187
- // pmInfo.bounds = new DG.Rect(jPos * this.positionWidthWithMargin, y, this._positionWidth, h);
188
- // y += h;
189
- // }
190
-
191
150
  const maxHeight = (heightMode == PositionHeight.Entropy) ?
192
151
  (absoluteMaxHeight * (alphabetSizeLog - (this.sumForHeightCalc)) / alphabetSizeLog) :
193
152
  absoluteMaxHeight;
194
- let y: number = axisHeight * r + (absoluteMaxHeight - maxHeight - 1);
153
+ let y: number = axisHeight * dpr + (absoluteMaxHeight - maxHeight - 1);
195
154
 
196
155
  const entries = Object.entries(this._freqs)
197
156
  .sort((a, b) => {
@@ -209,20 +168,21 @@ export class PositionInfo {
209
168
  // const m: string = entry[0];
210
169
  const h: number = maxHeight * pmInfo.count / this.rowCount;
211
170
 
212
- pmInfo.bounds = new DG.Rect(jPos * dpr * positionWidthWithMargin, y, positionWidth * dpr, h);
171
+ pmInfo.bounds = new DG.Rect(
172
+ (posIdx - firstVisiblePosIdx) * dpr * positionWidthWithMargin, y,
173
+ positionWidth * dpr, h);
213
174
  y += h;
214
175
  }
215
176
  }
216
177
 
217
178
  render(g: CanvasRenderingContext2D,
218
- fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number,
219
- positionWidthWithMargin: number, firstVisibleIndex: number, cp: SeqPalette
179
+ fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number, cp: SeqPalette
220
180
  ) {
221
181
  for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
222
182
  if (monomer !== '-') {
223
183
  const monomerTxt = monomerToShort(monomer, 5);
224
184
  const b = pmInfo.bounds!;
225
- const left = b.left - positionWidthWithMargin * firstVisibleIndex;
185
+ const left = b.left;
226
186
 
227
187
  g.resetTransform();
228
188
  g.strokeStyle = 'lightgray';
@@ -271,6 +231,7 @@ export enum PROPS {
271
231
  fitArea = 'fitArea',
272
232
  minHeight = 'minHeight',
273
233
  maxHeight = 'maxHeight',
234
+ showPositionLabels = 'showPositionLabels',
274
235
  positionMarginState = 'positionMarginState',
275
236
  positionMargin = 'positionMargin',
276
237
 
@@ -280,15 +241,18 @@ export enum PROPS {
280
241
 
281
242
  const defaults: WebLogoProps = WebLogoPropsDefault;
282
243
 
283
- enum RecalcLevel {
244
+ enum WlRenderLevel {
284
245
  None = 0,
246
+ Render = 1,
285
247
  Layout = 1,
286
248
  Freqs = 2,
287
249
  }
288
250
 
251
+ const POSITION_LABELS_HEIGHT: number = 12;
252
+
289
253
  export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
290
254
  public static residuesSet = 'nucleotides';
291
- private static viewerCount: number = -1;
255
+ private static viewerCount: number = 0;
292
256
 
293
257
  private viewed: boolean = false;
294
258
 
@@ -302,34 +266,35 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
302
266
  private host?: HTMLDivElement;
303
267
  private msgHost?: HTMLElement;
304
268
  private canvas: HTMLCanvasElement;
305
- private slider: DG.RangeSlider;
269
+ private readonly slider: DG.RangeSlider;
306
270
  private readonly textBaseline: CanvasTextBaseline;
307
271
 
308
- private axisHeight: number = 12;
309
-
310
272
  private seqCol: DG.Column<string> | null = null;
311
- // private maxLength: number = 100;
312
273
  private positions: PositionInfo[] = [];
313
274
 
314
- private rowsNull: number = 0;
315
275
  private visibleSlider: boolean = false;
316
276
  private allowResize: boolean = true;
317
277
  private turnOfResizeForOneSetValue: boolean = false;
318
278
 
319
- // Viewer's properties (likely they should be public so that they can be set outside)
279
+ // Viewer's properties (likely they should be public)
320
280
  // -- Data --
321
281
  public sequenceColumnName: string | null;
322
282
  public skipEmptySequences: boolean;
323
283
  public skipEmptyPositions: boolean;
324
284
 
325
285
  // -- Style --
326
- private _positionWidth: number;
327
- public positionWidth: number;
286
+ /** Gets value from properties or {@link setOptions} */ public positionWidth: number;
287
+ /** Scaled value to fit area */ private _positionWidth: number;
288
+ private _positionWidthWithMargin: number;
289
+ public get positionWidthWithMargin(): number { return this._positionWidthWithMargin; }
290
+
328
291
  public minHeight: number;
329
292
  public backgroundColor: number = 0xFFFFFFFF;
330
293
  public maxHeight: number;
294
+ public showPositionLabels: boolean;
331
295
  public positionMarginState: PositionMarginStates;
332
- public positionMargin: number = 0;
296
+ /** Gets value from properties or setOptions */ public positionMargin: number = 0;
297
+ /** Scaled value to fit area */ private _positionMargin: number;
333
298
  public startPositionName: string | null;
334
299
  public endPositionName: string | null;
335
300
  public fixWidth: boolean;
@@ -337,7 +302,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
337
302
  public horizontalAlignment: HorizontalAlignments;
338
303
  public fitArea: boolean;
339
304
  public shrinkEmptyTail: boolean;
340
- public positionHeight: string;
305
+ public positionHeight: PositionHeight;
341
306
 
342
307
  // -- Behavior --
343
308
  public filterSource: FilterSources;
@@ -362,7 +327,9 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
362
327
  return res;
363
328
  }
364
329
 
365
- /** For startPosition equals to endPosition Length is 1 */
330
+ /** Full length of {@link positions}.
331
+ * Inclusive, for startPosition equals to endPosition Length is 1
332
+ */
366
333
  public get Length(): number {
367
334
  if (this.skipEmptyPositions)
368
335
  return this.positions.length;
@@ -370,42 +337,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
370
337
  return this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
371
338
  }
372
339
 
373
- /** Calculate new position data basic on {@link positionMarginState} and {@link positionMargin} */
374
- private get positionWidthWithMargin() {
375
- return this._positionWidth + this.positionMarginValue;
376
- }
377
-
378
- private get positionMarginValue() {
379
- if (this.positionMarginState === PositionMarginStates.AUTO &&
380
- this.unitsHandler?.getAlphabetIsMultichar() === true
381
- ) return this.positionMargin;
382
-
383
- if (this.positionMarginState === PositionMarginStates.ON)
340
+ private get positionMarginValue(): number {
341
+ if (this.positionMarginState === PositionMarginStates.AUTO && this.unitsHandler!.getAlphabetIsMultichar() === true)
342
+ return this.positionMargin;
343
+ else if (this.positionMarginState === PositionMarginStates.ON)
384
344
  return this.positionMargin;
385
-
386
- return 0;
387
- }
388
-
389
- /** Count of position rendered for calculations countOfRenderPositions */
390
- private get countOfRenderPositions() {
391
- if (this.host == null)
392
- return 0;
393
-
394
- const r = window.devicePixelRatio;
395
- if (r > 1)
396
- return this.canvasWidthWithRatio / this.positionWidthWithMargin;
397
345
  else
398
- return this.canvas.width / (this.positionWidthWithMargin * r);
399
- }
400
-
401
- private get canvasWidthWithRatio() {
402
- return this.canvas.width * window.devicePixelRatio;
403
- }
404
-
405
-
406
- /** Position of start rendering */
407
- private get firstVisibleIndex(): number {
408
- return (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
346
+ return 0;
409
347
  }
410
348
 
411
349
  constructor() {
@@ -435,7 +373,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
435
373
  this.backgroundColor = this.int(PROPS.backgroundColor, defaults.backgroundColor,
436
374
  {category: PROPS_CATS.STYLE});
437
375
  this.positionHeight = this.string(PROPS.positionHeight, defaults.positionHeight,
438
- {category: PROPS_CATS.STYLE, choices: Object.values(PositionHeight)});
376
+ {category: PROPS_CATS.STYLE, choices: Object.values(PositionHeight)}) as PositionHeight;
439
377
  this._positionWidth = this.positionWidth = this.float(PROPS.positionWidth, defaults.positionWidth,
440
378
  {category: PROPS_CATS.STYLE/* editor: 'slider', min: 4, max: 64, postfix: 'px' */});
441
379
 
@@ -445,13 +383,15 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
445
383
  this.horizontalAlignment = this.string(PROPS.horizontalAlignment, defaults.horizontalAlignment,
446
384
  {category: PROPS_CATS.LAYOUT, choices: Object.values(HorizontalAlignments)}) as HorizontalAlignments;
447
385
  this.fixWidth = this.bool(PROPS.fixWidth, defaults.fixWidth,
448
- {category: PROPS_CATS.LAYOUT});
386
+ {category: PROPS_CATS.LAYOUT, userEditable: false});
449
387
  this.fitArea = this.bool(PROPS.fitArea, defaults.fitArea,
450
388
  {category: PROPS_CATS.LAYOUT});
451
389
  this.minHeight = this.float(PROPS.minHeight, defaults.minHeight,
452
390
  {category: PROPS_CATS.LAYOUT/*, editor: 'slider', min: 25, max: 250, postfix: 'px'*/});
453
391
  this.maxHeight = this.float(PROPS.maxHeight, defaults.maxHeight,
454
392
  {category: PROPS_CATS.LAYOUT/*, editor: 'slider', min: 25, max: 500, postfix: 'px'*/});
393
+ this.showPositionLabels = this.bool(PROPS.showPositionLabels, defaults.showPositionLabels,
394
+ {category: PROPS_CATS.LAYOUT});
455
395
  this.positionMarginState = this.string(PROPS.positionMarginState, defaults.positionMarginState,
456
396
  {category: PROPS_CATS.LAYOUT, choices: Object.values(PositionMarginStates)}) as PositionMarginStates;
457
397
  let defaultValueForPositionMargin = 0;
@@ -466,7 +406,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
466
406
  const style: DG.SliderOptions = {style: 'barbell'};
467
407
  this.slider = ui.rangeSlider(0, 100, 0, 20, false, style);
468
408
  this.canvas = ui.canvas();
409
+ this.canvas.classList.value = 'bio-wl-canvas';
469
410
  this.canvas.style.width = '100%';
411
+
412
+ /* this.root.style.background = '#FFEEDD'; */
470
413
  }
471
414
 
472
415
  // -- Data --
@@ -489,7 +432,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
489
432
 
490
433
  private viewSubs: Unsubscribable[] = [];
491
434
 
492
- private destroyView() {
435
+ private async destroyView(): Promise<void> {
493
436
  for (const sub of this.viewSubs) sub.unsubscribe();
494
437
  this.viewSubs = [];
495
438
 
@@ -505,13 +448,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
505
448
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.destroyView() end`);
506
449
  }
507
450
 
508
- private buildView() {
451
+ private async buildView(): Promise<void> {
509
452
  const dataFrameTxt: string = this.dataFrame ? 'data' : 'null';
510
453
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView( dataFrame = ${dataFrameTxt} ) start`);
454
+ const dpr = window.devicePixelRatio;
511
455
 
512
456
  this.helpUrl = '/help/visualize/viewers/web-logo.md';
513
457
 
514
- this.msgHost = ui.div('No message');
458
+ this.msgHost = ui.div('No message', {classes: 'bio-wl-msg'});
515
459
  this.msgHost.style.display = 'none';
516
460
 
517
461
  this.canvas = ui.canvas();
@@ -525,19 +469,20 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
525
469
 
526
470
  this.visibleSlider = false;
527
471
 
528
- this.host = ui.div([this.msgHost, this.canvas]);
529
-
530
- this.host.style.justifyContent = 'center';
531
- this.host.style.alignItems = 'center';
532
- this.host.style.position = 'relative';
533
- this.host.style.setProperty('overflow', 'hidden', 'important');
472
+ this.host = ui.div([this.msgHost, this.canvas],
473
+ {
474
+ classes: 'bio-wl-host',
475
+ style: {
476
+ display: 'flex',
477
+ flexDirection: 'row',
478
+ /** For alignContent to have an effect */ flexWrap: 'wrap',
479
+ /* backgroundColor: '#EEFFEE' */
480
+ }
481
+ });
534
482
 
535
483
  this.root.append(this.host);
536
484
  this.root.append(this.slider.root);
537
485
 
538
- this.updateSlider();
539
- this.render(RecalcLevel.Freqs, 'init');
540
-
541
486
  if (!!this.error) {
542
487
  this.msgHost!.innerText = this.error.message;
543
488
  ui.tooltip.bind(this.msgHost!, this.error.stack);
@@ -557,6 +502,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
557
502
  this.viewSubs.push(
558
503
  fromEvent<WheelEvent>(this.canvas, 'wheel').subscribe(this.canvasOnWheel.bind(this)));
559
504
 
505
+ await this.render(WlRenderLevel.Freqs, 'buildView');
560
506
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView() end`);
561
507
  }
562
508
 
@@ -564,8 +510,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
564
510
  private rootOnSizeChanged(): void {
565
511
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.rootOnSizeChanged(), start `);
566
512
 
567
- this.updateSlider();
568
- this.render(RecalcLevel.Layout, 'rootOnSizeChanged');
513
+ this.render(WlRenderLevel.Layout, 'rootOnSizeChanged').then(() => {});
569
514
  }
570
515
 
571
516
  /** Assigns {@link seqCol} and {@link cp} based on {@link sequenceColumnName} and calls {@link render}().
@@ -583,8 +528,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
583
528
  const separator: string = this.seqCol!.getTag(bioTAGS.separator);
584
529
  this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
585
530
 
586
- this.updatePositions();
587
531
  this.cp = pickUpPalette(this.seqCol);
532
+ this.updatePositions();
588
533
  this.error = null;
589
534
  } catch (err: any) {
590
535
  this.seqCol = null;
@@ -606,8 +551,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
606
551
  /** Updates {@link positionNames} and calculates {@link startPosition} and {@link endPosition}.
607
552
  */
608
553
  private updatePositions(): void {
609
- if (!this.seqCol)
610
- return;
554
+ if (!this.seqCol) return;
611
555
 
612
556
  const dfFilter = this.filterSource === FilterSources.Filtered ? this.dataFrame.filter :
613
557
  this.dataFrame.selection;
@@ -629,68 +573,191 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
629
573
  this.endPosition = (this.endPositionName && this.positionNames &&
630
574
  this.positionNames.includes(this.endPositionName)) ?
631
575
  this.positionNames.indexOf(this.endPositionName) : (maxLength - 1);
632
- }
633
576
 
634
- private get widthArea() {
635
- return this.Length * this.positionWidth / window.devicePixelRatio;
577
+ this.render(WlRenderLevel.Freqs, 'updatePositions').then(() => {});
636
578
  }
637
579
 
638
- private get heightArea() {
639
- return Math.min(this.maxHeight, Math.max(this.minHeight, this.root.clientHeight));
580
+ setSliderVisibility(visible: boolean): void {
581
+ if (visible) {
582
+ this.slider.root.style.display = 'inherit';
583
+ this.visibleSlider = true;
584
+ } else {
585
+ this.slider.root.style.display = 'none';
586
+ this.visibleSlider = false;
587
+ }
640
588
  }
641
589
 
642
- private get xScale() {
643
- return this.widthArea > 0 ? (this.root.clientWidth - this.Length * this.positionMarginValue) / this.widthArea : 0;
644
- }
590
+ /** Updates {@link host}, {@link canvas}, {@link slider}.
591
+ * Calls {@link render} with {@link WlRenderLevel.Layout}
592
+ */
593
+ private calcLayout(dpr: number): void {
594
+ if (!this.host || !this.canvas || !this.slider) return;
595
+ if (this.root.clientHeight === 0 || this.root.clientWidth === 0) return;
596
+
597
+ this.host.classList.remove('bio-wl-fixWidth', 'bio-wl-fitArea');
598
+ this.canvas.classList.remove('bio-wl-fixWidth', 'bio-wl-fitArea');
645
599
 
646
- private get yScale() {
647
- return this.root.clientHeight / this.heightArea;
600
+ this._positionWidth = this.positionWidth;
601
+ this._positionMargin = this.positionMargin;
602
+ this._positionWidthWithMargin = this._positionWidth + this.positionMarginValue;
603
+
604
+ if (this.fixWidth)
605
+ this.calcLayoutFixWidth(dpr);
606
+ else if (this.fitArea)
607
+ this.calcLayoutFitArea(dpr);
608
+ else
609
+ this.calcLayoutNoFitArea(dpr);
648
610
  }
649
611
 
650
- private checkIsHideSlider(): boolean {
651
- let showSliderWithFitArea = true;
652
- const minScale = Math.min(this.xScale, this.yScale);
612
+ /** */
613
+ private calcLayoutFixWidth(dpr: number): void {
614
+ if (!this.host || !this.canvas || !this.slider) return; // for es-lint
615
+
616
+ this.host.classList.add('bio-wl-fixWidth');
617
+ this.canvas.classList.add('bio-wl-fitArea');
618
+
619
+ const areaWidth: number = this._positionWidthWithMargin * this.Length;
620
+ const areaHeight: number = Math.min(Math.max(this.minHeight, this.root.clientHeight), this.maxHeight);
621
+
622
+ this.host.style.justifyContent = HorizontalAlignments.LEFT;
623
+ this.host.style.removeProperty('margin-left');
624
+ this.host.style.removeProperty('margin-top');
625
+
626
+ this.host.style.width = this.canvas.style.width = `${areaWidth}px`;
627
+ this.host.style.height = this.canvas.style.height = `${areaHeight}px`;
628
+ this.host.style.left = this.canvas.style.left = '0';
629
+ this.host.style.top = this.canvas.style.top = '0';
630
+ this.host.style.setProperty('overflow', 'hidden', 'important');
631
+
632
+ this.slider.root.style.display = 'none';
653
633
 
654
- if (((minScale == this.xScale) || (minScale <= 1)) && (this.fitArea))
655
- showSliderWithFitArea = false;
634
+ this.slider.setValues(0, this.Length - 1, 0, this.Length - 1);
656
635
 
657
- return ((this.fixWidth || Math.ceil(this.canvas.width / this.positionWidthWithMargin) >= this.Length) ||
658
- (showSliderWithFitArea));
636
+ this.canvas.width = areaWidth * dpr;
637
+ this.canvas.height = areaHeight * dpr;
659
638
  }
660
639
 
661
- setSliderVisibility(visible: boolean): void {
662
- if (visible) {
663
- this.slider.root.style.display = 'inherit';
664
- this.visibleSlider = true;
640
+ private calcLayoutNoFitArea(dpr: number): void {
641
+ if (!this.host || !this.canvas || !this.slider) return; // for es-lint
642
+
643
+ const areaWidth: number = this._positionWidthWithMargin * this.Length;
644
+ const areaHeight: number = Math.min(Math.max(this.minHeight, this.root.clientHeight), this.maxHeight);
645
+
646
+ const height = areaHeight;
647
+ const width = Math.min(this.root.clientWidth, areaWidth);
648
+
649
+ this.canvas.style.width = `${width}px`;
650
+ this.canvas.style.height = `${height}px`;
651
+ this.host.style.width = `${width}px`;
652
+ this.host.style.height = `${this.root.clientHeight}px`;
653
+
654
+ // host style flex-direction: row;
655
+ this.host.style.justifyContent = this.horizontalAlignment;
656
+ this.host.style.alignContent =
657
+ this.verticalAlignment === VerticalAlignments.TOP ? 'start' :
658
+ this.verticalAlignment === VerticalAlignments.MIDDLE ? 'center' :
659
+ this.verticalAlignment === VerticalAlignments.BOTTOM ? 'end' :
660
+ 'inherit';
661
+
662
+ if (this.root.clientHeight < this.minHeight) {
663
+ this.host.style.alignContent = 'start'; /* For vertical scroller to work properly */
664
+ this.host.style.width = `${width + 6}px`; /* */
665
+ }
666
+
667
+ this.host.style.width = `${this.host}px`;
668
+
669
+ const sliderVisibility = areaWidth > width;
670
+ this.setSliderVisibility(sliderVisibility);
671
+ if (sliderVisibility) {
672
+ this.slider.root.style.removeProperty('display');
673
+ this.host.style.justifyContent = 'left'; /* For horizontal scroller to prevent */
674
+ this.host.style.height = `${this.root.clientHeight - this.slider.root.offsetHeight}px`;
675
+ this.slider.root.style.top = `${this.host.offsetHeight}px`;
676
+
677
+ let newMin = Math.min(Math.max(0, this.slider.min), this.Length - 0.001);
678
+ let newMax = Math.min(Math.max(0, this.slider.max), this.Length - 0.001);
679
+
680
+ const visibleLength = this.root.clientWidth / this._positionWidthWithMargin;
681
+ newMax = Math.min(Math.max(newMin, 0) + visibleLength, this.Length - 0.001);
682
+ newMin = Math.max(0, Math.min(newMax, this.Length - 0.001) - visibleLength);
683
+
684
+ this.slider.setValues(0, this.Length - 0.001, newMin, newMax);
665
685
  } else {
666
- this.slider.root.style.display = 'none';
667
- this.visibleSlider = false;
686
+ //
687
+ this.slider.setValues(0, this.Length - 0.001, 0, this.Length - 0.001);
668
688
  }
689
+
690
+ this.canvas.width = width * dpr;
691
+ this.canvas.height = height * dpr;
669
692
  }
670
693
 
671
- /** Updates {@link slider}, needed to set slider options and to update slider position. */
672
- private updateSlider(): void {
673
- if (this.checkIsHideSlider())
674
- this.setSliderVisibility(false);
675
- else
676
- this.setSliderVisibility(true);
677
-
678
- if ((this.slider != null) && (this.canvas != null)) {
679
- const diffEndScrollAndSliderMin = Math.max(0,
680
- Math.floor(this.slider.min + this.canvas.width / this.positionWidthWithMargin) - this.Length);
681
- let newMin = Math.floor(this.slider.min - diffEndScrollAndSliderMin);
682
- let newMax = Math.floor(this.slider.min - diffEndScrollAndSliderMin) +
683
- Math.floor(this.canvas.width / this.positionWidthWithMargin);
684
- if (this.checkIsHideSlider()) {
685
- newMin = 0;
686
- newMax = Math.max(newMin, this.Length - 1);
687
- }
688
- this.turnOfResizeForOneSetValue = true;
689
- this.slider.setValues(0, this.Length,
690
- newMin, newMax);
694
+ private calcLayoutFitArea(dpr: number): void {
695
+ if (!this.host || !this.canvas || !this.slider) return; // for es-lint
696
+
697
+ const originalAreaWidthWoMargins: number = this._positionWidth * this.Length;
698
+ const originalAreaHeight: number = Math.min(Math.max(this.minHeight, this.root.clientHeight), this.maxHeight);
699
+
700
+ // TODO: scale
701
+ const xScale = originalAreaWidthWoMargins > 0 ?
702
+ (this.root.clientWidth - this.positionMarginValue * this.Length) / originalAreaWidthWoMargins : 0;
703
+ const yScale = this.root.clientHeight / originalAreaHeight;
704
+ const scale = Math.max(1, Math.min(xScale, yScale));
705
+
706
+ this._positionWidth = this.positionWidth * scale;
707
+ // Do not scale this._positionMargin
708
+ this._positionWidthWithMargin = this._positionWidth + this.positionMarginValue;
709
+ const areaWidth = (this._positionWidth + this.positionMarginValue) * this.Length;
710
+ const areaHeight = scale * originalAreaHeight;
711
+
712
+ const height = areaHeight;
713
+ const width = Math.min(this.root.clientWidth, areaWidth);
714
+
715
+ this.canvas.style.width = `${width}px`;
716
+ this.canvas.style.height = `${height}px`;
717
+ this.host.style.width = `${width}px`;
718
+ this.host.style.height = `${this.root.clientHeight}px`;
719
+
720
+ // host style flex-direction: row;
721
+ this.host.style.justifyContent = this.horizontalAlignment;
722
+ this.host.style.alignContent =
723
+ this.verticalAlignment === VerticalAlignments.TOP ? 'start' :
724
+ this.verticalAlignment === VerticalAlignments.MIDDLE ? 'center' :
725
+ this.verticalAlignment === VerticalAlignments.BOTTOM ? 'end' :
726
+ 'inherit';
727
+
728
+ if (this.root.clientHeight < this.minHeight) {
729
+ this.host.style.alignContent = 'start'; /* For vertical scroller to work properly */
730
+ this.host.style.width = `${width + 6}px`; /* */
731
+ }
732
+
733
+ this.host.style.width = `${this.host}px`;
734
+
735
+ const sliderVisibility = areaWidth > width;
736
+ this.setSliderVisibility(sliderVisibility);
737
+ if (sliderVisibility) {
738
+ this.slider.root.style.removeProperty('display');
739
+ this.host.style.justifyContent = 'left'; /* For horizontal scroller to prevent */
740
+ this.host.style.height = `${this.root.clientHeight - this.slider.root.offsetHeight}px`;
741
+ this.slider.root.style.top = `${this.host.offsetHeight}px`;
742
+
743
+ let newMin = Math.min(Math.max(0, this.slider.min), this.Length - 0.001);
744
+ let newMax = Math.min(Math.max(0, this.slider.max), this.Length - 0.001);
745
+
746
+ const visibleLength = this.root.clientWidth / this._positionWidthWithMargin;
747
+ newMax = Math.min(Math.max(newMin, 0) + visibleLength, this.Length - 0.001);
748
+ newMin = Math.max(0, Math.min(newMax, this.Length - 0.001) - visibleLength);
749
+
750
+ this.slider.setValues(0, this.Length - 0.001, newMin, newMax);
751
+ } else {
752
+ //
753
+ this.slider.setValues(0, this.Length - 0.001, 0, this.Length - 0.001);
691
754
  }
755
+
756
+ this.canvas.width = width * dpr;
757
+ this.canvas.height = height * dpr;
692
758
  }
693
759
 
760
+
694
761
  /** Handler of property change events.
695
762
  * @param {DG.Property} property - property which was changed.
696
763
  */
@@ -698,37 +765,35 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
698
765
  super.onPropertyChanged(property);
699
766
 
700
767
  switch (property.name) {
768
+ case PROPS.sequenceColumnName:
769
+ this.updateSeqCol();
770
+ break;
701
771
  case PROPS.sequenceColumnName:
702
772
  case PROPS.startPositionName:
703
773
  case PROPS.endPositionName:
704
774
  case PROPS.filterSource:
705
- this.updateSeqCol();
706
- break;
707
- case PROPS.positionWidth:
708
- this._positionWidth = this.positionWidth;
709
- this.updateSlider();
710
- break;
711
- case PROPS.fixWidth:
712
- case PROPS.fitArea:
713
- case PROPS.positionMargin:
714
- this.updateSlider();
715
- break;
716
775
  case PROPS.shrinkEmptyTail:
717
776
  case PROPS.skipEmptyPositions:
777
+ case PROPS.positionHeight:
718
778
  this.updatePositions();
719
779
  break;
720
- }
721
-
722
- switch (property.name) {
780
+ // this.positionWidth obtains a new value
781
+ // this.updateSlider updates this._positionWidth
782
+ case PROPS.minHeight:
783
+ case PROPS.maxHeight:
784
+ case PROPS.positionWidth:
785
+ case PROPS.showPositionLabels:
723
786
  case PROPS.fixWidth:
724
787
  case PROPS.fitArea:
725
- case PROPS.positionWidth:
788
+ case PROPS.horizontalAlignment:
789
+ case PROPS.verticalAlignment:
726
790
  case PROPS.positionMargin:
727
- this.render(RecalcLevel.Layout, 'onPropertyChanged');
791
+ case PROPS.positionMarginState:
792
+ this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`).then(() => {});
728
793
  break;
729
794
 
730
- default:
731
- this.render(RecalcLevel.Freqs, 'onPropertyChanged');
795
+ case PROPS.backgroundColor:
796
+ this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`).then(() => {});
732
797
  break;
733
798
  }
734
799
  }
@@ -755,11 +820,20 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
755
820
 
756
821
  public get onSizeChanged(): Observable<void> { return this._onSizeChanged; }
757
822
 
823
+ private _onFreqsCalculated: Subject<void> = new Subject<void>();
824
+
825
+ /** Allows {@link VdRegionsViewer} to fit enclosed WebLogo viewers */
826
+ public get onFreqsCalculated(): Observable<void> { return this._onFreqsCalculated; }
827
+
828
+ /** Allows {@link VdRegionsViewer} to fit enclosed WebLogo viewers */
829
+ private _onLayoutCalculated: Subject<void> = new Subject<void>();
830
+ public get onLayoutCalculated(): Observable<void> { return this._onLayoutCalculated; }
831
+
758
832
  // -- Routines --
759
833
 
760
834
  getMonomer(p: DG.Point, dpr: number): [number, string | null, PositionMonomerInfo | null] {
761
- const calculatedX = p.x + this.firstVisibleIndex * this.positionWidthWithMargin * dpr;
762
- const jPos = Math.floor(p.x / (this.positionWidthWithMargin * dpr) + this.firstVisibleIndex);
835
+ const calculatedX = p.x;
836
+ const jPos = Math.floor(p.x / (this._positionWidthWithMargin * dpr) + Math.floor(this.slider.min));
763
837
  const position: PositionInfo = this.positions[jPos];
764
838
 
765
839
  if (position === undefined)
@@ -791,12 +865,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
791
865
  }
792
866
  }
793
867
 
794
- private renderRequested: boolean = false;
795
868
  /** default value of RecalcLevel.Freqs is for recalc from the scratch at the beginning */
796
- private recalcLevelRequested: RecalcLevel = RecalcLevel.Freqs;
869
+ private renderLevelRequested: WlRenderLevel = WlRenderLevel.Freqs;
797
870
 
798
871
  /** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
799
- render(recalcLevel: RecalcLevel, reason: string): void {
872
+ render(recalcLevel: WlRenderLevel, reason: string): Promise<void> {
800
873
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>` +
801
874
  `.render( recalcLevel=${recalcLevel}, reason='${reason}' )`);
802
875
 
@@ -823,7 +896,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
823
896
  const splitted = this.unitsHandler.splitted;
824
897
  for (let rowI = 0; rowI < dfRowCount; ++rowI) {
825
898
  if (dfFilter.get(rowI)) {
826
- const seqMList: string[] = splitted[rowI];
899
+ const seqMList: ISeqSplitted = splitted[rowI];
827
900
  for (let jPos = 0; jPos < length; ++jPos) {
828
901
  const m: string = seqMList[this.startPosition + jPos] || '-';
829
902
  const pmInfo = this.positions[jPos].getFreq(m);
@@ -839,30 +912,32 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
839
912
  }
840
913
  //#endregion
841
914
  this._removeEmptyPositions();
915
+ this._onFreqsCalculated.next();
842
916
  };
843
917
 
844
918
  /** Calculate layout of monomers on screen (canvas) based on freqs, required to handle mouse events */
845
- const calculateLayoutInt = (dpr: number): void => {
919
+ const calculateLayoutInt = (dpr: number, positionLabelsHeight: number): void => {
846
920
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), start `);
847
921
 
848
- const length = this.positions.length;
849
- this.calcSize();
850
- const absoluteMaxHeight = this.canvas.height - this.axisHeight * dpr;
922
+ this.calcLayout(dpr);
923
+ const absoluteMaxHeight = this.canvas.height - positionLabelsHeight * dpr;
851
924
  const alphabetSize = this.getAlphabetSize();
852
925
  if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
853
926
  grok.shell.error('WebLogo: alphabet is undefined.');
854
927
  const alphabetSizeLog = Math.log2(alphabetSize);
855
928
 
856
- for (let jPos = 0; jPos < length; jPos++) {
857
- this.positions[jPos].calcScreen(jPos, absoluteMaxHeight, this.positionHeight as PositionHeight,
858
- alphabetSizeLog, this.positionWidthWithMargin, this._positionWidth, dpr, this.axisHeight);
929
+ for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); ++jPos) {
930
+ this.positions[jPos].calcScreen(jPos, this.slider.min, absoluteMaxHeight, this.positionHeight,
931
+ alphabetSizeLog, this._positionWidthWithMargin, this._positionWidth, dpr, positionLabelsHeight);
859
932
  }
933
+ _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), end `);
934
+ this._onLayoutCalculated.next();
860
935
  };
861
936
 
862
937
  /** Render WebLogo sensitive to changes in params of rendering
863
- *@param {boolean} recalcFreqs - indicates that need to recalculate data for rendering
938
+ *@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
864
939
  */
865
- const renderInt = (recalcLevel: RecalcLevel) => {
940
+ const renderInt = (recalcLevel: WlRenderLevel) => {
866
941
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), ` +
867
942
  `start `);
868
943
  if (this.msgHost) {
@@ -884,8 +959,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
884
959
  this.slider.root.style.width = `${this.host.clientWidth}px`;
885
960
 
886
961
  const dpr: number = window.devicePixelRatio;
887
- if (recalcLevel >= RecalcLevel.Freqs) calculateFreqsInt();
888
- if (recalcLevel >= RecalcLevel.Layout) calculateLayoutInt(window.devicePixelRatio);
962
+ /** 0 is for no position labels */
963
+ const positionLabelsHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT : 0;
964
+ if (recalcLevel >= WlRenderLevel.Freqs) calculateFreqsInt();
965
+ if (recalcLevel >= WlRenderLevel.Layout) calculateLayoutInt(window.devicePixelRatio, positionLabelsHeight);
889
966
 
890
967
  const length: number = this.Length;
891
968
  g.resetTransform();
@@ -893,10 +970,6 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
893
970
  g.fillRect(0, 0, this.canvas.width, this.canvas.height);
894
971
  g.textBaseline = this.textBaseline;
895
972
 
896
- const maxCountOfRowsRendered = this.countOfRenderPositions + 1;
897
- const firstVisibleIndex = (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
898
- const lastVisibleIndex = Math.min(length, firstVisibleIndex + maxCountOfRowsRendered);
899
-
900
973
  //#region Plot positionNames
901
974
  const positionFontSize = 10 * dpr;
902
975
  g.resetTransform();
@@ -907,145 +980,44 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
907
980
  const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
908
981
  (this._positionWidth * dpr - 2) / posNameMaxWidth;
909
982
 
910
- for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
911
- const pos: PositionInfo = this.positions[jPos];
912
- g.resetTransform();
913
- g.setTransform(
914
- hScale, 0, 0, 1,
915
- jPos * this.positionWidthWithMargin * dpr + this._positionWidth * dpr / 2 -
916
- this.positionWidthWithMargin * firstVisibleIndex, 0);
917
- g.fillText(pos.label, 0, 0);
983
+ if (positionLabelsHeight > 0) {
984
+ renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
985
+ this.positions, this.slider.min, this.slider.max);
918
986
  }
919
987
  //#endregion Plot positionNames
920
988
  const fontStyle = '16px Roboto, Roboto Local, sans-serif';
921
989
  // Hacks to scale uppercase characters to target rectangle
922
990
  const uppercaseLetterAscent = 0.25;
923
991
  const uppercaseLetterHeight = 12.2;
924
- for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
992
+ for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
925
993
  this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
926
- this.positionWidthWithMargin, firstVisibleIndex, this.cp);
994
+ /* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
927
995
  }
928
996
 
929
997
  _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end`);
930
998
  };
931
999
 
932
- this.recalcLevelRequested = Math.max(this.recalcLevelRequested, recalcLevel);
933
- if (!this.renderRequested) {
934
- this.renderRequested = true;
935
- // requestAnimationFrame callback will be executed after this.render()
936
- switch (this.recalcLevelRequested) {
937
- case RecalcLevel.Freqs:
938
- /* Avoiding [Violation] 'requestAnimationFrame' handler took too much */
939
- window.setTimeout(() => {
940
- renderInt(this.recalcLevelRequested);
941
- this.recalcLevelRequested = RecalcLevel.None;
942
- this.renderRequested = false;
943
- }, 0 /* next event cycle */);
944
- break;
945
-
946
- case RecalcLevel.Layout:
947
- case RecalcLevel.None:
948
- window.requestAnimationFrame((time: number) => {
949
- renderInt(this.recalcLevelRequested);
950
- this.recalcLevelRequested = RecalcLevel.None;
951
- this.renderRequested = false;
952
- });
953
- break;
954
- }
955
- }
1000
+ this.renderLevelRequested = Math.max(this.renderLevelRequested, recalcLevel);
1001
+ return new Promise<void>((resolve, reject) => {
1002
+ window.setTimeout(() => {
1003
+ try {
1004
+ if (this.renderLevelRequested > WlRenderLevel.None) {
1005
+ try {
1006
+ renderInt(this.renderLevelRequested);
1007
+ } finally {
1008
+ this.renderLevelRequested = WlRenderLevel.None;
1009
+ }
1010
+ }
1011
+ } catch (err: any) {
1012
+ reject(err);
1013
+ }
1014
+ }, 0 /* next event cycle */);
1015
+ });
956
1016
  }
957
1017
 
958
1018
  private _lastWidth: number;
959
1019
  private _lastHeight: number;
960
1020
 
961
- /** Calculate canvas size an positionWidth and updates properties */
962
- private calcSize() {
963
- if (!this.host) return;
964
-
965
- const dpr: number = window.devicePixelRatio;
966
-
967
- let width: number = this.widthArea;
968
- let height = this.heightArea;
969
-
970
- if ((this.fitArea) && (!this.visibleSlider)) {
971
- const scale = Math.max(1, Math.min(this.xScale, this.yScale));
972
- width = width * scale;
973
- height = height * scale;
974
- this._positionWidth = this.positionWidth * scale;
975
- }
976
-
977
- width = this.Length * this.positionWidthWithMargin / dpr;
978
-
979
- this.canvas.width = this.root.clientWidth * dpr;
980
- this.canvas.style.width = `${this.root.clientWidth}px`;
981
-
982
- // const canvasHeight: number = width > this.root.clientWidth ? height - 8 : height;
983
- this.host.style.setProperty('height', `${height}px`);
984
- const canvasHeight: number = this.host.clientHeight;
985
- this.canvas.height = canvasHeight * dpr;
986
-
987
- // Adjust host and root width
988
- if (this.fixWidth) {
989
- // full width for canvas host and root
990
- this.root.style.width = this.host.style.width = `${width}px`;
991
- this.root.style.height = `${height}px`;
992
- this.root.style.overflow = 'hidden';
993
- this.host.style.setProperty('overflow-y', 'hidden', 'important');
994
- } else {
995
- // allow scroll canvas in root
996
- this.root.style.width = this.host.style.width = '100%';
997
- this.host.style.overflowX = 'auto!important';
998
- this.host.style.setProperty('text-align', this.horizontalAlignment);
999
-
1000
- const sliderHeight = this.visibleSlider ? 10 : 0;
1001
-
1002
- // vertical alignment
1003
- let hostTopMargin = 0;
1004
- switch (this.verticalAlignment) {
1005
- case 'top':
1006
- hostTopMargin = 0;
1007
- break;
1008
- case 'middle':
1009
- hostTopMargin = Math.max(0, (this.root.clientHeight - height) / 2);
1010
- break;
1011
- case 'bottom':
1012
- hostTopMargin = Math.max(0, this.root.clientHeight - height - sliderHeight);
1013
- break;
1014
- }
1015
- // horizontal alignment
1016
- let hostLeftMargin = 0;
1017
- switch (this.horizontalAlignment) {
1018
- case 'left':
1019
- hostLeftMargin = 0;
1020
- break;
1021
- case 'center':
1022
- hostLeftMargin = Math.max(0, (this.root.clientWidth - width) / 2);
1023
- break;
1024
- case 'right':
1025
- hostLeftMargin = Math.max(0, this.root.clientWidth - width);
1026
- break;
1027
- }
1028
- this.host.style.setProperty('margin-top', `${hostTopMargin}px`, 'important');
1029
- this.host.style.setProperty('margin-left', `${hostLeftMargin}px`, 'important');
1030
- if (this.slider != null)
1031
- this.slider.root.style.setProperty('margin-top', `${hostTopMargin + canvasHeight}px`, 'important');
1032
-
1033
-
1034
- if (this.root.clientHeight <= height) {
1035
- this.host.style.setProperty('height', `${this.root.clientHeight}px`);
1036
- this.host.style.setProperty('overflow-y', null);
1037
- } else {
1038
- this.host.style.setProperty('overflow-y', 'hidden', 'important');
1039
- }
1040
- }
1041
-
1042
- if (this._lastWidth !== this.root.clientWidth && this._lastHeight !== this.root.clientHeight) {
1043
- this._lastWidth = this.root.clientWidth;
1044
- this._lastHeight = this.root.clientHeight;
1045
- this._onSizeChanged.next();
1046
- }
1047
- }
1048
-
1049
1021
  public getAlphabetSize(): number {
1050
1022
  return this.unitsHandler?.getAlphabetSize() ?? 0;
1051
1023
  }
@@ -1053,20 +1025,17 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1053
1025
  // -- Handle events --
1054
1026
 
1055
1027
  private sliderOnValuesChanged(_value: any): void {
1056
- if ((this.host == null)) return;
1057
-
1028
+ // if ((this.host == null)) return;
1029
+ // const dpr = window.devicePixelRatio;
1030
+ //
1058
1031
  try {
1059
- /* Resize slider if we can resize do that */
1060
- if ((this.allowResize) && (!this.turnOfResizeForOneSetValue) &&
1061
- (this.visibleSlider)) {
1062
- const countOfPositions = Math.ceil(this.slider.max - this.slider.min);
1063
- const calculatedWidth = (this.canvas.width / countOfPositions) - this.positionMarginValue;
1064
- // saving positionWidth value global (even if slider is not visible)
1065
- this.positionWidth = calculatedWidth;
1066
- this._positionWidth = calculatedWidth;
1067
- }
1068
- this.turnOfResizeForOneSetValue = false;
1069
- this.render(RecalcLevel.Layout, 'sliderOnValuesChanged');
1032
+ const val = {
1033
+ minRange: this.slider.minRange,
1034
+ min: this.slider.min, max: this.slider.max,
1035
+ maxRange: this.slider.maxRange
1036
+ };
1037
+ _package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged( ${JSON.stringify(val)} ), start`);
1038
+ this.render(WlRenderLevel.Layout, 'sliderOnValuesChanged').then(() => {});
1070
1039
  } catch (err: any) {
1071
1040
  const errMsg = errorToConsole(err);
1072
1041
  _package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged() error:\n' + errMsg);
@@ -1079,7 +1048,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1079
1048
  try {
1080
1049
  this.updatePositions();
1081
1050
  if (this.filterSource === FilterSources.Filtered)
1082
- this.render(RecalcLevel.Freqs, 'dataFrameFilterOnChanged');
1051
+ this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged').then(() => {});
1083
1052
  } catch (err: any) {
1084
1053
  const errMsg = errorToConsole(err);
1085
1054
  _package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n' + errMsg);
@@ -1091,7 +1060,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1091
1060
  _package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()');
1092
1061
  try {
1093
1062
  if (this.filterSource === FilterSources.Selected)
1094
- this.render(RecalcLevel.Freqs, 'dataFrameSelectionOnChanged');
1063
+ this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged').then(() => {});
1095
1064
  } catch (err: any) {
1096
1065
  const errMsg = errorToConsole(err);
1097
1066
  _package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n' + errMsg);
@@ -1157,10 +1126,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1157
1126
  }
1158
1127
 
1159
1128
  private canvasOnWheel(e: WheelEvent) {
1129
+ const dpr = window.devicePixelRatio;
1160
1130
  try {
1161
- if (!this.visibleSlider)
1162
- return;
1163
- const countOfScrollPositions = (e.deltaY / 100) * Math.max(Math.floor((this.countOfRenderPositions) / 2), 1);
1131
+ if (!this.visibleSlider) return;
1132
+ const visibleLength = this.canvas.width / (this._positionWidthWithMargin * dpr);
1133
+ const countOfScrollPositions = (e.deltaY / 100) * Math.max(Math.floor((visibleLength) / 5), 1);
1164
1134
  this.slider.scrollBy(this.slider.min + countOfScrollPositions);
1165
1135
  } catch (err: any) {
1166
1136
  const errMsg = errorToConsole(err);
@@ -1170,10 +1140,24 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1170
1140
  }
1171
1141
  }
1172
1142
 
1143
+ function renderPositionLabels(g: CanvasRenderingContext2D,
1144
+ dpr: number, hScale: number, positionWidthWithMargin: number, positionWidth: number,
1145
+ positions: PositionInfo[], firstVisiblePosIdx: number, lastVisiblePosIdx: number
1146
+ ): void {
1147
+ for (let jPos = Math.floor(firstVisiblePosIdx); jPos <= Math.floor(lastVisiblePosIdx); jPos++) {
1148
+ const pos: PositionInfo = positions[jPos];
1149
+ g.resetTransform();
1150
+ g.setTransform(
1151
+ hScale, 0, 0,
1152
+ 1, (jPos - firstVisiblePosIdx) * positionWidthWithMargin * dpr + positionWidth * dpr / 2, 0);
1153
+ g.fillText(pos.label, 0, 1);
1154
+ }
1155
+ }
1156
+
1173
1157
  export function checkSeqForMonomerAtPos(
1174
1158
  df: DG.DataFrame, unitsHandler: UnitsHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
1175
1159
  ): boolean {
1176
- const seqMList: string[] = unitsHandler.splitted[rowI];
1160
+ const seqMList: ISeqSplitted = unitsHandler.splitted[rowI];
1177
1161
  const seqM = at.pos < seqMList.length ? seqMList[at.pos] : null;
1178
1162
  return ((seqM === monomer) || (seqM === '' && monomer === '-'));
1179
1163
  }
@@ -1184,7 +1168,7 @@ export function countForMonomerAtPosition(
1184
1168
  let count = 0;
1185
1169
  let rowI = -1;
1186
1170
  while ((rowI = filter.findNext(rowI, true)) != -1) {
1187
- const seqMList: string[] = uh.splitted[rowI];
1171
+ const seqMList: ISeqSplitted = uh.splitted[rowI];
1188
1172
  const seqMPos: number = at.pos;
1189
1173
  const seqM: string | null = seqMPos < seqMList.length ? seqMList[seqMPos] : null;
1190
1174
  if (seqM === monomer) count++;