@datagrok/bio 2.11.42 → 2.12.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/detectors.js +11 -11
  4. package/dist/36.js +1 -1
  5. package/dist/36.js.map +1 -1
  6. package/dist/413.js +1 -1
  7. package/dist/413.js.map +1 -1
  8. package/dist/590.js +1 -1
  9. package/dist/590.js.map +1 -1
  10. package/dist/709.js +1 -1
  11. package/dist/709.js.map +1 -1
  12. package/dist/895.js +1 -1
  13. package/dist/895.js.map +1 -1
  14. package/dist/package-test.js +2 -2
  15. package/dist/package-test.js.map +1 -1
  16. package/dist/package.js +2 -2
  17. package/dist/package.js.map +1 -1
  18. package/package.json +10 -10
  19. package/src/analysis/sequence-activity-cliffs.ts +9 -9
  20. package/src/analysis/sequence-diversity-viewer.ts +3 -3
  21. package/src/analysis/sequence-search-base-viewer.ts +2 -2
  22. package/src/analysis/sequence-similarity-viewer.ts +10 -10
  23. package/src/analysis/sequence-space.ts +26 -23
  24. package/src/calculations/monomerLevelMols.ts +13 -11
  25. package/src/package.ts +8 -8
  26. package/src/tests/WebLogo-layout-tests.ts +5 -2
  27. package/src/tests/WebLogo-positions-test.ts +5 -5
  28. package/src/tests/bio-tests.ts +13 -6
  29. package/src/tests/converters-test.ts +4 -4
  30. package/src/tests/detectors-benchmark-tests.ts +5 -5
  31. package/src/tests/detectors-tests.ts +13 -13
  32. package/src/tests/fasta-export-tests.ts +10 -4
  33. package/src/tests/mm-distance-tests.ts +10 -10
  34. package/src/tests/msa-tests.ts +8 -15
  35. package/src/tests/renderers-monomer-placer.ts +3 -3
  36. package/src/tests/renderers-test.ts +6 -8
  37. package/src/tests/splitters-test.ts +14 -13
  38. package/src/tests/to-atomic-level-tests.ts +2 -2
  39. package/src/tests/units-handler-get-region.ts +4 -4
  40. package/src/tests/units-handler-splitted-tests.ts +19 -17
  41. package/src/tests/units-handler-tests.ts +32 -32
  42. package/src/utils/cell-renderer.ts +40 -34
  43. package/src/utils/check-input-column.ts +5 -5
  44. package/src/utils/context-menu.ts +9 -6
  45. package/src/utils/convert.ts +9 -9
  46. package/src/utils/get-region-func-editor.ts +11 -11
  47. package/src/utils/get-region.ts +10 -12
  48. package/src/utils/macromolecule-column-widget.ts +4 -3
  49. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +1 -1
  50. package/src/utils/multiple-sequence-alignment-ui.ts +6 -6
  51. package/src/utils/pepsea.ts +1 -0
  52. package/src/utils/poly-tool/transformation.ts +3 -3
  53. package/src/utils/save-as-fasta.ts +14 -15
  54. package/src/utils/sequence-to-mol.ts +4 -4
  55. package/src/viewers/web-logo-viewer.ts +46 -54
  56. package/src/widgets/bio-substructure-filter.ts +3 -3
  57. package/src/widgets/composition-analysis-widget.ts +8 -8
@@ -6,7 +6,7 @@ import $ from 'cash-dom';
6
6
  import wu from 'wu';
7
7
  import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
8
8
 
