@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.
- package/CHANGELOG.md +15 -1
- 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 +4 -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.ts +2 -1
- package/src/tests/converters-test.ts +1 -1
- package/src/tests/msa-tests.ts +2 -3
- package/src/tests/renderers-test.ts +37 -3
- 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 -22
- 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/viewers/vd-regions-viewer.ts +88 -51
- package/src/viewers/web-logo-viewer.ts +327 -343
- 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,45 +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
|
-
// 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 *
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
327
|
-
|
|
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:
|
|
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
|
-
/**
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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.
|
|
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
|
-
|
|
635
|
-
return this.Length * this.positionWidth / window.devicePixelRatio;
|
|
577
|
+
this.render(WlRenderLevel.Freqs, 'updatePositions').then(() => {});
|
|
636
578
|
}
|
|
637
579
|
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
647
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
655
|
-
showSliderWithFitArea = false;
|
|
634
|
+
this.slider.setValues(0, this.Length - 1, 0, this.Length - 1);
|
|
656
635
|
|
|
657
|
-
|
|
658
|
-
|
|
636
|
+
this.canvas.width = areaWidth * dpr;
|
|
637
|
+
this.canvas.height = areaHeight * dpr;
|
|
659
638
|
}
|
|
660
639
|
|
|
661
|
-
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
667
|
-
this.
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
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.
|
|
788
|
+
case PROPS.horizontalAlignment:
|
|
789
|
+
case PROPS.verticalAlignment:
|
|
726
790
|
case PROPS.positionMargin:
|
|
727
|
-
|
|
791
|
+
case PROPS.positionMarginState:
|
|
792
|
+
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`).then(() => {});
|
|
728
793
|
break;
|
|
729
794
|
|
|
730
|
-
|
|
731
|
-
this.render(
|
|
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
|
|
762
|
-
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));
|
|
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
|
|
869
|
+
private renderLevelRequested: WlRenderLevel = WlRenderLevel.Freqs;
|
|
797
870
|
|
|
798
871
|
/** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
|
|
799
|
-
render(recalcLevel:
|
|
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:
|
|
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
|
-
|
|
849
|
-
this.
|
|
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 =
|
|
857
|
-
this.positions[jPos].calcScreen(jPos, absoluteMaxHeight, this.positionHeight
|
|
858
|
-
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);
|
|
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 {
|
|
938
|
+
*@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
|
|
864
939
|
*/
|
|
865
|
-
const renderInt = (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
|
-
|
|
888
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1163
|
-
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);
|
|
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:
|
|
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:
|
|
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++;
|