@datagrok/bio 2.16.4 → 2.16.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.
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Aleksandr Tanas",
6
6
  "email": "atanas@datagrok.ai"
7
7
  },
8
- "version": "2.16.4",
8
+ "version": "2.16.6",
9
9
  "description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
10
10
  "repository": {
11
11
  "type": "git",
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@biowasm/aioli": "^3.1.0",
40
- "@datagrok-libraries/bio": "^5.45.3",
40
+ "@datagrok-libraries/bio": "^5.45.4",
41
41
  "@datagrok-libraries/chem-meta": "^1.2.7",
42
42
  "@datagrok-libraries/math": "^1.2.2",
43
43
  "@datagrok-libraries/ml": "^6.7.4",
package/src/package.ts CHANGED
@@ -27,6 +27,7 @@ import {ISeqHelper} from '@datagrok-libraries/bio/src/utils/seq-helper';
27
27
  import {RDModule} from '@datagrok-libraries/chem-meta/src/rdkit-api';
28
28
  import {getRdKitModule} from '@datagrok-libraries/bio/src/chem/rdkit-module';
29
29
  import {ISeqHandler} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
30
+ import {MmcrTemps} from '@datagrok-libraries/bio/src/utils/cell-renderer-consts';
30
31
 
31
32
  import {getMacromoleculeColumns} from './utils/ui-utils';
32
33
  import {MacromoleculeDifferenceCellRenderer, MacromoleculeSequenceCellRenderer,} from './utils/cell-renderer';
@@ -630,7 +631,7 @@ export async function toAtomicLevel(
630
631
  const pi = DG.TaskBarProgressIndicator.create('Converting to atomic level ...');
631
632
  try {
632
633
  await initBioPromise;
633
- const monomerLib = _package.monomerLib;
634
+ const monomerLib = seqCol.temp[MmcrTemps.overriddenLibrary] ?? _package.monomerLib;
634
635
  const seqHelper = _package.seqHelper;
635
636
  const rdKitModule = _package.rdKitModule;
636
637
  await sequenceToMolfile(table, seqCol, nonlinear, highlight, monomerLib, seqHelper, rdKitModule);
@@ -85,6 +85,7 @@ category('monomerLibraries', () => {
85
85
 
86
86
  const overriddenMonomerLib = monomerLib.override({[overMon.polymerType]: {[overMon.symbol]: overMon}}, 'test');
87
87
  const resOverMon = overriddenMonomerLib.getMonomer(overMon.polymerType, overMon.symbol);
88
+ if (resOverMon) resOverMon.lib = undefined; // cleanup to prevent infinite recursive comparison
88
89
  expectObject(resOverMon as any, overMon);
89
90
  });
90
91
  });
@@ -81,8 +81,8 @@ category('SeqHandler: getHelm', () => {
81
81
  await grok.data.detectSemanticTypes(df);
82
82
 
83
83
  const sh = seqHelper.getSeqHandler(seqCol);
84
- const resSemValue = await sh.getHelm(0);
85
- const resHelm = resSemValue.value;
84
+ const resMValue = await sh.getValue(0);
85
+ const resHelm = resMValue.helm;
86
86
  expect(resHelm, tgtHelm);
87
87
  }
88
88
  });
@@ -94,53 +94,33 @@ export class HelmToMolfileConverter {
94
94
  }
95
95
 
96
96
 
