@datagrok/bio 2.11.42 → 2.12.1

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 (61) hide show
  1. package/CHANGELOG.md +14 -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 +3 -3
  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/files/tests/libraries/HELMmonomerSchema.json +1 -1
  19. package/package.json +11 -11
  20. package/src/analysis/sequence-activity-cliffs.ts +9 -9
  21. package/src/analysis/sequence-diversity-viewer.ts +3 -3
  22. package/src/analysis/sequence-search-base-viewer.ts +2 -2
  23. package/src/analysis/sequence-similarity-viewer.ts +10 -10
  24. package/src/analysis/sequence-space.ts +26 -23
  25. package/src/calculations/monomerLevelMols.ts +13 -11
  26. package/src/package.ts +12 -15
  27. package/src/tests/WebLogo-layout-tests.ts +5 -2
  28. package/src/tests/WebLogo-positions-test.ts +5 -5
  29. package/src/tests/bio-tests.ts +13 -6
  30. package/src/tests/converters-test.ts +4 -4
  31. package/src/tests/detectors-benchmark-tests.ts +5 -5
  32. package/src/tests/detectors-tests.ts +13 -13
  33. package/src/tests/fasta-export-tests.ts +10 -4
  34. package/src/tests/mm-distance-tests.ts +10 -10
  35. package/src/tests/msa-tests.ts +8 -15
  36. package/src/tests/renderers-monomer-placer.ts +3 -3
  37. package/src/tests/renderers-test.ts +6 -8
  38. package/src/tests/splitters-test.ts +14 -13
  39. package/src/tests/substructure-filters-tests.ts +143 -1
  40. package/src/tests/to-atomic-level-tests.ts +2 -2
  41. package/src/tests/units-handler-get-region.ts +4 -4
  42. package/src/tests/units-handler-splitted-tests.ts +19 -17
  43. package/src/tests/units-handler-tests.ts +32 -32
  44. package/src/utils/cell-renderer.ts +40 -34
  45. package/src/utils/check-input-column.ts +5 -5
  46. package/src/utils/context-menu.ts +9 -6
  47. package/src/utils/convert.ts +9 -9
  48. package/src/utils/get-region-func-editor.ts +11 -11
  49. package/src/utils/get-region.ts +10 -12
  50. package/src/utils/macromolecule-column-widget.ts +4 -3
  51. package/src/utils/monomer-lib/library-file-manager/event-manager.ts +1 -1
  52. package/src/utils/multiple-sequence-alignment-ui.ts +6 -6
  53. package/src/utils/pepsea.ts +1 -0
  54. package/src/utils/poly-tool/transformation.ts +3 -3
  55. package/src/utils/poly-tool/ui.ts +46 -135
  56. package/src/utils/save-as-fasta.ts +14 -15
  57. package/src/utils/sequence-to-mol.ts +4 -4
  58. package/src/viewers/web-logo-viewer.ts +46 -54
  59. package/src/widgets/bio-substructure-filter-types.ts +19 -45
  60. package/src/widgets/bio-substructure-filter.ts +45 -23
  61. package/src/widgets/composition-analysis-widget.ts +8 -8
@@ -5,144 +5,55 @@ import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {addTransformedColumn} from './transformation';
7
7
  import {RULES_PATH, RULES_STORAGE_NAME} from './transformation';
8
+ import {ActiveFiles} from '@datagrok-libraries/utils/src/settings/active-files-base';
8
9
 
