@datagrok/bio 2.4.49 → 2.4.50
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/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 +1 -1
- package/src/viewers/vd-regions-viewer.ts +67 -46
- package/src/viewers/web-logo-viewer.ts +40 -26
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.4.
|
|
8
|
+
"version": "2.4.50",
|
|
9
9
|
"description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import * as ui from 'datagrok-api/ui';
|
|
2
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
3
|
import * as DG from 'datagrok-api/dg';
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import {
|
|
5
|
+
import {fromEvent, Unsubscribable} from 'rxjs';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
IVdRegionsViewer,
|
|
7
9
|
VdRegion, VdRegionType,
|
|
8
10
|
} from '@datagrok-libraries/bio/src/viewers/vd-regions';
|
|
9
|
-
import {FilterSources, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
10
|
-
|
|
11
|
+
import {FilterSources, IWebLogoViewer, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
12
|
+
|
|
13
|
+
import {WebLogoViewer, PROPS as wlPROPS} from '../viewers/web-logo-viewer';
|
|
14
|
+
|
|
15
|
+
import {_package} from '../package';
|
|
11
16
|
|
|
12
17
|
const vrt = VdRegionType;
|
|
13
18
|
|
|
@@ -101,22 +106,29 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
101
106
|
// this.mlbView.dockManager.dock(this.regionsFg.root, DG.DOCK_TYPE.LEFT, rootNode, 'Filter regions', 0.2);
|
|
102
107
|
|
|
103
108
|
this.subs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
104
|
-
this.subs.push(
|
|
109
|
+
this.subs.push(fromEvent<MouseEvent>(this.root, 'mousemove').subscribe(this.rootOnMouseMove.bind(this)));
|
|
105
110
|
|
|
106
111
|
// await this.buildView('init'); // init
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
114
|
+
override detach() {
|
|
115
|
+
if (this.setDataInProgress) return;
|
|
116
|
+
const superDetach = super.detach.bind(this);
|
|
117
|
+
this.detachPromise = this.detachPromise.then(async () => { // detach
|
|
118
|
+
await this.viewPromise;
|
|
119
|
+
if (this.viewed) {
|
|
120
|
+
await this.destroyView('detach');
|
|
121
|
+
this.viewed = false;
|
|
116
122
|
}
|
|
123
|
+
superDetach();
|
|
117
124
|
});
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
override onTableAttached() {
|
|
128
|
+
super.onTableAttached();
|
|
129
|
+
this.setData(this.dataFrame, this.regions);
|
|
130
|
+
}
|
|
131
|
+
|
|
120
132
|
public override onPropertyChanged(property: DG.Property | null): void {
|
|
121
133
|
super.onPropertyChanged(property);
|
|
122
134
|
|
|
@@ -156,39 +168,41 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
156
168
|
|
|
157
169
|
// TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
|
|
158
170
|
public setData(mlbDf: DG.DataFrame, regions: VdRegion[]) {
|
|
159
|
-
|
|
171
|
+
if (!this.setDataInProgress) this.setDataInProgress = true; else return;
|
|
172
|
+
_package.logger.debug('Bio: VdRegionsViewer.setData()');
|
|
173
|
+
|
|
160
174
|
this.viewPromise = this.viewPromise.then(async () => { // setData
|
|
161
175
|
if (this.viewed) {
|
|
162
|
-
await this.destroyView('setData');
|
|
176
|
+
await this.destroyView('setData');
|
|
163
177
|
this.viewed = false;
|
|
164
178
|
}
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
}).then(async () => {
|
|
180
|
+
await this.detachPromise;
|
|
181
|
+
// Wait whether this.dataFrame assigning has called detach() before continue set data and build view
|
|
182
|
+
|
|
183
|
+
// -- Data --
|
|
184
|
+
this.regions = regions;
|
|
185
|
+
if (this.dataFrame.dart !== mlbDf.dart) this.dataFrame = mlbDf; // causes detach and onTableAttached
|
|
186
|
+
}).then(async () => {
|
|
171
187
|
if (!this.viewed) {
|
|
172
|
-
await this.buildView('setData');
|
|
188
|
+
await this.buildView('setData');
|
|
173
189
|
this.viewed = true;
|
|
174
190
|
}
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await this.destroyView('detach'); // detach
|
|
183
|
-
this.viewed = false;
|
|
184
|
-
}
|
|
185
|
-
superDetach();
|
|
191
|
+
}).catch((err: any) => {
|
|
192
|
+
const errMsg = err instanceof Error ? err.message : err.toString();
|
|
193
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
194
|
+
grok.shell.error(errMsg);
|
|
195
|
+
_package.logger.error(errMsg, undefined, stack);
|
|
196
|
+
}).finally(() => {
|
|
197
|
+
this.setDataInProgress = false;
|
|
186
198
|
});
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
// -- View --
|
|
190
202
|
|
|
191
203
|
private viewPromise: Promise<void> = Promise.resolve();
|
|
204
|
+
private detachPromise: Promise<void> = Promise.resolve();
|
|
205
|
+
private setDataInProgress: boolean = false;
|
|
192
206
|
|
|
193
207
|
private host: HTMLElement | null = null;
|
|
194
208
|
private filterSourceInput: DG.InputBase<boolean | null> | null = null;
|
|
@@ -223,25 +237,32 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
223
237
|
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
224
238
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
225
239
|
|
|
226
|
-
|
|
240
|
+
const logoPromiseList: Promise<[number, string, WebLogoViewer]>[] = [];
|
|
227
241
|
for (let orderI = 0; orderI < orderList.length; orderI++) {
|
|
228
|
-
const regionChains: { [chain: string]: WebLogoViewer } = {};
|
|
229
242
|
for (const chain of this.chains) {
|
|
230
243
|
const region: VdRegion | undefined = regionsFiltered
|
|
231
244
|
.find((r) => r.order == orderList[orderI] && r.chain == chain);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
logoPromiseList.push((async () => {
|
|
246
|
+
const wl: WebLogoViewer = await this.dataFrame.plot.fromType('WebLogo', {
|
|
247
|
+
sequenceColumnName: region!.sequenceColumnName,
|
|
248
|
+
startPositionName: region!.positionStartName,
|
|
249
|
+
endPositionName: region!.positionEndName,
|
|
250
|
+
fixWidth: true,
|
|
251
|
+
skipEmptyPositions: this.skipEmptyPositions,
|
|
252
|
+
positionWidth: this.positionWidth,
|
|
253
|
+
positionHeight: this.positionHeight,
|
|
254
|
+
}) as WebLogoViewer;
|
|
255
|
+
return [orderI, chain, wl];
|
|
256
|
+
})());
|
|
241
257
|
}
|
|
242
|
-
// WebLogo creation fires onRootSizeChanged event even before control being added to this.logos
|
|
243
|
-
this.logos[orderI] = regionChains;
|
|
244
258
|
}
|
|
259
|
+
const logoList: [number, string, WebLogoViewer][] = await Promise.all(logoPromiseList);
|
|
260
|
+
// Fill in this.logos with created viewers
|
|
261
|
+
this.logos = new Array(orderList.length);
|
|
262
|
+
for (let orderI = 0; orderI < orderList.length; ++orderI)
|
|
263
|
+
this.logos[orderI] = {};
|
|
264
|
+
for (const [orderI, chain, wl] of logoList)
|
|
265
|
+
this.logos[orderI][chain] = wl;
|
|
245
266
|
|
|
246
267
|
// ui.tableFromMap()
|
|
247
268
|
// DG.HtmlTable.create()
|
|
@@ -5,24 +5,27 @@ import * as DG from 'datagrok-api/dg';
|
|
|
5
5
|
import wu from 'wu';
|
|
6
6
|
import {fromEvent, Unsubscribable} from 'rxjs';
|
|
7
7
|
|
|
8
|
-
import {SliderOptions} from 'datagrok-api/dg';
|
|
9
8
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
10
9
|
import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
|
|
11
10
|
import {
|
|
12
|
-
|
|
11
|
+
monomerToShort,
|
|
12
|
+
pickUpPalette,
|
|
13
|
+
pickUpSeqCol,
|
|
13
14
|
TAGS as bioTAGS,
|
|
14
15
|
} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
15
16
|
import {
|
|
16
|
-
|
|
17
|
+
FilterSources,
|
|
18
|
+
HorizontalAlignments,
|
|
17
19
|
PositionHeight,
|
|
20
|
+
PositionMarginStates,
|
|
18
21
|
positionSeparator,
|
|
22
|
+
TAGS as wlTAGS,
|
|
19
23
|
VerticalAlignments,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
FilterSources,
|
|
24
|
+
WebLogoProps,
|
|
25
|
+
WebLogoPropsDefault,
|
|
23
26
|
} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
24
27
|
import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
import {_package} from '../package';
|
|
27
30
|
|
|
28
31
|
declare global {
|
|
@@ -450,7 +453,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
450
453
|
this.filterSource = this.string(PROPS.filterSource, defaults.filterSource,
|
|
451
454
|
{category: PROPS_CATS.BEHAVIOR, choices: Object.values(FilterSources)}) as FilterSources;
|
|
452
455
|
|
|
453
|
-
const style: SliderOptions = {style: 'barbell'};
|
|
456
|
+
const style: DG.SliderOptions = {style: 'barbell'};
|
|
454
457
|
this.slider = ui.rangeSlider(0, 100, 0, 20, false, style);
|
|
455
458
|
this.canvas = ui.canvas();
|
|
456
459
|
this.canvas.style.width = '100%';
|
|
@@ -576,7 +579,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
576
579
|
} catch (err: any) {
|
|
577
580
|
this.seqCol = null;
|
|
578
581
|
this.error = err instanceof Error ? err : new Error(err.toString());
|
|
579
|
-
|
|
582
|
+
throw err;
|
|
580
583
|
}
|
|
581
584
|
if (!this.seqCol) {
|
|
582
585
|
this.unitsHandler = null;
|
|
@@ -700,7 +703,18 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
700
703
|
break;
|
|
701
704
|
}
|
|
702
705
|
|
|
703
|
-
|
|
706
|
+
switch (property.name) {
|
|
707
|
+
case PROPS.fixWidth:
|
|
708
|
+
case PROPS.fitArea:
|
|
709
|
+
case PROPS.positionWidth:
|
|
710
|
+
case PROPS.positionMargin:
|
|
711
|
+
this.render(RecalcLevel.Layout, 'onPropertyChanged');
|
|
712
|
+
break;
|
|
713
|
+
|
|
714
|
+
default:
|
|
715
|
+
this.render(RecalcLevel.Freqs, 'onPropertyChanged');
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
704
718
|
}
|
|
705
719
|
|
|
706
720
|
/** Add filter handlers when table is a attached */
|
|
@@ -751,7 +765,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
751
765
|
|
|
752
766
|
/** Helper function for remove empty positions */
|
|
753
767
|
// TODO: use this function in from core
|
|
754
|
-
protected removeWhere(array: Array<
|
|
768
|
+
protected removeWhere<T>(array: Array<T>, predicate: (item: T) => boolean): Array<any> {
|
|
755
769
|
const length = array.length;
|
|
756
770
|
let updateIterator = 0;
|
|
757
771
|
for (let deleteIterator = 0; deleteIterator < length; deleteIterator++) {
|
|
@@ -767,7 +781,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
767
781
|
/** Function for removing empty positions */
|
|
768
782
|
protected _removeEmptyPositions() {
|
|
769
783
|
if (this.skipEmptyPositions)
|
|
770
|
-
this.removeWhere(this.positions, (item) => item?.
|
|
784
|
+
this.removeWhere(this.positions, (item) => item?.getFreq('-')?.count === item?.rowCount);
|
|
771
785
|
}
|
|
772
786
|
|
|
773
787
|
private renderRequested: boolean = false;
|
|
@@ -780,11 +794,11 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
780
794
|
`.render( recalcLevel=${recalcLevel}, reason='${reason}' )`);
|
|
781
795
|
|
|
782
796
|
/** Calculate freqs of monomers */
|
|
783
|
-
const calculateFreqsInt = (
|
|
797
|
+
const calculateFreqsInt = (): void => {
|
|
784
798
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateFreqsInt(), start `);
|
|
785
|
-
|
|
786
799
|
if (!this.host || !this.seqCol || !this.dataFrame) return;
|
|
787
800
|
|
|
801
|
+
const length: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
788
802
|
this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
|
|
789
803
|
const posCount: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
790
804
|
this.positions = new Array(posCount);
|
|
@@ -815,9 +829,10 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
815
829
|
};
|
|
816
830
|
|
|
817
831
|
/** Calculate layout of monomers on screen (canvas) based on freqs, required to handle mouse events */
|
|
818
|
-
const calculateLayoutInt = (
|
|
832
|
+
const calculateLayoutInt = (dpr: number): void => {
|
|
819
833
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), start `);
|
|
820
834
|
|
|
835
|
+
const length = this.positions.length;
|
|
821
836
|
this.calcSize();
|
|
822
837
|
const absoluteMaxHeight = this.canvas.height - this.axisHeight * dpr;
|
|
823
838
|
const alphabetSize = this.getAlphabetSize();
|
|
@@ -835,7 +850,8 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
835
850
|
*@param {boolean} recalcFreqs - indicates that need to recalculate data for rendering
|
|
836
851
|
*/
|
|
837
852
|
const renderInt = (recalcLevel: RecalcLevel) => {
|
|
838
|
-
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ),
|
|
853
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), ` +
|
|
854
|
+
`start `);
|
|
839
855
|
if (this.msgHost) {
|
|
840
856
|
if (this.seqCol && !this.cp) {
|
|
841
857
|
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
@@ -854,11 +870,11 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
854
870
|
|
|
855
871
|
this.slider.root.style.width = `${this.host.clientWidth}px`;
|
|
856
872
|
|
|
857
|
-
const length: number = this.Length;
|
|
858
873
|
const dpr: number = window.devicePixelRatio;
|
|
859
|
-
if (recalcLevel >= RecalcLevel.Freqs) calculateFreqsInt(
|
|
860
|
-
if (recalcLevel >= RecalcLevel.Layout) calculateLayoutInt(
|
|
874
|
+
if (recalcLevel >= RecalcLevel.Freqs) calculateFreqsInt();
|
|
875
|
+
if (recalcLevel >= RecalcLevel.Layout) calculateLayoutInt(window.devicePixelRatio);
|
|
861
876
|
|
|
877
|
+
const length: number = this.Length;
|
|
862
878
|
g.resetTransform();
|
|
863
879
|
g.fillStyle = DG.Color.toHtml(this.backgroundColor);
|
|
864
880
|
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
@@ -896,7 +912,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
896
912
|
this.positionWidthWithMargin, firstVisibleIndex, this.cp);
|
|
897
913
|
}
|
|
898
914
|
|
|
899
|
-
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end
|
|
915
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end`);
|
|
900
916
|
};
|
|
901
917
|
|
|
902
918
|
this.recalcLevelRequested = Math.max(this.recalcLevelRequested, recalcLevel);
|
|
@@ -1039,7 +1055,8 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
1039
1055
|
_package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterChanged()');
|
|
1040
1056
|
try {
|
|
1041
1057
|
this.updatePositions();
|
|
1042
|
-
this.
|
|
1058
|
+
if (this.filterSource === FilterSources.Filtered)
|
|
1059
|
+
this.render(RecalcLevel.Freqs, 'dataFrameFilterOnChanged');
|
|
1043
1060
|
} catch (err: any) {
|
|
1044
1061
|
const errMsg = errorToConsole(err);
|
|
1045
1062
|
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n' + errMsg);
|
|
@@ -1050,7 +1067,8 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
1050
1067
|
private dataFrameSelectionOnChanged(_value: any): void {
|
|
1051
1068
|
_package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()');
|
|
1052
1069
|
try {
|
|
1053
|
-
this.
|
|
1070
|
+
if (this.filterSource === FilterSources.Selected)
|
|
1071
|
+
this.render(RecalcLevel.Freqs, 'dataFrameSelectionOnChanged');
|
|
1054
1072
|
} catch (err: any) {
|
|
1055
1073
|
const errMsg = errorToConsole(err);
|
|
1056
1074
|
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n' + errMsg);
|
|
@@ -1132,10 +1150,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
1132
1150
|
export function checkSeqForMonomerAtPos(
|
|
1133
1151
|
df: DG.DataFrame, unitsHandler: UnitsHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
|
|
1134
1152
|
): boolean {
|
|
1135
|
-
// if (!filter.get(rowI)) return false;
|
|
1136
|
-
// TODO: Use BitSet.get(idx)
|
|
1137
|
-
if (!filter.getSelectedIndexes().includes(rowI)) return false;
|
|
1138
|
-
|
|
1139
1153
|
const seqMList: string[] = unitsHandler.splitted[rowI];
|
|
1140
1154
|
const seqM = at.pos < seqMList.length ? seqMList[at.pos] : null;
|
|
1141
1155
|
return ((seqM === monomer) || (seqM === '' && monomer === '-'));
|