@datagrok/bio 2.10.22 → 2.10.23
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 +2 -2
- package/src/package.ts +4 -4
- package/src/tests/WebLogo-positions-test.ts +13 -13
- package/src/tests/detectors-benchmark-tests.ts +2 -2
- package/src/tests/detectors-tests.ts +4 -1
- package/src/utils/cell-renderer.ts +7 -1
- package/src/utils/err-info.ts +28 -0
- package/src/viewers/vd-regions-viewer.ts +56 -31
- package/src/viewers/web-logo-viewer.ts +108 -102
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.10.
|
|
8
|
+
"version": "2.10.23",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@biowasm/aioli": "^3.1.0",
|
|
37
|
-
"@datagrok-libraries/bio": "^5.38.
|
|
37
|
+
"@datagrok-libraries/bio": "^5.38.12",
|
|
38
38
|
"@datagrok-libraries/chem-meta": "^1.0.1",
|
|
39
39
|
"@datagrok-libraries/ml": "^6.3.49",
|
|
40
40
|
"@datagrok-libraries/tutorials": "^1.3.6",
|
package/src/package.ts
CHANGED
|
@@ -385,7 +385,7 @@ export async function getRegionTopMenu(
|
|
|
385
385
|
//input: object options {optional: true}
|
|
386
386
|
//output: viewer result
|
|
387
387
|
//editor: Bio:SeqActivityCliffsEditor
|
|
388
|
-
export async function activityCliffs(df: DG.DataFrame, macroMolecule: DG.Column
|
|
388
|
+
export async function activityCliffs(df: DG.DataFrame, macroMolecule: DG.Column<string>, activities: DG.Column,
|
|
389
389
|
similarity: number, methodName: DimReductionMethods, options?: (IUMAPOptions | ITSNEOptions) & Options,
|
|
390
390
|
): Promise<DG.Viewer | undefined> {
|
|
391
391
|
if (!checkInputColumnUI(macroMolecule, 'Activity Cliffs'))
|
|
@@ -495,8 +495,8 @@ export async function sequenceSpaceTopMenu(
|
|
|
495
495
|
embedYCol = table.columns.byName(embedColsNames[1]);
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
embedXCol.init((i) => embeddings[i][0]);
|
|
499
|
-
embedYCol.init((i) => embeddings[i][1]);
|
|
498
|
+
embedXCol.init((i) => embeddings[i] ? embeddings[i][0] : undefined);
|
|
499
|
+
embedYCol.init((i) => embeddings[i] ? embeddings[i][1] : undefined);
|
|
500
500
|
const progress = (_nEpoch / epochsLength * 100);
|
|
501
501
|
pg.update(progress, `Running sequence space ... ${progress.toFixed(0)}%`);
|
|
502
502
|
}
|
|
@@ -613,7 +613,7 @@ export async function sequenceSpaceTopMenu(
|
|
|
613
613
|
sp = (v as DG.TableView).scatterPlot({x: embedColsNames[0], y: embedColsNames[1], title: 'Sequence space'});
|
|
614
614
|
}
|
|
615
615
|
} */
|
|
616
|
-
}
|
|
616
|
+
}
|
|
617
617
|
|
|
618
618
|
//top-menu: Bio | Convert | To Atomic Level...
|
|
619
619
|
//name: To Atomic Level
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as grok from 'datagrok-api/grok';
|
|
2
2
|
import * as DG from 'datagrok-api/dg';
|
|
3
3
|
|
|
4
|
-
import {category, expect, expectArray, test,
|
|
4
|
+
import {category, expect, expectArray, test, testEvent} from '@datagrok-libraries/utils/src/test';
|
|
5
5
|
import {ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
6
6
|
import {
|
|
7
7
|
countForMonomerAtPosition,
|
|
@@ -30,9 +30,9 @@ ATC-G-TTGC--
|
|
|
30
30
|
seqCol.setTag(bioTAGS.aligned, 'SEQ.MSA');
|
|
31
31
|
|
|
32
32
|
const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo')) as WebLogoViewer;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
|
|
34
|
+
tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
|
|
35
|
+
}, 200);
|
|
36
36
|
const positions: PI[] = wlViewer['positions'];
|
|
37
37
|
|
|
38
38
|
const resAllDf1: PI[] = [
|
|
@@ -82,9 +82,9 @@ ATC-G-TTGC--
|
|
|
82
82
|
df.filter.fireChanged();
|
|
83
83
|
const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo',
|
|
84
84
|
{'shrinkEmptyTail': true})) as WebLogoViewer;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
|
|
86
|
+
tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
|
|
87
|
+
}, 200);
|
|
88
88
|
const positions: PI[] = wlViewer['positions'];
|
|
89
89
|
|
|
90
90
|
const resAllDf1: PI[] = [
|
|
@@ -120,9 +120,9 @@ ATC-G-TTGC--
|
|
|
120
120
|
|
|
121
121
|
const wlViewer: WebLogoViewer = (await df.plot.fromType('WebLogo',
|
|
122
122
|
{'skipEmptyPositions': true})) as WebLogoViewer;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
|
|
124
|
+
tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
|
|
125
|
+
}, 200);
|
|
126
126
|
const resPosList: PI[] = wlViewer['positions'];
|
|
127
127
|
|
|
128
128
|
const tgtPosList: PI[] = [
|
|
@@ -156,9 +156,9 @@ ATC-G-TTGC--
|
|
|
156
156
|
endPositionName: '7',
|
|
157
157
|
skipEmptyPositions: true,
|
|
158
158
|
})) as WebLogoViewer;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
await testEvent(wlViewer.onLayoutCalculated, () => {}, () => {
|
|
160
|
+
tv.dockManager.dock(wlViewer.root, DG.DOCK_TYPE.DOWN);
|
|
161
|
+
}, 200);
|
|
162
162
|
const resPosList: PI[] = wlViewer['positions'];
|
|
163
163
|
const tgtPosList: PI[] = [
|
|
164
164
|
new PI(2, '3', {'C': new PMI(5)}),
|
|
@@ -29,7 +29,7 @@ category('detectorsBenchmark', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
test('fastaDnaLong1e6Few50', async () => {
|
|
32
|
-
await detectMacromoleculeBenchmark(
|
|
32
|
+
await detectMacromoleculeBenchmark(20, NOTATION.FASTA, ALPHABET.DNA, 1E6, 50);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// -- separator --
|
|
@@ -49,7 +49,7 @@ category('detectorsBenchmark', () => {
|
|
|
49
49
|
async function detectMacromoleculeBenchmark(
|
|
50
50
|
maxET: number, notation: NOTATION, alphabet: ALPHABET, length: number, count: number, separator?: string,
|
|
51
51
|
): Promise<number> {
|
|
52
|
-
return await benchmark<DG.FuncCall, DG.Column>(
|
|
52
|
+
return await benchmark<DG.FuncCall, DG.Column>(maxET,
|
|
53
53
|
(): DG.FuncCall => {
|
|
54
54
|
const col: DG.Column = generate(notation, [...getAlphabet(alphabet)], length, count, separator);
|
|
55
55
|
const funcCall: DG.FuncCall = detectFunc.prepare({col: col});
|
|
@@ -182,7 +182,10 @@ MWRSWY-CKHP`;
|
|
|
182
182
|
const df: DG.DataFrame = await readFile(samples[key]);
|
|
183
183
|
// await grok.data.detectSemanticTypes(df);
|
|
184
184
|
return df;
|
|
185
|
-
})()
|
|
185
|
+
})().catch((err: any) => {
|
|
186
|
+
delete _samplesDfs[key];
|
|
187
|
+
throw err;
|
|
188
|
+
});
|
|
186
189
|
}
|
|
187
190
|
return _samplesDfs[key];
|
|
188
191
|
};
|
|
@@ -27,6 +27,7 @@ import * as C from './constants';
|
|
|
27
27
|
import {_package, getBioLib} from '../package';
|
|
28
28
|
import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
29
29
|
import {getSplitter} from '@datagrok-libraries/bio/src/utils/macromolecule/utils';
|
|
30
|
+
import {errInfo} from './err-info';
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
type TempType = { [tagName: string]: any };
|
|
@@ -136,7 +137,12 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
|
|
|
136
137
|
|
|
137
138
|
// TODO: Store temp data to GridColumn
|
|
138
139
|
// Now the renderer requires data frame table Column underlying GridColumn
|
|
139
|
-
|
|
140
|
+
let grid: DG.Grid | undefined = undefined;
|
|
141
|
+
try { grid = gridCell.grid; } catch (err: any) {
|
|
142
|
+
grid = undefined;
|
|
143
|
+
const [errMsg, errStack] = errInfo(err);
|
|
144
|
+
_package.logger.error(errMsg, undefined, errStack);
|
|
145
|
+
}
|
|
140
146
|
const tableCol: DG.Column = gridCell.cell.column;
|
|
141
147
|
if (!grid || !tableCol) return;
|
|
142
148
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
export function errMsg(err: any): string {
|
|
6
|
+
if (typeof err === 'string' || err instanceof String)
|
|
7
|
+
return err as string;
|
|
8
|
+
else if (err.constructor.name === 'StateError')
|
|
9
|
+
return err['message'];
|
|
10
|
+
else if (err.constructor.name === 'StateError' && '$thrownJsError' in err)
|
|
11
|
+
return errMsg(err['$thrownJsError']);
|
|
12
|
+
else if (err instanceof Error)
|
|
13
|
+
return (err as Error).message;
|
|
14
|
+
else
|
|
15
|
+
return err.toString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function errStack(err: any): string | undefined {
|
|
19
|
+
if (err instanceof Error)
|
|
20
|
+
return err.stack;
|
|
21
|
+
else if (err.constructor.name === 'StateError' && '$thrownJsError' in err)
|
|
22
|
+
return errStack(err['$thrownJsError']);
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function errInfo(err: any): [string, string | undefined] {
|
|
27
|
+
return [errMsg(err), errStack(err)];
|
|
28
|
+
}
|
|
@@ -152,10 +152,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
override detach() {
|
|
155
|
-
if (this.setDataInProgress) return;
|
|
156
155
|
const superDetach = super.detach.bind(this);
|
|
157
156
|
this.detachPromise = this.detachPromise.then(async () => { // detach
|
|
158
157
|
await this.viewPromise;
|
|
158
|
+
if (this.setDataInProgress) return; // check setDataInProgress synced
|
|
159
159
|
if (this.viewed) {
|
|
160
160
|
await this.destroyView('detach');
|
|
161
161
|
this.viewed = false;
|
|
@@ -166,7 +166,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
166
166
|
|
|
167
167
|
override onTableAttached() {
|
|
168
168
|
super.onTableAttached();
|
|
169
|
-
this.setData(this.
|
|
169
|
+
this.setData(this.regions);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
public override onPropertyChanged(property: DG.Property | null): void {
|
|
@@ -180,7 +180,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
180
180
|
switch (property.name) {
|
|
181
181
|
case PROPS.regionTypes:
|
|
182
182
|
case PROPS.chains:
|
|
183
|
-
this.setData(this.
|
|
183
|
+
this.setData(this.regions);
|
|
184
184
|
break;
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -211,42 +211,66 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
211
211
|
break;
|
|
212
212
|
|
|
213
213
|
default:
|
|
214
|
-
this.setData(this.
|
|
214
|
+
this.setData(this.regions); // onPropertyChanged
|
|
215
215
|
break;
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
// -- Data --
|
|
220
220
|
|
|
221
|
+
// private static viewerCount = 0;
|
|
222
|
+
// private viewerId: number = ++VdRegionsViewer.viewerCount;
|
|
223
|
+
// private setDataInCount: number = 0;
|
|
224
|
+
|
|
221
225
|
// TODO: .onTableAttached is not calling on dataFrame set, onPropertyChanged also not calling
|
|
222
|
-
public setData(
|
|
223
|
-
|
|
224
|
-
_package.logger.debug('Bio: VdRegionsViewer.setData()'
|
|
226
|
+
public setData(regions: VdRegion[]) {
|
|
227
|
+
// const setDataInId = ++this.setDataInCount;
|
|
228
|
+
_package.logger.debug('Bio: VdRegionsViewer.setData(), in, ' +
|
|
229
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
230
|
+
`regions.length = ${regions.length}`
|
|
231
|
+
);
|
|
225
232
|
|
|
226
233
|
this.viewPromise = this.viewPromise.then(async () => { // setData
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
// _package.logger.debug('Bio: VdRegionsViewer.setData(), in sync, ' +
|
|
235
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
236
|
+
// `regions.length = ${regions.length}`);
|
|
237
|
+
if (!this.setDataInProgress) this.setDataInProgress = true; else return; // check setDataInProgress synced
|
|
238
|
+
// _package.logger.debug('Bio: VdRegionsViewer.setData(), start, ' +
|
|
239
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
240
|
+
// `regions.length = ${regions.length}`);
|
|
241
|
+
try {
|
|
242
|
+
if (this.viewed) {
|
|
243
|
+
// _package.logger.debug('Bio: VdRegionsViewer.setData(), destroyView, ' +
|
|
244
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
245
|
+
// `regions.length = ${regions.length}`);
|
|
246
|
+
await this.destroyView('setData');
|
|
247
|
+
this.viewed = false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await this.detachPromise;
|
|
251
|
+
// Wait whether this.dataFrame assigning has called detach() before continue set data and build view
|
|
252
|
+
|
|
253
|
+
// -- Data --
|
|
254
|
+
this.regions = regions;
|
|
255
|
+
|
|
256
|
+
if (!this.viewed) {
|
|
257
|
+
// _package.logger.debug('Bio: VdRegionsViewer.setData(), buildView, ' +
|
|
258
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
259
|
+
// `regions.length = ${regions.length}`);
|
|
260
|
+
await this.buildView('setData');
|
|
261
|
+
this.viewed = true;
|
|
262
|
+
}
|
|
263
|
+
} catch (err: any) {
|
|
264
|
+
const errMsg = err instanceof Error ? err.message : err.toString();
|
|
265
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
266
|
+
grok.shell.error(errMsg);
|
|
267
|
+
_package.logger.error(errMsg, undefined, stack);
|
|
268
|
+
} finally {
|
|
269
|
+
// _package.logger.debug('Bio: VdRegionsViewer.setData(), finally, ' +
|
|
270
|
+
// `viewerId = ${this.viewerId}, setDataInId = ${setDataInId}, ` +
|
|
271
|
+
// `regions.length = ${regions.length}`);
|
|
272
|
+
this.setDataInProgress = false;
|
|
242
273
|
}
|
|
243
|
-
}).catch((err: any) => {
|
|
244
|
-
const errMsg = err instanceof Error ? err.message : err.toString();
|
|
245
|
-
const stack = err instanceof Error ? err.stack : undefined;
|
|
246
|
-
grok.shell.error(errMsg);
|
|
247
|
-
_package.logger.error(errMsg, undefined, stack);
|
|
248
|
-
}).finally(() => {
|
|
249
|
-
this.setDataInProgress = false;
|
|
250
274
|
});
|
|
251
275
|
}
|
|
252
276
|
|
|
@@ -266,7 +290,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
266
290
|
private async destroyView(purpose: string): Promise<void> {
|
|
267
291
|
// TODO: Unsubscribe from and remove all view elements
|
|
268
292
|
_package.logger.debug(`Bio: VdRegionsViewer.destroyView( mainLayout = ${!this.mainLayout ? 'none' : 'value'} ), ` +
|
|
269
|
-
`purpose = '${purpose}'`);
|
|
293
|
+
`purpose = '${purpose}', this.regions.length = ${this.regions.length}`);
|
|
270
294
|
if (this.filterSourceInput) {
|
|
271
295
|
//
|
|
272
296
|
ui.empty(this.filterSourceInput.root);
|
|
@@ -284,7 +308,8 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
284
308
|
}
|
|
285
309
|
|
|
286
310
|
private async buildView(purpose: string): Promise<void> {
|
|
287
|
-
_package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` +
|
|
311
|
+
_package.logger.debug(`Bio: VdRegionsViewer.buildView() begin, ` +
|
|
312
|
+
`purpose = '${purpose}', this.regions.length = ${this.regions.length}`);
|
|
288
313
|
|
|
289
314
|
const regionsFiltered: VdRegion[] = this.regions.filter((r: VdRegion) => this.regionTypes.includes(r.type));
|
|
290
315
|
const orderList: number[] = Array.from(new Set(regionsFiltered.map((r) => r.order))).sort();
|
|
@@ -18,6 +18,8 @@ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
|
|
|
18
18
|
import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
|
|
19
19
|
import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
|
|
20
20
|
|
|
21
|
+
import {errInfo} from '../utils/err-info';
|
|
22
|
+
|
|
21
23
|
import {_package} from '../package';
|
|
22
24
|
|
|
23
25
|
declare global {
|
|
@@ -403,25 +405,30 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
403
405
|
// -- Data --
|
|
404
406
|
|
|
405
407
|
setData(): void {
|
|
406
|
-
|
|
407
|
-
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.setData() `);
|
|
408
|
-
|
|
408
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.setData(), in`);
|
|
409
409
|
this.viewPromise = this.viewPromise.then(async () => { // setData
|
|
410
|
-
if (this.
|
|
411
|
-
|
|
412
|
-
this.viewed
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this.viewed
|
|
410
|
+
if (!this.setDataInProgress) this.setDataInProgress = true; else return; // check setDataInProgress synced
|
|
411
|
+
try {
|
|
412
|
+
if (this.viewed) {
|
|
413
|
+
this.renderRequestSub.unsubscribe();
|
|
414
|
+
await this.destroyView();
|
|
415
|
+
this.viewed = false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await this.detachPromise;
|
|
419
|
+
this.updateSeqCol();
|
|
420
|
+
|
|
421
|
+
if (!this.viewed) {
|
|
422
|
+
await this.buildView(); //requests rendering
|
|
423
|
+
this.viewed = true;
|
|
424
|
+
}
|
|
425
|
+
} catch (err: any) {
|
|
426
|
+
const [errMsg, errStack] = errInfo(err);
|
|
427
|
+
grok.shell.error(errMsg);
|
|
428
|
+
_package.logger.error(errMsg, undefined, errStack);
|
|
429
|
+
} finally {
|
|
430
|
+
this.setDataInProgress = false;
|
|
422
431
|
}
|
|
423
|
-
}).finally(() => {
|
|
424
|
-
this.setDataInProgress = false;
|
|
425
432
|
});
|
|
426
433
|
}
|
|
427
434
|
|
|
@@ -450,6 +457,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
450
457
|
const dataFrameTxt: string = this.dataFrame ? 'data' : 'null';
|
|
451
458
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView( dataFrame = ${dataFrameTxt} ) start`);
|
|
452
459
|
const dpr = window.devicePixelRatio;
|
|
460
|
+
this.viewSubs.push(DG.debounce(this.renderRequest)
|
|
461
|
+
.subscribe(this.renderRequestOnDebounce.bind(this)));
|
|
453
462
|
|
|
454
463
|
this.helpUrl = '/help/visualize/viewers/web-logo.md';
|
|
455
464
|
|
|
@@ -508,7 +517,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
508
517
|
private rootOnSizeChanged(): void {
|
|
509
518
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.rootOnSizeChanged(), start `);
|
|
510
519
|
|
|
511
|
-
this.render(WlRenderLevel.Layout, 'rootOnSizeChanged')
|
|
520
|
+
this.render(WlRenderLevel.Layout, 'rootOnSizeChanged');
|
|
512
521
|
}
|
|
513
522
|
|
|
514
523
|
/** Assigns {@link seqCol} and {@link cp} based on {@link sequenceColumnName} and calls {@link render}().
|
|
@@ -571,7 +580,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
571
580
|
this.positionNames.includes(this.endPositionName)) ?
|
|
572
581
|
this.positionNames.indexOf(this.endPositionName) : (maxLength - 1);
|
|
573
582
|
|
|
574
|
-
this.render(WlRenderLevel.Freqs, 'updatePositions')
|
|
583
|
+
this.render(WlRenderLevel.Freqs, 'updatePositions');
|
|
575
584
|
}
|
|
576
585
|
|
|
577
586
|
private getFilter(): DG.BitSet {
|
|
@@ -801,11 +810,11 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
801
810
|
case PROPS.verticalAlignment:
|
|
802
811
|
case PROPS.positionMargin:
|
|
803
812
|
case PROPS.positionMarginState:
|
|
804
|
-
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`)
|
|
813
|
+
this.render(WlRenderLevel.Layout, `onPropertyChanged(${property.name})`);
|
|
805
814
|
break;
|
|
806
815
|
|
|
807
816
|
case PROPS.backgroundColor:
|
|
808
|
-
this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`)
|
|
817
|
+
this.render(WlRenderLevel.Render, `onPropertyChanged(${property.name})`);
|
|
809
818
|
break;
|
|
810
819
|
}
|
|
811
820
|
}
|
|
@@ -822,12 +831,12 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
822
831
|
|
|
823
832
|
/** Remove all handlers when table is a detach */
|
|
824
833
|
public override async detach() {
|
|
825
|
-
if (this.setDataInProgress) return;
|
|
826
834
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.detach(), `);
|
|
827
835
|
|
|
828
836
|
const superDetach = super.detach.bind(this);
|
|
829
837
|
this.detachPromise = this.detachPromise.then(async () => { // detach
|
|
830
838
|
await this.viewPromise;
|
|
839
|
+
if (this.setDataInProgress) return; // check setDataInProgress synced
|
|
831
840
|
if (this.viewed) {
|
|
832
841
|
await this.destroyView();
|
|
833
842
|
this.viewed = false;
|
|
@@ -888,12 +897,24 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
888
897
|
}
|
|
889
898
|
|
|
890
899
|
/** default value of RecalcLevel.Freqs is for recalc from the scratch at the beginning */
|
|
891
|
-
private
|
|
900
|
+
private requestedRenderLevel: WlRenderLevel = WlRenderLevel.Freqs;
|
|
901
|
+
private readonly renderRequest: Subject<WlRenderLevel> = new Subject<WlRenderLevel>();
|
|
902
|
+
private renderRequestSub: Unsubscribable;
|
|
892
903
|
|
|
893
904
|
/** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
|
|
894
|
-
render(
|
|
905
|
+
render(renderLevel: WlRenderLevel, reason: string): void {
|
|
895
906
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>` +
|
|
896
|
-
`.render( recalcLevelVal=${
|
|
907
|
+
`.render( recalcLevelVal=${renderLevel}, reason='${reason}' )`);
|
|
908
|
+
this.requestedRenderLevel = Math.max(this.requestedRenderLevel, renderLevel);
|
|
909
|
+
this.renderRequest.next(this.requestedRenderLevel);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/** Render WebLogo sensitive to changes in params of rendering
|
|
913
|
+
*@param {WlRenderLevel} recalcLevel - indicates that need to recalculate data for rendering
|
|
914
|
+
*/
|
|
915
|
+
protected async renderInt(renderLevel: WlRenderLevel): Promise<void> {
|
|
916
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( renderLevel=${renderLevel} ), ` +
|
|
917
|
+
`start `);
|
|
897
918
|
|
|
898
919
|
/** Calculate freqs of monomers */
|
|
899
920
|
const calculateFreqsInt = (): void => {
|
|
@@ -960,85 +981,70 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
960
981
|
this._onLayoutCalculated.next();
|
|
961
982
|
};
|
|
962
983
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if (this.msgHost) {
|
|
970
|
-
if (this.seqCol && !this.cp) {
|
|
971
|
-
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
972
|
-
this.msgHost!.style.display = '';
|
|
973
|
-
} else {
|
|
974
|
-
this.msgHost!.style.display = 'none';
|
|
975
|
-
}
|
|
984
|
+
if (this.msgHost) {
|
|
985
|
+
if (this.seqCol && !this.cp) {
|
|
986
|
+
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
987
|
+
this.msgHost!.style.display = '';
|
|
988
|
+
} else {
|
|
989
|
+
this.msgHost!.style.display = 'none';
|
|
976
990
|
}
|
|
991
|
+
}
|
|
977
992
|
|
|
978
|
-
|
|
979
|
-
|
|
993
|
+
if (!this.seqCol || !this.dataFrame || !this.cp || this.host == null || this.slider == null)
|
|
994
|
+
return;
|
|
980
995
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const g = this.canvas.getContext('2d');
|
|
990
|
-
if (!g) return;
|
|
991
|
-
|
|
992
|
-
const length: number = this.Length;
|
|
993
|
-
g.resetTransform();
|
|
994
|
-
g.fillStyle = intToHtmlA(this.backgroundColor);
|
|
995
|
-
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
996
|
-
g.textBaseline = this.textBaseline;
|
|
997
|
-
|
|
998
|
-
//#region Plot positionNames
|
|
999
|
-
const positionFontSize = 10 * dpr;
|
|
1000
|
-
g.resetTransform();
|
|
1001
|
-
g.fillStyle = 'black';
|
|
1002
|
-
g.textAlign = 'center';
|
|
1003
|
-
g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
|
|
1004
|
-
const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
|
|
1005
|
-
const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
|
|
1006
|
-
(this._positionWidth * dpr - 2) / posNameMaxWidth;
|
|
1007
|
-
|
|
1008
|
-
if (positionLabelsHeight > 0) {
|
|
1009
|
-
renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
|
|
1010
|
-
this.positions, this.slider.min, this.slider.max);
|
|
1011
|
-
}
|
|
1012
|
-
//#endregion Plot positionNames
|
|
1013
|
-
const fontStyle = '16px Roboto, Roboto Local, sans-serif';
|
|
1014
|
-
// Hacks to scale uppercase characters to target rectangle
|
|
1015
|
-
const uppercaseLetterAscent = 0.25;
|
|
1016
|
-
const uppercaseLetterHeight = 12.2;
|
|
1017
|
-
for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
|
|
1018
|
-
this.positions[jPos].render(g, (m) => { return this.unitsHandler!.isGap(m); },
|
|
1019
|
-
fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
|
|
1020
|
-
/* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
|
|
1021
|
-
}
|
|
996
|
+
const dpr: number = window.devicePixelRatio;
|
|
997
|
+
/** 0 is for no position labels */
|
|
998
|
+
const positionLabelsHeight = this.showPositionLabels ? POSITION_LABELS_HEIGHT : 0;
|
|
999
|
+
if (renderLevel >= WlRenderLevel.Freqs) calculateFreqsInt();
|
|
1000
|
+
this.calcLayout(dpr); // after _skipEmptyPositions
|
|
1001
|
+
if (this.positions.length === 0 || this.startPosition === -1 || this.endPosition === -1) return;
|
|
1002
|
+
if (renderLevel >= WlRenderLevel.Layout) calculateLayoutInt(window.devicePixelRatio, positionLabelsHeight);
|
|
1022
1003
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1004
|
+
const g = this.canvas.getContext('2d');
|
|
1005
|
+
if (!g) return;
|
|
1025
1006
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1007
|
+
const length: number = this.Length;
|
|
1008
|
+
g.resetTransform();
|
|
1009
|
+
g.fillStyle = intToHtmlA(this.backgroundColor);
|
|
1010
|
+
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1011
|
+
g.textBaseline = this.textBaseline;
|
|
1012
|
+
|
|
1013
|
+
//#region Plot positionNames
|
|
1014
|
+
const positionFontSize = 10 * dpr;
|
|
1015
|
+
g.resetTransform();
|
|
1016
|
+
g.fillStyle = 'black';
|
|
1017
|
+
g.textAlign = 'center';
|
|
1018
|
+
g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
|
|
1019
|
+
const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
|
|
1020
|
+
const hScale = posNameMaxWidth < (this._positionWidth * dpr - 2) ? 1 :
|
|
1021
|
+
(this._positionWidth * dpr - 2) / posNameMaxWidth;
|
|
1022
|
+
|
|
1023
|
+
if (positionLabelsHeight > 0) {
|
|
1024
|
+
renderPositionLabels(g, dpr, hScale, this._positionWidthWithMargin, this._positionWidth,
|
|
1025
|
+
this.positions, this.slider.min, this.slider.max);
|
|
1026
|
+
}
|
|
1027
|
+
//#endregion Plot positionNames
|
|
1028
|
+
const fontStyle = '16px Roboto, Roboto Local, sans-serif';
|
|
1029
|
+
// Hacks to scale uppercase characters to target rectangle
|
|
1030
|
+
const uppercaseLetterAscent = 0.25;
|
|
1031
|
+
const uppercaseLetterHeight = 12.2;
|
|
1032
|
+
for (let jPos = Math.floor(this.slider.min); jPos <= Math.floor(this.slider.max); jPos++) {
|
|
1033
|
+
this.positions[jPos].render(g, (m) => { return this.unitsHandler!.isGap(m); },
|
|
1034
|
+
fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
|
|
1035
|
+
/* this._positionWidthWithMargin, firstVisiblePosIdx,*/ this.cp);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${renderLevel} ), end`);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private renderRequestOnDebounce(renderLevel: WlRenderLevel): void {
|
|
1042
|
+
this.requestedRenderLevel = WlRenderLevel.None;
|
|
1043
|
+
this.renderInt(renderLevel)
|
|
1044
|
+
.catch((err: any) => {
|
|
1045
|
+
const [errMsg, errStack] = errInfo(err);
|
|
1046
|
+
_package.logger.error(errMsg, undefined, errStack);
|
|
1047
|
+
});
|
|
1042
1048
|
}
|
|
1043
1049
|
|
|
1044
1050
|
private _lastWidth: number;
|
|
@@ -1062,7 +1068,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1062
1068
|
};
|
|
1063
1069
|
_package.logger.debug(
|
|
1064
1070
|
`Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged( ${JSON.stringify(val)} ), start`);
|
|
1065
|
-
this.render(WlRenderLevel.Layout, 'sliderOnValuesChanged')
|
|
1071
|
+
this.render(WlRenderLevel.Layout, 'sliderOnValuesChanged');
|
|
1066
1072
|
} catch (err: any) {
|
|
1067
1073
|
const errMsg = errorToConsole(err);
|
|
1068
1074
|
_package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged() error:\n` + errMsg);
|
|
@@ -1075,7 +1081,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1075
1081
|
try {
|
|
1076
1082
|
this.updatePositions();
|
|
1077
1083
|
if (this.filterSource === FilterSources.Filtered)
|
|
1078
|
-
this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged')
|
|
1084
|
+
this.render(WlRenderLevel.Freqs, 'dataFrameFilterOnChanged');
|
|
1079
1085
|
} catch (err: any) {
|
|
1080
1086
|
const errMsg = errorToConsole(err);
|
|
1081
1087
|
_package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n` + errMsg);
|
|
@@ -1087,7 +1093,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
|
|
|
1087
1093
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()`);
|
|
1088
1094
|
try {
|
|
1089
1095
|
if (this.filterSource === FilterSources.Selected)
|
|
1090
|
-
this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged')
|
|
1096
|
+
this.render(WlRenderLevel.Freqs, 'dataFrameSelectionOnChanged');
|
|
1091
1097
|
} catch (err: any) {
|
|
1092
1098
|
const errMsg = errorToConsole(err);
|
|
1093
1099
|
_package.logger.error(`Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n` + errMsg);
|