@datagrok/bio 2.8.4 → 2.8.6

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.
@@ -16,16 +16,18 @@ const Tags = new class {
16
16
  const svgMolOptions = {autoCrop: true, autoCropMargin: 0, suppressChiralText: true};
17
17
 
18
18
  export class MonomerTooltipHandler {
19
- private readonly grid: DG.Grid;
19
+ private readonly gridCol: DG.GridColumn;
20
20
 
21
- constructor(grid: DG.Grid) {
22
- this.grid = grid;
23
- this.grid.onCellTooltip(this.onCellTooltip.bind(this));
21
+ constructor(gridCol: DG.GridColumn) {
22
+ this.gridCol = gridCol;
23
+ this.gridCol.grid.onCellTooltip(this.onCellTooltip.bind(this));
24
24
  }
25
25
 
26
26
  private onCellTooltip(gridCell: DG.GridCell, x: number, y: number): any {
27
- if (gridCell.grid.dart != this.grid.dart || !gridCell.tableColumn || !gridCell.isTableCell ||
28
- gridCell.tableColumn.semType != 'Monomer') return false;
27
+ if (
28
+ gridCell.grid.dart != this.gridCol.grid.dart || gridCell.gridColumn.dart != this.gridCol.dart ||
29
+ !gridCell.tableColumn || !gridCell.isTableCell
30
+ ) return false;
29
31
 
30
32
  const alphabet = gridCell.tableColumn.getTag(bioTAGS.alphabet);
31
33
  const monomerName = gridCell.cell.value;
@@ -46,13 +48,11 @@ export class MonomerTooltipHandler {
46
48
  return true; // To prevent default tooltip behaviour
47
49
  }
48
50
 
49
- public static getOrCreate(grid: DG.Grid): MonomerTooltipHandler {
50
- const gridTemp: { [tempName: string]: any } = grid.dataFrame.temp;
51
- if (!(Tags.tooltipHandlerTemp in gridTemp)) {
52
- gridTemp[Tags.tooltipHandlerTemp] = new MonomerTooltipHandler(grid);
53
- grid.temp = gridTemp;
54
- }
55
- return gridTemp[Tags.tooltipHandlerTemp];
51
+ public static getOrCreate(gridCol: DG.GridColumn): MonomerTooltipHandler {
52
+ let res = gridCol.temp[Tags.tooltipHandlerTemp];
53
+ if (!res)
54
+ res = gridCol.temp[Tags.tooltipHandlerTemp] = new MonomerTooltipHandler(gridCol);
55
+ return res;
56
56
  }
57
57
  }
58
58
 
@@ -81,7 +81,7 @@ export class MonomerCellRenderer extends DG.GridCellRenderer {
81
81
  _cellStyle: DG.GridCellStyle
82
82
  ): void {
83
83
  if (gridCell.gridRow < 0) return;
84
- MonomerTooltipHandler.getOrCreate(gridCell.grid);
84
+ MonomerTooltipHandler.getOrCreate(gridCell.gridColumn);
85
85
 
86
86
 
87
87
  g.font = `12px monospace`;
@@ -107,7 +107,7 @@ export function wrapSequence(seq: string, splitter: SplitterFunc, lineWidth: num
107
107
  const seqLineList: string[] = [];
108
108
  while (seqPos < seqLength) {
109
109
  /* join sliced monomer into line */
110
- const seqLine: string[] = seqMonomerList.slice(seqPos, seqPos + lineWidth);
110
+ const seqLine: string[] = wu(seqMonomerList).slice(seqPos, seqPos + lineWidth).toArray();
111
111
  const seqLineTxt: string = seqLine.map((m) => m.length > 1 ? `[${m}]` : m).join('');
112
112
  seqLineList.push(seqLineTxt);
113
113
  seqPos += seqLine.length;
@@ -1,10 +1,13 @@
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
+
1
5
  import {delay} from '@datagrok-libraries/utils/src/test';
2
6
  import {checkInputColumnUI} from './check-input-column';
3
7
  import {splitAlignedSequences} from '@datagrok-libraries/bio/src/utils/splitter';
4
8
  import * as C from './constants';
5
9
  import {TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule/consts';
6
- import * as grok from 'datagrok-api/grok';
7
- import * as DG from 'datagrok-api/dg';
10
+ import {SEM_TYPES} from './constants';
8
11
 
9
12
 
10
13
  export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<string>): Promise<DG.DataFrame> {
@@ -14,6 +17,7 @@ export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<s
14
17
  if (!checkInputColumnUI(seqCol, 'Sequence space')) return table;
15
18
 
16
19
  const tempDf = splitAlignedSequences(seqCol);
20
+ tempDf.name = 'splitToMonomers';
17
21
  const originalDf = seqCol.dataFrame;
18
22
  for (const tempCol of tempDf.columns) {
19
23
  // TODO: GROK-11212
@@ -21,9 +25,39 @@ export async function splitToMonomersUI(table: DG.DataFrame, seqCol: DG.Column<s
21
25
  tempCol.semType = C.SEM_TYPES.MONOMER;
22
26
  tempCol.setTag(bioTAGS.alphabet, seqCol.getTag(bioTAGS.alphabet));
23
27
  }
24
- // Create the new data frame to enable platform to setup cell renderers
25
- const newDf = originalDf.join(tempDf, [], [], undefined, undefined, DG.JOIN_TYPE.LEFT, false);
26
- grok.shell.addTableView(newDf);
27
28
 
28
- return newDf;
29
+ const colNameRe = /(\d+)(?: \((\d+)\))?/;
30
+ const generateNewColName = (srcName: string): string => {
31
+ colNameRe.lastIndex = 0;
32
+ const ma = srcName.match(colNameRe);
33
+ if (!ma) return srcName;
34
+ return `${ma[1]} (${parseInt(ma[2] ?? 0) + 1})`;
35
+ };
36
+
37
+ // if (tempDf.columns.length === 0) return;
38
+
39
+ for (let tempColI = 0; tempColI < tempDf.columns.length; tempColI++) {
40
+ const tempCol = tempDf.columns.byIndex(tempColI);
41
+ tempCol.semType = SEM_TYPES.MONOMER;
42
+ tempCol.setTag(bioTAGS.alphabet, seqCol.getTag(bioTAGS.alphabet));
43
+
44
+ const wdMax = 100;
45
+ let wdCount = 0;
46
+ while (originalDf.columns.byName(tempCol.name) && wdCount < wdMax) {
47
+ tempCol.name = generateNewColName(tempCol.name);
48
+ wdCount++;
49
+ }
50
+
51
+ originalDf.columns.add(tempCol);
52
+ }
53
+
54
+ // originalDf.join(tempDf, [], [], undefined, undefined, DG.JOIN_TYPE.LEFT, true);
55
+ await grok.data.detectSemanticTypes(originalDf);
56
+
57
+ for (let tempColI = 0; tempColI < tempDf.columns.length; tempColI++) {
58
+ const tempCol = tempDf.columns.byIndex(tempColI);
59
+ tempCol.setTag(DG.TAGS.CELL_RENDERER, 'Monomer');
60
+ }
61
+
62
+ return originalDf;
29
63
  }
@@ -42,6 +42,24 @@ const vrt = VdRegionType;
42
42
  // new VdRegion(vrt.FR, 'FR4', 'Heavy', 7, '118', null/*128*/),
43
43
  // ];
44
44
 
45
+ export enum PROPS_CATS {
46
+ STYLE = 'Style',
47
+ BEHAVIOR = 'Behavior',
48
+ LAYOUT = 'Layout',
49
+ DATA = 'Data',
50
+ }
51
+
52
+ export enum PROPS {
53
+ // -- Data --
54
+ skipEmptyPositions = 'skipEmptyPositions',
55
+ regionTypes = 'regionTypes',
56
+ chains = 'chains',
57
+
58
+ // -- Style --
59
+ positionWidth = 'positionWidth',
60
+ positionHeight = 'positionHeight',
61
+ }
62
+
45
63
  /** Viewer with tabs based on description of chain regions.
46
64
  * Used to define regions of an immunoglobulin LC.
47
65
  */
@@ -69,20 +87,24 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
69
87
  constructor() {
70
88
  super();
71
89
 
90
+ // -- Data --
91
+ this.skipEmptyPositions = this.bool(PROPS.skipEmptyPositions, false,
92
+ {category: PROPS_CATS.DATA});
93
+
72
94
  // To prevent ambiguous numbering scheme in MLB
73
- this.regionTypes = this.stringList('regionTypes', [vrt.CDR],
74
- {choices: Object.values(vrt).filter((t) => t != vrt.Unknown)}) as VdRegionType[];
75
- this.chains = this.stringList('chains', ['Heavy', 'Light'],
76
- {choices: ['Heavy', 'Light']});
77
- // this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
78
-
79
- this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
80
- this.positionWidth = this.float('positionWidth', 16, {
81
- editor: 'slider', min: 0, max: 64,
95
+ this.regionTypes = this.stringList(PROPS.regionTypes, [vrt.CDR], {
96
+ category: PROPS_CATS.DATA, choices: Object.values(vrt).filter((t) => t != vrt.Unknown)
97
+ }) as VdRegionType[];
98
+ this.chains = this.stringList(PROPS.chains, ['Heavy', 'Light'],
99
+ {category: PROPS_CATS.DATA, choices: ['Heavy', 'Light']});
100
+
101
+ // -- Layout --
102
+ this.positionWidth = this.float(PROPS.positionWidth, 16, {
103
+ category: PROPS_CATS.LAYOUT, editor: 'slider', min: 0, max: 64,
82
104
  description: 'Internal WebLogo viewers property width of position. A value of zero means autofit to the width.'
83
105
  });
84
- this.positionHeight = this.string('positionHeight', PositionHeight.Entropy,
85
- {choices: Object.keys(PositionHeight)}) as PositionHeight;
106
+ this.positionHeight = this.string(PROPS.positionHeight, PositionHeight.Entropy,
107
+ {category: PROPS_CATS.LAYOUT, choices: Object.keys(PositionHeight)}) as PositionHeight;
86
108
  }
87
109
 
88
110
  public async init() {
@@ -140,28 +162,35 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
140
162
  return;
141
163
  }
142
164
 
143
- if (property) {
144
- switch (property.name) {
145
- case 'regionTypes':
146
- break;
147
- case 'chains':
148
- break;
149
- case 'sequenceColumnNamePostfix':
150
- break;
151
- // for (let orderI = 0; orderI < this.logos.length; orderI++) {
152
- // for (let chainI = 0; chainI < this.chains.length; chainI++) {
153
- // const chain: string = this.chains[chainI];
154
- // this.logos[orderI][chain].setOptions({skipEmptyPositions: this.skipEmptyPositions});
155
- // }
156
- // }
157
- // this.calcSize();
158
- }
165
+ switch (property.name) {
166
+ case PROPS.regionTypes:
167
+ case PROPS.chains:
168
+ this.setData(this.dataFrame, this.regions);
169
+ break;
159
170
  }
160
171
 
161
172
  switch (property.name) {
162
- case 'skipEmptyPositions':
163
- case 'positionWidth':
164
- case 'positionHeight':
173
+ case PROPS.skipEmptyPositions:
174
+ for (let orderI = 0; orderI < this.logos.length; ++orderI) {
175
+ for (const chain of this.chains)
176
+ this.logos[orderI][chain].setOptions({[wlPROPS.skipEmptyPositions]: this.skipEmptyPositions});
177
+ }
178
+ this.calcSize();
179
+ break;
180
+
181
+ case PROPS.positionWidth:
182
+ this.calcSize();
183
+ break;
184
+
185
+ case PROPS.positionHeight:
186
+ for (let orderI = 0; orderI < this.logos.length; ++orderI) {
187
+ for (const chain of this.chains)
188
+ this.logos[orderI][chain].setOptions({[wlPROPS.positionWidth]: this.positionWidth});
189
+ }
190
+ this.calcSize();
191
+ break;
192
+
193
+ default:
165
194
  this.setData(this.dataFrame, this.regions); // onPropertyChanged
166
195
  break;
167
196
  }
@@ -265,8 +294,10 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
265
294
  this.logos = new Array(orderList.length);
266
295
  for (let orderI = 0; orderI < orderList.length; ++orderI)
267
296
  this.logos[orderI] = {};
268
- for (const [orderI, chain, wl] of logoList)
297
+ for (const [orderI, chain, wl] of logoList) {
269
298
  this.logos[orderI][chain] = wl;
299
+ this.viewSubs.push(wl.onFreqsCalculated.subscribe(() => { this.calcSize(); }));
300
+ }
270
301
 
271
302
  // ui.tableFromMap()
272
303
  // DG.HtmlTable.create()
@@ -336,35 +367,41 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
336
367
  private calcSize() {
337
368
  _package.logger.debug(`Bio: VdRegionsViewer.calcSize(), start`);
338
369
  const calcSizeInt = (): void => {
339
- const dpr: number = window.devicePixelRatio;
340
- const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
341
-
342
- const maxHeight: number = Math.min(logoHeight,
343
- Math.max(...this.logos.map((wlDict) =>
344
- Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
345
- );
370
+ // Postponed calcSizeInt can result call after the viewer has been closed (on tests)
371
+ if (!this.host) return;
346
372
 
373
+ const logoHeight = (this.root.clientHeight - 54) / this.chains.length;
347
374
  let totalPos: number = 0;
348
375
  for (let orderI = 0; orderI < this.logos.length; orderI++) {
349
- for (const chain of this.chains)
350
- this.logos[orderI][chain].root.style.height = `${maxHeight}px`;
376
+ for (const chain of this.chains) {
377
+ const wl = this.logos[orderI][chain];
378
+ wl.root.style.height = `${logoHeight}px`;
379
+ }
351
380
 
352
381
  totalPos += Math.max(...this.chains.map((chain) => this.logos[orderI][chain].Length));
353
382
  }
354
383
 
355
- if (this.positionWidth === 0 && this.logos.length > 0 && totalPos > 0) {
356
- const leftPad = 22/* Chain label */;
357
- const rightPad = 6 + 6 + 1;
358
- const logoMargin = 8 + 1;
359
- const fitPositionWidth =
360
- (this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos * dpr;
361
-
362
- for (let orderI = 0; orderI < this.logos.length; orderI++) {
363
- for (let chainI = 0; chainI < this.chains.length; chainI++) {
364
- const chain: string = this.chains[chainI];
365
- this.logos[orderI][chain].setOptions({positionWidth: fitPositionWidth});
384
+ if (this.positionWidth === 0) {
385
+ if (this.logos.length > 0 && totalPos > 0) {
386
+ const leftPad = 22/* Chain label */;
387
+ const rightPad = 6 + 6 + 1;
388
+ const logoMargin = 8 + 1;
389
+ const fitPositionWidth =
390
+ (this.root.clientWidth - leftPad - (this.logos.length - 1) * logoMargin - rightPad) / totalPos;
391
+
392
+ for (let orderI = 0; orderI < this.logos.length; orderI++) {
393
+ for (const chain of this.chains) {
394
+ const wl = this.logos[orderI][chain];
395
+ wl.setOptions({[wlPROPS.positionWidth]: fitPositionWidth});
396
+ wl.root.style.width = `${fitPositionWidth * wl.Length}px`;
397
+ }
366
398
  }
367
399
  }
400
+ } else {
401
+ for (let orderI = 0; orderI < this.logos.length; orderI++) {
402
+ for (const chain of this.chains)
403
+ this.logos[orderI][chain].setOptions({[wlPROPS.positionWidth]: this.positionWidth});
404
+ }
368
405
  }
369
406
 
370
407
  if (this.positionWidth === 0)