@datagrok/bio 2.4.48 → 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.
@@ -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 * as rxjs from 'rxjs';
5
- import {WebLogoViewer, PROPS as wlPROPS} from '../viewers/web-logo-viewer';
6
- import {IVdRegionsViewer,
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
- import {Unsubscribable} from 'rxjs';
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(rxjs.fromEvent<MouseEvent>(this.root, 'mousemove').subscribe(this.rootOnMouseMove.bind(this)));
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
- public override async onTableAttached() {
110
- const superOnTableAttached = super.onTableAttached.bind(this);
111
- this.viewPromise = this.viewPromise.then(async () => { // onTableAttached
112
- superOnTableAttached();
113
- if (!this.viewed) {
114
- await this.buildView('onTableAttached'); // onTableAttached
115
- this.viewed = true;
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
- console.debug('Bio: VdRegionsViewer.setData()');
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'); // setData
176
+ await this.destroyView('setData');
163
177
  this.viewed = false;
164
178
  }
165
- });
166
-
167
- this.regions = regions;
168
- this.dataFrame = mlbDf; // causes detach and onTableAttached
169
-
170
- this.viewPromise = this.viewPromise.then(async () => { // setData
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'); // setData
188
+ await this.buildView('setData');
173
189
  this.viewed = true;
174
190
  }
175
- });
176
- }
177
-
178
- override detach() {
179
- const superDetach = super.detach.bind(this);
180
- this.viewPromise = this.viewPromise.then(async () => { // detach
181
- if (this.viewed) {
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
- this.logos = [];
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
- regionChains[chain] = (await this.dataFrame.plot.fromType('WebLogo', {
233
- sequenceColumnName: region!.sequenceColumnName,
234
- startPositionName: region!.positionStartName,
235
- endPositionName: region!.positionEndName,
236
- fixWidth: true,
237
- skipEmptyPositions: this.skipEmptyPositions,
238
- positionWidth: this.positionWidth,
239
- positionHeight: this.positionHeight,
240
- })) as unknown as WebLogoViewer;
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
- getSplitter, monomerToShort, pickUpPalette, pickUpSeqCol, SplitterFunc,
11
+ monomerToShort,
12
+ pickUpPalette,
13
+ pickUpSeqCol,
13
14
  TAGS as bioTAGS,
14
15
  } from '@datagrok-libraries/bio/src/utils/macromolecule';
15
16
  import {
16
- WebLogoPropsDefault, WebLogoProps,
17
+ FilterSources,
18
+ HorizontalAlignments,
17
19
  PositionHeight,
20
+ PositionMarginStates,
18
21
  positionSeparator,
22
+ TAGS as wlTAGS,
19
23
  VerticalAlignments,
20
- HorizontalAlignments,
21
- PositionMarginStates,
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
- import {TAGS as wlTAGS} from '@datagrok-libraries/bio/src/viewers/web-logo';
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
- _package.logger.error(this.error.message, undefined, this.error.stack);
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
- this.render(RecalcLevel.Freqs, 'onPropertyChanged');
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<any>, predicate: (T: any) => boolean): Array<any> {
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?.freq['-']?.count === item.rowCount);
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 = (length: number): void => {
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 = (length: number, dpr: number): void => {
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} ), start `);
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(length);
860
- if (recalcLevel >= RecalcLevel.Layout) calculateLayoutInt(length, window.devicePixelRatio);
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.render(RecalcLevel.Freqs, 'dataFrameFilterOnChanged');
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.render(RecalcLevel.Freqs, 'dataFrameSelectionOnChanged');
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 === '-'));