@datagrok/bio 2.10.7 → 2.10.9

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": "Leonid Stolbov",
6
6
  "email": "lstolbov@datagrok.ai"
7
7
  },
8
- "version": "2.10.7",
8
+ "version": "2.10.9",
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",
@@ -14,8 +14,25 @@ const LEFT_HELM_WRAPPER = 'PEPTIDE1{';
14
14
  const RIGHT_HELM_WRAPPER = '}$$$$';
15
15
  const ALL_MONOMERS = '<All>';
16
16
 
17
+ type MetaData = {
18
+ leftTerminal: string,
19
+ rightTerminal: string,
20
+ transformationType: TRANSFORMATION_TYPE,
21
+ cyclizationType: CYCLIZATION_TYPE,
22
+ }
23
+
24
+ type ConnectionData = {
25
+ monomerPosition: number,
26
+ attachmentPoint: number,
27
+ }
28
+
29
+ const enum TRANSFORMATION_TYPE {
30
+ CYCLIZATION = 'Cyclization',
31
+ }
32
+
17
33
  const enum CYCLIZATION_TYPE {
18
34
  NO = 'N-O',
35
+ NCys = 'N-Cys',
19
36
  R3 = 'R3-R3',
20
37
  }
21
38
 
@@ -32,72 +49,147 @@ export function _setPeptideColumn(col: DG.Column): void {
32
49
  // col.setTag('cell.renderer', 'sequence');
33
50
  }
34
51
 
