@datagrok/bio 2.4.46 → 2.4.47
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/scripts/sequence_generator.py +8 -0
- package/src/package.ts +1 -1
- package/src/tests/WebLogo-positions-test.ts +7 -7
- package/src/viewers/web-logo-viewer.ts +382 -273
|
@@ -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
|
|
6
|
+
import {fromEvent, Unsubscribable} from 'rxjs';
|
|
7
7
|
|
|
8
8
|
import {SliderOptions} from 'datagrok-api/dg';
|
|
9
9
|
import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
|
|
@@ -46,12 +46,12 @@ DG.Rect.prototype.contains = function(x: number, y: number): boolean {
|
|
|
46
46
|
|
|
47
47
|
export class PositionMonomerInfo {
|
|
48
48
|
/** Sequences count with monomer in position */
|
|
49
|
-
count: number;
|
|
49
|
+
public count: number;
|
|
50
50
|
|
|
51
51
|
/** Remember screen coords rect */
|
|
52
|
-
bounds
|
|
52
|
+
public bounds?: DG.Rect;
|
|
53
53
|
|
|
54
|
-
constructor(count: number = 0, bounds
|
|
54
|
+
constructor(count: number = 0, bounds?: DG.Rect) {
|
|
55
55
|
this.count = count;
|
|
56
56
|
this.bounds = bounds;
|
|
57
57
|
}
|
|
@@ -64,26 +64,171 @@ export class PositionInfo {
|
|
|
64
64
|
/** Position name from column tag*/
|
|
65
65
|
public readonly name: string;
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
private readonly _freqs: { [m: string]: PositionMonomerInfo };
|
|
68
|
+
|
|
68
69
|
rowCount: number;
|
|
69
70
|
sumForHeightCalc: number;
|
|
70
71
|
|
|
71
72
|
/** freq = {}, rowCount = 0
|
|
72
73
|
* @param {number} pos Position in sequence
|
|
73
74
|
* @param {string} name Name of position ('111A', '111.1', etc)
|
|
74
|
-
* @param {string[]}
|
|
75
|
+
* @param {string[]} freqs frequency of monomers in position
|
|
75
76
|
* @param {number} rowCount Count of elements in column
|
|
76
77
|
* @param {number} sumForHeightCalc Sum of all monomer counts for height calculation
|
|
77
78
|
*/
|
|
78
79
|
constructor(pos: number, name: string,
|
|
79
|
-
|
|
80
|
+
freqs: { [m: string]: PositionMonomerInfo } = {}, rowCount: number = 0, sumForHeightCalc: number = 0,
|
|
80
81
|
) {
|
|
81
82
|
this.pos = pos;
|
|
82
83
|
this.name = name;
|
|
83
|
-
this.
|
|
84
|
+
this._freqs = freqs;
|
|
84
85
|
this.rowCount = rowCount;
|
|
85
86
|
this.sumForHeightCalc = sumForHeightCalc;
|
|
86
87
|
}
|
|
88
|
+
|
|
89
|
+
public getMonomers(): string[] {
|
|
90
|
+
return Object.keys(this._freqs);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public getFreq(m: string): PositionMonomerInfo {
|
|
94
|
+
let res: PositionMonomerInfo = this._freqs[m];
|
|
95
|
+
if (!res) res = this._freqs[m] = new PositionMonomerInfo();
|
|
96
|
+
return res;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public setFreq(m: string, value: PositionMonomerInfo): void {
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getMonomerAt(calculatedX: number, y: number): string | undefined {
|
|
104
|
+
const findRes = Object.entries(this._freqs)
|
|
105
|
+
.find(([m, pmInfo]) => {
|
|
106
|
+
return pmInfo.bounds!.contains(calculatedX, y);
|
|
107
|
+
});
|
|
108
|
+
return !!findRes ? findRes[0] : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
calcHeights(heightMode: PositionHeight): void {
|
|
112
|
+
/*
|
|
113
|
+
this.positions[jPos].rowCount = 0;
|
|
114
|
+
for (const m in this.positions[jPos].freq)
|
|
115
|
+
this.positions[jPos].rowCount += this.positions[jPos].freq[m].count;
|
|
116
|
+
if (this.positionHeight == PositionHeight.Entropy) {
|
|
117
|
+
this.positions[jPos].sumForHeightCalc = 0;
|
|
118
|
+
for (const m in this.positions[jPos].freq) {
|
|
119
|
+
const pn = this.positions[jPos].freq[m].count / this.positions[jPos].rowCount;
|
|
120
|
+
this.positions[jPos].sumForHeightCalc += -pn * Math.log2(pn);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**/
|
|
124
|
+
|
|
125
|
+
this.rowCount = 0;
|
|
126
|
+
for (const [m, pmInfo] of Object.entries(this._freqs))
|
|
127
|
+
this.rowCount += pmInfo.count;
|
|
128
|
+
|
|
129
|
+
this.sumForHeightCalc = 0;
|
|
130
|
+
if (heightMode === PositionHeight.Entropy) {
|
|
131
|
+
for (const [m, pmInfo] of Object.entries(this._freqs)) {
|
|
132
|
+
const pn = pmInfo.count / this.rowCount;
|
|
133
|
+
this.sumForHeightCalc += -pn * Math.log2(pn);
|
|
134
|
+
}
|
|
135
|
+
} else if (heightMode === PositionHeight.full) {
|
|
136
|
+
for (const [m, pmInfo] of Object.entries(this._freqs)) {
|
|
137
|
+
const pn = pmInfo.count / this.rowCount;
|
|
138
|
+
this.sumForHeightCalc += pn;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const k = 42;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
calcScreen(
|
|
145
|
+
jPos: number, absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
|
|
146
|
+
positionWidthWithMargin: number, positionWidth: number, r: number, axisHeight: number
|
|
147
|
+
): void {
|
|
148
|
+
// const rowCount = this.positions[jPos].rowCount;
|
|
149
|
+
// const alphabetSize = this.getAlphabetSize();
|
|
150
|
+
// if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
|
|
151
|
+
// grok.shell.error('WebLogo: alphabet is undefined.');
|
|
152
|
+
//
|
|
153
|
+
// const alphabetSizeLog = Math.log2(alphabetSize);
|
|
154
|
+
// const maxHeight = (this.positionHeight == PositionHeight.Entropy) ?
|
|
155
|
+
// (absoluteMaxHeight * (alphabetSizeLog - (this.positions[jPos].sumForHeightCalc)) / alphabetSizeLog) :
|
|
156
|
+
// absoluteMaxHeight;
|
|
157
|
+
//
|
|
158
|
+
// let y: number = this.axisHeight * r + (absoluteMaxHeight - maxHeight - 1);
|
|
159
|
+
//
|
|
160
|
+
// const entries = Object.entries(freq).sort((a, b) => {
|
|
161
|
+
// if (a[0] !== '-' && b[0] !== '-')
|
|
162
|
+
// return b[1].count - a[1].count;
|
|
163
|
+
// else if (a[0] === '-' && b[0] === '-')
|
|
164
|
+
// return 0;
|
|
165
|
+
// else if (a[0] === '-')
|
|
166
|
+
// return -1;
|
|
167
|
+
// else /* (b[0] === '-') */
|
|
168
|
+
// return +1;
|
|
169
|
+
// });
|
|
170
|
+
// for (const entry of entries) {
|
|
171
|
+
// const pmInfo: PositionMonomerInfo = entry[1];
|
|
172
|
+
// // const m: string = entry[0];
|
|
173
|
+
// const h: number = maxHeight * pmInfo.count / rowCount;
|
|
174
|
+
//
|
|
175
|
+
// pmInfo.bounds = new DG.Rect(jPos * this.positionWidthWithMargin, y, this._positionWidth, h);
|
|
176
|
+
// y += h;
|
|
177
|
+
// }
|
|
178
|
+
|
|
179
|
+
const maxHeight = (heightMode == PositionHeight.Entropy) ?
|
|
180
|
+
(absoluteMaxHeight * (alphabetSizeLog - (this.sumForHeightCalc)) / alphabetSizeLog) :
|
|
181
|
+
absoluteMaxHeight;
|
|
182
|
+
let y: number = axisHeight * r + (absoluteMaxHeight - maxHeight - 1);
|
|
183
|
+
|
|
184
|
+
const entries = Object.entries(this._freqs)
|
|
185
|
+
.sort((a, b) => {
|
|
186
|
+
if (a[0] !== '-' && b[0] !== '-')
|
|
187
|
+
return b[1].count - a[1].count;
|
|
188
|
+
else if (a[0] === '-' && b[0] === '-')
|
|
189
|
+
return 0;
|
|
190
|
+
else if (a[0] === '-')
|
|
191
|
+
return -1;
|
|
192
|
+
else /* (b[0] === '-') */
|
|
193
|
+
return +1;
|
|
194
|
+
});
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
const pmInfo: PositionMonomerInfo = entry[1];
|
|
197
|
+
// const m: string = entry[0];
|
|
198
|
+
const h: number = maxHeight * pmInfo.count / this.rowCount;
|
|
199
|
+
|
|
200
|
+
pmInfo.bounds = new DG.Rect(jPos * positionWidthWithMargin, y, positionWidth, h);
|
|
201
|
+
y += h;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
render(g: CanvasRenderingContext2D,
|
|
206
|
+
fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number,
|
|
207
|
+
positionWidthWithMargin: number, firstVisibleIndex: number, cp: SeqPalette
|
|
208
|
+
) {
|
|
209
|
+
for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
|
|
210
|
+
if (monomer !== '-') {
|
|
211
|
+
const monomerTxt = monomerToShort(monomer, 5);
|
|
212
|
+
const b = pmInfo.bounds!;
|
|
213
|
+
const left = b.left - positionWidthWithMargin * firstVisibleIndex;
|
|
214
|
+
|
|
215
|
+
g.resetTransform();
|
|
216
|
+
g.strokeStyle = 'lightgray';
|
|
217
|
+
g.lineWidth = 1;
|
|
218
|
+
g.rect(left, b.top, b.width, b.height);
|
|
219
|
+
g.fillStyle = cp.get(monomer) ?? cp.get('other');
|
|
220
|
+
g.textAlign = 'left';
|
|
221
|
+
g.font = fontStyle;
|
|
222
|
+
//g.fillRect(b.left, b.top, b.width, b.height);
|
|
223
|
+
const mTm: TextMetrics = g.measureText(monomerTxt);
|
|
224
|
+
|
|
225
|
+
g.setTransform(
|
|
226
|
+
b.width / mTm.width, 0, 0, b.height / uppercaseLetterHeight,
|
|
227
|
+
left, b.top);
|
|
228
|
+
g.fillText(monomerTxt, 0, -uppercaseLetterAscent);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
87
232
|
}
|
|
88
233
|
|
|
89
234
|
export enum PROPS_CATS {
|
|
@@ -123,6 +268,12 @@ export enum PROPS {
|
|
|
123
268
|
|
|
124
269
|
const defaults: WebLogoProps = WebLogoPropsDefault;
|
|
125
270
|
|
|
271
|
+
enum RecalcLevel {
|
|
272
|
+
None = 0,
|
|
273
|
+
Layout = 1,
|
|
274
|
+
Freqs = 2,
|
|
275
|
+
}
|
|
276
|
+
|
|
126
277
|
export class WebLogoViewer extends DG.JsViewer {
|
|
127
278
|
public static residuesSet = 'nucleotides';
|
|
128
279
|
private static viewerCount: number = -1;
|
|
@@ -148,7 +299,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
148
299
|
// private maxLength: number = 100;
|
|
149
300
|
private positions: PositionInfo[] = [];
|
|
150
301
|
|
|
151
|
-
private rowsMasked: number = 0;
|
|
152
302
|
private rowsNull: number = 0;
|
|
153
303
|
private visibleSlider: boolean = false;
|
|
154
304
|
private allowResize: boolean = true;
|
|
@@ -184,6 +334,8 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
184
334
|
private startPosition: number = -1;
|
|
185
335
|
private endPosition: number = -1;
|
|
186
336
|
|
|
337
|
+
private error: Error | null = null;
|
|
338
|
+
|
|
187
339
|
private get filter(): DG.BitSet {
|
|
188
340
|
let res: DG.BitSet;
|
|
189
341
|
switch (this.filterSource) {
|
|
@@ -243,8 +395,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
243
395
|
return (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
|
|
244
396
|
}
|
|
245
397
|
|
|
246
|
-
private viewSubs: rxjs.Unsubscribable[] = [];
|
|
247
|
-
|
|
248
398
|
constructor() {
|
|
249
399
|
super();
|
|
250
400
|
|
|
@@ -306,55 +456,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
306
456
|
this.canvas.style.width = '100%';
|
|
307
457
|
}
|
|
308
458
|
|
|
309
|
-
private init(): void {
|
|
310
|
-
if (this.initialized) {
|
|
311
|
-
_package.logger.error('Bio: WebLogoViewer.init() second initialization!');
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
this.initialized = true;
|
|
316
|
-
this.helpUrl = '/help/visualize/viewers/web-logo.md';
|
|
317
|
-
|
|
318
|
-
this.msgHost = ui.div('No message');
|
|
319
|
-
this.msgHost.style.display = 'none';
|
|
320
|
-
|
|
321
|
-
this.canvas = ui.canvas();
|
|
322
|
-
this.canvas.style.width = '100%';
|
|
323
|
-
|
|
324
|
-
//this.slider.setShowHandles(false);
|
|
325
|
-
this.slider.root.style.position = 'absolute';
|
|
326
|
-
this.slider.root.style.zIndex = '999';
|
|
327
|
-
this.slider.root.style.display = 'none';
|
|
328
|
-
this.slider.root.style.height = '0.7em';
|
|
329
|
-
|
|
330
|
-
this.visibleSlider = false;
|
|
331
|
-
|
|
332
|
-
this.subs.push(this.slider.onValuesChanged.subscribe(this.sliderOnValuesChanged.bind(this)));
|
|
333
|
-
|
|
334
|
-
this.host = ui.div([this.msgHost, this.canvas]);
|
|
335
|
-
|
|
336
|
-
this.host.style.justifyContent = 'center';
|
|
337
|
-
this.host.style.alignItems = 'center';
|
|
338
|
-
this.host.style.position = 'relative';
|
|
339
|
-
this.host.style.setProperty('overflow', 'hidden', 'important');
|
|
340
|
-
|
|
341
|
-
this.subs.push(
|
|
342
|
-
rxjs.fromEvent<MouseEvent>(this.canvas, 'mousemove').subscribe(this.canvasOnMouseMove.bind(this)));
|
|
343
|
-
this.subs.push(
|
|
344
|
-
rxjs.fromEvent<MouseEvent>(this.canvas, 'mousedown').subscribe(this.canvasOnMouseDown.bind(this)));
|
|
345
|
-
|
|
346
|
-
this.subs.push(rxjs.fromEvent<WheelEvent>(this.canvas, 'wheel').subscribe(this.canvasOnWheel.bind(this)));
|
|
347
|
-
|
|
348
|
-
this.subs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
349
|
-
|
|
350
|
-
this.root.append(this.host);
|
|
351
|
-
this.root.append(this.slider.root);
|
|
352
|
-
|
|
353
|
-
this._calculate(window.devicePixelRatio);
|
|
354
|
-
this.updateSlider();
|
|
355
|
-
this.render(true);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
459
|
// -- Data --
|
|
359
460
|
|
|
360
461
|
setData(): void {
|
|
@@ -373,9 +474,14 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
373
474
|
|
|
374
475
|
// -- View --
|
|
375
476
|
|
|
477
|
+
private viewSubs: Unsubscribable[] = [];
|
|
478
|
+
|
|
376
479
|
private destroyView() {
|
|
480
|
+
for (const sub of this.viewSubs) sub.unsubscribe();
|
|
481
|
+
this.viewSubs = [];
|
|
482
|
+
|
|
377
483
|
const dataFrameTxt = `${this.dataFrame ? 'data' : 'null'}`;
|
|
378
|
-
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.
|
|
484
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.destroyView( dataFrame = ${dataFrameTxt} ) start`);
|
|
379
485
|
super.detach();
|
|
380
486
|
|
|
381
487
|
this.viewSubs.forEach((sub) => sub.unsubscribe());
|
|
@@ -383,30 +489,70 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
383
489
|
this.msgHost = undefined;
|
|
384
490
|
this.host = undefined;
|
|
385
491
|
|
|
386
|
-
this.initialized = false;
|
|
387
492
|
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.destroyView() end`);
|
|
388
493
|
}
|
|
389
494
|
|
|
390
495
|
private buildView() {
|
|
391
|
-
super.onTableAttached();
|
|
392
|
-
|
|
393
496
|
const dataFrameTxt: string = this.dataFrame ? 'data' : 'null';
|
|
394
|
-
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.
|
|
497
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView( dataFrame = ${dataFrameTxt} ) start`);
|
|
498
|
+
|
|
499
|
+
this.helpUrl = '/help/visualize/viewers/web-logo.md';
|
|
500
|
+
|
|
501
|
+
this.msgHost = ui.div('No message');
|
|
502
|
+
this.msgHost.style.display = 'none';
|
|
395
503
|
|
|
396
|
-
|
|
504
|
+
this.canvas = ui.canvas();
|
|
505
|
+
this.canvas.style.width = '100%';
|
|
506
|
+
|
|
507
|
+
//this.slider.setShowHandles(false);
|
|
508
|
+
this.slider.root.style.position = 'absolute';
|
|
509
|
+
this.slider.root.style.zIndex = '999';
|
|
510
|
+
this.slider.root.style.display = 'none';
|
|
511
|
+
this.slider.root.style.height = '0.7em';
|
|
512
|
+
|
|
513
|
+
this.visibleSlider = false;
|
|
514
|
+
|
|
515
|
+
this.host = ui.div([this.msgHost, this.canvas]);
|
|
516
|
+
|
|
517
|
+
this.host.style.justifyContent = 'center';
|
|
518
|
+
this.host.style.alignItems = 'center';
|
|
519
|
+
this.host.style.position = 'relative';
|
|
520
|
+
this.host.style.setProperty('overflow', 'hidden', 'important');
|
|
521
|
+
|
|
522
|
+
this.root.append(this.host);
|
|
523
|
+
this.root.append(this.slider.root);
|
|
524
|
+
|
|
525
|
+
this.updateSlider();
|
|
526
|
+
this.render(RecalcLevel.Freqs, 'init');
|
|
527
|
+
|
|
528
|
+
if (!!this.error) {
|
|
529
|
+
this.msgHost!.innerText = this.error.message;
|
|
530
|
+
ui.tooltip.bind(this.msgHost!, this.error.stack);
|
|
531
|
+
this.msgHost!.style.setProperty('display', null);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (this.dataFrame) {
|
|
397
535
|
this.viewSubs.push(this.dataFrame.filter.onChanged.subscribe(this.dataFrameFilterOnChanged.bind(this)));
|
|
398
536
|
this.viewSubs.push(this.dataFrame.selection.onChanged.subscribe(this.dataFrameSelectionOnChanged.bind(this)));
|
|
399
537
|
}
|
|
400
|
-
|
|
401
|
-
this.
|
|
402
|
-
|
|
538
|
+
this.viewSubs.push(ui.onSizeChanged(this.root).subscribe(this.rootOnSizeChanged.bind(this)));
|
|
539
|
+
this.viewSubs.push(this.slider.onValuesChanged.subscribe(this.sliderOnValuesChanged.bind(this)));
|
|
540
|
+
this.viewSubs.push(
|
|
541
|
+
fromEvent<MouseEvent>(this.canvas, 'mousemove').subscribe(this.canvasOnMouseMove.bind(this)));
|
|
542
|
+
this.viewSubs.push(
|
|
543
|
+
fromEvent<MouseEvent>(this.canvas, 'mousedown').subscribe(this.canvasOnMouseDown.bind(this)));
|
|
544
|
+
this.viewSubs.push(
|
|
545
|
+
fromEvent<WheelEvent>(this.canvas, 'wheel').subscribe(this.canvasOnWheel.bind(this)));
|
|
546
|
+
|
|
547
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.buildView() end`);
|
|
403
548
|
}
|
|
404
549
|
|
|
405
550
|
/** Handler of changing size WebLogo */
|
|
406
551
|
private rootOnSizeChanged(): void {
|
|
407
|
-
this.
|
|
552
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.rootOnSizeChanged(), start `);
|
|
553
|
+
|
|
408
554
|
this.updateSlider();
|
|
409
|
-
this.render(
|
|
555
|
+
this.render(RecalcLevel.Layout, 'rootOnSizeChanged');
|
|
410
556
|
}
|
|
411
557
|
|
|
412
558
|
/** Assigns {@link seqCol} and {@link cp} based on {@link sequenceColumnName} and calls {@link render}().
|
|
@@ -426,10 +572,11 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
426
572
|
|
|
427
573
|
this.updatePositions();
|
|
428
574
|
this.cp = pickUpPalette(this.seqCol);
|
|
575
|
+
this.error = null;
|
|
429
576
|
} catch (err: any) {
|
|
430
577
|
this.seqCol = null;
|
|
431
|
-
this.
|
|
432
|
-
this.
|
|
578
|
+
this.error = err instanceof Error ? err : new Error(err.toString());
|
|
579
|
+
_package.logger.error(this.error.message, undefined, this.error.stack);
|
|
433
580
|
}
|
|
434
581
|
if (!this.seqCol) {
|
|
435
582
|
this.unitsHandler = null;
|
|
@@ -440,7 +587,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
440
587
|
}
|
|
441
588
|
}
|
|
442
589
|
}
|
|
443
|
-
this.render();
|
|
444
590
|
}
|
|
445
591
|
|
|
446
592
|
/** Updates {@link positionNames} and calculates {@link startPosition} and {@link endPosition}.
|
|
@@ -449,15 +595,8 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
449
595
|
if (!this.seqCol)
|
|
450
596
|
return;
|
|
451
597
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const indices: Int32Array = this.dataFrame.filter.getSelectedIndexes();
|
|
455
|
-
categories = Array.from(new Set(
|
|
456
|
-
Array.from(Array(indices.length).keys()).map((i: number) => this.seqCol!.get(indices[i]))));
|
|
457
|
-
} else {
|
|
458
|
-
categories = this.seqCol.categories;
|
|
459
|
-
}
|
|
460
|
-
const maxLength = categories.length > 0 ? Math.max(...this.unitsHandler!.splitted.map((mList) => mList.length)) : 0;
|
|
598
|
+
const maxLength = wu(this.unitsHandler!.splitted).map((mList) => mList ? mList.length : 0)
|
|
599
|
+
.reduce((max, l) => Math.max(max, l), 0);
|
|
461
600
|
|
|
462
601
|
// Get position names from data column tag 'positionNames'
|
|
463
602
|
const positionNamesTxt = this.seqCol.getTag(wlTAGS.positionNames);
|
|
@@ -561,12 +700,12 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
561
700
|
break;
|
|
562
701
|
}
|
|
563
702
|
|
|
564
|
-
this.render(
|
|
703
|
+
this.render(RecalcLevel.Freqs, 'onPropertyChanged');
|
|
565
704
|
}
|
|
566
705
|
|
|
567
706
|
/** Add filter handlers when table is a attached */
|
|
568
707
|
public override onTableAttached() {
|
|
569
|
-
_package.logger.debug(
|
|
708
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.onTableAttached(), `);
|
|
570
709
|
|
|
571
710
|
// -- Props editors --
|
|
572
711
|
|
|
@@ -587,17 +726,16 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
587
726
|
getMonomer(p: DG.Point): [number, string | null, PositionMonomerInfo | null] {
|
|
588
727
|
const calculatedX = p.x + this.firstVisibleIndex * this.positionWidthWithMargin;
|
|
589
728
|
const jPos = Math.floor(p.x / this.positionWidthWithMargin + this.firstVisibleIndex);
|
|
590
|
-
const position = this.positions[jPos];
|
|
729
|
+
const position: PositionInfo = this.positions[jPos];
|
|
591
730
|
|
|
592
|
-
if (position
|
|
731
|
+
if (position === undefined)
|
|
593
732
|
return [jPos, null, null];
|
|
594
733
|
|
|
595
|
-
const monomer: string | undefined =
|
|
596
|
-
.find((m) => position.freq[m].bounds.contains(calculatedX, p.y));
|
|
734
|
+
const monomer: string | undefined = position.getMonomerAt(calculatedX, p.y);
|
|
597
735
|
if (monomer === undefined)
|
|
598
736
|
return [jPos, null, null];
|
|
599
737
|
|
|
600
|
-
return [jPos, monomer, position.
|
|
738
|
+
return [jPos, monomer, position.getFreq(monomer)];
|
|
601
739
|
};
|
|
602
740
|
|
|
603
741
|
/** Helper function for rendering
|
|
@@ -632,187 +770,166 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
632
770
|
this.removeWhere(this.positions, (item) => item?.freq['-']?.count === item.rowCount);
|
|
633
771
|
}
|
|
634
772
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
|
|
773
|
+
private renderRequested: boolean = false;
|
|
774
|
+
/** default value of RecalcLevel.Freqs is for recalc from the scratch at the beginning */
|
|
775
|
+
private recalcLevelRequested: RecalcLevel = RecalcLevel.Freqs;
|
|
639
776
|
|
|
640
|
-
|
|
777
|
+
/** Renders requested repeatedly will be performed once on window.requestAnimationFrame() */
|
|
778
|
+
render(recalcLevel: RecalcLevel, reason: string): void {
|
|
779
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>` +
|
|
780
|
+
`.render( recalcLevel=${recalcLevel}, reason='${reason}' )`);
|
|
641
781
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
this.
|
|
647
|
-
}
|
|
782
|
+
/** Calculate freqs of monomers */
|
|
783
|
+
const calculateFreqsInt = (length: number): void => {
|
|
784
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateFreqsInt(), start `);
|
|
785
|
+
|
|
786
|
+
if (!this.host || !this.seqCol || !this.dataFrame) return;
|
|
648
787
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
for (const rowI of selRowIndices) {
|
|
657
|
-
const seqM: string[] = this.unitsHandler.splitted[rowI];
|
|
658
|
-
for (let jPos = 0; jPos < this.Length; jPos++) {
|
|
659
|
-
const pmInfo = this.positions[jPos].freq;
|
|
660
|
-
const m: string = seqM[this.startPosition + jPos] || '-';
|
|
661
|
-
if (!(m in pmInfo))
|
|
662
|
-
pmInfo[m] = new PositionMonomerInfo();
|
|
663
|
-
pmInfo[m].count++;
|
|
788
|
+
this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
|
|
789
|
+
const posCount: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
|
|
790
|
+
this.positions = new Array(posCount);
|
|
791
|
+
for (let jPos = 0; jPos < length; jPos++) {
|
|
792
|
+
const posName: string = this.positionNames[this.startPosition + jPos];
|
|
793
|
+
this.positions[jPos] = new PositionInfo(this.startPosition + jPos, posName);
|
|
664
794
|
}
|
|
665
|
-
}
|
|
666
795
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
for (const m in this.positions[jPos].freq) {
|
|
677
|
-
const pn = this.positions[jPos].freq[m].count / this.positions[jPos].rowCount;
|
|
678
|
-
this.positions[jPos].sumForHeightCalc += -pn * Math.log2(pn);
|
|
796
|
+
// 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
|
|
797
|
+
const selRowIndices = this.filter.getSelectedIndexes();
|
|
798
|
+
|
|
799
|
+
for (const rowI of selRowIndices) {
|
|
800
|
+
const seqMList: string[] = this.unitsHandler.splitted[rowI];
|
|
801
|
+
for (let jPos = 0; jPos < length; ++jPos) {
|
|
802
|
+
const m: string = seqMList[this.startPosition + jPos] || '-';
|
|
803
|
+
const pmInfo = this.positions[jPos].getFreq(m);
|
|
804
|
+
pmInfo.count++;
|
|
679
805
|
}
|
|
680
806
|
}
|
|
681
|
-
}
|
|
682
|
-
//#endregion
|
|
683
|
-
this._removeEmptyPositions();
|
|
684
807
|
|
|
685
|
-
|
|
808
|
+
//#region Polish freq counts
|
|
809
|
+
for (let jPos = 0; jPos < length; jPos++) {
|
|
810
|
+
// delete this.positions[jPos].freq['-'];
|
|
811
|
+
this.positions[jPos].calcHeights(this.positionHeight as PositionHeight);
|
|
812
|
+
}
|
|
813
|
+
//#endregion
|
|
814
|
+
this._removeEmptyPositions();
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
/** Calculate layout of monomers on screen (canvas) based on freqs, required to handle mouse events */
|
|
818
|
+
const calculateLayoutInt = (length: number, dpr: number): void => {
|
|
819
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.calculateLayoutInt(), start `);
|
|
686
820
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const freq: { [c: string]: PositionMonomerInfo } = this.positions[jPos].freq;
|
|
690
|
-
const rowCount = this.positions[jPos].rowCount;
|
|
821
|
+
this.calcSize();
|
|
822
|
+
const absoluteMaxHeight = this.canvas.height - this.axisHeight * dpr;
|
|
691
823
|
const alphabetSize = this.getAlphabetSize();
|
|
692
824
|
if ((this.positionHeight == PositionHeight.Entropy) && (alphabetSize == null))
|
|
693
825
|
grok.shell.error('WebLogo: alphabet is undefined.');
|
|
694
|
-
|
|
695
|
-
|
|
696
826
|
const alphabetSizeLog = Math.log2(alphabetSize);
|
|
697
|
-
const maxHeight = (this.positionHeight == PositionHeight.Entropy) ?
|
|
698
|
-
(absoluteMaxHeight * (alphabetSizeLog - (this.positions[jPos].sumForHeightCalc)) / alphabetSizeLog) :
|
|
699
|
-
absoluteMaxHeight;
|
|
700
|
-
|
|
701
|
-
let y: number = this.axisHeight * r + (absoluteMaxHeight - maxHeight - 1);
|
|
702
|
-
|
|
703
|
-
const entries = Object.entries(freq).sort((a, b) => {
|
|
704
|
-
if (a[0] !== '-' && b[0] !== '-')
|
|
705
|
-
return b[1].count - a[1].count;
|
|
706
|
-
else if (a[0] === '-' && b[0] === '-')
|
|
707
|
-
return 0;
|
|
708
|
-
else if (a[0] === '-')
|
|
709
|
-
return -1;
|
|
710
|
-
else /* (b[0] === '-') */
|
|
711
|
-
return +1;
|
|
712
|
-
});
|
|
713
|
-
for (const entry of entries) {
|
|
714
|
-
const pmInfo: PositionMonomerInfo = entry[1];
|
|
715
|
-
// const m: string = entry[0];
|
|
716
|
-
const h: number = maxHeight * pmInfo.count / rowCount;
|
|
717
827
|
|
|
718
|
-
|
|
719
|
-
|
|
828
|
+
for (let jPos = 0; jPos < length; jPos++) {
|
|
829
|
+
this.positions[jPos].calcScreen(jPos, absoluteMaxHeight, this.positionHeight as PositionHeight,
|
|
830
|
+
alphabetSizeLog, this.positionWidthWithMargin, this._positionWidth, dpr, this.axisHeight);
|
|
720
831
|
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/** Render WebLogo sensitive to changes in params of rendering
|
|
835
|
+
*@param {boolean} recalcFreqs - indicates that need to recalculate data for rendering
|
|
836
|
+
*/
|
|
837
|
+
const renderInt = (recalcLevel: RecalcLevel) => {
|
|
838
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), start `);
|
|
839
|
+
if (this.msgHost) {
|
|
840
|
+
if (this.seqCol && !this.cp) {
|
|
841
|
+
this.msgHost!.innerText = `Unknown palette (column semType: '${this.seqCol.semType}').`;
|
|
842
|
+
this.msgHost!.style.display = '';
|
|
843
|
+
} else {
|
|
844
|
+
this.msgHost!.style.display = 'none';
|
|
845
|
+
}
|
|
735
846
|
}
|
|
736
|
-
}
|
|
737
847
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
848
|
+
if (!this.seqCol || !this.dataFrame || !this.cp || this.startPosition === -1 ||
|
|
849
|
+
this.endPosition === -1 || this.host == null || this.slider == null)
|
|
850
|
+
return;
|
|
741
851
|
|
|
742
|
-
|
|
743
|
-
|
|
852
|
+
const g = this.canvas.getContext('2d');
|
|
853
|
+
if (!g) return;
|
|
744
854
|
|
|
745
|
-
|
|
855
|
+
this.slider.root.style.width = `${this.host.clientWidth}px`;
|
|
746
856
|
|
|
747
|
-
|
|
857
|
+
const length: number = this.Length;
|
|
858
|
+
const dpr: number = window.devicePixelRatio;
|
|
859
|
+
if (recalcLevel >= RecalcLevel.Freqs) calculateFreqsInt(length);
|
|
860
|
+
if (recalcLevel >= RecalcLevel.Layout) calculateLayoutInt(length, window.devicePixelRatio);
|
|
748
861
|
|
|
749
|
-
if (recalc)
|
|
750
|
-
this._calculate(r);
|
|
751
|
-
|
|
752
|
-
g.resetTransform();
|
|
753
|
-
g.fillStyle = DG.Color.toHtml(this.backgroundColor);
|
|
754
|
-
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
755
|
-
g.textBaseline = this.textBaseline;
|
|
756
|
-
|
|
757
|
-
const maxCountOfRowsRendered = this.countOfRenderPositions + 1;
|
|
758
|
-
const firstVisibleIndex = (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
|
|
759
|
-
const lastVisibleIndex = Math.min(this.Length, firstVisibleIndex + maxCountOfRowsRendered);
|
|
760
|
-
|
|
761
|
-
//#region Plot positionNames
|
|
762
|
-
const positionFontSize = 10 * r;
|
|
763
|
-
g.resetTransform();
|
|
764
|
-
g.fillStyle = 'black';
|
|
765
|
-
g.textAlign = 'center';
|
|
766
|
-
g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
|
|
767
|
-
const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
|
|
768
|
-
const hScale = posNameMaxWidth < (this._positionWidth - 2) ? 1 : (this._positionWidth - 2) / posNameMaxWidth;
|
|
769
|
-
|
|
770
|
-
for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
|
|
771
|
-
const pos: PositionInfo = this.positions[jPos];
|
|
772
862
|
g.resetTransform();
|
|
773
|
-
g.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
863
|
+
g.fillStyle = DG.Color.toHtml(this.backgroundColor);
|
|
864
|
+
g.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
865
|
+
g.textBaseline = this.textBaseline;
|
|
866
|
+
|
|
867
|
+
const maxCountOfRowsRendered = this.countOfRenderPositions + 1;
|
|
868
|
+
const firstVisibleIndex = (this.visibleSlider) ? Math.floor(this.slider.min) : 0;
|
|
869
|
+
const lastVisibleIndex = Math.min(length, firstVisibleIndex + maxCountOfRowsRendered);
|
|
870
|
+
|
|
871
|
+
//#region Plot positionNames
|
|
872
|
+
const positionFontSize = 10 * dpr;
|
|
873
|
+
g.resetTransform();
|
|
874
|
+
g.fillStyle = 'black';
|
|
875
|
+
g.textAlign = 'center';
|
|
876
|
+
g.font = `${positionFontSize.toFixed(1)}px Roboto, Roboto Local, sans-serif`;
|
|
877
|
+
const posNameMaxWidth = Math.max(...this.positions.map((pos) => g.measureText(pos.name).width));
|
|
878
|
+
const hScale = posNameMaxWidth < (this._positionWidth - 2) ? 1 : (this._positionWidth - 2) / posNameMaxWidth;
|
|
879
|
+
|
|
880
|
+
for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
|
|
881
|
+
const pos: PositionInfo = this.positions[jPos];
|
|
882
|
+
g.resetTransform();
|
|
883
|
+
g.setTransform(
|
|
884
|
+
hScale, 0, 0, 1,
|
|
885
|
+
jPos * this.positionWidthWithMargin + this._positionWidth / 2 -
|
|
886
|
+
this.positionWidthWithMargin * firstVisibleIndex, 0);
|
|
887
|
+
g.fillText(pos.name, 0, 0);
|
|
888
|
+
}
|
|
889
|
+
//#endregion Plot positionNames
|
|
890
|
+
const fontStyle = '16px Roboto, Roboto Local, sans-serif';
|
|
891
|
+
// Hacks to scale uppercase characters to target rectangle
|
|
892
|
+
const uppercaseLetterAscent = 0.25;
|
|
893
|
+
const uppercaseLetterHeight = 12.2;
|
|
894
|
+
for (let jPos = this.firstVisibleIndex; jPos < lastVisibleIndex; jPos++) {
|
|
895
|
+
this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight,
|
|
896
|
+
this.positionWidthWithMargin, firstVisibleIndex, this.cp);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
_package.logger.debug(`Bio: WebLogoViewer<${this.viewerId}>.render.renderInt( recalcLevel=${recalcLevel} ), end `);
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
this.recalcLevelRequested = Math.max(this.recalcLevelRequested, recalcLevel);
|
|
903
|
+
if (!this.renderRequested) {
|
|
904
|
+
this.renderRequested = true;
|
|
905
|
+
// requestAnimationFrame callback will be executed after this.render()
|
|
906
|
+
switch (this.recalcLevelRequested) {
|
|
907
|
+
case RecalcLevel.Freqs:
|
|
908
|
+
/* Avoiding [Violation] 'requestAnimationFrame' handler took too much */
|
|
909
|
+
window.setTimeout(() => {
|
|
910
|
+
renderInt(this.recalcLevelRequested);
|
|
911
|
+
this.recalcLevelRequested = RecalcLevel.None;
|
|
912
|
+
this.renderRequested = false;
|
|
913
|
+
}, 0 /* next event cycle */);
|
|
914
|
+
break;
|
|
915
|
+
|
|
916
|
+
case RecalcLevel.Layout:
|
|
917
|
+
case RecalcLevel.None:
|
|
918
|
+
window.requestAnimationFrame((time: number) => {
|
|
919
|
+
renderInt(this.recalcLevelRequested);
|
|
920
|
+
this.recalcLevelRequested = RecalcLevel.None;
|
|
921
|
+
this.renderRequested = false;
|
|
922
|
+
});
|
|
923
|
+
break;
|
|
806
924
|
}
|
|
807
925
|
}
|
|
808
926
|
}
|
|
809
927
|
|
|
810
928
|
/** Calculate canvas size an positionWidth and updates properties */
|
|
811
929
|
private calcSize() {
|
|
812
|
-
if (!this.host)
|
|
813
|
-
return;
|
|
930
|
+
if (!this.host) return;
|
|
814
931
|
|
|
815
|
-
const
|
|
932
|
+
const dpr: number = window.devicePixelRatio;
|
|
816
933
|
|
|
817
934
|
let width: number = this.widthArea;
|
|
818
935
|
let height = this.heightArea;
|
|
@@ -824,15 +941,15 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
824
941
|
this._positionWidth = this.positionWidth * scale;
|
|
825
942
|
}
|
|
826
943
|
|
|
827
|
-
width = this.Length * this.positionWidthWithMargin /
|
|
944
|
+
width = this.Length * this.positionWidthWithMargin / dpr;
|
|
828
945
|
|
|
829
|
-
this.canvas.width = this.root.clientWidth *
|
|
946
|
+
this.canvas.width = this.root.clientWidth * dpr;
|
|
830
947
|
this.canvas.style.width = `${this.root.clientWidth}px`;
|
|
831
948
|
|
|
832
949
|
// const canvasHeight: number = width > this.root.clientWidth ? height - 8 : height;
|
|
833
950
|
this.host.style.setProperty('height', `${height}px`);
|
|
834
951
|
const canvasHeight: number = this.host.clientHeight;
|
|
835
|
-
this.canvas.height = canvasHeight *
|
|
952
|
+
this.canvas.height = canvasHeight * dpr;
|
|
836
953
|
|
|
837
954
|
// Adjust host and root width
|
|
838
955
|
if (this.fixWidth) {
|
|
@@ -910,33 +1027,33 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
910
1027
|
this._positionWidth = calculatedWidth;
|
|
911
1028
|
}
|
|
912
1029
|
this.turnOfResizeForOneSetValue = false;
|
|
913
|
-
this.render(
|
|
1030
|
+
this.render(RecalcLevel.Layout, 'sliderOnValuesChanged');
|
|
914
1031
|
} catch (err: any) {
|
|
915
1032
|
const errMsg = errorToConsole(err);
|
|
916
|
-
_package.logger.error('Bio: WebLogoViewer.sliderOnValuesChanged() error:\n' + errMsg);
|
|
1033
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.sliderOnValuesChanged() error:\n' + errMsg);
|
|
917
1034
|
//throw err; // Do not throw to prevent disabling event handler
|
|
918
1035
|
}
|
|
919
1036
|
}
|
|
920
1037
|
|
|
921
1038
|
private dataFrameFilterOnChanged(_value: any): void {
|
|
922
|
-
_package.logger.debug('Bio: WebLogoViewer.dataFrameFilterChanged()');
|
|
1039
|
+
_package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterChanged()');
|
|
923
1040
|
try {
|
|
924
1041
|
this.updatePositions();
|
|
925
|
-
this.render();
|
|
1042
|
+
this.render(RecalcLevel.Freqs, 'dataFrameFilterOnChanged');
|
|
926
1043
|
} catch (err: any) {
|
|
927
1044
|
const errMsg = errorToConsole(err);
|
|
928
|
-
_package.logger.error('Bio: WebLogoViewer.dataFrameFilterOnChanged() error:\n' + errMsg);
|
|
1045
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameFilterOnChanged() error:\n' + errMsg);
|
|
929
1046
|
//throw err; // Do not throw to prevent disabling event handler
|
|
930
1047
|
}
|
|
931
1048
|
}
|
|
932
1049
|
|
|
933
1050
|
private dataFrameSelectionOnChanged(_value: any): void {
|
|
934
|
-
_package.logger.debug('Bio: WebLogoViewer.dataFrameSelectionOnChanged()');
|
|
1051
|
+
_package.logger.debug('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged()');
|
|
935
1052
|
try {
|
|
936
|
-
this.render();
|
|
1053
|
+
this.render(RecalcLevel.Freqs, 'dataFrameSelectionOnChanged');
|
|
937
1054
|
} catch (err: any) {
|
|
938
1055
|
const errMsg = errorToConsole(err);
|
|
939
|
-
_package.logger.error('Bio: WebLogoViewer.dataFrameSelectionOnChanged() error:\n' + errMsg);
|
|
1056
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.dataFrameSelectionOnChanged() error:\n' + errMsg);
|
|
940
1057
|
//throw err; // Do not throw to prevent disabling event handler
|
|
941
1058
|
}
|
|
942
1059
|
}
|
|
@@ -970,7 +1087,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
970
1087
|
}
|
|
971
1088
|
} catch (err: any) {
|
|
972
1089
|
const errMsg = errorToConsole(err);
|
|
973
|
-
_package.logger.error('Bio: WebLogoViewer.canvasOnMouseMove() error:\n' + errMsg);
|
|
1090
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.canvasOnMouseMove() error:\n' + errMsg);
|
|
974
1091
|
//throw err; // Do not throw to prevent disabling event handler
|
|
975
1092
|
}
|
|
976
1093
|
}
|
|
@@ -985,10 +1102,6 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
985
1102
|
if (this.dataFrame && this.seqCol && this.unitsHandler && monomer) {
|
|
986
1103
|
const atPI: PositionInfo = this.positions[jPos];
|
|
987
1104
|
|
|
988
|
-
// this.dataFrame.selection.init((rowI: number) => {
|
|
989
|
-
// return checkSeqForMonomerAtPos(
|
|
990
|
-
// this.dataFrame, this.seqCol!, this.filter, rowI, this.splitter!, monomer, atPI);
|
|
991
|
-
// });
|
|
992
1105
|
// Calculate a new BitSet object for selection to prevent interfering with existing
|
|
993
1106
|
const selBS: DG.BitSet = DG.BitSet.create(this.dataFrame.selection.length, (rowI: number) => {
|
|
994
1107
|
return checkSeqForMonomerAtPos(this.dataFrame, this.unitsHandler!, this.filter, rowI, monomer, atPI);
|
|
@@ -997,7 +1110,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
997
1110
|
}
|
|
998
1111
|
} catch (err: any) {
|
|
999
1112
|
const errMsg = errorToConsole(err);
|
|
1000
|
-
_package.logger.error('Bio: WebLogoViewer.canvasOnMouseDown() error:\n' + errMsg);
|
|
1113
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.canvasOnMouseDown() error:\n' + errMsg);
|
|
1001
1114
|
//throw err; // Do not throw to prevent disabling event handler
|
|
1002
1115
|
}
|
|
1003
1116
|
}
|
|
@@ -1010,7 +1123,7 @@ export class WebLogoViewer extends DG.JsViewer {
|
|
|
1010
1123
|
this.slider.scrollBy(this.slider.min + countOfScrollPositions);
|
|
1011
1124
|
} catch (err: any) {
|
|
1012
1125
|
const errMsg = errorToConsole(err);
|
|
1013
|
-
_package.logger.error('Bio: WebLogoViewer.canvasOnWheel() error:\n' + errMsg);
|
|
1126
|
+
_package.logger.error('Bio: WebLogoViewer<${this.viewerId}>.canvasOnWheel() error:\n' + errMsg);
|
|
1014
1127
|
//throw err; // Do not throw to prevent disabling event handler
|
|
1015
1128
|
}
|
|
1016
1129
|
}
|
|
@@ -1031,17 +1144,13 @@ export function checkSeqForMonomerAtPos(
|
|
|
1031
1144
|
export function countForMonomerAtPosition(
|
|
1032
1145
|
df: DG.DataFrame, uh: UnitsHandler, filter: DG.BitSet, monomer: string, at: PositionInfo
|
|
1033
1146
|
): number {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
// return correctMonomerFilter(iRow, monomer, jPos);
|
|
1044
|
-
// }).reduce<number>((count, iRow) => count + 1, 0);
|
|
1045
|
-
const monomerAtPosRowCount = posMList.filter((m) => m == monomer).reduce((count, _m) => count + 1, 0);
|
|
1046
|
-
return monomerAtPosRowCount;
|
|
1147
|
+
let count = 0;
|
|
1148
|
+
let rowI = -1;
|
|
1149
|
+
while ((rowI = filter.findNext(rowI, true)) != -1) {
|
|
1150
|
+
const seqMList: string[] = uh.splitted[rowI];
|
|
1151
|
+
const seqMPos: number = at.pos;
|
|
1152
|
+
const seqM: string | null = seqMPos < seqMList.length ? seqMList[seqMPos] : null;
|
|
1153
|
+
if (seqM === monomer) count++;
|
|
1154
|
+
}
|
|
1155
|
+
return count;
|
|
1047
1156
|
}
|