97
- public convertToMolfileV3KColumn(
98
- helmCol: DG.Column<string>
99
- ): DG.Column<string> {
97
+ public convertToMolfileV3KColumn(helmCol: DG.Column<string>): DG.Column<string> {
100
98
  const df = helmCol.dataFrame;
101
- const polymerGraphColumn: DG.Column<string> = this.getPolymerGraphColumn(helmCol);
102
- const molfileList = polymerGraphColumn.toList().map(
103
- (pseudoMolfile: string, idx: number) => {
104
- const helm = helmCol.get(idx);
105
- if (!helm) return '';
106
-
107
- let resMolfileWithMap: MolfileWithMap;
108
- try {
109
- resMolfileWithMap = this.getPolymerMolfile(helm, pseudoMolfile);
110
- } catch (err: any) {
111
- const [errMsg, errStack] = errInfo(err);
112
- _package.logger.error(errMsg, undefined, errStack);
113
- resMolfileWithMap = MolfileWithMap.createEmpty();
114
- }
115
- return resMolfileWithMap.molfile;
116
- });
99
+ const molfileList = this.convertToMolfileV3K(helmCol.toList()).map((mwm) => mwm.molfile);
117
100
  const molColName = getUnusedColName(df, `molfileV2K(${helmCol.name})`);
118
101
  const molfileColumn = DG.Column.fromList('string', molColName, molfileList);
119
102
  return molfileColumn;
120
103
  }
121
104
 
122
105
  /** Gets list of monomer molfiles */
123
- public convertToMolfileV3K(helmCol: DG.Column<string>, rdKitModule: RDModule, monomerLib: IMonomerLibBase): MolfileWithMap[] {
124
- const polymerGraphColumn: DG.Column<string> = this.getPolymerGraphColumn(helmCol);
125
- const rowCount = helmCol.length;
126
- const resList: MolfileWithMap[] = new Array<MolfileWithMap>(rowCount);
127
- for (let rowIdx = 0; rowIdx < rowCount; ++rowIdx) {
128
- const helm = helmCol.get(rowIdx);
106
+ public convertToMolfileV3K(helmList: string[]): MolfileWithMap[] {
107
+ const resList: MolfileWithMap[] = new Array<MolfileWithMap>(helmList.length);
108
+ for (let i = 0; i < helmList.length; ++i) {
109
+ const helm = helmList[i];
129
110
  if (!helm) {
130
- resList[rowIdx] = MolfileWithMap.createEmpty();
111
+ resList[i] = MolfileWithMap.createEmpty();
131
112
  continue;
132
113
  }
133
114
 
134
- const pseudoMolfile = polymerGraphColumn.get(rowIdx)!;
135
115
  let resMolfile: MolfileWithMap;
136
116
  try {
137
- resMolfile = this.getPolymerMolfile(helm, pseudoMolfile);
117
+ resMolfile = this.getPolymerMolfile(helm);
138
118
  } catch (err: any) {
139
119
  const [errMsg, errStack] = errInfo(err);
140
120
  _package.logger.error(errMsg, undefined, errStack);
141
121
  resMolfile = MolfileWithMap.createEmpty();
142
122
  }
143
- resList[rowIdx] = resMolfile;
123
+ resList[i] = resMolfile;
144
124
  }
145
125
  return resList;
146
126
  }
@@ -152,16 +132,15 @@ export class HelmToMolfileConverter {
152
132
  return molfileCol;
153
133
  }
154
134
 
155
- private getPolymerMolfile(
156
- helm: string, polymerGraph: string
157
- ): MolfileWithMap {
135
+ private getPolymerMolfile(helm: string): MolfileWithMap {
158
136
  const woGapsRes = this.helmHelper.removeGaps(helm);
159
137
  const woGapsHelm = woGapsRes.resHelm;
160
138
  const woGapsReverseMap = new Map<number, number>();
161
139
  for (const [orgPosIdx, woGapsPosIdx] of (woGapsRes.monomerMap?.entries() ?? [])) {
162
140
  woGapsReverseMap.set(woGapsPosIdx, orgPosIdx);
163
141
  }
164
- const globalPositionHandler = new GlobalMonomerPositionHandler(polymerGraph);
142
+ const pseudoMolfile = this.helmHelper.getMolfiles([woGapsHelm])[0];
143
+ const globalPositionHandler = new GlobalMonomerPositionHandler(pseudoMolfile);
165
144
  const woGapsPolymer = new Polymer(woGapsHelm, this.rdKitModule, this.monomerLib);
166
145
  globalPositionHandler.monomerSymbols.forEach((monomerSymbol: string, monomerIdx: number) => {
167
146
  const shift = globalPositionHandler.getMonomerShifts(monomerIdx);
@@ -1,4 +1,6 @@
1
1
  import {HELM_MONOMER_TYPE, HELM_POLYMER_TYPE} from '@datagrok-libraries/bio/src/utils/const';
2
+ import {cleanupHelmSymbol} from '@datagrok-libraries/bio/src/helm/utils';
3
+
2
4
  import {Bond} from './types';
3
5
 
4
6
  /** Wrapper over simple polymer substring of HELM, like RNA1{d(A)p} */
@@ -41,7 +43,7 @@ export class SimplePolymer {
41
43
  return id;
42
44
  }
43
45
 
44
- private getMonomerSymbolsAndTypes(): {monomers: string[], monomerTypes: HELM_MONOMER_TYPE[]} {
46
+ private getMonomerSymbolsAndTypes(): { monomers: string[], monomerTypes: HELM_MONOMER_TYPE[] } {
45
47
  const helmWrapperRegex = new RegExp(`${this.polymerType}${this.idx}{|}`, 'g');
46
48
  const monomerGroups = this.simplePolymer.replace(helmWrapperRegex, '').split('.');
47
49
  const monomerList: string[] = [];
@@ -51,7 +53,7 @@ export class SimplePolymer {
51
53
  // monomerList.push(...splitted);
52
54
  // WARNING: only the groups of the form r(A)p, as in RNA, are supported
53
55
 
54
- monomerList.push(monomerGroup);
56
+ monomerList.push(cleanupHelmSymbol(monomerGroup));
55
57
  // const monomerTypes = splitted.map(
56
58
  // (_, idx) => (idx % 2 === 0) ? HELM_MONOMER_TYPE.BACKBONE : HELM_MONOMER_TYPE.BRANCH
57
59
  // );
@@ -46,6 +46,10 @@ export class MonomerLibBase implements IMonomerLibBase {
46
46
  ) {
47
47
  this._isEmpty = !this._monomers || Object.keys(this._monomers).length === 0 ||
48
48
  Object.entries(this._monomers).every(([_, ptMonomers]) => Object.keys(ptMonomers).length === 0);
49
+ for (const [_monomerType, monomersOfType] of Object.entries(this._monomers)) {
50
+ for (const [_monomerSymbol, monomer] of Object.entries(monomersOfType))
51
+ monomer.lib = this;
52
+ }
49
53
  }
50
54
 
51
55
  getMonomerSymbolsByType(polymerType: PolymerType): string[] {
@@ -43,10 +43,6 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
43
43
  public readonly error: string | undefined = undefined,
44
44
  ) {
45
45
  super(monomers, source);
46
- for (const [_monomerType, monomersOfType] of Object.entries(this._monomers)) {
47
- for (const [_monomerSymbol, monomer] of Object.entries(monomersOfType))
48
- monomer.lib = this;
49
- }
50
46
  }
51
47
 
52
48
  toJSON(): Monomer[] {
@@ -237,8 +233,10 @@ export class MonomerLib extends MonomerLibBase implements IMonomerLib {
237
233
  return resStr;
238
234
  }
239
235
 
236
+ static overrideCounter: number = 0;
237
+
240
238
  override(data: MonomerLibData, source: string): IMonomerLibBase {
241
- return new OverriddenMonomerLib(data, source, this);
239
+ return new OverriddenMonomerLib(data, `override: ${++MonomerLib.overrideCounter}, ${source}`, this);
242
240
  }
243
241
  }
244
242
 
@@ -16,7 +16,7 @@ import {MonomerLibManager} from '../lib-manager';
16
16
  import {LIB_PATH} from '../consts';
17
17
 
18
18
  import '../../../../css/monomer-manager.css';
19
- import { MONOMER_RENDERER_TAGS } from '@datagrok-libraries/bio/src/utils/cell-renderer';
19
+ import {MONOMER_RENDERER_TAGS} from '@datagrok-libraries/bio/src/utils/cell-renderer';
20
20
 
21
21
  // columns of monomers dataframe, note that rgroups is hidden and will be displayed as separate columns
22
22
  export enum MONOMER_DF_COLUMN_NAMES {
@@ -51,7 +51,6 @@ export const MONOMER_DF_COLUMNS = {
51
51
 
52
52
 
53
53
  export class MonomerManager implements IMonomerManager {
54
-
55
54
  private adjustColWidths() {
56
55
  setTimeout(() => {
57
56
  if (this.tv?.grid) {
@@ -87,7 +86,6 @@ export class MonomerManager implements IMonomerManager {
87
86
  }, 500);
88
87
  }
89
88
  }
90
-
91
89
  }, () => this.tv?.dataFrame);
92
90
  }
93
91
 
@@ -203,6 +201,7 @@ export class MonomerManager implements IMonomerManager {
203
201
  }
204
202
  })
205
203
  );
204
+ this.tv.grid && (this.tv.grid.props.allowEdit = false); // disable editing
206
205
  return this.tv;
207
206
  }
208
207
 
@@ -239,6 +238,10 @@ export class MonomerManager implements IMonomerManager {
239
238
  });
240
239
  ribbons = ribbons.filter((r) => r.length > 0);
241
240
 
241
+ const newMonomerButton = ui.icons.add(() => {
242
+ this._newMonomerForm.setEmptyMonomer();
243
+ }, 'Add New Monomer');
244
+
242
245
  const editButton = ui.icons.edit(() => {
243
246
  if ((this.tv?.dataFrame?.currentRowIdx ?? -1) < 0) return;
244
247
  this.cloneMonomer(this.tv!.dataFrame.rows.get(this.tv!.dataFrame.currentRowIdx));
@@ -277,19 +280,19 @@ export class MonomerManager implements IMonomerManager {
277
280
  DG.Utils.download(libName!, lib!, 'text/plain');
278
281
  }, 'Download Monomer Library');
279
282
 
280
- ribbons.push([editButton, deleteButton, downloadButton]);
283
+ ribbons.push([newMonomerButton, editButton, deleteButton, downloadButton]);
281
284
  this.tv.setRibbonPanels(ribbons);
282
285
 
283
286
 
284
287
  this.tv.name = MonomerManager.VIEW_NAME;
285
288
  this.libInput = ui.input.choice('Monomer Library', {value: libName, items: availableMonLibs, nullable: false, onValueChanged: async () => {
286
- try {
287
- const df = await this.getMonomersDf(this.libInput.value!);
289
+ try {
290
+ const df = await this.getMonomersDf(this.libInput.value!);
288
291
  this.tv!.dataFrame = df;
289
292
  this.adjustColWidths();
290
- } catch (e) {
291
- console.error(e);
292
- }
293
+ } catch (e) {
294
+ console.error(e);
295
+ }
293
296
  }});
294
297
  this.libInput.addOptions(ui.icons.add(() => { this.createNewLibDialog(); }, 'Create new monomer library...'));
295
298
  const monForm = this._newMonomerForm.form;
@@ -370,6 +373,7 @@ export class MonomerManager implements IMonomerManager {
370
373
  df.col(rgName)!.semType = DG.SEMTYPE.MOLECULE;
371
374
  });
372
375
  df.currentRowIdx = -1;
376
+ // eslint-disable-next-line rxjs/no-ignored-subscription
373
377
  df.onCurrentRowChanged.subscribe((_) => {
374
378
  try {
375
379
  if (df.currentRowIdx === -1 || this._newMonomerForm.molChanged)
@@ -541,6 +545,7 @@ class MonomerForm implements INewMonomerForm {
541
545
  await this.saveMonomer();
542
546
  });
543
547
  // this.saveButton.style.pointerEvents = 'revert';
548
+ // eslint-disable-next-line rxjs/no-async-subscribe
544
549
  this.molSketcher.subs.push(this.molSketcher.onChanged.subscribe(async () => {
545
550
  if (!this.triggerMolChange) {
546
551
  this.triggerMolChange = true;
@@ -551,6 +556,9 @@ class MonomerForm implements INewMonomerForm {
551
556
  let smiles = this.molSketcher.getSmiles();
552
557
  if (!smiles) {
553
558
  this.rgroupsGrid.items = [];
559
+ this.rgroupsGrid.render();
560
+ this.saveValidationResult = 'Monomer molecule is required';
561
+ this.invalidateSaveButton();
554
562
  return;
555
563
  }
556
564
  smiles = getCorrectedSmiles([], smiles);
@@ -559,6 +567,8 @@ class MonomerForm implements INewMonomerForm {
559
567
  if (rGroupMatches.length === 0) {
560
568
  this.rgroupsGrid.items = [];
561
569
  this.rgroupsGrid.render();
570
+ this.saveValidationResult = 'At least one R-group is required';
571
+ this.invalidateSaveButton();
562
572
  return;
563
573
  }
564
574
  const rGroupNums = rGroupMatches.map((match) => Number.parseInt(match[0].match(/[1-9]/g)![0]));
@@ -606,6 +616,7 @@ class MonomerForm implements INewMonomerForm {
606
616
  [HELM_RGROUP_FIELDS.LABEL]: 'Label',
607
617
  },
608
618
  });
619
+ // eslint-disable-next-line rxjs/no-ignored-subscription
609
620
  this.rgroupsGrid.onItemChanged.subscribe(() => this.onMonomerInputChanged());
610
621
 
611
622
  this.rgroupsGridRoot = ui.divV([this.rgroupsGrid.root]);
@@ -633,17 +644,19 @@ class MonomerForm implements INewMonomerForm {
633
644
  'Meta': ui.divV([this.metaGrid.root]),
634
645
  'Colors': this.colorsEditor.form,
635
646
  }, false);
647
+ }
636
648
 
649
+ invalidateSaveButton() {
650
+ if (this.saveValidationResult)
651
+ this.saveButton.classList.add('d4-disabled');
652
+ else
653
+ this.saveButton.classList.remove('d4-disabled');
637
654
  }
638
655
 
639
656
  onMonomerInputChanged() {
640
657
  setTimeout(() => {
641
658
  this.saveValidationResult = this.validateInputs();
642
- if (this.saveValidationResult)
643
- this.saveButton.classList.add('d4-disabled');
644
- else
645
- this.saveButton.classList.remove('d4-disabled');
646
-
659
+ this.invalidateSaveButton();
647
660
  const monomerExists = this.polymerTypeInput.value && this.polymerTypeInput.value &&
648
661
  !!this.getMonomerLib()?.getMonomer(this.polymerTypeInput.value as PolymerType, this.monomerSymbolInput.value);
649
662
 
@@ -651,6 +664,27 @@ class MonomerForm implements INewMonomerForm {
651
664
  }, 200);
652
665
  }
653
666
 
667
+ setEmptyMonomer() {
668
+ this.triggerMolChange = false;
669
+ this.molSketcher.setSmiles('');
670
+ // leave polymer and monomer type as is
671
+ this.monomerSymbolInput.value = '';
672
+ this.monomerNameInput.value = '';
673
+ this.monomerIdInput.value = null;
674
+ this.monomerNaturalAnalogInput.value = null;
675
+ this.rgroupsGrid.items = [];
676
+ this.metaGrid.items = [];
677
+ this.rgroupsGrid.render();
678
+ this.metaGrid.render();
679
+ this.rgroupsGridRoot.style.display = 'none';
680
+ this.onMonomerInputChanged();
681
+ this.colorsEditor.colors = {
682
+ line: '#000000',
683
+ background: '#000000',
684
+ text: '#000000',
685
+ };
686
+ }
687
+
654
688
  setMonomer(monomer: Monomer) {
655
689
  this.triggerMolChange = false;
656
690
  this.molSketcher.setSmiles(monomer.smiles);
@@ -661,7 +695,7 @@ class MonomerForm implements INewMonomerForm {
661
695
  this.monomerIdInput.value = monomer.id;
662
696
  this.monomerNaturalAnalogInput.value = monomer.naturalAnalog ?? null;
663
697
  this.rgroupsGrid.items = resolveRGroupInfo(monomer.rgroups);
664
- this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
698
+ this.metaGrid.items = Object.entries(monomer.meta ?? {}).filter(([k, _v]) => k?.toLowerCase() !== 'colors').map(([k, v]) => {
665
699
  return {Property: k, Value: v};
666
700
  });
667
701
  this.rgroupsGrid.render();
@@ -681,7 +715,7 @@ class MonomerForm implements INewMonomerForm {
681
715
  } catch (e) {
682
716
  console.error(e);
683
717
  }
684
-
718
+
685
719
  this.colorsEditor.colors = {
686
720
  line: colorsObj.line ?? '#000000',
687
721
  background: colorsObj.background ?? '#000000',
@@ -693,7 +727,7 @@ class MonomerForm implements INewMonomerForm {
693
727
  const rGroupsPane = this.inputsTabControl.panes.find((p) => p.name?.toLowerCase() === 'r-groups');
694
728
  rGroupsPane && (rGroupsPane.header.style.removeProperty('background-color'));
695
729
  if (!this.molSketcher.getSmiles()) return 'Monomer Molecule field is required';
696
- for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput, this.monomerIdInput]) {
730
+ for (const i of [this.polymerTypeInput, this.monomerTypeInput, this.monomerSymbolInput, this.monomerNameInput]) {
697
731
  if (i.value == null || i.value === '')
698
732
  return `${i.caption} field is required`;
699
733
  }
@@ -703,17 +737,18 @@ class MonomerForm implements INewMonomerForm {
703
737
  if (!rgroupError) {
704
738
  outerFor:
705
739
  for (const item of this.rgroupsGrid.items) {
706
- for (const [k, v] of Object.entries(item))
707
- if (!v){
740
+ for (const [k, v] of Object.entries(item)) {
741
+ if (!v) {
708
742
  rgroupError = `R-group ${k} field is required for ${item[HELM_RGROUP_FIELDS.LABEL]}`;
709
743
  break outerFor;
710
- }
744
+ }
745
+ }
711
746
  }
712
747
  }
713
748
 
714
- if (!rgroupError && this.rgroupsGrid.hasErrors()){
749
+ if (!rgroupError && this.rgroupsGrid.hasErrors())
715
750
  rgroupError = 'R-group fields contain errors';
716
- }
751
+
717
752
  if (rgroupError) {
718
753
  rGroupsPane && (rGroupsPane.header.style.setProperty('background-color', '#ff000030'));
719
754
  return rgroupError;
@@ -730,7 +765,6 @@ class MonomerForm implements INewMonomerForm {
730
765
  }
731
766
 
732
767
  get form() {
733
-
734
768
  this.inputsTabControl.root.classList.add('monomer-manager-form-tab-control');
735
769
  this.inputsTabControl.header.style.marginBottom = '10px';
736
770
  const saveB = ui.buttonsInput([this.saveButton]);
@@ -826,11 +860,11 @@ class MonomerForm implements INewMonomerForm {
826
860
  try {
827
861
  // first remove the existing monomer with that symbol
828
862
  const monomerIdx = libJSON.findIndex((m) => m.symbol === monomer.symbol && m.polymerType === monomer.polymerType);
829
- if (monomerIdx >= 0) {
863
+ if (monomerIdx >= 0)
830
864
  libJSON[monomerIdx] = {...monomer, lib: undefined, wem: undefined};
831
- } else {
865
+ else
832
866
  libJSON.push({...monomer, lib: undefined, wem: undefined});
833
- }
867
+
834
868
  await grok.dapi.files.writeAsText(LIB_PATH + libName, JSON.stringify(libJSON));
835
869
  await (await MonomerLibManager.getInstance()).loadLibraries(true);
836
870
  await this.refreshTable(monomer.symbol);
@@ -839,6 +873,7 @@ class MonomerForm implements INewMonomerForm {
839
873
  grok.shell.error('Error saving monomer');
840
874
  console.error(e);
841
875
  }
876
+ this.onMonomerInputChanged();
842
877
  };
843
878
  let infoTable: HTMLDivElement | null = null;
844
879
  let promptMessage = '';
@@ -882,9 +917,9 @@ class MonomerForm implements INewMonomerForm {
882
917
  meta[item['Property']] = item['Value'];
883
918
  });
884
919
  const addingItem = this.metaGrid.addingItem;
885
- if (addingItem && addingItem['Property'] && addingItem['Value']) {
920
+ if (addingItem && addingItem['Property'] && addingItem['Value'])
886
921
  meta[addingItem['Property']] = addingItem['Value'];
887
- }
922
+
888
923
  //console.log(this.metaGrid.addingItem);
889
924
  if (this.colorsEditor.colors.line !== '#000000' || this.colorsEditor.colors.background !== '#000000' || this.colorsEditor.colors.text !== '#000000')
890
925
  meta.colors = {default: this.colorsEditor.colors};
@@ -975,7 +1010,7 @@ function getCorrectedMolBlock(molBlock: string) {
975
1010
 
976
1011
  if (rgpLineIdx === -1) {
977
1012
  // number of r groups has 3 empty slots before it, atom numbers have 4 empty slots before them
978
- let rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3,' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
1013
+ const rgpLine = `M RGP${rgroupLineNums.length.toString().padStart(3, ' ')}${Object.entries(rgroupLineNumbers).map(([atomLine, rGroupNum]) => `${atomLine.toString().padStart(4, ' ')}${rGroupNum.toString().padStart(4, ' ')}`).join('')}`;
979
1014
  const mEndIdx = lines.findIndex((line) => line.startsWith('M') && line.includes('END'));
980
1015
  lines.splice(mEndIdx, 0, rgpLine);
981
1016
  }
@@ -1042,9 +1077,8 @@ class ColorsEditor {
1042
1077
  };
1043
1078
 
1044
1079
  this._colors = colsHex;
1045
- for (const key in this._colorInputs) {
1080
+ for (const key in this._colorInputs)
1046
1081
  this._colorInputs[key as keyof ColorsEditor['_colors']].value = colsHex[key as keyof ColorsEditor['_colors']];
1047
- }
1048
1082
  }
1049
1083
 
1050
1084
  get colorsMetaFormat() {
@@ -1054,4 +1088,4 @@ class ColorsEditor {
1054
1088
  get form() {
1055
1089
  return ui.form(Object.values(this._colorInputs));
1056
1090
  }
1057
- }
1091
+ }
@@ -14,7 +14,7 @@ import {GAP_SYMBOL, GapOriginals} from '@datagrok-libraries/bio/src/utils/macrom
14
14
  import {CellRendererBackBase, GridCellRendererTemp} from '@datagrok-libraries/bio/src/utils/cell-renderer-back-base';
15
15
  import {HelmTypes} from '@datagrok-libraries/bio/src/helm/consts';
16
16
  import {HelmType} from '@datagrok-libraries/bio/src/helm/types';
17
- import {ISeqHandler, ConvertFunc, JoinerFunc, SeqTemps} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
17
+ import {ISeqHandler, ConvertFunc, JoinerFunc, SeqTemps, MacromoleculeValueBase} from '@datagrok-libraries/bio/src/utils/macromolecule/seq-handler';
18
18
 
19
19
  import {SeqHelper} from './seq-helper';
20
20
 
@@ -241,18 +241,16 @@ export class SeqHandler implements ISeqHandler {
241
241
  }
242
242
 
243
243
  /** Any Macromolecule can be represented on Helm format. The reverse is not always possible. */
244
- public async getHelm(rowIdx: number, options?: any): Promise<DG.SemanticValue<string>> {
244
+ public async getValue(rowIdx: number, options?: any): Promise<MacromoleculeValueBase> {
245
245
  const seq: string = this.column.get(rowIdx);
246
- let resHelmSV: DG.SemanticValue<string>;
246
+ let resHelm: string;
247
247
  if (this.notationProvider)
248
- resHelmSV = await this.notationProvider.getHelm(seq, options);
248
+ resHelm = await this.notationProvider.getHelm(seq, options);
249
249
  else {
250
- const resHelm = this.convertToHelm(seq);
251
- resHelmSV = DG.SemanticValue.fromValueType(resHelm, DG.SEMTYPE.MACROMOLECULE, NOTATION.HELM);
252
- // TODO: set tags from column
250
+ resHelm = this.convertToHelm(seq);
253
251
  }
254
-
255
- return resHelmSV;
252
+ const resMValue = new MacromoleculeValueBase(resHelm, this, rowIdx);
253
+ return resMValue;
256
254
  }
257
255
 
258
256
  private _stats: SeqColStats | null = null;
@@ -56,7 +56,7 @@ export class SeqHelper implements ISeqHelper {
56
56
 
57
57
  //#region From HelmToMolfileConverter.convertToRdKitBeautifiedMolfileColumn
58
58
 
59
- const molfilesV3K = converter.convertToMolfileV3K(helmCol, this.rdKitModule, monomerLib);
59
+ const molfilesV3K = converter.convertToMolfileV3K(helmCol.toList());
60
60
 
61
61
  const beautifiedMolList: (RDMol | null)[] = molfilesV3K.map((item) => {
62
62
  const molfile = item.molfile;
@@ -395,14 +395,14 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
395
395
 
396
396
  // -- Data --
397
397
  this.sequenceColumnName = this.string(PROPS.sequenceColumnName, defaults.sequenceColumnName,
398
- {category: PROPS_CATS.DATA});
398
+ {category: PROPS_CATS.DATA, semType: DG.SEMTYPE.MACROMOLECULE});
399
399
  const aggExcludeList = [DG.AGG.KEY, DG.AGG.PIVOT, DG.AGG.MISSING_VALUE_COUNT, DG.AGG.SKEW, DG.AGG.KURT,
400
400
  DG.AGG.SELECTED_ROWS_COUNT];
401
401
  const aggChoices = Object.values(DG.AGG).filter((agg) => !aggExcludeList.includes(agg));
402
402
  this.valueAggrType = this.string(PROPS.valueAggrType, defaults.valueAggrType,
403
403
  {category: PROPS_CATS.DATA, choices: aggChoices}) as DG.AggregationType;
404
404
  this.valueColumnName = this.string(PROPS.valueColumnName, defaults.valueColumnName,
405
- {category: PROPS_CATS.DATA});
405
+ {category: PROPS_CATS.DATA, columnTypeFilter: 'numerical'});
406
406
  this.startPositionName = this.string(PROPS.startPositionName, defaults.startPositionName,
407
407
  {category: PROPS_CATS.DATA});
408
408
  this.endPositionName = this.string(PROPS.endPositionName, defaults.endPositionName,
@@ -1022,20 +1022,18 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1022
1022
 
1023
1023
  // 2022-05-05 askalkin instructed to show WebLogo based on filter (not selection)
1024
1024
  const dfRowCount = this.dataFrame.rowCount;
1025
-
1025
+ const filterIndexes = dfFilter.getSelectedIndexes();
1026
1026
  for (let jPos = 0; jPos < length; ++jPos) {
1027
1027
  // Here we want to build lists of values for every monomer in position jPos
1028
- for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1029
- if (dfFilter.get(rowI)) {
1030
- const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1031
- const om: string = jPos < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1032
- this.seqHandler.defaultGapOriginal;
1033
- const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1034
- const pi = this.positions[jPos];
1035
- const pmi = pi.getFreq(cm);
1036
- ++pi.sumRowCount;
1037
- pmi.value = ++pmi.rowCount;
1038
- }
1028
+ for (const rowI of filterIndexes) {
1029
+ const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1030
+ const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1031
+ this.seqHandler.defaultGapOriginal;
1032
+ const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1033
+ const pi = this.positions[jPos];
1034
+ const pmi = pi.getFreq(cm);
1035
+ ++pi.sumRowCount;
1036
+ pmi.value = ++pmi.rowCount;
1039
1037
  }
1040
1038
  if (this.valueAggrType === DG.AGG.TOTAL_COUNT) continue;
1041
1039
 
@@ -1048,13 +1046,13 @@ export class WebLogoViewer extends DG.JsViewer implements IWebLogoViewer {
1048
1046
  } catch { valueCol = null; }
1049
1047
  if (!valueCol) continue; // fallback to TOTAL_COUNT
1050
1048
 
1051
- for (let rowI = 0; rowI < dfRowCount; ++rowI) {
1052
- if (dfFilter.get(rowI)) { // respect the filter
1053
- const seqMList: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1054
- const cm: string = seqMList.getCanonical(this.startPosition + jPos);
1055
- const value: number | null = valueCol.get(rowI);
1056
- this.positions[jPos].getFreq(cm).push(value);
1057
- }
1049
+ for (const rowI of filterIndexes) {
1050
+ const seqS: ISeqSplitted = this.seqHandler.getSplitted(rowI);
1051
+ const om: string = jPos + this.startPosition < seqS.length ? seqS.getCanonical(this.startPosition + jPos) :
1052
+ this.seqHandler.defaultGapOriginal;
1053
+ const cm: string = this.seqHandler.defaultGapOriginal === om ? GAP_SYMBOL : om;
1054
+ const value: number | null = valueCol.get(rowI);
1055
+ this.positions[jPos].getFreq(cm).push(value);
1058
1056
  }
1059
1057
  this.positions[jPos].aggregate(this.valueAggrType);
1060
1058
  }