35
- async function enumerator(
36
- molColumn: DG.Column, cyclizationType: CYCLIZATION_TYPE, leftTerminal: string, rightTerminal: string
37
- ): Promise<void> {
38
- function hasR3Terminals(helm: string, leftTerminal: string, rightTerminal: string): boolean {
39
- if (leftTerminal === ALL_MONOMERS || rightTerminal === ALL_MONOMERS)
52
+ abstract class TransformationBase {
53
+ constructor(helmColumn: DG.Column<string>, meta: MetaData) {
54
+ this.helmColumn = helmColumn;
55
+ this.leftTerminal = meta.leftTerminal;
56
+ this.rightTerminal = meta.rightTerminal;
57
+ }
58
+
59
+ protected helmColumn: DG.Column<string>;
60
+ protected leftTerminal?: string;
61
+ protected rightTerminal?: string;
62
+
63
+ protected abstract hasTerminals(helm: string): boolean;
64
+ protected abstract getTransformedHelm(helm: string): string;
65
+ protected abstract getLinkedPositions(helm: string): [number, number];
66
+
67
+ transform(): string[] {
68
+ const resultList = this.helmColumn.toList().map((helm: string) => {
69
+ if (this.hasTerminals(helm))
70
+ return this.getTransformedHelm(helm);
71
+ return helm;
72
+ });
73
+ return resultList;
74
+ }
75
+ }
76
+
77
+ class TransformationNCys extends TransformationBase {
78
+ constructor(helmColumn: DG.Column<string>, meta: MetaData) {
79
+ super(helmColumn, meta);
80
+ }
81
+
82
+ protected hasTerminals(helm: string): boolean {
83
+ if (! helm.includes(this.rightTerminal + RIGHT_HELM_WRAPPER))
84
+ return false;
85
+ if (this.leftTerminal === ALL_MONOMERS)
40
86
  return true;
41
- const positions = getLinkedR3Positions(helm);
42
- return positions.every((el) => el > 0);
87
+ return helm.includes(LEFT_HELM_WRAPPER + this.leftTerminal);
43
88
  }
44
89
 
45
- function hasNOTerminals(helm: string, leftTerminal: string, rightTerminal: string): boolean {
46
- if (leftTerminal === ALL_MONOMERS || rightTerminal === ALL_MONOMERS)
90
+ protected getLinkedPositions(helm: string): [number, number] {
91
+ return [1, getNumberOfMonomers(helm)];
92
+ }
93
+
94
+ protected getTransformedHelm(helm: string): string {
95
+ const positions = this.getLinkedPositions(helm);
96
+ const source = {monomerPosition: positions[0], attachmentPoint: 1};
97
+ const target = {monomerPosition: positions[1], attachmentPoint: 3};
98
+ return getHelmCycle(helm, source, target);
99
+ }
100
+ }
101
+
102
+ class TransformationNO extends TransformationBase {
103
+ constructor(helmColumn: DG.Column<string>, meta: MetaData) {
104
+ super(helmColumn, meta);
105
+ }
106
+
107
+ protected hasTerminals(helm: string): boolean {
108
+ if (this.leftTerminal === ALL_MONOMERS || this.rightTerminal === ALL_MONOMERS)
47
109
  return true;
48
- return helm.includes(LEFT_HELM_WRAPPER + leftTerminal) && helm.includes(rightTerminal + RIGHT_HELM_WRAPPER);
110
+ return helm.includes(LEFT_HELM_WRAPPER + this.leftTerminal) &&
111
+ helm.includes(this.rightTerminal + RIGHT_HELM_WRAPPER);
49
112
  }
50
113
 
51
- function applyModification(helm: string): string {
52
- if (cyclizationType === CYCLIZATION_TYPE.R3)
53
- return applyR3Modification(helm);
54
- else
55
- return applyNOModification(helm);
114
+ protected getLinkedPositions(helm: string): [number, number] {
115
+ return [1, getNumberOfMonomers(helm)];
56
116
  }
57
117
 
58
- function applyNOModification(helm: string): string {
59
- if (hasNOTerminals(helm, leftTerminal, rightTerminal))
60
- return getNOCycle(helm, getLinkedNOPositions(helm));
61
- return helm;
118
+ protected getTransformedHelm(helm: string): string {
119
+ const positions = this.getLinkedPositions(helm);
120
+ const source = {monomerPosition: positions[0], attachmentPoint: 1};
121
+ const target = {monomerPosition: positions[1], attachmentPoint: 2};
122
+ return getHelmCycle(helm, source, target);
62
123
  }
124
+ }
63
125
 
64
- function applyR3Modification(helm: string): string {
65
- if (hasR3Terminals(helm, leftTerminal, rightTerminal))
66
- return getR3Cycle(helm, getLinkedR3Positions(helm));
67
- return helm;
126
+ class TransformationR3 extends TransformationBase {
127
+ constructor(helmColumn: DG.Column<string>, meta: MetaData) {
128
+ super(helmColumn, meta);
68
129
  }
69
130
 
70
- function getLinkedR3Positions(helm: string): [number, number] {
131
+ protected hasTerminals(helm: string): boolean {
132
+ if (this.leftTerminal === ALL_MONOMERS || this.rightTerminal === ALL_MONOMERS)
133
+ return true;
134
+ const positions = this.getLinkedPositions(helm);
135
+ return positions.every((el) => el > 0);
136
+ }
137
+
138
+ protected getLinkedPositions(helm: string): [number, number] {
71
139
  const seq = helm.replace(LEFT_HELM_WRAPPER, '').replace(RIGHT_HELM_WRAPPER, '');
72
140
  const monomers = seq.split('.');
73
- const start = monomers.findIndex((el) => el === leftTerminal);
74
- const end = monomers.findIndex((el, idx) => el === rightTerminal && idx > start);
141
+ const start = monomers.findIndex((el) => el === this.leftTerminal);
142
+ const end = monomers.findIndex((el, idx) => el === this.rightTerminal && idx > start);
75
143
  return [start + 1, end + 1];
76
144
  }
77
145
 
78
- function getLinkedNOPositions(helm: string): [number, number] {
79
- const seq = helm.replace(LEFT_HELM_WRAPPER, '').replace(RIGHT_HELM_WRAPPER, '');
80
- const lastMonomerNumber = seq.split('.').length;
81
- return [1, lastMonomerNumber];
146
+ protected getTransformedHelm(helm: string): string {
147
+ const positions = this.getLinkedPositions(helm);
148
+ const source = {monomerPosition: positions[0], attachmentPoint: 3};
149
+ const target = {monomerPosition: positions[1], attachmentPoint: 3};
150
+ return getHelmCycle(helm, source, target);
82
151
  }
152
+ }
83
153
 
84
- function getR3Cycle(helm: string, position: [number, number]): string {
85
- const result = helm.replace(RIGHT_HELM_WRAPPER,
86
- `}$PEPTIDE1,PEPTIDE1,${position[0]}:R3-${position[1]}:R3${'$'.repeat(6)}`);
87
- return result;
88
- }
154
+ class PolymerTransformation {
155
+ private constructor() {}
89
156
 
90
- function getNOCycle(helm: string, position: [number, number]): string {
91
- const result = helm.replace(RIGHT_HELM_WRAPPER,
92
- `}$PEPTIDE1,PEPTIDE1,${position[1]}:R2-${position[0]}:R1${'$'.repeat(6)}`);
93
- return result;
157
+ static getInstance(molColumn: DG.Column<string>, meta: MetaData): TransformationBase {
158
+ const cyclizationType = meta.cyclizationType;
159
+ return (cyclizationType === CYCLIZATION_TYPE.R3) ? new TransformationR3(molColumn, meta) :
160
+ (cyclizationType === CYCLIZATION_TYPE.NCys) ? new TransformationNCys(molColumn, meta) :
161
+ new TransformationNO(molColumn, meta);
94
162
  }
163
+ }
95
164
 
165
+ function getNumberOfMonomers(helm: string): number {
166
+ const seq = helm.replace(LEFT_HELM_WRAPPER, '').replace(RIGHT_HELM_WRAPPER, '');
167
+ return seq.split('.').length;
168
+ }
169
+
170
+ function getHelmCycle(helm: string, source: ConnectionData, target: ConnectionData): string {
171
+ return helm.replace(RIGHT_HELM_WRAPPER,
172
+ `}$PEPTIDE1,PEPTIDE1,${
173
+ source.monomerPosition
174
+ }:R${
175
+ source.attachmentPoint
176
+ }-${
177
+ target.monomerPosition
178
+ }:R${
179
+ target.attachmentPoint
180
+ }${'$'.repeat(6)}`
181
+ );
182
+ }
183
+
184
+ async function addTransformedColumn(
185
+ molColumn: DG.Column<string>, meta: MetaData
186
+ ): Promise<void> {
96
187
  const df = molColumn.dataFrame;
97
188
  const uh = UnitsHandler.getOrCreate(molColumn);
98
189
  const sourceHelmCol = uh.convert(NOTATION.HELM);
99
- const targetList = sourceHelmCol.toList().map((helm) => applyModification(helm));
100
- const colName = df.columns.getUnusedName('Cyclization(' + molColumn.name + ')');
190
+ const pt = PolymerTransformation.getInstance(sourceHelmCol, meta);
191
+ const targetList = pt.transform();
192
+ const colName = df.columns.getUnusedName(`${meta.transformationType}(` + molColumn.name + ')');
101
193
  const targetHelmCol = DG.Column.fromList('string', colName, targetList);
102
194
 
103
195
  addCommonTags(targetHelmCol);
@@ -109,50 +201,91 @@ async function enumerator(
109
201
  }
110
202
 
111
203
  export function _getEnumeratorWidget(molColumn: DG.Column): DG.Widget {
112
- function updateMonomerList(): void {
113
- console.log('hi from update:');
114
- if (cyclizationTypeChoice.value === cyclizationTypes[0]) {
115
- console.log('hi from first branch:');
116
- monomerList = [ALL_MONOMERS].concat(
204
+ function getMonomerList(cyclizationType: CYCLIZATION_TYPE): string[] {
205
+ if (cyclizationType === cyclizationTypes[0]) {
206
+ return [ALL_MONOMERS].concat(
117
207
  monomerLib.getMonomerSymbolsByType(HELM_POLYMER_TYPE.PEPTIDE)
118
208
  );
119
- } else if (cyclizationTypeChoice.value === cyclizationTypes[1]) {
120
- monomerList = [ALL_MONOMERS].concat(
209
+ }
210
+ if (cyclizationType === cyclizationTypes[1]) {
211
+ return [ALL_MONOMERS].concat(
121
212
  monomerLib.getMonomerSymbolsByRGroup(3, HELM_POLYMER_TYPE.PEPTIDE)
122
213
  );
123
- console.log('hi from second branch:');
124
214
  }
125
- leftTerminalChoice = ui.choiceInput('R1:', monomerList[0], monomerList);
126
- rightTerminalChoice = ui.choiceInput('R2:', monomerList[0], monomerList);
215
+ return ['C'];
216
+ }
217
+
218
+ function updateMonomerList(): void {
219
+ if (cyclizationTypeChoice.value === CYCLIZATION_TYPE.NCys) {
220
+ monomerList1 = getMonomerList(CYCLIZATION_TYPE.NO);
221
+ monomerList2 = getMonomerList(CYCLIZATION_TYPE.NCys);
222
+ } else {
223
+ monomerList1 = getMonomerList(cyclizationTypeChoice.value as CYCLIZATION_TYPE);
224
+ monomerList2 = [...monomerList1];
225
+ }
226
+
227
+ leftTerminalChoice = ui.choiceInput(
228
+ 'R1:', monomerList1[0], monomerList1, () => { onRGroupValueChange.next(); }
229
+ );
230
+ rightTerminalChoice = ui.choiceInput('R2:', monomerList2[0], monomerList2, () => { onRGroupValueChange.next(); });
231
+ onRGroupValueChange.next();
127
232
  ui.empty(terminalControls);
128
233
  [leftTerminalChoice, rightTerminalChoice].forEach((el) => { terminalControls.appendChild(el.root); });
129
234
  }
130
235
 
131
236
  const onCyclizationChoice = new rxjs.Subject<string>();
132
- onCyclizationChoice.subscribe(() => updateMonomerList());
237
+ const onRGroupValueChange = new rxjs.Subject<string>();
238
+ onCyclizationChoice.subscribe(() => {
239
+ meta.cyclizationType = cyclizationTypeChoice.value!;
240
+ updateMonomerList();
241
+ });
242
+ onRGroupValueChange.subscribe(() => {
243
+ meta.rightTerminal = rightTerminalChoice.value!;
244
+ meta.leftTerminal = leftTerminalChoice.value!;
245
+ });
133
246
 
134
- const modifications = ['Cyclization'];
135
- const modificationChoice = ui.choiceInput('Modification', modifications[0], modifications);
136
247
 
137
- const cyclizationTypes = [CYCLIZATION_TYPE.NO, CYCLIZATION_TYPE.R3];
248
+ const meta = {} as MetaData;
249
+ const transformations = [TRANSFORMATION_TYPE.CYCLIZATION];
250
+ const transformationChoice = ui.choiceInput(
251
+ 'Modification', transformations[0], transformations, () => meta.transformationType = transformationChoice.value!
252
+ );
253
+
254
+ const cyclizationTypes = [CYCLIZATION_TYPE.NO, CYCLIZATION_TYPE.R3, CYCLIZATION_TYPE.NCys];
138
255
  const cyclizationTypeChoice = ui.choiceInput(
139
256
  'Type', cyclizationTypes[0], cyclizationTypes, () => { onCyclizationChoice.next(); }
140
257
  );
141
258
 
142
259
  const monomerLib = MonomerLibHelper.instance.getBioLib();
143
- let monomerList: string[] = [];
144
- let leftTerminalChoice = ui.choiceInput('R1:', monomerList[0], monomerList);
145
- let rightTerminalChoice = ui.choiceInput('R2:', monomerList[0], monomerList);
260
+ let monomerList1: string[] = [];
261
+ let monomerList2: string[] = [];
262
+ let leftTerminalChoice = ui.choiceInput(
263
+ 'R1:', monomerList1[0], monomerList1, () => {
264
+ meta.leftTerminal = leftTerminalChoice.value!;
265
+ }
266
+ );
267
+ let rightTerminalChoice = ui.choiceInput('R2:', monomerList2[0], monomerList2, () => {
268
+ meta.rightTerminal = rightTerminalChoice.value!;
269
+ });
146
270
  const terminalControls = ui.divV([leftTerminalChoice.root, rightTerminalChoice.root]);
147
271
 
272
+ function updateMeta() {
273
+ meta.cyclizationType = cyclizationTypeChoice.value!;
274
+ meta.leftTerminal = leftTerminalChoice.value!;
275
+ meta.rightTerminal = rightTerminalChoice.value!;
276
+ meta.transformationType = transformationChoice.value!;
277
+ }
278
+
148
279
  updateMonomerList();
149
280
 
281
+ updateMeta();
282
+
150
283
  const btn = ui.bigButton('Run', async () =>
151
- enumerator(molColumn, cyclizationTypeChoice.value!, leftTerminalChoice.value!, rightTerminalChoice.value!)
284
+ addTransformedColumn(molColumn, meta)
152
285
  );
153
286
 
154
287
  const div = ui.div([
155
- modificationChoice,
288
+ transformationChoice,
156
289
  cyclizationTypeChoice,
157
290
  terminalControls,
158
291
  btn
@@ -158,16 +158,10 @@ export class MonomerLib implements IMonomerLib {
158
158
  monomers = monomers.filter((monomer) => {
159
159
  if (!monomer?.rgroups)
160
160
  return false;
161
- console.log('monomer symbol:', monomer.symbol);
162
161
  let criterion = monomer?.rgroups.length >= rGroupNumber;
163
- console.log(`has rGroupNumber ${rGroupNumber}`, criterion);
164
162
  const molfileHandler = MolfileHandler.getInstance(monomer.molfile);
165
- console.log(molfileHandler.atomTypes);
166
163
  const rGroupIndices = findAllIndices(molfileHandler.atomTypes, 'R#');
167
- console.log('rGroup indices', rGroupIndices);
168
- console.log(molfileHandler.pairsOfBondedAtoms);
169
164
  criterion &&= true;
170
- console.log('criterion', criterion);
171
165
  return criterion;
172
166
  });
173
167
  return monomers.map((monomer) => monomer?.symbol!);
@@ -17,7 +17,7 @@ import {delay} from '@datagrok-libraries/utils/src/test';
17
17
  import {debounceTime} from 'rxjs/operators';
18
18
 
19
19
  export class BioSubstructureFilter extends DG.Filter {
20
- bioFilter: FastaFilter | SeparatorFilter | HelmFilter | null = null;
20
+ bioFilter: BioFilterBase | null = null;
21
21
  bitset: DG.BitSet | null = null;
22
22
  loader: HTMLDivElement = ui.loader();
23
23
  onBioFilterChangedSubs?: Subscription;
@@ -75,6 +75,10 @@ export class BioSubstructureFilter extends DG.Filter {
75
75
  let onChangedEvent: any = this.bioFilter.onChanged;
76
76
  onChangedEvent = onChangedEvent.pipe(debounceTime(this._debounceTime));
77
77
  this.onBioFilterChangedSubs = onChangedEvent.subscribe(async (_: any) => await this._onInputChanged());
78
+
79
+ this.subs.push(grok.events.onResetFilterRequest.subscribe((_value: any) => {
80
+ this.bioFilter?.resetFilter();
81
+ }));
78
82
  }
79
83
 
80
84
  detach() {
@@ -123,7 +127,7 @@ export class BioSubstructureFilter extends DG.Filter {
123
127
  } else {
124
128
  this.calculating = true;
125
129
  try {
126
- this.bitset = await this.bioFilter?.substrucrureSearch(this.column!)!;
130
+ this.bitset = await this.bioFilter?.substructureSearch(this.column!)!;
127
131
  this.calculating = false;
128
132
  this.dataFrame?.rows.requestFilter();
129
133
  } finally {
@@ -147,9 +151,11 @@ abstract class BioFilterBase {
147
151
  set substructure(s: string) {
148
152
  }
149
153
 
150
- async substrucrureSearch(_column: DG.Column): Promise<DG.BitSet | null> {
154
+ async substructureSearch(_column: DG.Column): Promise<DG.BitSet | null> {
151
155
  return null;
152
156
  }
157
+
158
+ abstract resetFilter(): void;
153
159
  }
154
160
 
155
161
  class FastaFilter extends BioFilterBase {
@@ -175,8 +181,12 @@ class FastaFilter extends BioFilterBase {
175
181
  this.substructureInput.value = s;
176
182
  }
177
183
 
178
- async substrucrureSearch(column: DG.Column): Promise<DG.BitSet | null> {
179
- return await linearSubstructureSearch(this.substructure, column);
184
+ async substructureSearch(column: DG.Column): Promise<DG.BitSet | null> {
185
+ return linearSubstructureSearch(this.substructure, column);
186
+ }
187
+
188
+ resetFilter(): void {
189
+ this.substructureInput.value = '';
180
190
  }
181
191
  }
182
192
 
@@ -211,8 +221,8 @@ export class SeparatorFilter extends FastaFilter {
211
221
  this.substructureInput.value = s;
212
222
  }
213
223
 
214
- async substrucrureSearch(column: DG.Column): Promise<DG.BitSet | null> {
215
- return await linearSubstructureSearch(this.substructure, column, this.colSeparator);
224
+ async substructureSearch(column: DG.Column): Promise<DG.BitSet | null> {
225
+ return linearSubstructureSearch(this.substructure, column, this.colSeparator);
216
226
  }
217
227
  }
218
228
 
@@ -276,11 +286,17 @@ export class HelmFilter extends BioFilterBase {
276
286
  }
277
287
  }
278
288
 
279
- async substrucrureSearch(column: DG.Column): Promise<DG.BitSet | null> {
289
+ async substructureSearch(column: DG.Column): Promise<DG.BitSet | null> {
280
290
  ui.setUpdateIndicator(this._filterPanel, true);
281
291
  await delay(10);
282
292
  const res = await helmSubstructureSearch(this.substructure, column);
283
293
  ui.setUpdateIndicator(this._filterPanel, false);
284
294
  return res;
285
295
  }
296
+
297
+ resetFilter(): void {
298
+ console.debug('Bio: HelmFilter.resetFilter()');
299
+ this.helmSubstructure = '';
300
+ this.updateFilterPanel(this.substructure);
301
+ }
286
302
  }