@datagrok/bio 2.4.54 → 2.6.0

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