9
- export type UserRuleSettings = {
10
- included: string[],
11
- notIncluded: string[],
12
- }
13
-
14
- async function getAllAvailableRuleFiles(): Promise<string[]> {
15
- const list = await grok.dapi.files.list(RULES_PATH);
16
- const paths = list.map((fileInfo) => {
17
- return fileInfo.fullPath;
18
- });
19
-
20
- return paths;
21
- }
22
-
23
- async function getUserRulesSettings(): Promise<UserRuleSettings> {
24
- const resStr: string = await grok.dapi.userDataStorage.getValue(RULES_STORAGE_NAME, 'Settings', true);
25
- const res = resStr ? JSON.parse(resStr) : {included: [], enotIncludedxplicit: []};
26
-
27
- res.included = res.included instanceof Array ? res.included : [];
28
- res.notIncluded = res.notIncluded instanceof Array ? res.notIncluded : [];
29
-
30
- return res!;
31
- }
32
-
33
- async function setUserLibSettings(value: UserRuleSettings): Promise<void> {
34
- await grok.dapi.userDataStorage.postValue(RULES_STORAGE_NAME, 'Settings', JSON.stringify(value), true);
35
- }
36
-
37
- export class PolyTool {
38
- ruleFiles: string[];
39
- userRuleSettings: UserRuleSettings;
40
- ruleFilesInputs: HTMLDivElement;// DG.InputBase<boolean | null>[];
41
- dialog: DG.Dialog;
42
-
43
- constructor() {
44
- this.ruleFiles = [];
45
- this.userRuleSettings = {included: [], notIncluded: []};
46
- }
47
-
48
- private updateRulesSelectionStatus(ruleFileName: string, isSelected: boolean): void {
49
- const isRuleFileSelected = this.userRuleSettings.included.includes(ruleFileName);
50
-
51
- if (!isRuleFileSelected && isSelected) {
52
- this.userRuleSettings.included.push(ruleFileName);
53
- this.userRuleSettings.included = this.userRuleSettings.included.sort();
54
-
55
- const index = this.userRuleSettings.notIncluded.indexOf(ruleFileName);
56
- if (index > -1)
57
- this.userRuleSettings.notIncluded.splice(index, 1);
58
- } else {
59
- const index = this.userRuleSettings.included.indexOf(ruleFileName);
60
- if (index > -1)
61
- this.userRuleSettings.included.splice(index, 1);
62
-
63
- this.userRuleSettings.notIncluded.push(ruleFileName);
64
- this.userRuleSettings.notIncluded = this.userRuleSettings.notIncluded.sort();
65
- }
66
-
67
- setUserLibSettings(this.userRuleSettings);
10
+ class RuleInputs extends ActiveFiles {
11
+ constructor(path: string, userStorageName: string, ext: string ) {
12
+ super(path, userStorageName, ext);
68
13
  }
14
+ }
69
15
 
70
- private getAddButton(): HTMLButtonElement {
71
- return ui.button('ADD RULES', () => {
72
- DG.Utils.openFile({
73
- accept: '.csv',
74
- open: async (selectedFile) => {
75
- const content = await selectedFile.text();
76
- await grok.dapi.files.writeAsText(RULES_PATH + `${selectedFile.name}`, content);
77
- this.updateRulesSelectionStatus(selectedFile.name, false);
78
- const cb = ui.boolInput(
79
- selectedFile.name,
80
- false,
81
- (isSelected: boolean) => this.updateRulesSelectionStatus(RULES_PATH + `${selectedFile.name}`, isSelected)
82
- );
83
- this.ruleFilesInputs.append(cb.root);
84
- },
85
- });
16
+ export async function getPolyToolDialog(): Promise<DG.Dialog> {
17
+ const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
18
+ if (!targetColumns)
19
+ throw new Error('No dataframe with macromolecule columns open');
20
+
21
+ const targetColumnInput = ui.columnInput(
22
+ 'Column', grok.shell.t, targetColumns[0], null,
23
+ {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE}
24
+ );
25
+
26
+ const generateHelmChoiceInput = ui.boolInput('Get HELM', true);
27
+ ui.tooltip.bind(generateHelmChoiceInput.root, 'Add HELM column');
28
+
29
+ const chiralityEngineInput = ui.boolInput('Chirality engine', false);
30
+ const ruleInputs = new RuleInputs(RULES_PATH, RULES_STORAGE_NAME, '.csv');
31
+ const rulesForm = await ruleInputs.getForm();
32
+
33
+ const div = ui.div([
34
+ targetColumnInput,
35
+ generateHelmChoiceInput,
36
+ chiralityEngineInput,
37
+ 'Rules used',
38
+ rulesForm
39
+ ]);
40
+
41
+ const dialog = ui.dialog('Poly Tool')
42
+ .add(div)
43
+ .onOK(async () => {
44
+ const molCol = targetColumnInput.value;
45
+ if (!molCol) {
46
+ grok.shell.warning('No marcomolecule column chosen!');
47
+ return;
48
+ }
49
+
50
+ const files = await ruleInputs.getActive();
51
+
52
+ addTransformedColumn(molCol!,
53
+ generateHelmChoiceInput.value!,
54
+ files,
55
+ chiralityEngineInput.value!);
86
56
  });
87
- }
88
-
89
- private async getRuleFilesBlock(): Promise<DG.InputBase<boolean | null>[]> {
90
- this.ruleFiles = await getAllAvailableRuleFiles();
91
- this.userRuleSettings = await getUserRulesSettings();
92
- const cBoxes: DG.InputBase<boolean | null>[] = [];
93
-
94
- for (let i = 0; i < this.ruleFiles.length; i++) {
95
- const ruleFileName = this.ruleFiles[i];
96
- const isRuleFileSelected = this.userRuleSettings.included.includes(ruleFileName);
97
- const cb = ui.boolInput(
98
- ruleFileName.replace(RULES_PATH, ''),
99
- isRuleFileSelected,
100
- (isSelected: boolean) => this.updateRulesSelectionStatus(ruleFileName, isSelected)
101
- );
102
-
103
- cBoxes.push(cb);
104
- }
105
- return cBoxes;
106
- }
107
57
 
108
- async getPolyToolDialog(): Promise<DG.Dialog> {
109
- const targetColumns = grok.shell.t.columns.bySemTypeAll(DG.SEMTYPE.MACROMOLECULE);
110
- if (!targetColumns)
111
- throw new Error('No dataframe with macromolecule columns open');
112
-
113
- const targetColumnInput = ui.columnInput(
114
- 'Column', grok.shell.t, targetColumns[0], null,
115
- {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE}
116
- );
117
-
118
- const generateHelmChoiceInput = ui.boolInput('Get HELM', true);
119
- ui.tooltip.bind(generateHelmChoiceInput.root, 'Add HELM column');
120
-
121
- const addButton = this.getAddButton();
122
- this.ruleFilesInputs = ui.div(await this.getRuleFilesBlock());
123
- //const rulesFiles = ui.div(this.ruleFilesInputs);
124
- const chiralityEngineInput = ui.boolInput('Chirality engine', false);
125
-
126
- const div = ui.div([
127
- targetColumnInput,
128
- generateHelmChoiceInput,
129
- chiralityEngineInput,
130
- 'Rules used',
131
- this.ruleFilesInputs,
132
- addButton
133
- ]);
134
-
135
- this.dialog = ui.dialog('Poly Tool')
136
- .add(div)
137
- .onOK(async () => {
138
- const molCol = targetColumnInput.value;
139
- if (!molCol) {
140
- grok.shell.warning('No marcomolecule column chosen!');
141
- return;
142
- }
143
- addTransformedColumn(molCol!, generateHelmChoiceInput.value!, this.userRuleSettings.included, chiralityEngineInput.value!);
144
- });
145
-
146
- return this.dialog;
147
- }
58
+ return dialog;
148
59
  }
@@ -3,8 +3,8 @@ import * as ui from 'datagrok-api/ui';
3
3
  import * as grok from 'datagrok-api/grok';
4
4
 
5
5
  import wu from 'wu';
6
- import {splitterAsFasta, SplitterFunc} from '@datagrok-libraries/bio/src/utils/macromolecule';
7
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
6
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
7
+ import {ISeqSplitted} from '@datagrok-libraries/bio/src/utils/macromolecule/types';
8
8
 
9
9
  const FASTA_LINE_WIDTH = 60;
10
10
 
@@ -28,8 +28,8 @@ export function saveAsFastaUI() {
28
28
  .filter((gc: DG.GridColumn) => {
29
29
  const col: DG.Column | null = gc.column;
30
30
  if (col && col.semType === DG.SEMTYPE.MACROMOLECULE) {
31
- const uh = UnitsHandler.getOrCreate(col);
32
- return uh.isFasta();
31
+ const sh = SeqHandler.forColumn(col);
32
+ return sh.isFasta();
33
33
  }
34
34
  return false;
35
35
  }).toArray();
@@ -77,17 +77,16 @@ export function saveAsFastaUI() {
77
77
  export function saveAsFastaDo(
78
78
  idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n',
79
79
  ): string {
80
- const splitter: SplitterFunc = splitterAsFasta;
81
-
80
+ const sh = SeqHandler.forColumn(seqCol);
82
81
  const fastaLines: string[] = [];
83
82
 
84
- for (let rowI: number = 0; rowI < seqCol.length; rowI++) {
83
+ for (let rowIdx: number = 0; rowIdx < seqCol.length; rowIdx++) {
85
84
  // multiple identifiers separated by vertical bars
86
85
  // https://en.wikipedia.org/wiki/FASTA_format
87
86
 
88
- const seqId: string = idColList.map((col) => col.get(rowI).toString()).join('|');
89
- const seq: string = seqCol.get(rowI);
90
- const seqLineList: string[] = wrapSequence(seq, splitter, lineWidth);
87
+ const seqId: string = idColList.map((col) => col.get(rowIdx).toString()).join('|');
88
+ const srcSS = sh.getSplitted(rowIdx);
89
+ const seqLineList: string[] = wrapSequence(srcSS, lineWidth);
91
90
 
92
91
  fastaLines.push(`>${seqId}${lineSeparator}`);
93
92
  for (const line of seqLineList)
@@ -99,16 +98,16 @@ export function saveAsFastaDo(
99
98
  }
100
99
 
101
100
  /* split sequence for monomers to prevent wrapping monomer partially */
102
- export function wrapSequence(seq: string, splitter: SplitterFunc, lineWidth: number = FASTA_LINE_WIDTH): string[] {
103
- const seqMonomerList = splitter(seq);
101
+ export function wrapSequence(srcSS: ISeqSplitted, lineWidth: number = FASTA_LINE_WIDTH): string[] {
104
102
  let seqPos: number = 0;
105
- const seqLength: number = seqMonomerList.length;
103
+ const seqLength: number = srcSS.length;
106
104
 
107
105
  const seqLineList: string[] = [];
108
106
  while (seqPos < seqLength) {
109
107
  /* join sliced monomer into line */
110
- const seqLine: string[] = wu(seqMonomerList).slice(seqPos, seqPos + lineWidth).toArray();
111
- const seqLineTxt: string = seqLine.map((m) => m.length > 1 ? `[${m}]` : m).join('');
108
+ const seqLine = wu(srcSS.originals).slice(seqPos, seqPos + lineWidth).toArray();
109
+ const seqLineTxt: string = seqLine.map((om) => om.length > 1 ? `[${om}]` : om)
110
+ .reduce((a, b) => a + b, '');
112
111
  seqLineList.push(seqLineTxt);
113
112
  seqPos += seqLine.length;
114
113
  }
@@ -8,7 +8,7 @@ import {helm2mol} from './helm-to-molfile';
8
8
  import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
9
9
  import {checkInputColumnUI} from './check-input-column';
10
10
  import {getMonomerLibHelper} from '../package';
11
- import {UnitsHandler} from '@datagrok-libraries/bio/src/utils/units-handler';
11
+ import {SeqHandler} from '@datagrok-libraries/bio/src/utils/seq-handler';
12
12
  import {NOTATION} from '@datagrok-libraries/bio/src/utils/macromolecule';
13
13
 
14
14
  export async function sequenceToMolfile(df: DG.DataFrame, macroMolecule: DG.Column, nonlinear: boolean): Promise<void> {
@@ -17,9 +17,9 @@ export async function sequenceToMolfile(df: DG.DataFrame, macroMolecule: DG.Colu
17
17
  return;
18
18
  }
19
19
  if (nonlinear) {
20
- const seqUh = UnitsHandler.getOrCreate(macroMolecule);
21
- if (!seqUh.isHelm())
22
- macroMolecule = seqUh.convert(NOTATION.HELM);
20
+ const seqSh = SeqHandler.forColumn(macroMolecule);
21
+ if (!seqSh.isHelm())
22
+ macroMolecule = seqSh.convert(NOTATION.HELM);
23
23
  helm2mol(df, macroMolecule);
24
24
  return;
25
25
  }
@@ -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
  }