@datagrok-libraries/bio 5.53.3 → 5.54.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.
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@datagrok-libraries/bio",
3
3
  "author": {
4
- "name": "Leonid Stolbov",
5
- "email": "lstolbov@datagrok.ai"
4
+ "name": "Davit Rizhinashvili",
5
+ "email": "drizhinashvili@datagrok.ai"
6
6
  },
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
10
  "friendlyName": "Datagrok bio library",
11
- "version": "5.53.3",
11
+ "version": "5.54.0",
12
12
  "description": "Bio utilities, types supporting Macromolecule, Molecule3D data",
13
13
  "dependencies": {
14
14
  "@datagrok-libraries/chem-meta": "^1.2.7",
@@ -36,7 +36,7 @@
36
36
  "@types/wu": "^2.1.44",
37
37
  "@typescript-eslint/eslint-plugin": "^8.8.1",
38
38
  "@typescript-eslint/parser": "^8.8.1",
39
- "datagrok-tools": "latest",
39
+ "datagrok-tools": "4.14.9",
40
40
  "eslint": "8.57.1",
41
41
  "eslint-config-google": "^0.14.0",
42
42
  "eslint-plugin-deprecation": "^3.0.0",
@@ -49,7 +49,7 @@
49
49
  "link-utils": "npm link @datagrok-libraries/utils",
50
50
  "link-all": "npm link @datagrok-libraries/chem-meta datagrok-api @datagrok-libraries/gridext @datagrok-libraries/utils @datagrok-libraries/ml",
51
51
  "build-bio": "git clean -f -X -d ./src && tsc",
52
- "build": "tsc",
52
+ "build": "grok check --soft && tsc",
53
53
  "build-all": "npm --prefix ./../../libraries/chem-meta run build && npm --prefix ./../../js-api run build && npm --prefix ./../../libraries/gridext run build && npm --prefix ./../../libraries/utils run build && npm --prefix ./../../libraries/ml run build && npm run build",
54
54
  "lint": "eslint \"./src/**/*.ts\"",
55
55
  "lint-fix": "eslint \"./src/**/*.ts\" --fix"
@@ -18,4 +18,9 @@ export declare const enum tempTAGS {
18
18
  referenceSequence = "reference-sequence",
19
19
  currentWord = "current-word"
20
20
  }
21
+ export declare const MULTILINE_TAGS: {
22
+ renderMultiline: string;
23
+ monomersPerLine: string;
24
+ maxVisibleLines: string;
25
+ };
21
26
  //# sourceMappingURL=cell-renderer-consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell-renderer-consts.d.ts","sourceRoot":"","sources":["cell-renderer-consts.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B;;;CAGxC,CAAC;AAEF,kDAAkD;AAClD,0BAAkB,SAAS;IACzB,gBAAgB,sCAAsC;IACtD,SAAS,+BAA+B;IACxC,kBAAkB,wCAAwC;IAC1D,mBAAmB,yCAAyC;IAC5D,SAAS,+BAA+B;IACxC,aAAa,mCAAmC;IAChD,QAAQ,8BAA8B;IAEtC,uBAAuB,qCAAqC;IAC5D,iBAAiB,uCAAuC;CACzD;AAED,0BAAkB,QAAQ;IACxB,iBAAiB,uBAAuB;IACxC,WAAW,iBAAiB;CAC7B"}
1
+ {"version":3,"file":"cell-renderer-consts.d.ts","sourceRoot":"","sources":["cell-renderer-consts.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B;;;CAGxC,CAAC;AAEF,kDAAkD;AAClD,0BAAkB,SAAS;IACzB,gBAAgB,sCAAsC;IACtD,SAAS,+BAA+B;IACxC,kBAAkB,wCAAwC;IAC1D,mBAAmB,yCAAyC;IAC5D,SAAS,+BAA+B;IACxC,aAAa,mCAAmC;IAChD,QAAQ,8BAA8B;IAEtC,uBAAuB,qCAAqC;IAC5D,iBAAiB,uCAAuC;CACzD;AAED,0BAAkB,QAAQ;IACxB,iBAAiB,uBAAuB;IACxC,WAAW,iBAAiB;CAC7B;AAED,eAAO,MAAM,cAAc;;;;CAI1B,CAAC"}
@@ -2,4 +2,9 @@ export const rendererSettingsChangedState = {
2
2
  true: '1',
3
3
  false: '0',
4
4
  };
5
+ export const MULTILINE_TAGS = {
6
+ renderMultiline: '.mm.cellRenderer.renderMultiline',
7
+ monomersPerLine: '.mm.cellRenderer.monomersPerLine',
8
+ maxVisibleLines: '.mm.cellRenderer.maxVisibleLines'
9
+ };
5
10
  //# sourceMappingURL=cell-renderer-consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell-renderer-consts.js","sourceRoot":"","sources":["cell-renderer-consts.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;CACX,CAAC"}
1
+ {"version":3,"file":"cell-renderer-consts.js","sourceRoot":"","sources":["cell-renderer-consts.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;CACX,CAAC;AAqBF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,eAAe,EAAE,kCAAkC;IACnD,eAAe,EAAE,kCAAkC;IACnD,eAAe,EAAE,kCAAkC;CACpD,CAAC"}
@@ -29,10 +29,17 @@ export declare class MonomerPlacer extends CellRendererBackBase<string> {
29
29
  _monomerStructureMap: {
30
30
  [key: string]: HTMLElement;
31
31
  };
32
+ private _ellipsisBounds;
33
+ private _totalLinesNeeded;
34
+ private _lineHeight;
35
+ private _cellBounds;
32
36
  private seqHelper;
33
37
  private sysMonomerLib;
34
38
  /** View is required to subscribe and handle for data frame changes */
35
39
  constructor(gridCol: DG.GridColumn | null, tableCol: DG.Column<string>, logger: ILogger, monomerLengthLimit: number, propsProvider: () => MonomerPlacerProps);
40
+ private calculateFontBasedSpacing;
41
+ private shouldUseMultilineRendering;
42
+ private calculateMultiLineLayoutDynamic;
36
43
  init(): Promise<void>;
37
44
  static getFontSettings(tableCol?: DG.Column): {
38
45
  font: string;
@@ -45,8 +52,8 @@ export declare class MonomerPlacer extends CellRendererBackBase<string> {
45
52
  /** Returns monomers lengths of the {@link rowIdx} and cumulative sums for borders, monomer places */
46
53
  getCellMonomerLengths(rowIdx: number, newWidth: number): [number[], number[]];
47
54
  private getSummedMonomerLengths;
48
- private getCellMonomerLengthsForSeq;
49
55
  private getCellMonomerLengthsForSeqValue;
56
+ private getCellMonomerLengthsForSeq;
50
57
  private getCellMonomerLengthsForSeqMsa;
51
58
  /** Returns seq position for pointer x */
52
59
  getPosition(rowIdx: number, x: number, width: number, positionShiftPadding?: number): number | null;
@@ -1 +1 @@
1
- {"version":3,"file":"cell-renderer-monomer-placer.d.ts","sourceRoot":"","sources":["cell-renderer-monomer-placer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cell-renderer-monomer-placer.d.ts","sourceRoot":"","sources":["cell-renderer-monomer-placer.ts"],"names":[],"mappings":"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"}
@@ -51,6 +51,10 @@ export class MonomerPlacer extends CellRendererBackBase {
51
51
  this._processedMaxVisibleSeqLength = 0;
52
52
  this._monomerLengthMap = {}; // caches the lengths to save time on g.measureText
53
53
  this._monomerStructureMap = {}; // caches the atomic structures of monomers
54
+ this._ellipsisBounds = undefined;
55
+ this._totalLinesNeeded = 0;
56
+ this._lineHeight = 20;
57
+ this._cellBounds = new Map();
54
58
  this.sysMonomerLib = null;
55
59
  this.padding = 5;
56
60
  this._leftThreeDotsPadding = 0;
@@ -64,18 +68,88 @@ export class MonomerPlacer extends CellRendererBackBase {
64
68
  if (this.tableCol && this.gridCol) {
65
69
  this.subs.push(this.tableCol.dataFrame.onCurrentRowChanged.subscribe(() => {
66
70
  const df = this.tableCol.dataFrame;
67
- const grid = this.gridCol.grid;
68
71
  if (df.currentRowIdx === -1) {
69
72
  this.tableCol.temp["reference-sequence" /* tempTAGS.referenceSequence */] = null;
70
73
  this.tableCol.temp["current-word" /* tempTAGS.currentWord */] = null;
71
74
  this.invalidateGrid();
72
75
  }
73
76
  }));
77
+ const resetTriggerTags = [
78
+ bioTAGS.positionShift,
79
+ 'renderMultiline'
80
+ ];
81
+ this.subs.push(DG.debounce(this.tableCol.dataFrame.onMetadataChanged.pipe(operators.filter((a) => a.args.source === this.tableCol && resetTriggerTags.includes(a.args.key))), 200).subscribe((_) => {
82
+ this.reset();
83
+ }));
74
84
  this.subs.push(DG.debounce(this.tableCol.dataFrame.onMetadataChanged.pipe(operators.filter((a) => a.args.source === this.tableCol && a.args.key === bioTAGS.positionShift)), 200).subscribe((_) => {
75
85
  this.reset();
76
86
  }));
77
87
  }
78
88
  }
89
+ calculateFontBasedSpacing(g) {
90
+ const metrics = g.measureText('M');
91
+ // Get font size directly from the column's temp properties for safety.
92
+ // This avoids parsing the font string and breaking the props interface.
93
+ let fontSize = 12; // Default font size
94
+ if (this.tableCol?.temp[".mm.cellRenderer.fontSize" /* MmcrTemps.fontSize */]) {
95
+ const sizeFromCol = this.tableCol.temp[".mm.cellRenderer.fontSize" /* MmcrTemps.fontSize */];
96
+ if (typeof sizeFromCol === 'number' && !isNaN(sizeFromCol))
97
+ fontSize = Math.max(sizeFromCol, 1);
98
+ }
99
+ // Line height is calculated based on the safe font size.
100
+ const lineHeight = Math.max(fontSize * 1.4, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + 4);
101
+ // Monomer spacing is proportional to character width.
102
+ const monomerSpacing = Math.max(2, this.props.fontCharWidth * 0.2);
103
+ return { lineHeight, monomerSpacing };
104
+ }
105
+ shouldUseMultilineRendering(tableCol) {
106
+ const renderMultiline = tableCol.getTag('renderMultiline');
107
+ return renderMultiline === 'true';
108
+ }
109
+ calculateMultiLineLayoutDynamic(g, w, h, subParts, positionShift, maxLengthOfMonomer) {
110
+ // --- 1. Setup ---
111
+ const { lineHeight, monomerSpacing } = this.calculateFontBasedSpacing(g);
112
+ const availableWidth = w - (this.padding * 2);
113
+ // --- 2. Find the widest monomer in the sequence to set a uniform column width ---
114
+ let maxMonomerWidth = 0;
115
+ const monomers = [];
116
+ if (subParts.length > 0) {
117
+ for (let i = positionShift; i < subParts.length; i++) {
118
+ const om = subParts.getOriginal(i);
119
+ const shortMon = this.props.monomerToShort(om, maxLengthOfMonomer);
120
+ monomers.push({ text: shortMon, posIdx: i });
121
+ maxMonomerWidth = Math.max(maxMonomerWidth, g.measureText(shortMon).width);
122
+ }
123
+ }
124
+ if (monomers.length === 0)
125
+ return { lineLayouts: [], lineHeight: lineHeight };
126
+ // --- 3. Calculate how many uniform columns can fit ---
127
+ const uniformColumnWidth = maxMonomerWidth;
128
+ let colsPerLine = Math.floor((availableWidth + monomerSpacing) / (uniformColumnWidth + monomerSpacing));
129
+ colsPerLine = Math.max(1, colsPerLine);
130
+ // --- 4. Generate the final grid layout ---
131
+ const availableHeightForLines = h - (this.padding * 2);
132
+ const linesToRenderCount = Math.max(0, Math.floor(availableHeightForLines / lineHeight));
133
+ const lineLayouts = [];
134
+ let monomerIdx = 0;
135
+ for (let lineIdx = 0; lineIdx < linesToRenderCount && monomerIdx < monomers.length; lineIdx++) {
136
+ const elementsForLine = [];
137
+ for (let colIdx = 0; colIdx < colsPerLine && monomerIdx < monomers.length; colIdx++) {
138
+ const monomer = monomers[monomerIdx];
139
+ const xPos = this.padding + colIdx * (uniformColumnWidth + monomerSpacing);
140
+ elementsForLine.push({
141
+ posIdx: monomer.posIdx,
142
+ x: xPos,
143
+ width: uniformColumnWidth, // Use the uniform width for all slots
144
+ om: monomer.text,
145
+ isSeparator: false,
146
+ });
147
+ monomerIdx++;
148
+ }
149
+ lineLayouts.push({ lineIdx: lineIdx, elements: elementsForLine });
150
+ }
151
+ return { lineLayouts, lineHeight };
152
+ }
79
153
  async init() {
80
154
  await Promise.all([
81
155
  (async () => {
@@ -115,6 +189,7 @@ export class MonomerPlacer extends CellRendererBackBase {
115
189
  this._monomerLengthList = null;
116
190
  this._monomerLengthMap = {};
117
191
  this._monomerStructureMap = {};
192
+ this._cellBounds.clear();
118
193
  super.reset();
119
194
  this.invalidateGrid();
120
195
  }
@@ -157,6 +232,27 @@ export class MonomerPlacer extends CellRendererBackBase {
157
232
  }
158
233
  return resSum;
159
234
  }
235
+ getCellMonomerLengthsForSeqValue(value, width) {
236
+ const sh = this.seqHelper.getSeqHandler(this.tableCol);
237
+ const minMonWidth = this.props.separatorWidth + 1 * this.props.fontCharWidth;
238
+ const visibleSeqStart = this.positionShift;
239
+ const maxVisibleSeqLength = Math.ceil(width / minMonWidth) + visibleSeqStart;
240
+ const seqSS = sh.splitter(value);
241
+ const visibleSeqEnd = Math.min(maxVisibleSeqLength, seqSS.length);
242
+ const res = new Array(visibleSeqEnd - visibleSeqStart);
243
+ let seqWidth = 0;
244
+ for (let seqMonI = visibleSeqStart; seqMonI < visibleSeqEnd; ++seqMonI) {
245
+ const seqMonLabel = seqSS.getOriginal(seqMonI);
246
+ const shortMon = this.props.monomerToShort(seqMonLabel, this.monomerLengthLimit);
247
+ const separatorWidth = sh.isSeparator() ? this.separatorWidth : this.props.separatorWidth;
248
+ const seqMonWidth = separatorWidth + shortMon.length * this.props.fontCharWidth;
249
+ res[seqMonI - visibleSeqStart] = seqMonWidth;
250
+ seqWidth += seqMonWidth;
251
+ if (seqWidth > width)
252
+ break;
253
+ }
254
+ return res;
255
+ }
160
256
  getCellMonomerLengthsForSeq(rowIdx) {
161
257
  const logPrefix = `${this.toLog()}.getCellMonomerLengthsForSeq()`;
162
258
  // this.logger.debug(`${logPrefix}, start`);
@@ -185,27 +281,6 @@ export class MonomerPlacer extends CellRendererBackBase {
185
281
  }
186
282
  return res;
187
283
  }
188
- getCellMonomerLengthsForSeqValue(value, width) {
189
- const sh = this.seqHelper.getSeqHandler(this.tableCol);
190
- const minMonWidth = this.props.separatorWidth + 1 * this.props.fontCharWidth;
191
- const visibleSeqStart = this.positionShift;
192
- const maxVisibleSeqLength = Math.ceil(width / minMonWidth) + visibleSeqStart;
193
- const seqSS = sh.splitter(value);
194
- const visibleSeqEnd = Math.min(maxVisibleSeqLength, seqSS.length);
195
- const res = new Array(visibleSeqEnd - visibleSeqStart);
196
- let seqWidth = 0;
197
- for (let seqMonI = visibleSeqStart; seqMonI < visibleSeqEnd; ++seqMonI) {
198
- const seqMonLabel = seqSS.getOriginal(seqMonI);
199
- const shortMon = this.props.monomerToShort(seqMonLabel, this.monomerLengthLimit);
200
- const separatorWidth = sh.isSeparator() ? this.separatorWidth : this.props.separatorWidth;
201
- const seqMonWidth = separatorWidth + shortMon.length * this.props.fontCharWidth;
202
- res[seqMonI - visibleSeqStart] = seqMonWidth;
203
- seqWidth += seqMonWidth;
204
- if (seqWidth > width)
205
- break;
206
- }
207
- return res;
208
- }
209
284
  getCellMonomerLengthsForSeqMsa() {
210
285
  var _a;
211
286
  const logPrefix = `${this.toLog()}.getCellMonomerLengthsForSeqMsa()`;
@@ -289,116 +364,143 @@ export class MonomerPlacer extends CellRendererBackBase {
289
364
  const isRenderedOnGrid = gridCell.grid?.canvas === g.canvas;
290
365
  if (!this.seqHelper)
291
366
  return;
292
- const gridCol = this.gridCol;
293
367
  const tableCol = this.tableCol;
294
- const dpr = window.devicePixelRatio;
295
368
  const positionShift = this.positionShift;
296
- const logPrefix = `${this.toLog()}.render()`;
297
- this.logger.debug(`${logPrefix}, start`);
298
- // Cell renderer settings
299
- let maxLengthOfMonomer = this.monomerLengthLimit;
300
- if (mmcrTAGS.maxMonomerLength in tableCol.tags) {
301
- const v = parseInt(tableCol.getTag(mmcrTAGS.maxMonomerLength));
302
- maxLengthOfMonomer = !isNaN(v) && v ? v : 50;
303
- }
304
- if (".mm.cellRenderer.maxMonomerLength" /* MmcrTemps.maxMonomerLength */ in tableCol.temp) {
305
- const v = tableCol.temp[".mm.cellRenderer.maxMonomerLength" /* MmcrTemps.maxMonomerLength */];
306
- maxLengthOfMonomer = !isNaN(v) && v ? v : 50;
307
- }
308
369
  g.save();
309
370
  try {
310
371
  const sh = this.seqHelper.getSeqHandler(tableCol);
372
+ let maxLengthOfMonomer = this.monomerLengthLimit;
373
+ if (mmcrTAGS.maxMonomerLength in tableCol.tags) {
374
+ const v = parseInt(tableCol.getTag(mmcrTAGS.maxMonomerLength));
375
+ maxLengthOfMonomer = !isNaN(v) && v ? v : 50;
376
+ }
311
377
  if (tableCol.temp[".mm.cellRenderer.settingsChanged" /* MmcrTemps.rendererSettingsChanged */] === rendererSettingsChangedState.true ||
312
378
  this.monomerLengthLimit != maxLengthOfMonomer) {
379
+ // this if means that the mm renderer settings have changed,
380
+ // particularly monomer representation and max width.
313
381
  let gapLength = 0;
314
382
  const msaGapLength = 8;
315
383
  gapLength = tableCol.temp[".mm.cellRenderer.gapLength" /* MmcrTemps.gapLength */] ?? gapLength;
316
- // this event means that the mm renderer settings have changed,
317
- // particularly monomer representation and max width.
318
384
  this.setMonomerLengthLimit(maxLengthOfMonomer);
319
385
  this.setSeparatorWidth(sh.isMsa() ? msaGapLength : gapLength);
320
386
  tableCol.temp[".mm.cellRenderer.settingsChanged" /* MmcrTemps.rendererSettingsChanged */] = rendererSettingsChangedState.false;
321
387
  }
322
- let [maxLengthWords, maxLengthWordsSum] = this.getCellMonomerLengths(gridCell.tableRowIndex, w);
323
- const _maxIndex = maxLengthWords.length;
324
- const value = gridCell.cell.value;
325
388
  const rowIdx = gridCell.cell.rowIndex;
326
- const paletteType = tableCol.getTag(bioTAGS.alphabet);
327
- const minDistanceRenderer = 50;
389
+ const value = gridCell.cell.value;
328
390
  if (isRenderedOnGrid)
329
- w = getUpdatedWidth(gridCol?.grid, g, x, w, dpr);
391
+ w = getUpdatedWidth(gridCell.grid, g, x, w, window.devicePixelRatio);
330
392
  g.beginPath();
331
- g.rect(x + this.padding, y, w - this.padding - 1, h);
393
+ g.rect(x, y, w, h);
332
394
  g.clip();
333
395
  g.font = this.props?.font ?? '12px monospace';
334
396
  g.textBaseline = 'top';
335
- //TODO: can this be replaced/merged with splitSequence?
336
397
  const units = tableCol.meta.units;
337
398
  const aligned = tableCol.getTag(bioTAGS.aligned);
338
399
  const separator = tableCol.getTag(bioTAGS.separator) ?? '';
339
- const minMonWidth = this.props.separatorWidth + 1 * this.props.fontCharWidth;
340
- const splitLimit = Math.ceil(w / minMonWidth) + positionShift;
400
+ const subParts = isRenderedOnGrid ? sh.getSplitted(rowIdx) : sh.splitter(value);
401
+ let drawStyle = DrawStyle.classic;
402
+ if (aligned?.includes('MSA') && units === NOTATION.SEPARATOR)
403
+ drawStyle = DrawStyle.MSA;
341
404
  const tempReferenceSequence = tableCol.temp["reference-sequence" /* tempTAGS.referenceSequence */];
342
405
  const tempCurrentWord = this.tableCol.temp["current-word" /* tempTAGS.currentWord */];
343
- if (tempCurrentWord && tableCol?.dataFrame?.currentRowIdx === -1)
344
- this.tableCol.temp["current-word" /* tempTAGS.currentWord */] = null;
345
406
  const referenceSequence = (() => {
346
- // @ts-ignore
347
- const splitterFunc = sh.getSplitter(splitLimit);
348
- const seqSS = splitterFunc(((tempReferenceSequence != null) && (tempReferenceSequence != '')) ?
407
+ const splitterFunc = sh.splitter;
408
+ const seqSS = splitterFunc(((tempReferenceSequence != null) && (tempReferenceSequence !== '')) ?
349
409
  tempReferenceSequence : tempCurrentWord ?? '');
350
- return wu.count(0).take(seqSS.length).slice(positionShift).map((posIdx) => seqSS.getOriginal(posIdx)).toArray();
410
+ return wu.count(0).take(seqSS.length).slice(positionShift).map((posIdx) => seqSS.getCanonical(posIdx)).toArray();
351
411
  })();
352
- const subParts = isRenderedOnGrid ? sh.getSplitted(rowIdx) : sh.splitter(value);
353
- if (!isRenderedOnGrid)
354
- maxLengthWordsSum = this.getSummedMonomerLengths(this.getCellMonomerLengthsForSeqValue(value, w));
355
- let drawStyle = DrawStyle.classic;
356
- if (aligned && aligned.includes('MSA') && units == NOTATION.SEPARATOR)
357
- drawStyle = DrawStyle.MSA;
358
- // if the sequence is rendered in shifted mode, we will also render three dots at start, indicating the shift
359
- this._leftThreeDotsPadding = this.shouldRenderShiftedThreeDots(positionShift) ? g.measureText(shiftedLeftPaddingText).width : 0;
360
- // currently selected position to highlight
361
412
  const selectedPosition = Number.parseInt(tableCol.getTag(bioTAGS.selectedPosition) ?? '-200');
362
- const visibleSeqLength = Math.min(subParts.length, splitLimit);
363
- for (let posIdx = positionShift; posIdx < visibleSeqLength; ++posIdx) {
364
- const om = posIdx < subParts.length ? subParts.getOriginal(posIdx) : sh.defaultGapOriginal;
365
- const cm = posIdx < subParts.length ? subParts.getCanonical(posIdx) : sh.defaultGapOriginal;
366
- let color = undefinedColor;
367
- const monomerLib = this.getMonomerLib();
368
- if (monomerLib) {
369
- const biotype = sh.defaultBiotype;
370
- //this.logger.debug(`${logPrefix}, biotype: ${biotype}, amino: ${amino}`);
371
- color = monomerLib.getMonomerTextColor(biotype, cm);
413
+ const shouldUseMultiLine = this.shouldUseMultilineRendering(tableCol) && drawStyle !== DrawStyle.MSA;
414
+ if (shouldUseMultiLine) {
415
+ const currentCellBounds = [];
416
+ const layout = this.calculateMultiLineLayoutDynamic(g, w, h, subParts, positionShift, maxLengthOfMonomer);
417
+ // --- NEW: Vertical Centering Logic for Single Lines ---
418
+ // Default to top-aligned layout
419
+ let yBase = y + this.padding;
420
+ // If there's exactly one line of content, calculate a new base Y to center it vertically.
421
+ if (layout.lineLayouts.length === 1)
422
+ yBase = y + (h - layout.lineHeight) / 2;
423
+ // --- End of New Logic ---
424
+ for (const lineLayout of layout.lineLayouts) {
425
+ // The Y position for each line is now based on our calculated `yBase`.
426
+ const lineY = yBase + (lineLayout.lineIdx * layout.lineHeight);
427
+ for (const element of lineLayout.elements) {
428
+ const elementX = x + element.x;
429
+ const monomer = element;
430
+ const monomerIndex = monomer.posIdx;
431
+ const cm = subParts.getCanonical(monomerIndex);
432
+ let color = undefinedColor;
433
+ const monomerLib = this.getMonomerLib();
434
+ if (monomerLib)
435
+ color = monomerLib.getMonomerTextColor(sh.defaultBiotype, cm);
436
+ let transparencyRate = 0.0;
437
+ if (gridCell.tableRowIndex !== tableCol.dataFrame.currentRowIdx && referenceSequence.length > 0) {
438
+ const refIndex = monomerIndex - positionShift;
439
+ if (refIndex >= 0 && refIndex < referenceSequence.length) {
440
+ const currentMonomerCanonical = cm;
441
+ const refMonomerCanonical = referenceSequence[refIndex];
442
+ if (currentMonomerCanonical === refMonomerCanonical)
443
+ transparencyRate = 0.7;
444
+ }
445
+ }
446
+ currentCellBounds.push({
447
+ lineIdx: lineLayout.lineIdx,
448
+ monomerIdx: monomerIndex - positionShift,
449
+ bounds: new DG.Rect(element.x, (lineY - y), element.width, layout.lineHeight),
450
+ sequencePosition: monomerIndex,
451
+ });
452
+ printLeftOrCentered(g, monomer.om, elementX, lineY, element.width, layout.lineHeight, {
453
+ color: color,
454
+ isMultiLineContext: true,
455
+ transparencyRate: transparencyRate,
456
+ selectedPosition: isNaN(selectedPosition) || selectedPosition < 1 ? undefined : selectedPosition,
457
+ wordIdx: monomerIndex
458
+ });
459
+ }
372
460
  }
373
- g.fillStyle = undefinedColor;
374
- const last = posIdx === subParts.length - 1;
375
- /*x1 = */
376
- const opts = {
377
- color: color, pivot: 0, left: true, transparencyRate: 1.0, separator: separator, last: last,
378
- drawStyle: drawStyle, maxWord: maxLengthWordsSum, wordIdx: posIdx - positionShift, gridCell: gridCell,
379
- referenceSequence: referenceSequence, maxLengthOfMonomer: maxLengthOfMonomer,
380
- monomerTextSizeMap: this._monomerLengthMap, logger: this.logger,
381
- selectedPosition: isNaN(selectedPosition) || selectedPosition < 1 ? undefined : selectedPosition - positionShift,
382
- };
383
- printLeftOrCentered(g, om, x + this.padding + this._leftThreeDotsPadding, y, w, h, opts);
384
- if (minDistanceRenderer > w)
385
- break;
461
+ if (gridCell.tableRowIndex !== null)
462
+ this._cellBounds.set(gridCell.tableRowIndex, currentCellBounds);
386
463
  }
387
- if (this.shouldRenderShiftedThreeDots(positionShift)) {
388
- const opts = {
389
- color: undefinedColor, pivot: 0, left: true, transparencyRate: 1.0, separator: separator, last: false,
390
- drawStyle: drawStyle, maxWord: maxLengthWordsSum, wordIdx: 0, gridCell: gridCell,
391
- maxLengthOfMonomer: maxLengthOfMonomer,
392
- monomerTextSizeMap: this._monomerLengthMap, logger: this.logger,
393
- };
394
- printLeftOrCentered(g, shiftedLeftPaddingText, x + this.padding, y, w, h, opts);
464
+ else {
465
+ // --- Single-line rendering path (is unchanged) ---
466
+ this._leftThreeDotsPadding = this.shouldRenderShiftedThreeDots(positionShift) ? g.measureText(shiftedLeftPaddingText).width : 0;
467
+ let [, maxLengthWordsSum] = this.getCellMonomerLengths(gridCell.tableRowIndex, w);
468
+ if (!isRenderedOnGrid)
469
+ maxLengthWordsSum = this.getSummedMonomerLengths(this.getCellMonomerLengthsForSeqValue(value, w));
470
+ const minMonomerWidth = this.props.separatorWidth + 1 * this.props.fontCharWidth;
471
+ const visibleSeqLength = Math.min(subParts.length, Math.ceil(w / (minMonomerWidth)) + positionShift);
472
+ for (let posIdx = positionShift; posIdx < visibleSeqLength; ++posIdx) {
473
+ const om = posIdx < subParts.length ? subParts.getOriginal(posIdx) : sh.defaultGapOriginal;
474
+ const cm = posIdx < subParts.length ? subParts.getCanonical(posIdx) : sh.defaultGapOriginal;
475
+ let color = undefinedColor;
476
+ if (this.getMonomerLib())
477
+ color = this.getMonomerLib().getMonomerTextColor(sh.defaultBiotype, cm);
478
+ const last = posIdx === subParts.length - 1;
479
+ const opts = {
480
+ color: color, pivot: 0, left: true, transparencyRate: 0.0,
481
+ separator: separator, last: last,
482
+ drawStyle: drawStyle, maxWord: maxLengthWordsSum, wordIdx: posIdx - positionShift, gridCell: gridCell,
483
+ referenceSequence: referenceSequence, maxLengthOfMonomer: maxLengthOfMonomer,
484
+ monomerTextSizeMap: this._monomerLengthMap, logger: this.logger,
485
+ selectedPosition: isNaN(selectedPosition) || selectedPosition < 1 ? undefined : selectedPosition - positionShift,
486
+ };
487
+ printLeftOrCentered(g, om, x + this.padding + this._leftThreeDotsPadding, y, w, h, opts);
488
+ }
489
+ if (this.shouldRenderShiftedThreeDots(positionShift)) {
490
+ const opts = {
491
+ color: undefinedColor, pivot: 0, left: true, transparencyRate: 0, separator: separator, last: false,
492
+ drawStyle: drawStyle, maxWord: maxLengthWordsSum, wordIdx: 0, gridCell: gridCell,
493
+ maxLengthOfMonomer: maxLengthOfMonomer,
494
+ monomerTextSizeMap: this._monomerLengthMap, logger: this.logger,
495
+ };
496
+ printLeftOrCentered(g, shiftedLeftPaddingText, x + this.padding, y, w, h, opts);
497
+ }
395
498
  }
396
499
  }
397
500
  catch (err) {
398
501
  const [errMsg, errStack] = errInfo(err);
399
502
  this.logger.error(errMsg, undefined, errStack);
400
503
  this.errors.push(err);
401
- //throw err; // Do not throw to prevent disabling renderer
402
504
  }
403
505
  finally {
404
506
  g.restore();
@@ -411,21 +513,29 @@ export class MonomerPlacer extends CellRendererBackBase {
411
513
  const logPrefix = `${this.toLog()}.onMouseMove()`;
412
514
  if (!this.seqHelper || gridCell.tableRowIndex == null)
413
515
  return;
414
- // if (gridCell.cell.column.getTag(bioTAGS.aligned) !== ALIGNMENT.SEQ_MSA)
415
- // return;
416
516
  const positionShift = this.positionShift;
417
517
  const gridCellBounds = gridCell.bounds;
418
- // const value: any = gridCell.cell.value;
419
- //
420
- // const maxLengthWords: number[] = seqColTemp.getCellMonomerLengths(gridCell.tableRowIndex!);
421
- // const maxLengthWordsSum: number[] = new Array<number>(maxLengthWords.length).fill(0);
422
- // for (let posI: number = 1; posI < maxLengthWords.length; posI++)
423
- // maxLengthWordsSum[posI] = maxLengthWordsSum[posI - 1] + maxLengthWords[posI];
424
- // const maxIndex = maxLengthWords.length;
425
518
  const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCellBounds.x);
426
- const leftPadding = this.shouldRenderShiftedThreeDots(positionShift) && (this._leftThreeDotsPadding ?? 0) > 0 ? this._leftThreeDotsPadding : 0;
427
- const left = this.getPosition(gridCell.tableRowIndex, argsX, gridCellBounds.width, leftPadding);
428
- this.logger.debug(`${logPrefix}, start, argsX: ${argsX}, left: ${left}`);
519
+ const argsY = e.offsetY - gridCellBounds.y;
520
+ // Reset cursor to default, as ellipsis is gone
521
+ if (this.gridCol?.grid?.canvas)
522
+ this.gridCol.grid.canvas.style.cursor = 'default';
523
+ let left = null;
524
+ const boundsForCell = this._cellBounds.get(gridCell.tableRowIndex);
525
+ if (boundsForCell) {
526
+ for (const bound of boundsForCell) {
527
+ if (bound.bounds.contains(argsX, argsY)) {
528
+ left = bound.monomerIdx;
529
+ break;
530
+ }
531
+ }
532
+ }
533
+ else {
534
+ // Single-line hit detection
535
+ const leftPadding = this.shouldRenderShiftedThreeDots(positionShift) && (this._leftThreeDotsPadding ?? 0) > 0 ? this._leftThreeDotsPadding : 0;
536
+ left = this.getPosition(gridCell.tableRowIndex, argsX, gridCellBounds.width, leftPadding);
537
+ }
538
+ this.logger.debug(`${logPrefix}, argsX: ${argsX}, argsY: ${argsY}, left: ${left}`);
429
539
  const sh = this.seqHelper.getSeqHandler(this.tableCol);
430
540
  const seqSS = sh.getSplitted(gridCell.tableRowIndex);
431
541
  if (left !== null && left >= 0 && left + positionShift < seqSS.length) {
@@ -437,7 +547,7 @@ export class MonomerPlacer extends CellRendererBackBase {
437
547
  };
438
548
  const tooltipElements = [];
439
549
  let monomerDiv = this._monomerStructureMap[seqMonomer.symbol];
440
- if (!monomerDiv || true) {
550
+ if (!monomerDiv) {
441
551
  const monomerLib = this.getMonomerLib();
442
552
  monomerDiv = this._monomerStructureMap[seqMonomer.symbol] = (() => {
443
553
  return monomerLib ? monomerLib.getTooltip(seqMonomer.biotype, seqMonomer.symbol) :