@datagrok/bio 2.4.54 → 2.6.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/detectors.js +23 -5
- 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/package.json +4 -4
- package/src/package-test.ts +1 -3
- package/src/tests/WebLogo-positions-test.ts +10 -14
- package/src/tests/detectors-benchmark-tests.ts +9 -9
- package/src/tests/detectors-tests.ts +30 -0
- package/src/tests/msa-tests.ts +4 -4
- package/src/tests/renderers-test.ts +26 -44
- package/src/tests/similarity-diversity-tests.ts +35 -52
- package/src/tests/splitters-test.ts +16 -6
- package/src/tests/utils/sequences-generators.ts +7 -3
- package/src/tests/viewers.ts +3 -7
- package/src/utils/convert.ts +0 -2
- package/src/viewers/web-logo-viewer.ts +75 -55
|
@@ -3,7 +3,7 @@ import * as ui from 'datagrok-api/ui';
|
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
4
|
|
|
5
5
|
import wu from 'wu';
|
|
6
|
-
import {fromEvent, Unsubscribable} from 'rxjs';
|
|
6
|
+
import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
|
|
7
7
|
|
|
8
8
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
9
9
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
FilterSources,
|
|
18
18
|
HorizontalAlignments,
|
|
19
|
+
IWebLogoViewer,
|
|
19
20
|
PositionHeight,
|
|
20
21
|
PositionMarginStates,
|
|
21
22
|
positionSeparator,
|
|
@@ -67,6 +68,9 @@ export class PositionInfo {
|
|
|
67
68
|
/** Position name from column tag*/
|
|
68
69
|
public readonly name: string;
|
|
69
70
|
|
|
71
|
+
private readonly _label: string | undefined;
|
|
72
|
+
public get label(): string { return !!this._label ? this._label : this.name; }
|
|
73
|
+
|
|
70
74
|
private readonly _freqs: { [m: string]: PositionMonomerInfo };
|
|
71
75
|
|
|
72
76
|
rowCount: number;
|
|
@@ -79,30 +83,33 @@ export class PositionInfo {
|
|
|
79
83
|
* @param {number} rowCount Count of elements in column
|
|
80
84
|
* @param {number} sumForHeightCalc Sum of all monomer counts for height calculation
|
|
81
85
|
*/
|
|
82
|
-
constructor(pos: number, name: string,
|
|
83
|
-
|
|
86
|
+
constructor(pos: number, name: string, freqs?: { [m: string]: PositionMonomerInfo },
|
|
87
|
+
options?: { rowCount?: number, sumForHeightCalc?: number, label?: string }
|
|
84
88
|
) {
|
|
85
89
|
this.pos = pos;
|
|
86
90
|
this.name = name;
|
|
87
|
-
this._freqs = freqs;
|
|
88
|
-
|
|
89
|
-
this.
|
|
91
|
+
this._freqs = freqs ?? {};
|
|
92
|
+
|
|
93
|
+
if (options?.rowCount) this.rowCount = options.rowCount;
|
|
94
|
+
if (options?.sumForHeightCalc) this.sumForHeightCalc = options.sumForHeightCalc;
|
|
95
|
+
if (options?.label) this._label = options.label;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
public getMonomers(): string[] {
|
|
93
99
|
return Object.keys(this._freqs);
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
public hasMonomer(m: string): boolean {
|
|
103
|
+
return m in this._freqs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Creates empty PositionMonomerInfo for {@link m} key if missed in {@link _freqs}. */
|
|
96
107
|
public getFreq(m: string): PositionMonomerInfo {
|
|
97
108
|
let res: PositionMonomerInfo = this._freqs[m];
|
|
98
109
|
if (!res) res = this._freqs[m] = new PositionMonomerInfo();
|
|
99
110
|
return res;
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
public setFreq(m: string, value: PositionMonomerInfo): void {
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
113
|
getMonomerAt(calculatedX: number, y: number): string | undefined {
|
|
107
114
|
const findRes = Object.entries(this._freqs)
|
|
108
115
|
.find(([m, pmInfo]) => {
|
|
@@ -277,7 +284,7 @@ enum RecalcLevel {
|
|
|
277
284
|
Freqs = 2,
|
|
278
285
|
}
|
|
279
286
|
|
|
280
|
-
export class WebLogoViewer extends DG.JsViewer {
|
|
287
|
+
export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
281
288
|
public static residuesSet = 'nucleotides';
|
|
282
289
|
private static viewerCount: number = -1;
|
|
283
290
|
|
|
@@ -319,13 +326,13 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
319
326
|
public minHeight: number;
|
|
320
327
|
public backgroundColor: number = 0xFFFFFFFF;
|
|
321
328
|
public maxHeight: number;
|
|
322
|
-
public positionMarginState:
|
|
329
|
+
public positionMarginState: PositionMarginStates;
|
|
323
330
|
public positionMargin: number = 0;
|
|
324
331
|
public startPositionName: string | null;
|
|
325
332
|
public endPositionName: string | null;
|
|
326
333
|
public fixWidth: boolean;
|
|
327
|
-
public verticalAlignment:
|
|
328
|
-
public horizontalAlignment:
|
|
334
|
+
public verticalAlignment: VerticalAlignments;
|
|
335
|
+
public horizontalAlignment: HorizontalAlignments;
|
|
329
336
|
public fitArea: boolean;
|
|
330
337
|
public shrinkEmptyTail: boolean;
|
|
331
338
|
public positionHeight: string;
|
|
@@ -334,6 +341,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
334
341
|
public filterSource: FilterSources;
|
|
335
342
|
|
|
336
343
|
private positionNames: string[] = [];
|
|
344
|
+
private positionLabels: string[] | undefined = undefined;
|
|
337
345
|
private startPosition: number = -1;
|
|
338
346
|
private endPosition: number = -1;
|
|
339
347
|
|
|
@@ -353,7 +361,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
353
361
|
}
|
|
354
362
|
|
|
355
363
|
/** For startPosition equals to endPosition Length is 1 */
|
|
356
|
-
|
|
364
|
+
public get Length(): number {
|
|
357
365
|
if (this.skipEmptyPositions)
|
|
358
366
|
return this.positions.length;
|
|
359
367
|
|
|
@@ -366,13 +374,13 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
366
374
|
}
|
|
367
375
|
|
|
368
376
|
private get positionMarginValue() {
|
|
369
|
-
if (
|
|
370
|
-
|
|
377
|
+
if (this.positionMarginState === PositionMarginStates.AUTO &&
|
|
378
|
+
this.unitsHandler?.getAlphabetIsMultichar() === true
|
|
379
|
+
) return this.positionMargin;
|
|
371
380
|
|
|
372
|
-
if (this.positionMarginState ===
|
|
381
|
+
if (this.positionMarginState === PositionMarginStates.ON)
|
|
373
382
|
return this.positionMargin;
|
|
374
383
|
|
|
375
|
-
|
|
376
384
|
return 0;
|
|
377
385
|
}
|
|
378
386
|
|
|
@@ -431,9 +439,9 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
431
439
|
|
|
432
440
|
// -- Layout --
|
|
433
441
|
this.verticalAlignment = this.string(PROPS.verticalAlignment, defaults.verticalAlignment,
|
|
434
|
-
{category: PROPS_CATS.LAYOUT, choices: Object.values(VerticalAlignments)});
|
|
442
|
+
{category: PROPS_CATS.LAYOUT, choices: Object.values(VerticalAlignments)}) as VerticalAlignments;
|
|
435
443
|
this.horizontalAlignment = this.string(PROPS.horizontalAlignment, defaults.horizontalAlignment,
|
|
436
|
-
{category: PROPS_CATS.LAYOUT, choices: Object.values(HorizontalAlignments)});
|
|
444
|
+
{category: PROPS_CATS.LAYOUT, choices: Object.values(HorizontalAlignments)}) as HorizontalAlignments;
|
|
437
445
|
this.fixWidth = this.bool(PROPS.fixWidth, defaults.fixWidth,
|
|
438
446
|
{category: PROPS_CATS.LAYOUT});
|
|
439
447
|
this.fitArea = this.bool(PROPS.fitArea, defaults.fitArea,
|
|
@@ -443,7 +451,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
443
451
|
this.maxHeight = this.float(PROPS.maxHeight, defaults.maxHeight,
|
|
444
452
|
{category: PROPS_CATS.LAYOUT/*, editor: 'slider', min: 25, max: 500, postfix: 'px'*/});
|
|
445
453
|
this.positionMarginState = this.string(PROPS.positionMarginState, defaults.positionMarginState,
|
|
446
|
-
{category: PROPS_CATS.LAYOUT, choices: Object.values(PositionMarginStates)});
|
|
454
|
+
{category: PROPS_CATS.LAYOUT, choices: Object.values(PositionMarginStates)}) as PositionMarginStates;
|
|
447
455
|
let defaultValueForPositionMargin = 0;
|
|
448
456
|
if (this.positionMarginState === 'auto') defaultValueForPositionMargin = 4;
|
|
449
457
|
this.positionMargin = this.int(PROPS.positionMargin, defaultValueForPositionMargin,
|
|
@@ -584,6 +592,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
584
592
|
if (!this.seqCol) {
|
|
585
593
|
this.unitsHandler = null;
|
|
586
594
|
this.positionNames = [];
|
|
595
|
+
this.positionLabels = [];
|
|
587
596
|
this.startPosition = -1;
|
|
588
597
|
this.endPosition = -1;
|
|
589
598
|
this.cp = null;
|
|
@@ -598,14 +607,19 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
598
607
|
if (!this.seqCol)
|
|
599
608
|
return;
|
|
600
609
|
|
|
601
|
-
const
|
|
602
|
-
.
|
|
610
|
+
const dfFilter = this.filterSource === FilterSources.Filtered ? this.dataFrame.filter :
|
|
611
|
+
this.dataFrame.selection;
|
|
612
|
+
const maxLength = wu.enumerate(this.unitsHandler!.splitted).map(([mList, rowI]) => {
|
|
613
|
+
return dfFilter.get(rowI) && !!mList ? mList.length : 0;
|
|
614
|
+
}).reduce((max, l) => Math.max(max, l), 0);
|
|
603
615
|
|
|
604
|
-
|
|
616
|
+
/** positionNames and positionLabel can be set up through the column's tags only */
|
|
605
617
|
const positionNamesTxt = this.seqCol.getTag(wlTAGS.positionNames);
|
|
606
|
-
|
|
607
|
-
this.positionNames = positionNamesTxt ? positionNamesTxt.split(positionSeparator).map((
|
|
608
|
-
[...Array(maxLength).keys()].map((jPos) => `${jPos + 1}`)
|
|
618
|
+
const positionLabelsTxt = this.seqCol.getTag(wlTAGS.positionLabels);
|
|
619
|
+
this.positionNames = !!positionNamesTxt ? positionNamesTxt.split(positionSeparator).map((v) => v.trim()) :
|
|
620
|
+
[...Array(maxLength).keys()].map((jPos) => `${jPos + 1}`)/* fallback if tag is not provided */;
|
|
621
|
+
this.positionLabels = !!positionLabelsTxt ? positionLabelsTxt.split(positionSeparator).map((v) => v.trim()) :
|
|
622
|
+
undefined;
|
|
609
623
|
|
|
610
624
|
this.startPosition = (this.startPositionName && this.positionNames &&
|
|
611
625
|
this.positionNames.includes(this.startPositionName)) ?
|
|
@@ -735,6 +749,10 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
735
749
|
}
|
|
736
750
|
}
|
|
737
751
|
|
|
752
|
+
private _onSizeChanged: Subject<void> = new Subject<void>();
|
|
753
|
+
|
|
754
|
+
public get onSizeChanged(): Observable<void> { return this._onSizeChanged; }
|
|
755
|
+
|
|
738
756
|
// -- Routines --
|
|
739
757
|
|
|
740
758
|
getMonomer(p: DG.Point): [number, string | null, PositionMonomerInfo | null] {
|
|
@@ -763,25 +781,12 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
763
781
|
return '';
|
|
764
782
|
}
|
|
765
783
|
|
|
766
|
-
/** Helper function for remove empty positions */
|
|
767
|
-
// TODO: use this function in from core
|
|
768
|
-
protected removeWhere<T>(array: Array<T>, predicate: (item: T) => boolean): Array<any> {
|
|
769
|
-
const length = array.length;
|
|
770
|
-
let updateIterator = 0;
|
|
771
|
-
for (let deleteIterator = 0; deleteIterator < length; deleteIterator++) {
|
|
772
|
-
if (!predicate(array[deleteIterator])) {
|
|
773
|
-
array[updateIterator] = array[deleteIterator];
|
|
774
|
-
updateIterator++;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
array.length = updateIterator;
|
|
778
|
-
return array;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
784
|
/** Function for removing empty positions */
|
|
782
785
|
protected _removeEmptyPositions() {
|
|
783
|
-
if (this.skipEmptyPositions)
|
|
784
|
-
this.
|
|
786
|
+
if (this.skipEmptyPositions) {
|
|
787
|
+
this.positions = wu(this.positions)
|
|
788
|
+
.filter((pi) => !pi.hasMonomer('-') || pi.getFreq('-').count !== pi.rowCount).toArray();
|
|
789
|
+
}
|
|
785
790
|
}
|
|
786
791
|
|
|
787
792
|
private renderRequested: boolean = false;
|
|
@@ -804,18 +809,24 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
804
809
|
this.positions = new Array(posCount);
|
|
805
810
|
for (let jPos = 0; jPos < length; jPos++) {
|
|
806
811
|
const posName: string = this.positionNames[this.startPosition + jPos];
|
|
807
|
-
|
|
812
|
+
const posLabel: string | undefined = this.positionLabels ?
|
|
813
|
+
this.positionLabels[this.startPosition + jPos] : undefined;
|
|
814
|
+
this.positions[jPos] = new PositionInfo(this.startPosition + jPos, posName, {}, {label: posLabel});
|
|
808
815
|
}
|
|
809
816
|
|
|
810
817
|
// 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
|
|
818
|
+
const dfFilter =
|
|
819
|
+
this.filterSource === FilterSources.Filtered ? this.dataFrame.filter : this.dataFrame.selection;
|
|
820
|
+
const dfRowCount = this.dataFrame.rowCount;
|
|
821
|
+
const splitted = this.unitsHandler.splitted;
|
|
822
|
+
for (let rowI = 0; rowI < dfRowCount; ++rowI) {
|
|
823
|
+
if (dfFilter.get(rowI)) {
|
|
824
|
+
const seqMList: string[] = splitted[rowI];
|
|
825
|
+
for (let jPos = 0; jPos < length; ++jPos) {
|
|
826
|
+
const m: string = seqMList[this.startPosition + jPos] || '-';
|
|
827
|
+
const pmInfo = this.positions[jPos].getFreq(m);
|
|
828
|
+
pmInfo.count++;
|
|
829
|
+
}
|
|
819
830
|
}
|
|
820
831
|
}
|
|
821
832
|
|
|
@@ -900,7 +911,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
900
911
|
hScale, 0, 0, 1,
|
|
901
912
|
jPos * this.positionWidthWithMargin + this._positionWidth / 2 -
|
|
902
913
|
this.positionWidthWithMargin * firstVisibleIndex, 0);
|
|
903
|
-
g.fillText(pos.
|
|
914
|
+
g.fillText(pos.label, 0, 0);
|
|
904
915
|
}
|
|
905
916
|
//#endregion Plot positionNames
|
|
906
917
|
const fontStyle = '16px Roboto, Roboto Local, sans-serif';
|
|
@@ -941,6 +952,9 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
941
952
|
}
|
|
942
953
|
}
|
|
943
954
|
|
|
955
|
+
private _lastWidth: number;
|
|
956
|
+
private _lastHeight: number;
|
|
957
|
+
|
|
944
958
|
/** Calculate canvas size an positionWidth and updates properties */
|
|
945
959
|
private calcSize() {
|
|
946
960
|
if (!this.host) return;
|
|
@@ -1021,6 +1035,12 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
1021
1035
|
this.host.style.setProperty('overflow-y', 'hidden', 'important');
|
|
1022
1036
|
}
|
|
1023
1037
|
}
|
|
1038
|
+
|
|
1039
|
+
if (this._lastWidth !== this.root.clientWidth && this._lastHeight !== this.root.clientHeight) {
|
|
1040
|
+
this._lastWidth = this.root.clientWidth;
|
|
1041
|
+
this._lastHeight = this.root.clientHeight;
|
|
1042
|
+
this._onSizeChanged.next();
|
|
1043
|
+
}
|
|
1024
1044
|
}
|
|
1025
1045
|
|
|
1026
1046
|
public getAlphabetSize(): number {
|