9
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
9
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
10
10
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
11
11
  import {
12
12
  monomerToShort, pickUpPalette, pickUpSeqCol, TAGS as bioTAGS, positionSeparator
@@ -17,8 +17,7 @@ import {
17
17
  } from '@datagrok-libraries/bio/src/viewers/web-logo';
18
18
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
19
19
  import {intToHtmlA} from '@datagrok-libraries/utils/src/color';
20
- import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
21
- import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
20
+ import {GAP_SYMBOL, ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
22
21
  import {testEvent} from '@datagrok-libraries/utils/src/test';
23
22
  import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
24
23
 
@@ -26,7 +25,6 @@ import {AggFunc, getAgg} from '../utils/agg';
26
25
  import {buildCompositionTable} from '../widgets/composition-analysis-widget';
27
26
 
28
27
  import {_package} from '../package';
29
- import {GAP_SYMBOL} from '../const';
30
28
 
31
29
  declare global {
32
30
  interface HTMLCanvasElement {
@@ -168,8 +166,7 @@ export class PositionInfo {
168
166
  }
169
167
  }
170
168
 
171
- calcScreen(
172
- isGap: (m: string) => boolean, posIdx: number, firstVisiblePosIdx: number,
169
+ calcScreen(posIdx: number, firstVisiblePosIdx: number,
173
170
  absoluteMaxHeight: number, heightMode: PositionHeight, alphabetSizeLog: number,
174
171
  positionWidthWithMargin: number, positionWidth: number, dpr: number, positionLabelsHeight: number
175
172
  ): void {
@@ -180,13 +177,13 @@ export class PositionInfo {
180
177
 
181
178
  const entries = Object.entries(this._freqs)
182
179
  .sort((a, b) => {
183
- if (!isGap(a[0]) && !isGap(b[0]))
180
+ if (a[0] !== GAP_SYMBOL && b[0] !== GAP_SYMBOL)
184
181
  return b[1].value - a[1].value;
185
- else if (isGap(a[0]) && isGap(b[0]))
182
+ else if (a[0] === GAP_SYMBOL && b[0] === GAP_SYMBOL)
186
183
  return 0;
187
- else if (isGap(a[0]))
184
+ else if (a[0] === GAP_SYMBOL)
188
185
  return -1;
189
- else /* (isGap(b[0])) */
186
+ else /* (b[0] === GAP_SYMBOL) */
190
187
  return +1;
191
188
  });
192
189
  for (const [_m, pmi] of entries) {
@@ -200,11 +197,10 @@ export class PositionInfo {
200
197
  }
201
198
 
202
199
  render(g: CanvasRenderingContext2D,
203
- isGap: (m: string) => boolean,
204
200
  fontStyle: string, uppercaseLetterAscent: number, uppercaseLetterHeight: number, cp: SeqPalette
205
201
  ) {
206
202
  for (const [monomer, pmInfo] of Object.entries(this._freqs)) {
207
- if (!isGap(monomer)) {
203
+ if (monomer !== GAP_SYMBOL) {
208
204
  const monomerTxt = monomerToShort(monomer, 5);
209
205
  const b = pmInfo.bounds!;
210
206
  const left = b.left;
@@ -303,7 +299,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
303
299
 
304
300
  private viewed: boolean = false;
305
301
 
306
- private unitsHandler: UnitsHandler | null;
302
+ private seqHandler: SeqHandler | null;
307
303
  private initialized: boolean = false;
308
304
 
309
305
  // private readonly colorScheme: ColorScheme = ColorSchemes[NucleotidesWebLogo.residuesSet];
@@ -373,7 +369,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
373
369
  }
374
370
 
375
371
  public get positionMarginValue(): number {
376
- if (this.positionMarginState === PositionMarginStates.AUTO && this.unitsHandler!.getAlphabetIsMultichar() === true)
372
+ if (this.positionMarginState === PositionMarginStates.AUTO && this.seqHandler!.getAlphabetIsMultichar() === true)
377
373
  return this.positionMargin;
378
374
  else if (this.positionMarginState === PositionMarginStates.ON)
379
375
  return this.positionMargin;
@@ -385,7 +381,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
385
381
  super();
386
382
 
387
383
  this.textBaseline = 'top';
388
- this.unitsHandler = null;
384
+ this.seqHandler = null;
389
385
 
390
386
  // -- Data --
391
387
  this.sequenceColumnName = this.string(PROPS.sequenceColumnName, defaults.sequenceColumnName,
@@ -589,7 +585,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
589
585
  }
590
586
  if (this.seqCol) {
591
587
  try {
592
- this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
588
+ this.seqHandler = SeqHandler.forColumn(this.seqCol);
593
589
 
594
590
  this.palette = pickUpPalette(this.seqCol);
595
591
  this.render(WlRenderLevel.Freqs, 'updateSeqCol()');
@@ -600,7 +596,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
600
596
  throw err;
601
597
  }
602
598
  if (!this.seqCol) {
603
- this.unitsHandler = null;
599
+ this.seqHandler = null;
604
600
  this.positionNames = [];
605
601
  this.positionLabels = [];
606
602
  this.startPosition = -1;
@@ -817,6 +813,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
817
813
  case PROPS.sequenceColumnName:
818
814
  this.updateSeqCol();
819
815
  break;
816
+ case PROPS.sequenceColumnName:
820
817
  case PROPS.startPositionName:
821
818
  case PROPS.endPositionName:
822
819
  case PROPS.filterSource:
@@ -956,9 +953,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
956
953
  // region updatePositions
957
954
 
958
955
  const dfFilter = this.getFilter();
959
- const maxLength: number = dfFilter.trueCount === 0 ? this.unitsHandler!.maxLength :
960
- wu.enumerate(this.unitsHandler!.splitted).map(([mList, rowI]) => {
961
- return dfFilter.get(rowI) && !!mList ? mList.length : 0;
956
+ const maxLength: number = dfFilter.trueCount === 0 ? this.seqHandler!.maxLength :
957
+ wu.count(0).take(this.seqHandler!.length).map((rowIdx) => {
958
+ const mList = this.seqHandler!.getSplitted(rowIdx);
959
+ return dfFilter.get(rowIdx) && !!mList ? mList.length : 0;
962
960
  }).reduce((max, l) => Math.max(max, l), 0);
963
961
 
964
962
  /** positionNames and positionLabel can be set up through the column's tags only */
@@ -979,7 +977,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
979
977
  // endregion updatePositions
980
978
 
981
979
  const length: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
982
- this.unitsHandler = UnitsHandler.getOrCreate(this.seqCol);
980
+ this.seqHandler = SeqHandler.forColumn(this.seqCol);
983
981
  const posCount: number = this.startPosition <= this.endPosition ? this.endPosition - this.startPosition + 1 : 0;
984
982
  this.positions = new Array(posCount);
985
983
  for (let jPos = 0; jPos < length; jPos++) {
@@ -991,18 +989,16 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
991
989
 
992
990
  // 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
993
991
  const dfRowCount = this.dataFrame.rowCount;
994
- const splitted = this.unitsHandler.splitted;
995
992
 
996
993
  for (let jPos = 0; jPos < length; ++jPos) {
994
+ const pi = this.positions[jPos];
997
995
  // Here we want to build lists of values for every monomer in position jPos
998
996
  for (let rowI = 0; rowI < dfRowCount; ++rowI) {
999
997
  if (dfFilter.get(rowI)) {
1000
- const seqMList: ISeqSplitted = splitted[rowI];
1001
- const om: string = seqMList[this.startPosition + jPos] || this.unitsHandler.defaultGapSymbol;
1002
- const cm: string = this.unitsHandler?.defaultGapSymbol === om ? GAP_SYMBOL : om;
1003
- const pi = this.positions[jPos];
1004
- const pmi = pi.getFreq(cm);
1005
998
  ++pi.sumRowCount;
999
+ const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1000
+ const cm: string = seqMList.getCanonical(this.startPosition + jPos);
1001
+ const pmi = pi.getFreq(cm);
1006
1002
  pmi.value = ++pmi.rowCount;
1007
1003
  }
1008
1004
  }
@@ -1019,10 +1015,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1019
1015
 
1020
1016
  for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1021
1017
  if (dfFilter.get(rowI)) { // respect the filter
1022
- const seqMList: ISeqSplitted = splitted[rowI];
1023
- const m: string = seqMList[this.startPosition + jPos] || this.unitsHandler.defaultGapSymbol;
1018
+ const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1019
+ const cm: string = seqMList.getCanonical(this.startPosition + jPos);
1024
1020
  const value: number | null = valueCol.get(rowI);
1025
- this.positions[jPos].getFreq(m).push(value);
1021
+ this.positions[jPos].getFreq(cm).push(value);
1026
1022
  }
1027
1023
  }
1028
1024
  this.positions[jPos].aggregate(this.valueAggrType);
@@ -1061,7 +1057,7 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1061
1057
  `this.positions.length = ${this.positions.length}, jPos = ${jPos}`);
1062
1058
  continue;
1063
1059
  }
1064
- this.positions[jPos].calcScreen((m) => { return this.unitsHandler!.isGap(m); },
1060
+ this.positions[jPos].calcScreen(
1065
1061
  jPos, this.slider.min, absoluteMaxHeight, this.positionHeight,
1066
1062
  alphabetSizeLog, this._positionWidthWithMargin, this._positionWidth, dpr, positionLabelsHeight);
1067
1063
  }
@@ -1117,10 +1113,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1117
1113
  // Hacks to scale uppercase characters to target rectangle
1118
1114
  const uppercaseLetterAscent = 0.25;
1119
1115
  const uppercaseLetterHeight = 12.2;
1120
- for (let jPos = firstPos; jPos <= lastPos; jPos++) {
1121
- this.positions[jPos].render(g, (m) => { return this.unitsHandler!.isGap(m); },
1122
- fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, this.palette);
1123
- }
1116
+ for (let jPos = firstPos; jPos <= lastPos; jPos++)
1117
+ this.positions[jPos].render(g, fontStyle, uppercaseLetterAscent, uppercaseLetterHeight, this.palette);
1124
1118
  } finally {
1125
1119
  g.restore();
1126
1120
  }
@@ -1135,18 +1129,16 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1135
1129
  return;
1136
1130
  }
1137
1131
  this.requestedRenderLevel = WlRenderLevel.None;
1138
- this.renderInt(renderLevel)
1139
- .catch((err: any) => {
1140
- const [errMsg, errStack] = errInfo(err);
1141
- _package.logger.error(errMsg, undefined, errStack);
1142
- });
1132
+ this.viewSyncer.sync(logPrefix, async () => {
1133
+ await this.renderInt(renderLevel);
1134
+ });
1143
1135
  }
1144
1136
 
1145
1137
  private _lastWidth: number;
1146
1138
  private _lastHeight: number;
1147
1139
 
1148
1140
  public getAlphabetSize(): number {
1149
- return this.unitsHandler?.getAlphabetSize() ?? 0;
1141
+ return this.seqHandler?.getAlphabetSize() ?? 0;
1150
1142
  }
1151
1143
 
1152
1144
  // -- Handle events --
@@ -1212,10 +1204,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1212
1204
  tooltipRows.push(pi.buildCompositionTable(this.palette!));
1213
1205
  const tooltipEl = ui.divV(tooltipRows);
1214
1206
  ui.tooltip.show(tooltipEl, args.x + 16, args.y + 16);
1215
- } else if (pi !== null && monomer && this.dataFrame && this.seqCol && this.unitsHandler) {
1207
+ } else if (pi !== null && monomer && this.dataFrame && this.seqCol && this.seqHandler) {
1216
1208
  // Monomer at position tooltip
1217
1209
  // const monomerAtPosSeqCount = countForMonomerAtPosition(
1218
- // this.dataFrame, this.unitsHandler!, this.getFilter(), monomer, atPI);
1210
+ // this.dataFrame, this.seqHandler!, this.getFilter(), monomer, atPI);
1219
1211
  const pmi = pi.getFreq(monomer);
1220
1212
 
1221
1213
  const tooltipRows = [
@@ -1243,10 +1235,10 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1243
1235
  const [pi, monomer] = this.getMonomer(this.canvas.getCursorPosition(args, dpr), dpr);
1244
1236
 
1245
1237
  // prevents deselect all rows if we miss monomer bounds
1246
- if (pi !== null && monomer !== null && this.dataFrame && this.seqCol && this.unitsHandler) {
1238
+ if (pi !== null && monomer !== null && this.dataFrame && this.seqCol && this.seqHandler) {
1247
1239
  // Calculate a new BitSet object for selection to prevent interfering with existing
1248
1240
  const selBS: DG.BitSet = DG.BitSet.create(this.dataFrame.selection.length, (rowI: number) => {
1249
- return checkSeqForMonomerAtPos(this.dataFrame, this.unitsHandler!, this.getFilter(), rowI, monomer, pi);
1241
+ return checkSeqForMonomerAtPos(this.dataFrame, this.seqHandler!, this.getFilter(), rowI, monomer, pi);
1250
1242
  });
1251
1243
  this.dataFrame.selection.init((i) => selBS.get(i));
1252
1244
  }
@@ -1281,8 +1273,8 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1281
1273
  const callLog = `invalidate(${caller ? ` <- ${caller} ` : ''})`;
1282
1274
  const logPrefix = `${this.viewerToLog()}.${callLog}`;
1283
1275
  // Put the event trigger in the tail of the synced calls queue.
1276
+ this.render(WlRenderLevel.None, callLog); // Put render request to the syncer
1284
1277
  this.viewSyncer.sync(`${logPrefix}`, async () => {
1285
- this.render(WlRenderLevel.None, callLog);
1286
1278
  this._onRendered.next();
1287
1279
  });
1288
1280
  }
@@ -1333,23 +1325,23 @@ function renderPositionLabels(g: CanvasRenderingContext2D,
1333
1325
  }
1334
1326
 
1335
1327
  export function checkSeqForMonomerAtPos(
1336
- df: DG.DataFrame, unitsHandler: UnitsHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
1328
+ df: DG.DataFrame, sh: SeqHandler, filter: DG.BitSet, rowI: number, monomer: string, at: PositionInfo,
1337
1329
  ): boolean {
1338
- const seqMList: ISeqSplitted = unitsHandler.splitted[rowI];
1339
- const seqM = at.pos < seqMList.length ? seqMList[at.pos] : null;
1340
- return ((seqM === monomer) || (seqM === '' && monomer === unitsHandler.defaultGapSymbol));
1330
+ const seqMList: ISeqSplitted = sh.getSplitted(rowI);
1331
+ const seqCM: string | null = at.pos < seqMList.length ? seqMList.getCanonical(at.pos) : null;
1332
+ return seqCM !== null && seqCM === monomer;
1341
1333
  }
1342
1334
 
1343
1335
  export function countForMonomerAtPosition(
1344
- df: DG.DataFrame, uh: UnitsHandler, filter: DG.BitSet, monomer: string, at: PositionInfo
1336
+ df: DG.DataFrame, sh: SeqHandler, filter: DG.BitSet, monomer: string, at: PositionInfo
1345
1337
  ): number {
1346
1338
  let count = 0;
1347
1339
  let rowI = -1;
1348
1340
  while ((rowI = filter.findNext(rowI, true)) != -1) {
1349
- const seqMList: ISeqSplitted = uh.splitted[rowI];
1341
+ const seqMList: ISeqSplitted = sh.getSplitted(rowI);
1350
1342
  const seqMPos: number = at.pos;
1351
- const seqM: string | null = seqMPos < seqMList.length ? seqMList[seqMPos] : null;
1352
- if (seqM === monomer) count++;
1343
+ const seqCM: string | null = seqMPos < seqMList.length ? seqMList.getCanonical(seqMPos) : null;
1344
+ if (seqCM !== null && seqCM === monomer) count++;
1353
1345
  }
1354
1346
  return count;
1355
1347
  }
@@ -10,14 +10,14 @@ import * as grok from 'datagrok-api/grok';
10
10
 
11
11
  import wu from 'wu';
12
12
  import $ from 'cash-dom';
13
- import {fromEvent, Observable, Subject, Subscription, Unsubscribable} from 'rxjs';
13
+ import {fromEvent, Observable, Subject, Unsubscribable} from 'rxjs';
14
14
 
15
15
  import {TAGS as bioTAGS, NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
16
16
  import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
17
17
  import {delay, testEvent} from '@datagrok-libraries/utils/src/test';
18
18
  import {getHelmHelper} from '@datagrok-libraries/bio/src/helm/helm-helper';
19
19
  import {IHelmWebEditor, IWebEditorApp} from '@datagrok-libraries/bio/src/helm/types';
20
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
20
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
21
21
  import {IRenderer} from '@datagrok-libraries/bio/src/types/renderer';
22
22
  import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
23
23
  import {PromiseSyncer} from '@datagrok-libraries/bio/src/utils/syncer';
@@ -120,7 +120,7 @@ export class BioSubstructureFilter extends DG.Filter implements IRenderer {
120
120
  this.filterSyncer.sync(logPrefix, async () => {
121
121
  superAttach(dataFrame);
122
122
  this.column = dataFrame.columns.bySemType(DG.SEMTYPE.MACROMOLECULE);
123
- const uh = UnitsHandler.getOrCreate(this.column!);
123
+ const sh = SeqHandler.forColumn(this.column!);
124
124
  this.columnName ??= this.column?.name;
125
125
  this.notation ??= this.column?.getTag(DG.TAGS.UNITS);
126
126
 
@@ -8,8 +8,8 @@ import {TAGS as bioTAGS, ALPHABET, getPaletteByType} from '@datagrok-libraries/b
8
8
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
9
9
  import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
10
10
  import '../../css/composition-analysis.css';
11
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
12
- import {GAP_SYMBOL} from '../const';
11
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
12
+ import {GAP_SYMBOL} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
13
13
 
14
14
 
15
15
  export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
@@ -30,12 +30,12 @@ export function getCompositionAnalysisWidget(val: DG.SemanticValue): DG.Widget {
30
30
  }
31
31
 
32
32
  const counts: { [m: string]: number } = {};
33
- const uh = UnitsHandler.getOrCreate(val.cell.column);
34
- const splitter = uh.getSplitter();
35
- const parts = splitter(val.value);
36
- wu(parts).filter((p) => !!p && p !== '').forEach((m: string) => {
37
- const count = counts[m] || 0;
38
- counts[m] = count + 1;
33
+ const sh = SeqHandler.forColumn(val.cell.column as DG.Column<string>);
34
+ const rowIdx = val.cell.rowIndex;
35
+ const parts = sh.getSplitted(rowIdx);
36
+ wu(parts.canonicals).filter((cm) => cm !== GAP_SYMBOL).forEach((cm) => {
37
+ const count = counts[cm] || 0;
38
+ counts[cm] = count + 1;
39
39
  });
40
40
  const table = buildCompositionTable(palette, counts);
41
41
  Array.from(table.rows).forEach((row) => {