@datagrok/bio 2.8.4 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -9
- package/README.md +39 -20
- 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/dockerfiles/Dockerfile +5 -4
- package/package.json +3 -3
- package/src/analysis/sequence-activity-cliffs.ts +8 -7
- package/src/analysis/sequence-similarity-viewer.ts +8 -8
- package/src/apps/web-logo-app.ts +26 -6
- package/src/calculations/monomerLevelMols.ts +6 -3
- package/src/package-test.ts +1 -0
- package/src/package-types.ts +0 -1
- package/src/package.ts +52 -10
- package/src/substructure-search/substructure-search.ts +84 -55
- package/src/tests/activity-cliffs-tests.ts +1 -1
- package/src/tests/converters-test.ts +1 -1
- package/src/tests/detectors-tests.ts +2 -2
- package/src/tests/msa-tests.ts +2 -3
- package/src/tests/renderers-test.ts +37 -3
- package/src/tests/scoring.ts +38 -0
- package/src/tests/splitters-test.ts +27 -1
- package/src/tests/units-handler-splitted-tests.ts +19 -12
- package/src/tests/units-handler-tests.ts +15 -15
- package/src/utils/cell-renderer.ts +31 -20
- package/src/utils/monomer-cell-renderer.ts +14 -14
- package/src/utils/save-as-fasta.ts +1 -1
- package/src/utils/split-to-monomers.ts +40 -6
- package/src/utils/ui-utils.ts +4 -4
- package/src/viewers/vd-regions-viewer.ts +88 -51
- package/src/viewers/web-logo-viewer.ts +307 -310
- package/src/widgets/composition-analysis-widget.ts +6 -2
|
@@ -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
|
-
|
|
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,15 +143,14 @@ export class PositionInfo {
|
|
|
153
143
|
}
|
|
154
144
|
|
|
155
145
|
calcScreen(
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
150
|
const maxHeight = (heightMode == PositionHeight.Entropy) ?
|
|
162
151
|
(absoluteMaxHeight * (alphabetSizeLog - (this.sumForHeightCalc)) / alphabetSizeLog) :
|
|
163
152
|
absoluteMaxHeight;
|
|
164
|
-
let y: number = axisHeight *
|
|
153
|
+
let y: number = axisHeight * dpr + (absoluteMaxHeight - maxHeight - 1);
|
|
165
154
|
|
|
166
155
|
const entries = Object.entries(this._freqs)
|
|
167
156
|
.sort((a, b) => {
|
|
@@ -179,20 +168,21 @@ export class PositionInfo {
|
|
|
179
168
|
// const m: string = entry[0];
|
|
180
169
|
const h: number = maxHeight * pmInfo.count / this.rowCount;
|
|
181
170
|
|
|
182
|
-
pmInfo.bounds = new DG.Rect(
|
|
171
|
+
pmInfo.bounds = new DG.Rect(
|
|
172
|
+
(posIdx - firstVisiblePosIdx) * dpr * positionWidthWithMargin, y,
|
|
173
|
+
positionWidth * dpr, h);
|
|
183
174
|
y += h;
|
|
184
175
|
}
|
|
185
176
|
}
|
|
186
177
|
|
|
187
178
|
render(g: CanvasRenderingContext2D,
|
|
188
|
-
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number,
|
|
189
|
-
positionWidthWithMargin: number, firstVisibleIndex: number, cp: SeqPalette
|
|
179
|
+
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number, cp: SeqPalette
|
|
190
180
|
) {
|
|
191
181
|
for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
|
|
192
182
|
if (monomer !== '-') {
|
|
193
183
|
const monomerTxt = monomerToShort(monomer, 5);
|
|
194
184
|
const b = pmInfo.bounds!;
|
|
195
|
-
const left = b.left
|
|
185
|
+
const left = b.left;
|
|
196
186
|
|
|
197
187
|
g.resetTransform();
|
|
198
188
|
g.strokeStyle = 'lightgray';
|
|
@@ -251,8 +241,9 @@ export enum PROPS {
|
|
|
251
241
|
|
|
252
242
|
const defaults: WebLogoProps = WebLogoPropsDefault;
|
|
253
243
|
|
|
254
|
-
enum
|
|
244
|
+
enum WlRenderLevel {
|
|
255
245
|
None = 0,
|
|
246
|
+
Render = 1,
|
|
256
247
|
Layout = 1,
|
|
257
248
|
Freqs = 2,
|
|
258
249
|
}
|
|
@@ -261,7 +252,7 @@ const POSITION_LABELS_HEIGHT: number = 12;
|
|
|
261
252
|
|
|
262
253
|
export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
263
254
|
public static residuesSet = 'nucleotides';
|
|
264
|
-
private static viewerCount: number =
|
|
255
|
+
private static viewerCount: number = 0;
|
|
265
256
|
|
|
266
257
|
private viewed: boolean = false;
|
|
267
258
|
|
|
@@ -275,33 +266,35 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
275
266
|
private host?: HTMLDivElement;
|
|
276
267
|
private msgHost?: HTMLElement;
|
|
277
268
|
private canvas: HTMLCanvasElement;
|
|
278
|
-
private slider: DG.RangeSlider;
|
|
269
|
+
private readonly slider: DG.RangeSlider;
|
|
279
270
|
private readonly textBaseline: CanvasTextBaseline;
|
|
280
271
|
|
|
281
272
|
private seqCol: DG.Column<string> | null = null;
|
|
282
|
-
// private maxLength: number = 100;
|
|
283
273
|
private positions: PositionInfo[] = [];
|
|
284
274
|
|
|
285
|
-
private rowsNull: number = 0;
|
|
286
275
|
private visibleSlider: boolean = false;
|
|
287
276
|
private allowResize: boolean = true;
|
|
288
277
|
private turnOfResizeForOneSetValue: boolean = false;
|
|
289
278
|
|
|
290
|
-
// Viewer's properties (likely they should be public
|
|
279
|
+
// Viewer's properties (likely they should be public)
|
|
291
280
|
// -- Data --
|
|
292
281
|
public sequenceColumnName: string | null;
|
|
293
282
|
public skipEmptySequences: boolean;
|
|
294
283
|
public skipEmptyPositions: boolean;
|
|
295
284
|
|
|
296
285
|
// -- Style --
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
|
|
299
291
|
public minHeight: number;
|
|
300
292
|
public backgroundColor: number = 0xFFFFFFFF;
|
|
301
293
|
public maxHeight: number;
|
|
302
294
|
public showPositionLabels: boolean;
|
|
303
295
|
public positionMarginState: PositionMarginStates;
|
|
304
|
-
public positionMargin: number = 0;
|
|
296
|
+
/** Gets value from properties or setOptions */ public positionMargin: number = 0;
|
|
297
|
+
/** Scaled value to fit area */ private _positionMargin: number;
|
|
305
298
|
public startPositionName: string | null;
|
|
306
299
|
public endPositionName: string | null;
|
|
307
300
|
public fixWidth: boolean;
|
|
@@ -309,7 +302,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
309
302
|
public horizontalAlignment: HorizontalAlignments;
|
|
310
303
|
public fitArea: boolean;
|
|
311
304
|
public shrinkEmptyTail: boolean;
|
|
312
|
-
public positionHeight:
|
|
305
|
+
public positionHeight: PositionHeight;
|
|
313
306
|
|
|
314
307
|
// -- Behavior --
|
|
315
308
|
public filterSource: FilterSources;
|
|
@@ -334,7 +327,9 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
334
327
|
return res;
|
|
335
328
|
}
|
|
336
329
|
|
|
337
|
-
/**
|
|
330
|
+
/** Full length of {@link positions}.
|
|
331
|
+
* Inclusive, for startPosition equals to endPosition Length is 1
|
|
332
|
+
*/
|
|
338
333
|
public get Length(): number {
|
|
339
334
|
if (this.skipEmptyPositions)
|
|
340
335
|
return this.positions.length;
|
|
@@ -342,42 +337,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
342
337
|
return this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
343
338
|
}
|
|
344
339
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
private get positionMarginValue() {
|
|
351
|
-
if (this.positionMarginState === PositionMarginStates.AUTO &&
|
|
352
|
-
this.unitsHandler?.getAlphabetIsMultichar() === true
|
|
353
|
-
) return this.positionMargin;
|
|
354
|
-
|
|
355
|
-
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)
|
|
356
344
|
return this.positionMargin;
|
|
357
|
-
|
|
358
|
-
return 0;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Count of position rendered for calculations countOfRenderPositions */
|
|
362
|
-
private get countOfRenderPositions() {
|
|
363
|
-
if (this.host == null)
|
|
364
|
-
return 0;
|
|
365
|
-
|
|
366
|
-
const r = window.devicePixelRatio;
|
|
367
|
-
if (r > 1)
|
|
368
|
-
return this.canvasWidthWithRatio / this.positionWidthWithMargin;
|
|
369
345
|
else
|
|
370
|
-
return
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private get canvasWidthWithRatio() {
|
|
374
|
-
return this.canvas.width * window.devicePixelRatio;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
/** Position of start rendering */
|
|
379
|
-
private get firstVisibleIndex(): number {
|
|
380
|
-
return (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
|
|
346
|
+
return 0;
|
|
381
347
|
}
|
|
382
348
|
|
|
383
349
|
constructor() {
|
|
@@ -407,7 +373,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
407
373
|
this.backgroundColor = this.int(PROPS.backgroundColor, defaults.backgroundColor,
|
|
408
374
|
{category: PROPS_CATS.STYLE});
|
|
409
375
|
this.positionHeight = this.string(PROPS.positionHeight, defaults.positionHeight,
|
|
410
|
-
{category: PROPS_CATS.STYLE, choices: Object.values(PositionHeight)});
|
|
376
|
+
{category: PROPS_CATS.STYLE, choices: Object.values(PositionHeight)}) as PositionHeight;
|
|
411
377
|
this._positionWidth = this.positionWidth = this.float(PROPS.positionWidth, defaults.positionWidth,
|
|
412
378
|
{category: PROPS_CATS.STYLE/* editor: 'slider', min: 4, max: 64, postfix: 'px' */});
|
|
413
379
|
|
|
@@ -417,7 +383,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
417
383
|
this.horizontalAlignment = this.string(PROPS.horizontalAlignment, defaults.horizontalAlignment,
|
|
418
384
|
{category: PROPS_CATS.LAYOUT, choices: Object.values(HorizontalAlignments)}) as HorizontalAlignments;
|
|
419
385
|
this.fixWidth = this.bool(PROPS.fixWidth, defaults.fixWidth,
|
|
420
|
-
{category: PROPS_CATS.LAYOUT});
|
|
386
|
+
{category: PROPS_CATS.LAYOUT, userEditable: false});
|
|
421
387
|
this.fitArea = this.bool(PROPS.fitArea, defaults.fitArea,
|
|
422
388
|
{category: PROPS_CATS.LAYOUT});
|
|
423
389
|
this.minHeight = this.float(PROPS.minHeight, defaults.minHeight,
|
|
@@ -440,7 +406,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
440
406
|
const style: DG.SliderOptions = {style: 'barbell'};
|
|
441
407
|
this.slider = ui.rangeSlider(0, 100, 0, 20, false, style);
|
|
442
408
|
this.canvas = ui.canvas();
|
|
409
|
+
this.canvas.classList.value = 'bio-wl-canvas';
|
|
443
410
|
this.canvas.style.width = '100%';
|
|
411
|
+
|
|
412
|
+
/* this.root.style.background = '#FFEEDD'; */
|
|
444
413
|
}
|
|
445
414
|
|
|
446
415
|
// -- Data --
|
|
@@ -463,7 +432,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
463
432
|
|
|
464
433
|
private viewSubs: Unsubscribable[] = [];
|
|
465
434
|
|
|
466
|
-
private destroyView() {
|
|
435
|
+
private async destroyView(): Promise<void> {
|
|
467
436
|
for (const sub of this.viewSubs) sub.unsubscribe();
|
|
468
437
|
this.viewSubs = [];
|
|
469
438
|
|
|
@@ -479,13 +448,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
479
448
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.destroyView() end`);
|
|
480
449
|
}
|
|
481
450
|
|
|
482
|
-
private buildView() {
|
|
451
|
+
private async buildView(): Promise<void> {
|
|
483
452
|
const dataFrameTxt: string = this.dataFrame ? 'data' : 'null';
|
|
484
453
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView( dataFrame = ${dataFrameTxt} ) start`);
|
|
454
|
+
const dpr = window.devicePixelRatio;
|
|
485
455
|
|
|
486
456
|
this.helpUrl = '/help/visualize/viewers/web-logo.md';
|
|
487
457
|
|
|
488
|
-
this.msgHost = ui.div('No message');
|
|
458
|
+
this.msgHost = ui.div('No message', {classes: 'bio-wl-msg'});
|
|
489
459
|
this.msgHost.style.display = 'none';
|
|
490
460
|
|
|
491
461
|
this.canvas = ui.canvas();
|
|
@@ -499,19 +469,20 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
499
469
|
|
|
500
470
|
this.visibleSlider = false;
|
|
501
471
|
|
|
502
|
-
this.host = ui.div([this.msgHost, this.canvas]
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
});
|
|
508
482
|
|
|
509
483
|
this.root.append(this.host);
|
|
510
484
|
this.root.append(this.slider.root);
|
|
511
485
|
|
|
512
|
-
this.updateSlider();
|
|
513
|
-
this.render(RecalcLevel.Freqs, 'init');
|
|
514
|
-
|
|
515
486
|
if (!!this.error) {
|
|
516
487
|
this.msgHost!.innerText = this.error.message;
|
|
517
488
|
ui.tooltip.bind(this.msgHost!, this.error.stack);
|
|
@@ -531,6 +502,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
531
502
|
this.viewSubs.push(
|
|
532
503
|
fromEvent<WheelEvent>(this.canvas, 'wheel').subscribe(this.canvasOnWheel.bind(this)));
|
|
533
504
|
|
|
505
|
+
await this.render(WlRenderLevel.Freqs, 'buildView');
|
|
534
506
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView() end`);
|
|
535
507
|
}
|
|
536
508
|
|
|
@@ -538,8 +510,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
538
510
|
private rootOnSizeChanged(): void {
|
|
539
511
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.rootOnSizeChanged(), start `);
|
|
540
512
|
|
|
541
|
-
this.
|
|
542
|
-
this.render(RecalcLevel.Layout, 'rootOnSizeChanged');
|
|
513
|
+
this.render(WlRenderLevel.Layout, 'rootOnSizeChanged').then(() => {});
|
|
543
514
|
}
|
|
544
515
|
|
|
545
516
|
/** Assigns {@link seqCol} and {@link cp} based on {@link sequenceColumnName} and calls {@link render}().
|
|
@@ -557,8 +528,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
557
528
|
const separator: string = this.seqCol!.getTag(bioTAGS.separator);
|
|
558
529
|
this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
|
|
559
530
|
|
|
560
|
-
this.updatePositions();
|
|
561
531
|
this.cp = pickUpPalette(this.seqCol);
|
|
532
|
+
this.updatePositions();
|
|
562
533
|
this.error = null;
|
|
563
534
|
} catch (err: any) {
|
|
564
535
|
this.seqCol = null;
|
|
@@ -580,8 +551,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
580
551
|
/** Updates {@link positionNames} and calculates {@link startPosition} and {@link endPosition}.
|
|
581
552
|
*/
|
|
582
553
|
private updatePositions(): void {
|
|
583
|
-
if (!this.seqCol)
|
|
584
|
-
return;
|
|
554
|
+
if (!this.seqCol) return;
|
|
585
555
|
|
|
586
556
|
const dfFilter = this.filterSource === FilterSources.Filtered ? this.dataFrame.filter :
|
|
587
557
|
this.dataFrame.selection;
|
|
@@ -603,68 +573,191 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
603
573
|
this.endPosition = (this.endPositionName && this.positionNames &&
|
|
604
574
|
this.positionNames.includes(this.endPositionName)) ?
|
|
605
575
|
this.positionNames.indexOf(this.endPositionName) : (maxLength - 1);
|
|
606
|
-
}
|
|
607
576
|
|
|
608
|
-
|
|
609
|
-
return this.Length * this.positionWidth / window.devicePixelRatio;
|
|
577
|
+
this.render(WlRenderLevel.Freqs, 'updatePositions').then(() => {});
|
|
610
578
|
}
|
|
611
579
|
|
|
612
|
-
|
|
613
|
-
|
|
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
|
+
}
|
|
614
588
|
}
|
|
615
589
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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');
|
|
619
599
|
|
|
620
|
-
|
|
621
|
-
|
|
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);
|
|
622
610
|
}
|
|
623
611
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
612
|
+
/** */
|
|
613
|
+
private calcLayoutFixWidth(dpr: number): void {
|
|
614
|
+
if (!this.host || !this.canvas || !this.slider) return; // for es-lint
|
|
627
615
|
|
|
628
|
-
|
|
629
|
-
|
|
616
|
+
this.host.classList.add('bio-wl-fixWidth');
|
|
617
|
+
this.canvas.classList.add('bio-wl-fitArea');
|
|
630
618
|
|
|
631
|
-
|
|
632
|
-
|
|
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';
|
|
633
|
+
|
|
634
|
+
this.slider.setValues(0, this.Length - 1, 0, this.Length - 1);
|
|
635
|
+
|
|
636
|
+
this.canvas.width = areaWidth * dpr;
|
|
637
|
+
this.canvas.height = areaHeight * dpr;
|
|
633
638
|
}
|
|
634
639
|
|
|
635
|
-
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
|
|
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);
|
|
639
685
|
} else {
|
|
640
|
-
|
|
641
|
-
this.
|
|
686
|
+
//
|
|
687
|
+
this.slider.setValues(0, this.Length - 0.001, 0, this.Length - 0.001);
|
|
642
688
|
}
|
|
689
|
+
|
|
690
|
+
this.canvas.width = width * dpr;
|
|
691
|
+
this.canvas.height = height * dpr;
|
|
643
692
|
}
|
|
644
693
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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`; /* */
|
|
665
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);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
this.canvas.width = width * dpr;
|
|
757
|
+
this.canvas.height = height * dpr;
|
|
666
758
|
}
|
|
667
759
|
|
|
760
|
+
|
|
668
761
|
/** Handler of property change events.
|
|
669
762
|
* @param {DG.Property} property - property which was changed.
|
|
670
763
|
*/
|
|
@@ -672,39 +765,35 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
672
765
|
super.onPropertyChanged(property);
|
|
673
766
|
|
|
674
767
|
switch (property.name) {
|
|
768
|
+
case PROPS.sequenceColumnName:
|
|
769
|
+
this.updateSeqCol();
|
|
770
|
+
break;
|
|
675
771
|
case PROPS.sequenceColumnName:
|
|
676
772
|
case PROPS.startPositionName:
|
|
677
773
|
case PROPS.endPositionName:
|
|
678
774
|
case PROPS.filterSource:
|
|
679
|
-
this.updateSeqCol();
|
|
680
|
-
break;
|
|
681
|
-
case PROPS.positionWidth:
|
|
682
|
-
this._positionWidth = this.positionWidth;
|
|
683
|
-
this.updateSlider();
|
|
684
|
-
break;
|
|
685
|
-
case PROPS.fixWidth:
|
|
686
|
-
case PROPS.fitArea:
|
|
687
|
-
case PROPS.positionMargin:
|
|
688
|
-
this.updateSlider();
|
|
689
|
-
break;
|
|
690
|
-
case PROPS.showPositionLabels:
|
|
691
775
|
case PROPS.shrinkEmptyTail:
|
|
692
776
|
case PROPS.skipEmptyPositions:
|
|
777
|
+
case PROPS.positionHeight:
|
|
693
778
|
this.updatePositions();
|
|
694
779
|
break;
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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:
|
|
698
786
|
case PROPS.fixWidth:
|
|
699
787
|
case PROPS.fitArea:
|
|
700
|
-
case PROPS.
|
|
701
|
-
case PROPS.
|
|
788
|
+
case PROPS.horizontalAlignment:
|
|
789
|
+
case PROPS.verticalAlignment:
|
|
702
790
|
case PROPS.positionMargin:
|
|
703
|
-
|
|
791
|
+
case PROPS.positionMarginState:
|
|
792
|
+
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`).then(() => {});
|
|
704
793
|
break;
|
|
705
794
|
|
|
706
|
-
|
|
707
|
-
this.render(
|
|
795
|
+
case PROPS.backgroundColor:
|
|
796
|
+
this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`).then(() => {});
|
|
708
797
|
break;
|
|
709
798
|
}
|
|
710
799
|
}
|
|
@@ -731,11 +820,20 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
731
820
|
|
|
732
821
|
public get onSizeChanged(): Observable<void> { return this._onSizeChanged; }
|
|
733
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
|
+
|
|
734
832
|
// -- Routines --
|
|
735
833
|
|
|
736
834
|
getMonomer(p: DG.Point, dpr: number): [number, string | null, PositionMonomerInfo | null] {
|
|
737
|
-
const calculatedX = p.x
|
|
738
|
-
const jPos = Math.floor(p.x / (this.
|
|
835
|
+
const calculatedX = p.x;
|
|
836
|
+
const jPos = Math.floor(p.x / (this._positionWidthWithMargin * dpr) + Math.floor(this.slider.min));
|
|
739
837
|
const position: PositionInfo = this.positions[jPos];
|
|
740
838
|
|
|
741
839
|
if (position === undefined)
|
|
@@ -767,12 +865,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
767
865
|
}
|
|
768
866
|
}
|
|
769
867
|
|
|
770
|
-
private renderRequested: boolean = false;
|
|
771
868
|
/** default value of RecalcLevel.Freqs is for recalc from the scratch at the beginning */
|
|
772
|
-
private
|
|
869
|
+
private renderLevelRequested: WlRenderLevel = WlRenderLevel.Freqs;
|
|
773
870
|
|
|
774
871
|
/** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
|
|
775
|
-
render(recalcLevel:
|
|
872
|
+
render(recalcLevel: WlRenderLevel, reason: string): Promise<void> {
|
|
776
873
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>` +
|
|
777
874
|
`.render( recalcLevel=${recalcLevel}, reason='${reason}' )`);
|
|
778
875
|
|
|
@@ -799,7 +896,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
799
896
|
const splitted = this.unitsHandler.splitted;
|
|
800
897
|
for (let rowI = 0; rowI < dfRowCount; ++rowI) {
|
|
801
898
|
if (dfFilter.get(rowI)) {
|
|
802
|
-
const seqMList:
|
|
899
|
+
const seqMList: ISeqSplitted = splitted[rowI];
|
|
803
900
|
for (let jPos = 0; jPos < length; ++jPos) {
|
|
804
901
|
const m: string = seqMList[this.startPosition + jPos] || '-';
|
|
805
902
|
const pmInfo = this.positions[jPos].getFreq(m);
|
|
@@ -815,30 +912,32 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
815
912
|
}
|
|
816
913
|
//#endregion
|
|
817
914
|
this._removeEmptyPositions();
|
|
915
|
+
this._onFreqsCalculated.next();
|
|
818
916
|
};
|
|
819
917
|
|
|
820
918
|
/** Calculate layout of monomers on screen (canvas) based on freqs, required to handle mouse events */
|
|
821
919
|
const calculateLayoutInt = (dpr: number, positionLabelsHeight: number): void => {
|
|
822
920
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), start `);
|
|
823
921
|
|
|
824
|
-
|
|
825
|
-
this.calcSize();
|
|
922
|
+
this.calcLayout(dpr);
|
|
826
923
|
const absoluteMaxHeight = this.canvas.height - positionLabelsHeight * dpr;
|
|
827
924
|
const alphabetSize = this.getAlphabetSize();
|
|
828
925
|
if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
|
|
829
926
|
grok.shell.error('WebLogo: alphabet is undefined.');
|
|
830
927
|
const alphabetSizeLog = Math.log2(alphabetSize);
|
|
831
928
|
|
|
832
|
-
for (let jPos =
|
|
833
|
-
this.positions[jPos].calcScreen(jPos, absoluteMaxHeight, this.positionHeight
|
|
834
|
-
alphabetSizeLog, this.
|
|
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);
|
|
835
932
|
}
|
|
933
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), end `);
|
|
934
|
+
this._onLayoutCalculated.next();
|
|
836
935
|
};
|
|
837
936
|
|
|
838
937
|
/** Render WebLogo sensitive to changes in params of rendering
|
|
839
|
-
*@param {
|
|
938
|
+
*@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
|
|
840
939
|
*/
|
|
841
|
-
const renderInt = (recalcLevel:
|
|
940
|
+
const renderInt = (recalcLevel: WlRenderLevel) => {
|
|
842
941
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), ` +
|
|
843
942
|
`start `);
|
|
844
943
|
if (this.msgHost) {
|
|
@@ -862,8 +961,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
862
961
|
const dpr: number = window.devicePixelRatio;
|
|
863
962
|
/** 0 is for no position labels */
|
|
864
963
|
const positionLabelsHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT : 0;
|
|
865
|
-
if (recalcLevel >=
|
|
866
|
-
if (recalcLevel >=
|
|
964
|
+
if (recalcLevel >= WlRenderLevel.Freqs) calculateFreqsInt();
|
|
965
|
+
if (recalcLevel >= WlRenderLevel.Layout) calculateLayoutInt(window.devicePixelRatio, positionLabelsHeight);
|
|
867
966
|
|
|
868
967
|
const length: number = this.Length;
|
|
869
968
|
g.resetTransform();
|
|
@@ -871,10 +970,6 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
871
970
|
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
872
971
|
g.textBaseline = this.textBaseline;
|
|
873
972
|
|
|
874
|
-
const maxCountOfRowsRendered = this.countOfRenderPositions + 1;
|
|
875
|
-
const firstVisibleIndex = (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
|
|
876
|
-
const lastVisibleIndex = Math.min(length, firstVisibleIndex + maxCountOfRowsRendered);
|
|
877
|
-
|
|
878
973
|
//#region Plot positionNames
|
|
879
974
|
const positionFontSize = 10 * dpr;
|
|
880
975
|
g.resetTransform();
|
|
@@ -886,139 +981,43 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
886
981
|
(this._positionWidth * dpr - 2) / posNameMaxWidth;
|
|
887
982
|
|
|
888
983
|
if (positionLabelsHeight > 0) {
|
|
889
|
-
renderPositionLabels(g, dpr, hScale, this.
|
|
890
|
-
this.positions, this.
|
|
984
|
+
renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
|
|
985
|
+
this.positions, this.slider.min, this.slider.max);
|
|
891
986
|
}
|
|
892
987
|
//#endregion Plot positionNames
|
|
893
988
|
const fontStyle = '16px Roboto, Roboto Local, sans-serif';
|
|
894
989
|
// Hacks to scale uppercase characters to target rectangle
|
|
895
990
|
const uppercaseLetterAscent = 0.25;
|
|
896
991
|
const uppercaseLetterHeight = 12.2;
|
|
897
|
-
for (let jPos = this.
|
|
992
|
+
for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
|
|
898
993
|
this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
|
|
899
|
-
this.
|
|
994
|
+
/* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
|
|
900
995
|
}
|
|
901
996
|
|
|
902
997
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end`);
|
|
903
998
|
};
|
|
904
999
|
|
|
905
|
-
this.
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
window.requestAnimationFrame((time: number) => {
|
|
922
|
-
renderInt(this.recalcLevelRequested);
|
|
923
|
-
this.recalcLevelRequested = RecalcLevel.None;
|
|
924
|
-
this.renderRequested = false;
|
|
925
|
-
});
|
|
926
|
-
break;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
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
|
+
});
|
|
929
1016
|
}
|
|
930
1017
|
|
|
931
1018
|
private _lastWidth: number;
|
|
932
1019
|
private _lastHeight: number;
|
|
933
1020
|
|
|
934
|
-
/** Calculate canvas size an positionWidth and updates properties */
|
|
935
|
-
private calcSize() {
|
|
936
|
-
if (!this.host) return;
|
|
937
|
-
|
|
938
|
-
const dpr: number = window.devicePixelRatio;
|
|
939
|
-
|
|
940
|
-
let width: number = this.widthArea;
|
|
941
|
-
let height = this.heightArea;
|
|
942
|
-
|
|
943
|
-
if ((this.fitArea) && (!this.visibleSlider)) {
|
|
944
|
-
const scale = Math.max(1, Math.min(this.xScale, this.yScale));
|
|
945
|
-
width = width * scale;
|
|
946
|
-
height = height * scale;
|
|
947
|
-
this._positionWidth = this.positionWidth * scale;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
width = this.Length * this.positionWidthWithMargin;
|
|
951
|
-
|
|
952
|
-
this.canvas.width = this.root.clientWidth * dpr;
|
|
953
|
-
this.canvas.style.width = `${this.root.clientWidth}px`;
|
|
954
|
-
|
|
955
|
-
// const canvasHeight: number = width > this.root.clientWidth ? height - 8 : height;
|
|
956
|
-
this.host.style.setProperty('height', `${height}px`);
|
|
957
|
-
const canvasHeight: number = this.host.clientHeight;
|
|
958
|
-
this.canvas.height = canvasHeight * dpr;
|
|
959
|
-
|
|
960
|
-
// Adjust host and root width
|
|
961
|
-
if (this.fixWidth) {
|
|
962
|
-
// full width for canvas host and root
|
|
963
|
-
this.root.style.width = this.host.style.width = `${width}px`;
|
|
964
|
-
this.root.style.height = `${height}px`;
|
|
965
|
-
this.root.style.overflow = 'hidden';
|
|
966
|
-
this.host.style.setProperty('overflow-y', 'hidden', 'important');
|
|
967
|
-
} else {
|
|
968
|
-
// allow scroll canvas in root
|
|
969
|
-
this.root.style.width = this.host.style.width = '100%';
|
|
970
|
-
this.host.style.overflowX = 'auto!important';
|
|
971
|
-
this.host.style.setProperty('text-align', this.horizontalAlignment);
|
|
972
|
-
|
|
973
|
-
const sliderHeight = this.visibleSlider ? 10 : 0;
|
|
974
|
-
|
|
975
|
-
// vertical alignment
|
|
976
|
-
let hostTopMargin = 0;
|
|
977
|
-
switch (this.verticalAlignment) {
|
|
978
|
-
case 'top':
|
|
979
|
-
hostTopMargin = 0;
|
|
980
|
-
break;
|
|
981
|
-
case 'middle':
|
|
982
|
-
hostTopMargin = Math.max(0, (this.root.clientHeight - height) / 2);
|
|
983
|
-
break;
|
|
984
|
-
case 'bottom':
|
|
985
|
-
hostTopMargin = Math.max(0, this.root.clientHeight - height - sliderHeight);
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
// horizontal alignment
|
|
989
|
-
let hostLeftMargin = 0;
|
|
990
|
-
switch (this.horizontalAlignment) {
|
|
991
|
-
case HorizontalAlignments.LEFT:
|
|
992
|
-
hostLeftMargin = 0;
|
|
993
|
-
break;
|
|
994
|
-
case HorizontalAlignments.CENTER:
|
|
995
|
-
hostLeftMargin = Math.max(0, (this.root.clientWidth - width) / 2);
|
|
996
|
-
break;
|
|
997
|
-
case HorizontalAlignments.RIGHT:
|
|
998
|
-
hostLeftMargin = Math.max(0, this.root.clientWidth - width);
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
this.host.style.setProperty('margin-top', `${hostTopMargin}px`, 'important');
|
|
1002
|
-
this.host.style.setProperty('margin-left', `${hostLeftMargin}px`, 'important');
|
|
1003
|
-
if (this.slider != null)
|
|
1004
|
-
this.slider.root.style.setProperty('margin-top', `${hostTopMargin + canvasHeight}px`, 'important');
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if (this.root.clientHeight <= height) {
|
|
1008
|
-
this.host.style.setProperty('height', `${this.root.clientHeight}px`);
|
|
1009
|
-
this.host.style.setProperty('overflow-y', null);
|
|
1010
|
-
} else {
|
|
1011
|
-
this.host.style.setProperty('overflow-y', 'hidden', 'important');
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (this._lastWidth !== this.root.clientWidth && this._lastHeight !== this.root.clientHeight) {
|
|
1016
|
-
this._lastWidth = this.root.clientWidth;
|
|
1017
|
-
this._lastHeight = this.root.clientHeight;
|
|
1018
|
-
this._onSizeChanged.next();
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
1021
|
public getAlphabetSize(): number {
|
|
1023
1022
|
return this.unitsHandler?.getAlphabetSize() ?? 0;
|
|
1024
1023
|
}
|
|
@@ -1026,20 +1025,17 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1026
1025
|
// -- Handle events --
|
|
1027
1026
|
|
|
1028
1027
|
private sliderOnValuesChanged(_value: any): void {
|
|
1029
|
-
if ((this.host == null)) return;
|
|
1030
|
-
|
|
1028
|
+
// if ((this.host == null)) return;
|
|
1029
|
+
// const dpr = window.devicePixelRatio;
|
|
1030
|
+
//
|
|
1031
1031
|
try {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
this._positionWidth = calculatedWidth;
|
|
1040
|
-
}
|
|
1041
|
-
this.turnOfResizeForOneSetValue = false;
|
|
1042
|
-
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(() => {});
|
|
1043
1039
|
} catch (err: any) {
|
|
1044
1040
|
const errMsg = errorToConsole(err);
|
|
1045
1041
|
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged() error:\n' + errMsg);
|
|
@@ -1052,7 +1048,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1052
1048
|
try {
|
|
1053
1049
|
this.updatePositions();
|
|
1054
1050
|
if (this.filterSource === FilterSources.Filtered)
|
|
1055
|
-
this.render(
|
|
1051
|
+
this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged').then(() => {});
|
|
1056
1052
|
} catch (err: any) {
|
|
1057
1053
|
const errMsg = errorToConsole(err);
|
|
1058
1054
|
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n' + errMsg);
|
|
@@ -1064,7 +1060,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1064
1060
|
_package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()');
|
|
1065
1061
|
try {
|
|
1066
1062
|
if (this.filterSource === FilterSources.Selected)
|
|
1067
|
-
this.render(
|
|
1063
|
+
this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged').then(() => {});
|
|
1068
1064
|
} catch (err: any) {
|
|
1069
1065
|
const errMsg = errorToConsole(err);
|
|
1070
1066
|
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n' + errMsg);
|
|
@@ -1130,10 +1126,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1130
1126
|
}
|
|
1131
1127
|
|
|
1132
1128
|
private canvasOnWheel(e: WheelEvent) {
|
|
1129
|
+
const dpr = window.devicePixelRatio;
|
|
1133
1130
|
try {
|
|
1134
|
-
if (!this.visibleSlider)
|
|
1135
|
-
|
|
1136
|
-
const countOfScrollPositions = (e.deltaY / 100) * Math.max(Math.floor((
|
|
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);
|
|
1137
1134
|
this.slider.scrollBy(this.slider.min + countOfScrollPositions);
|
|
1138
1135
|
} catch (err: any) {
|
|
1139
1136
|
const errMsg = errorToConsole(err);
|
|
@@ -1145,14 +1142,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1145
1142
|
|
|
1146
1143
|
function renderPositionLabels(g: CanvasRenderingContext2D,
|
|
1147
1144
|
dpr: number, hScale: number, positionWidthWithMargin: number, positionWidth: number,
|
|
1148
|
-
positions: PositionInfo[],
|
|
1149
|
-
|
|
1145
|
+
positions: PositionInfo[], firstVisiblePosIdx: number, lastVisiblePosIdx: number
|
|
1146
|
+
): void {
|
|
1147
|
+
for (let jPos = Math.floor(firstVisiblePosIdx); jPos <= Math.floor(lastVisiblePosIdx); jPos++) {
|
|
1150
1148
|
const pos: PositionInfo = positions[jPos];
|
|
1151
1149
|
g.resetTransform();
|
|
1152
1150
|
g.setTransform(
|
|
1153
|
-
hScale, 0, 0,
|
|
1154
|
-
jPos * positionWidthWithMargin * dpr + positionWidth * dpr / 2
|
|
1155
|
-
positionWidthWithMargin * firstVisibleIndex, 0);
|
|
1151
|
+
hScale, 0, 0,
|
|
1152
|
+
1, (jPos - firstVisiblePosIdx) * positionWidthWithMargin * dpr + positionWidth * dpr / 2, 0);
|
|
1156
1153
|
g.fillText(pos.label, 0, 1);
|
|
1157
1154
|
}
|
|
1158
1155
|
}
|
|
@@ -1160,7 +1157,7 @@ function renderPositionLabels(g: CanvasRenderingContext2D,
|
|
|
1160
1157
|
export function checkSeqForMonomerAtPos(
|
|
1161
1158
|
df: DG.DataFrame, unitsHandler: UnitsHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
|
|
1162
1159
|
): boolean {
|
|
1163
|
-
const seqMList:
|
|
1160
|
+
const seqMList: ISeqSplitted = unitsHandler.splitted[rowI];
|
|
1164
1161
|
const seqM = at.pos < seqMList.length ? seqMList[at.pos] : null;
|
|
1165
1162
|
return ((seqM === monomer) || (seqM === '' && monomer === '-'));
|
|
1166
1163
|
}
|
|
@@ -1171,7 +1168,7 @@ export function countForMonomerAtPosition(
|
|
|
1171
1168
|
let count = 0;
|
|
1172
1169
|
let rowI = -1;
|
|
1173
1170
|
while ((rowI = filter.findNext(rowI, true)) != -1) {
|
|
1174
|
-
const seqMList:
|
|
1171
|
+
const seqMList: ISeqSplitted = uh.splitted[rowI];
|
|
1175
1172
|
const seqMPos: number = at.pos;
|
|
1176
1173
|
const seqM: string | null = seqMPos < seqMList.length ? seqMList[seqMPos] : null;
|
|
1177
1174
|
if (seqM === monomer) count++;
|