@datagrok/bio 2.11.41 → 2.12.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 +14 -0
- package/README.md +1 -1
- package/detectors.js +11 -11
- package/dist/36.js +1 -1
- package/dist/36.js.map +1 -1
- package/dist/413.js +1 -1
- package/dist/413.js.map +1 -1
- package/dist/590.js +1 -1
- package/dist/590.js.map +1 -1
- package/dist/709.js +1 -1
- package/dist/709.js.map +1 -1
- package/dist/895.js +1 -1
- package/dist/895.js.map +1 -1
- package/dist/package-test.js +2 -2
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/package.json +10 -10
- package/src/analysis/sequence-activity-cliffs.ts +9 -9
- package/src/analysis/sequence-diversity-viewer.ts +3 -3
- package/src/analysis/sequence-search-base-viewer.ts +2 -2
- package/src/analysis/sequence-similarity-viewer.ts +10 -10
- package/src/analysis/sequence-space.ts +26 -23
- package/src/calculations/monomerLevelMols.ts +13 -11
- package/src/const.ts +5 -0
- package/src/package.ts +8 -8
- package/src/tests/WebLogo-layout-tests.ts +5 -2
- package/src/tests/WebLogo-positions-test.ts +20 -16
- package/src/tests/bio-tests.ts +19 -7
- package/src/tests/converters-test.ts +4 -4
- package/src/tests/detectors-benchmark-tests.ts +5 -5
- package/src/tests/detectors-tests.ts +13 -13
- package/src/tests/fasta-export-tests.ts +10 -4
- package/src/tests/mm-distance-tests.ts +10 -10
- package/src/tests/msa-tests.ts +8 -15
- package/src/tests/renderers-monomer-placer.ts +3 -3
- package/src/tests/renderers-test.ts +6 -8
- package/src/tests/splitters-test.ts +14 -13
- package/src/tests/to-atomic-level-tests.ts +2 -2
- package/src/tests/units-handler-get-region.ts +4 -4
- package/src/tests/units-handler-splitted-tests.ts +19 -17
- package/src/tests/units-handler-tests.ts +32 -32
- package/src/utils/cell-renderer.ts +40 -34
- package/src/utils/check-input-column.ts +5 -5
- package/src/utils/context-menu.ts +9 -6
- package/src/utils/convert.ts +9 -9
- package/src/utils/get-region-func-editor.ts +11 -11
- package/src/utils/get-region.ts +10 -12
- package/src/utils/macromolecule-column-widget.ts +9 -5
- package/src/utils/monomer-lib/library-file-manager/event-manager.ts +1 -1
- package/src/utils/multiple-sequence-alignment-ui.ts +6 -6
- package/src/utils/pepsea.ts +1 -0
- package/src/utils/poly-tool/transformation.ts +3 -3
- package/src/utils/save-as-fasta.ts +14 -15
- package/src/utils/sequence-to-mol.ts +4 -4
- package/src/viewers/web-logo-viewer.ts +95 -110
- package/src/widgets/bio-substructure-filter.ts +3 -3
- package/src/widgets/composition-analysis-widget.ts +26 -19
|
@@ -6,7 +6,7 @@ import $ from 'cash-dom';
|
|
|
6
6
|
import wu from 'wu';
|
|
7
7
|
import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
10
10
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
11
11
|
import {
|
|
12
12
|
monomerToShort, pickUpPalette, pickUpSeqCol, TAGS as bioTAGS, positionSeparator
|
|
@@ -17,8 +17,7 @@ import {
|
|
|
17
17
|
} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
18
18
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
19
19
|
import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
|
|
20
|
-
import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
21
|
-
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
20
|
+
import {GAP_SYMBOL, ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
22
21
|
import {testEvent} from '@datagrok-libraries/utils/src/test';
|
|
23
22
|
import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
|
|
24
23
|
|
|
@@ -81,12 +80,6 @@ export class PositionMonomerInfo {
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
export class PositionInfo {
|
|
84
|
-
/** Position in sequence */
|
|
85
|
-
public readonly pos: number;
|
|
86
|
-
|
|
87
|
-
/** Position name from column tag*/
|
|
88
|
-
public readonly name: string;
|
|
89
|
-
|
|
90
83
|
private readonly _label: string | undefined;
|
|
91
84
|
public get label(): string { return !!this._label ? this._label : this.name; }
|
|
92
85
|
|
|
@@ -105,11 +98,12 @@ export class PositionInfo {
|
|
|
105
98
|
* @param {number} rowCount Count of elements in column
|
|
106
99
|
* @param {number} sumForHeightCalc Sum of all monomer counts for height calculation
|
|
107
100
|
*/
|
|
108
|
-
constructor(
|
|
101
|
+
constructor(
|
|
102
|
+
/** Position in sequence */ public readonly pos: number,
|
|
103
|
+
/** Position name from column tag*/ public readonly name: string,
|
|
104
|
+
freqs?: { [m: string]: PositionMonomerInfo },
|
|
109
105
|
options?: { sumRowCount?: number, sumValueForHeight?: number, label?: string }
|
|
110
106
|
) {
|
|
111
|
-
this.pos = pos;
|
|
112
|
-
this.name = name;
|
|
113
107
|
this._freqs = freqs ?? {};
|
|
114
108
|
|
|
115
109
|
if (options?.sumRowCount) this.sumRowCount = options.sumRowCount;
|
|
@@ -172,8 +166,7 @@ export class PositionInfo {
|
|
|
172
166
|
}
|
|
173
167
|
}
|
|
174
168
|
|
|
175
|
-
calcScreen(
|
|
176
|
-
isGap: (m: string) => boolean, posIdx: number, firstVisiblePosIdx: number,
|
|
169
|
+
calcScreen(posIdx: number, firstVisiblePosIdx: number,
|
|
177
170
|
absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
|
|
178
171
|
positionWidthWithMargin: number, positionWidth: number, dpr: number, positionLabelsHeight: number
|
|
179
172
|
): void {
|
|
@@ -184,13 +177,13 @@ export class PositionInfo {
|
|
|
184
177
|
|
|
185
178
|
const entries = Object.entries(this._freqs)
|
|
186
179
|
.sort((a, b) => {
|
|
187
|
-
if (
|
|
180
|
+
if (a[0] !== GAP_SYMBOL && b[0] !== GAP_SYMBOL)
|
|
188
181
|
return b[1].value - a[1].value;
|
|
189
|
-
else if (
|
|
182
|
+
else if (a[0] === GAP_SYMBOL && b[0] === GAP_SYMBOL)
|
|
190
183
|
return 0;
|
|
191
|
-
else if (
|
|
184
|
+
else if (a[0] === GAP_SYMBOL)
|
|
192
185
|
return -1;
|
|
193
|
-
else /* (
|
|
186
|
+
else /* (b[0] === GAP_SYMBOL) */
|
|
194
187
|
return +1;
|
|
195
188
|
});
|
|
196
189
|
for (const [_m, pmi] of entries) {
|
|
@@ -204,11 +197,10 @@ export class PositionInfo {
|
|
|
204
197
|
}
|
|
205
198
|
|
|
206
199
|
render(g: CanvasRenderingContext2D,
|
|
207
|
-
isGap: (m: string) => boolean,
|
|
208
200
|
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number, cp: SeqPalette
|
|
209
201
|
) {
|
|
210
202
|
for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
|
|
211
|
-
if (
|
|
203
|
+
if (monomer !== GAP_SYMBOL) {
|
|
212
204
|
const monomerTxt = monomerToShort(monomer, 5);
|
|
213
205
|
const b = pmInfo.bounds!;
|
|
214
206
|
const left = b.left;
|
|
@@ -240,6 +232,8 @@ export class PositionInfo {
|
|
|
240
232
|
}
|
|
241
233
|
|
|
242
234
|
buildCompositionTable(palette: SeqPalette): HTMLTableElement {
|
|
235
|
+
if ('-' in this._freqs)
|
|
236
|
+
throw new Error(`Unexpected monomer symbol '-'.`);
|
|
243
237
|
return buildCompositionTable(palette,
|
|
244
238
|
Object.assign({}, ...Object.entries(this._freqs)
|
|
245
239
|
.map(([m, pmi]) => ({[m]: pmi.rowCount})))
|
|
@@ -305,7 +299,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
305
299
|
|
|
306
300
|
private viewed: boolean = false;
|
|
307
301
|
|
|
308
|
-
private
|
|
302
|
+
private seqHandler: SeqHandler | null;
|
|
309
303
|
private initialized: boolean = false;
|
|
310
304
|
|
|
311
305
|
// private readonly colorScheme: ColorScheme = ColorSchemes[NucleotidesWebLogo.residuesSet];
|
|
@@ -375,7 +369,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
375
369
|
}
|
|
376
370
|
|
|
377
371
|
public get positionMarginValue(): number {
|
|
378
|
-
if (this.positionMarginState === PositionMarginStates.AUTO && this.
|
|
372
|
+
if (this.positionMarginState === PositionMarginStates.AUTO && this.seqHandler!.getAlphabetIsMultichar() === true)
|
|
379
373
|
return this.positionMargin;
|
|
380
374
|
else if (this.positionMarginState === PositionMarginStates.ON)
|
|
381
375
|
return this.positionMargin;
|
|
@@ -387,7 +381,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
387
381
|
super();
|
|
388
382
|
|
|
389
383
|
this.textBaseline = 'top';
|
|
390
|
-
this.
|
|
384
|
+
this.seqHandler = null;
|
|
391
385
|
|
|
392
386
|
// -- Data --
|
|
393
387
|
this.sequenceColumnName = this.string(PROPS.sequenceColumnName, defaults.sequenceColumnName,
|
|
@@ -591,7 +585,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
591
585
|
}
|
|
592
586
|
if (this.seqCol) {
|
|
593
587
|
try {
|
|
594
|
-
this.
|
|
588
|
+
this.seqHandler = SeqHandler.forColumn(this.seqCol);
|
|
595
589
|
|
|
596
590
|
this.palette = pickUpPalette(this.seqCol);
|
|
597
591
|
this.render(WlRenderLevel.Freqs, 'updateSeqCol()');
|
|
@@ -602,7 +596,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
602
596
|
throw err;
|
|
603
597
|
}
|
|
604
598
|
if (!this.seqCol) {
|
|
605
|
-
this.
|
|
599
|
+
this.seqHandler = null;
|
|
606
600
|
this.positionNames = [];
|
|
607
601
|
this.positionLabels = [];
|
|
608
602
|
this.startPosition = -1;
|
|
@@ -616,13 +610,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
616
610
|
private getFilter(): DG.BitSet {
|
|
617
611
|
let dfFilterRes: DG.BitSet;
|
|
618
612
|
switch (this.filterSource) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
613
|
+
case FilterSources.Filtered:
|
|
614
|
+
dfFilterRes = this.dataFrame.filter;
|
|
615
|
+
break;
|
|
622
616
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
617
|
+
case FilterSources.Selected:
|
|
618
|
+
dfFilterRes = this.dataFrame.selection.trueCount === 0 ? this.dataFrame.filter : this.dataFrame.selection;
|
|
619
|
+
break;
|
|
626
620
|
}
|
|
627
621
|
return dfFilterRes;
|
|
628
622
|
}
|
|
@@ -816,45 +810,43 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
816
810
|
super.onPropertyChanged(property);
|
|
817
811
|
|
|
818
812
|
switch (property.name) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
813
|
+
case PROPS.sequenceColumnName:
|
|
814
|
+
this.updateSeqCol();
|
|
815
|
+
break;
|
|
816
|
+
case PROPS.sequenceColumnName:
|
|
817
|
+
case PROPS.startPositionName:
|
|
818
|
+
case PROPS.endPositionName:
|
|
819
|
+
case PROPS.filterSource:
|
|
820
|
+
case PROPS.shrinkEmptyTail:
|
|
821
|
+
case PROPS.skipEmptyPositions:
|
|
822
|
+
case PROPS.positionHeight: {
|
|
823
|
+
this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
case PROPS.valueColumnName:
|
|
827
|
+
case PROPS.valueAggrType: {
|
|
828
|
+
this.render(WlRenderLevel.Freqs, `onPropertyChanged( ${property.name} )`);
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
case PROPS.minHeight:
|
|
832
|
+
case PROPS.maxHeight:
|
|
833
|
+
case PROPS.positionWidth:
|
|
834
|
+
case PROPS.showPositionLabels:
|
|
835
|
+
case PROPS.fixWidth:
|
|
836
|
+
case PROPS.fitArea:
|
|
837
|
+
case PROPS.horizontalAlignment:
|
|
838
|
+
case PROPS.verticalAlignment:
|
|
839
|
+
case PROPS.positionMargin:
|
|
840
|
+
case PROPS.positionMarginState: {
|
|
838
841
|
// this.positionWidth obtains a new value
|
|
839
842
|
// this.updateSlider updates this._positionWidth
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
case PROPS.verticalAlignment:
|
|
848
|
-
case PROPS.positionMargin:
|
|
849
|
-
case PROPS.positionMarginState: {
|
|
850
|
-
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
|
|
851
|
-
break;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
case PROPS.backgroundColor: {
|
|
855
|
-
this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
|
|
856
|
-
break;
|
|
857
|
-
}
|
|
843
|
+
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case PROPS.backgroundColor: {
|
|
847
|
+
this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
858
850
|
}
|
|
859
851
|
}
|
|
860
852
|
|
|
@@ -928,8 +920,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
928
920
|
protected _removeEmptyPositions() {
|
|
929
921
|
if (this.skipEmptyPositions) {
|
|
930
922
|
this.positions = wu(this.positions).filter((pi) => {
|
|
931
|
-
|
|
932
|
-
return !pi.hasMonomer(gapSymbol) || pi.getFreq(gapSymbol).rowCount !== pi.sumRowCount;
|
|
923
|
+
return !pi.hasMonomer(GAP_SYMBOL) || pi.getFreq(GAP_SYMBOL).rowCount !== pi.sumRowCount;
|
|
933
924
|
}).toArray();
|
|
934
925
|
}
|
|
935
926
|
}
|
|
@@ -962,9 +953,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
962
953
|
// region updatePositions
|
|
963
954
|
|
|
964
955
|
const dfFilter = this.getFilter();
|
|
965
|
-
const maxLength: number = dfFilter.trueCount === 0 ? this.
|
|
966
|
-
wu.
|
|
967
|
-
|
|
956
|
+
const maxLength: number = dfFilter.trueCount === 0 ? this.seqHandler!.maxLength :
|
|
957
|
+
wu.count(0).take(this.seqHandler!.length).map((rowIdx) => {
|
|
958
|
+
const mList = this.seqHandler!.getSplitted(rowIdx);
|
|
959
|
+
return dfFilter.get(rowIdx) && !!mList ? mList.length : 0;
|
|
968
960
|
}).reduce((max, l) => Math.max(max, l), 0);
|
|
969
961
|
|
|
970
962
|
/** positionNames and positionLabel can be set up through the column's tags only */
|
|
@@ -985,7 +977,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
985
977
|
// endregion updatePositions
|
|
986
978
|
|
|
987
979
|
const length: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
988
|
-
this.
|
|
980
|
+
this.seqHandler = SeqHandler.forColumn(this.seqCol);
|
|
989
981
|
const posCount: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
990
982
|
this.positions = new Array(posCount);
|
|
991
983
|
for (let jPos = 0; jPos < length; jPos++) {
|
|
@@ -997,17 +989,16 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
997
989
|
|
|
998
990
|
// 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
|
|
999
991
|
const dfRowCount = this.dataFrame.rowCount;
|
|
1000
|
-
const splitted = this.unitsHandler.splitted;
|
|
1001
992
|
|
|
1002
993
|
for (let jPos = 0; jPos < length; ++jPos) {
|
|
994
|
+
const pi = this.positions[jPos];
|
|
1003
995
|
// Here we want to build lists of values for every monomer in position jPos
|
|
1004
996
|
for (let rowI = 0; rowI < dfRowCount; ++rowI) {
|
|
1005
997
|
if (dfFilter.get(rowI)) {
|
|
1006
|
-
const seqMList: ISeqSplitted = splitted[rowI];
|
|
1007
|
-
const m: string = seqMList[this.startPosition + jPos] || this.unitsHandler.defaultGapSymbol;
|
|
1008
|
-
const pi = this.positions[jPos];
|
|
1009
|
-
const pmi = pi.getFreq(m);
|
|
1010
998
|
++pi.sumRowCount;
|
|
999
|
+
const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
|
|
1000
|
+
const cm: string = seqMList.getCanonical(this.startPosition + jPos);
|
|
1001
|
+
const pmi = pi.getFreq(cm);
|
|
1011
1002
|
pmi.value = ++pmi.rowCount;
|
|
1012
1003
|
}
|
|
1013
1004
|
}
|
|
@@ -1024,10 +1015,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1024
1015
|
|
|
1025
1016
|
for (let rowI = 0; rowI < dfRowCount; ++rowI) {
|
|
1026
1017
|
if (dfFilter.get(rowI)) { // respect the filter
|
|
1027
|
-
const seqMList: ISeqSplitted =
|
|
1028
|
-
const
|
|
1018
|
+
const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
|
|
1019
|
+
const cm: string = seqMList.getCanonical(this.startPosition + jPos);
|
|
1029
1020
|
const value: number | null = valueCol.get(rowI);
|
|
1030
|
-
this.positions[jPos].getFreq(
|
|
1021
|
+
this.positions[jPos].getFreq(cm).push(value);
|
|
1031
1022
|
}
|
|
1032
1023
|
}
|
|
1033
1024
|
this.positions[jPos].aggregate(this.valueAggrType);
|
|
@@ -1066,7 +1057,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1066
1057
|
`this.positions.length = ${this.positions.length}, jPos = ${jPos}`);
|
|
1067
1058
|
continue;
|
|
1068
1059
|
}
|
|
1069
|
-
this.positions[jPos].calcScreen(
|
|
1060
|
+
this.positions[jPos].calcScreen(
|
|
1070
1061
|
jPos, this.slider.min, absoluteMaxHeight, this.positionHeight,
|
|
1071
1062
|
alphabetSizeLog, this._positionWidthWithMargin, this._positionWidth, dpr, positionLabelsHeight);
|
|
1072
1063
|
}
|
|
@@ -1078,9 +1069,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1078
1069
|
if (this.seqCol && !this.palette) {
|
|
1079
1070
|
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
1080
1071
|
this.msgHost!.style.display = '';
|
|
1081
|
-
} else
|
|
1072
|
+
} else
|
|
1082
1073
|
this.msgHost!.style.display = 'none';
|
|
1083
|
-
}
|
|
1084
1074
|
}
|
|
1085
1075
|
|
|
1086
1076
|
if (!this.seqCol || !this.dataFrame || !this.palette || this.host == null || this.slider == null)
|
|
@@ -1123,10 +1113,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1123
1113
|
// Hacks to scale uppercase characters to target rectangle
|
|
1124
1114
|
const uppercaseLetterAscent = 0.25;
|
|
1125
1115
|
const uppercaseLetterHeight = 12.2;
|
|
1126
|
-
for (let jPos = firstPos; jPos <= lastPos; jPos++)
|
|
1127
|
-
this.positions[jPos].render(g,
|
|
1128
|
-
fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, this.palette);
|
|
1129
|
-
}
|
|
1116
|
+
for (let jPos = firstPos; jPos <= lastPos; jPos++)
|
|
1117
|
+
this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, this.palette);
|
|
1130
1118
|
} finally {
|
|
1131
1119
|
g.restore();
|
|
1132
1120
|
}
|
|
@@ -1141,18 +1129,16 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1141
1129
|
return;
|
|
1142
1130
|
}
|
|
1143
1131
|
this.requestedRenderLevel = WlRenderLevel.None;
|
|
1144
|
-
this.
|
|
1145
|
-
.
|
|
1146
|
-
|
|
1147
|
-
_package.logger.error(errMsg, undefined, errStack);
|
|
1148
|
-
});
|
|
1132
|
+
this.viewSyncer.sync(logPrefix, async () => {
|
|
1133
|
+
await this.renderInt(renderLevel);
|
|
1134
|
+
});
|
|
1149
1135
|
}
|
|
1150
1136
|
|
|
1151
1137
|
private _lastWidth: number;
|
|
1152
1138
|
private _lastHeight: number;
|
|
1153
1139
|
|
|
1154
1140
|
public getAlphabetSize(): number {
|
|
1155
|
-
return this.
|
|
1141
|
+
return this.seqHandler?.getAlphabetSize() ?? 0;
|
|
1156
1142
|
}
|
|
1157
1143
|
|
|
1158
1144
|
// -- Handle events --
|
|
@@ -1218,10 +1204,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1218
1204
|
tooltipRows.push(pi.buildCompositionTable(this.palette!));
|
|
1219
1205
|
const tooltipEl = ui.divV(tooltipRows);
|
|
1220
1206
|
ui.tooltip.show(tooltipEl, args.x + 16, args.y + 16);
|
|
1221
|
-
} else if (pi !== null && monomer && this.dataFrame && this.seqCol && this.
|
|
1207
|
+
} else if (pi !== null && monomer && this.dataFrame && this.seqCol && this.seqHandler) {
|
|
1222
1208
|
// Monomer at position tooltip
|
|
1223
1209
|
// const monomerAtPosSeqCount = countForMonomerAtPosition(
|
|
1224
|
-
// this.dataFrame, this.
|
|
1210
|
+
// this.dataFrame, this.seqHandler!, this.getFilter(), monomer, atPI);
|
|
1225
1211
|
const pmi = pi.getFreq(monomer);
|
|
1226
1212
|
|
|
1227
1213
|
const tooltipRows = [
|
|
@@ -1233,9 +1219,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1233
1219
|
tooltipRows.push(ui.div(`${this.valueAggrType}: ${pmi.value.toFixed(3)}`));
|
|
1234
1220
|
const tooltipEl = ui.divV(tooltipRows);
|
|
1235
1221
|
ui.tooltip.show(tooltipEl, args.x + 16, args.y + 16);
|
|
1236
|
-
} else
|
|
1222
|
+
} else
|
|
1237
1223
|
ui.tooltip.hide();
|
|
1238
|
-
}
|
|
1239
1224
|
} catch (err: any) {
|
|
1240
1225
|
const errMsg = errorToConsole(err);
|
|
1241
1226
|
_package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.canvasOnMouseMove() error:\n` + errMsg);
|
|
@@ -1250,10 +1235,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1250
1235
|
const [pi, monomer] = this.getMonomer(this.canvas.getCursorPosition(args, dpr), dpr);
|
|
1251
1236
|
|
|
1252
1237
|
// prevents deselect all rows if we miss monomer bounds
|
|
1253
|
-
if (pi !== null && monomer !== null && this.dataFrame && this.seqCol && this.
|
|
1238
|
+
if (pi !== null && monomer !== null && this.dataFrame && this.seqCol && this.seqHandler) {
|
|
1254
1239
|
// Calculate a new BitSet object for selection to prevent interfering with existing
|
|
1255
1240
|
const selBS: DG.BitSet = DG.BitSet.create(this.dataFrame.selection.length, (rowI: number) => {
|
|
1256
|
-
return checkSeqForMonomerAtPos(this.dataFrame, this.
|
|
1241
|
+
return checkSeqForMonomerAtPos(this.dataFrame, this.seqHandler!, this.getFilter(), rowI, monomer, pi);
|
|
1257
1242
|
});
|
|
1258
1243
|
this.dataFrame.selection.init((i) => selBS.get(i));
|
|
1259
1244
|
}
|
|
@@ -1288,8 +1273,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1288
1273
|
const callLog = `invalidate(${caller ? ` <- ${caller} ` : ''})`;
|
|
1289
1274
|
const logPrefix = `${this.viewerToLog()}.${callLog}`;
|
|
1290
1275
|
// Put the event trigger in the tail of the synced calls queue.
|
|
1276
|
+
this.render(WlRenderLevel.None, callLog); // Put render request to the syncer
|
|
1291
1277
|
this.viewSyncer.sync(`${logPrefix}`, async () => {
|
|
1292
|
-
this.render(WlRenderLevel.None, callLog);
|
|
1293
1278
|
this._onRendered.next();
|
|
1294
1279
|
});
|
|
1295
1280
|
}
|
|
@@ -1340,23 +1325,23 @@ function renderPositionLabels(g: CanvasRenderingContext2D,
|
|
|
1340
1325
|
}
|
|
1341
1326
|
|
|
1342
1327
|
export function checkSeqForMonomerAtPos(
|
|
1343
|
-
df: DG.DataFrame,
|
|
1328
|
+
df: DG.DataFrame, sh: SeqHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
|
|
1344
1329
|
): boolean {
|
|
1345
|
-
const seqMList: ISeqSplitted =
|
|
1346
|
-
const
|
|
1347
|
-
return
|
|
1330
|
+
const seqMList: ISeqSplitted = sh.getSplitted(rowI);
|
|
1331
|
+
const seqCM: string | null = at.pos < seqMList.length ? seqMList.getCanonical(at.pos) : null;
|
|
1332
|
+
return seqCM !== null && seqCM === monomer;
|
|
1348
1333
|
}
|
|
1349
1334
|
|
|
1350
1335
|
export function countForMonomerAtPosition(
|
|
1351
|
-
df: DG.DataFrame,
|
|
1336
|
+
df: DG.DataFrame, sh: SeqHandler, filter: DG.BitSet, monomer: string, at: PositionInfo
|
|
1352
1337
|
): number {
|
|
1353
1338
|
let count = 0;
|
|
1354
1339
|
let rowI = -1;
|
|
1355
1340
|
while ((rowI = filter.findNext(rowI, true)) != -1) {
|
|
1356
|
-
const seqMList: ISeqSplitted =
|
|
1341
|
+
const seqMList: ISeqSplitted = sh.getSplitted(rowI);
|
|
1357
1342
|
const seqMPos: number = at.pos;
|
|
1358
|
-
const
|
|
1359
|
-
if (
|
|
1343
|
+
const seqCM: string | null = seqMPos < seqMList.length ? seqMList.getCanonical(seqMPos) : null;
|
|
1344
|
+
if (seqCM !== null && seqCM === monomer) count++;
|
|
1360
1345
|
}
|
|
1361
1346
|
return count;
|
|
1362
1347
|
}
|
|
@@ -10,14 +10,14 @@ import * as grok from 'datagrok-api/grok';
|
|
|
10
10
|
|
|
11
11
|
import wu from 'wu';
|
|
12
12
|
import $ from 'cash-dom';
|
|
13
|
-
import {fromEvent, Observable, Subject,
|
|
13
|
+
import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
|
|
14
14
|
|
|
15
15
|
import {TAGS as bioTAGS, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
16
16
|
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
17
17
|
import {delay, testEvent} from '@datagrok-libraries/utils/src/test';
|
|
18
18
|
import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
|
|
19
19
|
import {IHelmWebEditor, IWebEditorApp} from '@datagrok-libraries/bio/src/helm/types';
|
|
20
|
-
import {
|
|
20
|
+
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
21
21
|
import {IRenderer} from '@datagrok-libraries/bio/src/types/renderer';
|
|
22
22
|
import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
|
|
23
23
|
import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
|
|
@@ -120,7 +120,7 @@ export class BioSubstructureFilter extends DG.Filter implements IRenderer {
|
|
|
120
120
|
this.filterSyncer.sync(logPrefix, async () => {
|
|
121
121
|
superAttach(dataFrame);
|
|
122
122
|
this.column = dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
|
|
123
|
-
const
|
|
123
|
+
const sh = SeqHandler.forColumn(this.column!);
|
|
124
124
|
this.columnName ??= this.column?.name;
|
|
125
125
|
this.notation ??= this.column?.getTag(DG.TAGS.UNITS);
|
|
126
126
|
|
|
@@ -8,7 +8,8 @@ import {TAGS as bioTAGS, ALPHABET, getPaletteByType} from '@datagrok-libraries/b
|
|
|
8
8
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
9
9
|
import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
|
|
10
10
|
import '../../css/composition-analysis.css';
|
|
11
|
-
import {
|
|
11
|
+
import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
|
|
12
|
+
import {GAP_SYMBOL} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
@@ -17,24 +18,24 @@ export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
|
|
|
17
18
|
const alphabet = val.cell.column.tags[bioTAGS.alphabet];
|
|
18
19
|
let palette: SeqPalette = UnknownSeqPalettes.Color;
|
|
19
20
|
switch (alphabet) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
case ALPHABET.DNA:
|
|
22
|
+
case ALPHABET.RNA:
|
|
23
|
+
palette = getPaletteByType(ALPHABET.DNA);
|
|
24
|
+
break;
|
|
25
|
+
case ALPHABET.PT:
|
|
26
|
+
palette = getPaletteByType(ALPHABET.PT);
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
break;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const counts: { [m: string]: number } = {};
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const parts =
|
|
35
|
-
wu(parts).filter((
|
|
36
|
-
const count = counts[
|
|
37
|
-
counts[
|
|
33
|
+
const sh = SeqHandler.forColumn(val.cell.column as DG.Column<string>);
|
|
34
|
+
const rowIdx = val.cell.rowIndex;
|
|
35
|
+
const parts = sh.getSplitted(rowIdx);
|
|
36
|
+
wu(parts.canonicals).filter((cm) => cm !== GAP_SYMBOL).forEach((cm) => {
|
|
37
|
+
const count = counts[cm] || 0;
|
|
38
|
+
counts[cm] = count + 1;
|
|
38
39
|
});
|
|
39
40
|
const table = buildCompositionTable(palette, counts);
|
|
40
41
|
Array.from(table.rows).forEach((row) => {
|
|
@@ -57,15 +58,21 @@ export function buildCompositionTable(palette: SeqPalette, counts: { [m: string]
|
|
|
57
58
|
const maxRatio = maxValue! / sumValue;
|
|
58
59
|
const elMap: { [m: string]: HTMLElement } = Object.assign({}, ...Array.from(Object.entries(counts))
|
|
59
60
|
.sort((a, b) => b[1] - a[1])
|
|
60
|
-
.map(([
|
|
61
|
+
.map(([cm, value]) => {
|
|
61
62
|
const ratio = value / sumValue;
|
|
62
|
-
const color = palette.get(
|
|
63
|
+
const color = palette.get(cm);
|
|
63
64
|
const barDiv = ui.div('', {classes: 'macromolecule-cell-comp-analysis-bar'});
|
|
64
65
|
barDiv.style.width = `${50 * ratio / maxRatio}px`;
|
|
65
66
|
barDiv.style.backgroundColor = color;
|
|
67
|
+
if (GAP_SYMBOL === cm) {
|
|
68
|
+
barDiv.style.borderWidth = '1px';
|
|
69
|
+
barDiv.style.borderStyle = 'solid';
|
|
70
|
+
barDiv.style.borderColor = DG.Color.toHtml(DG.Color.lightGray);
|
|
71
|
+
}
|
|
72
|
+
const displayMonomer: string = GAP_SYMBOL === cm ? '-' : cm;
|
|
66
73
|
const valueDiv = ui.div(`${(100 * ratio).toFixed(2)}%`);
|
|
67
74
|
const el = ui.div([barDiv, valueDiv], {classes: 'macromolecule-cell-comp-analysis-value'});
|
|
68
|
-
return ({[
|
|
75
|
+
return ({[displayMonomer]: el});
|
|
69
76
|
}));
|
|
70
77
|
|
|
71
78
|
const table = ui.tableFromMap(elMap);
|