@datagrok/bio 1.7.22 → 1.7.25

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.
@@ -43,14 +43,18 @@ category('renderers', () => {
43
43
  `semType="${srcSeqCol!.semType}", units="${srcSeqCol!.getTag(DG.TAGS.UNITS)}", ` +
44
44
  `cell.renderer="${srcSeqCol!.getTag('cell.renderer')}"`);
45
45
  expect(srcSeqCol!.semType, DG.SEMTYPE.MACROMOLECULE);
46
- expect(srcSeqCol!.getTag(DG.TAGS.UNITS), 'fasta:SEQ:PT');
46
+ expect(srcSeqCol!.getTag(DG.TAGS.UNITS), 'fasta');
47
+ expect(srcSeqCol!.getTag('aligned'), 'SEQ');
48
+ expect(srcSeqCol!.getTag('alphabet'), 'PT');
47
49
  expect(srcSeqCol!.getTag('cell.renderer'), 'Macromolecule');
48
50
 
49
51
  const msaSeqCol: DG.Column | null = await multipleSequenceAlignmentAny(df, srcSeqCol!);
50
52
  tv.grid.invalidate();
51
53
 
52
54
  expect(msaSeqCol!.semType, DG.SEMTYPE.MACROMOLECULE);
53
- expect(msaSeqCol!.getTag(DG.TAGS.UNITS), 'fasta:SEQ.MSA:PT');
55
+ expect(msaSeqCol!.getTag(DG.TAGS.UNITS), 'fasta');
56
+ expect(msaSeqCol!.getTag('aligned'), 'SEQ.MSA');
57
+ expect(msaSeqCol!.getTag('alphabet'), 'PT');
54
58
  expect(msaSeqCol!.getTag('cell.renderer'), 'Macromolecule');
55
59
 
56
60
  dfList.push(df);
@@ -2,12 +2,11 @@ import * as C from './constants';
2
2
  import * as DG from 'datagrok-api/dg';
3
3
  import {AminoacidsPalettes} from '@datagrok-libraries/bio/src/aminoacids';
4
4
  import {NucleotidesPalettes} from '@datagrok-libraries/bio/src/nucleotides';
5
- import {UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
5
+ import {UnknownSeqPalette, UnknownSeqPalettes} from '@datagrok-libraries/bio/src/unknown';
6
6
  import {SplitterFunc, WebLogo} from '@datagrok-libraries/bio/src/viewers/web-logo';
7
7
  import {SeqPalette} from '@datagrok-libraries/bio/src/seq-palettes';
8
8
  import * as ui from 'datagrok-api/ui';
9
9
 
10
- export const lru = new DG.LruCache<any, any>();
11
10
  const undefinedColor = 'rgb(100,100,100)';
12
11
  const grayColor = '#808080';
13
12
 
@@ -44,6 +43,7 @@ export function processSequence(subParts: string[]): [string[], boolean] {
44
43
  return [text, simplified];
45
44
  }
46
45
 
46
+
47
47
  /**
48
48
  * A function that prints a string aligned to left or centered.
49
49
  *
@@ -61,60 +61,58 @@ export function processSequence(subParts: string[]): [string[], boolean] {
61
61
  * @param {boolean} [last=false] Is checker if element last or not.
62
62
  * @return {number} x coordinate to start printing at.
63
63
  */
64
- function printLeftOrCentered(
64
+ export function printLeftOrCentered(
65
65
  x: number, y: number, w: number, h: number,
66
66
  g: CanvasRenderingContext2D, s: string, color = undefinedColor,
67
67
  pivot: number = 0, left = false, transparencyRate: number = 1.0,
68
- separator: string = '', last: boolean = false): number {
68
+ separator: string = '', last: boolean = false, drawStyle: string = 'classic', maxWord: any = {}, maxWordIdx: number = 0, gridCell: any = {}): number {
69
69
  g.textAlign = 'start';
70
70
  const colorPart = s.substring(0);
71
- let grayPart = last ? '' : separator;
71
+ let grayPart = last ? '' : separator;
72
+ if (drawStyle === 'msa') {
73
+ grayPart = '';
74
+ }
72
75
 
73
- const textSize = g.measureText(colorPart + grayPart);
76
+ let textSize: any = g.measureText(colorPart + grayPart);
74
77
  const indent = 5;
75
78
 
76
- const colorTextSize = g.measureText(colorPart);
79
+ let colorTextSize = g.measureText(colorPart).width;
77
80
  const dy = (textSize.fontBoundingBoxAscent + textSize.fontBoundingBoxDescent) / 2;
81
+ textSize = textSize.width;
82
+ if (drawStyle === 'msa') {
83
+ if (colorTextSize > maxWord) {
84
+ maxWord[maxWordIdx] = colorTextSize;
85
+ gridCell.cell.column.temp = maxWord;
86
+ }
87
+ colorTextSize = maxWord[maxWordIdx];
88
+ textSize = maxWord[maxWordIdx];
89
+ }
78
90
 
79
91
  function draw(dx1: number, dx2: number): void {
80
92
  g.fillStyle = color;
81
93
  g.globalAlpha = transparencyRate;
82
94
  g.fillText(colorPart, x + dx1, y + dy);
83
- g.fillStyle = grayColor;
84
- g.fillText(grayPart, x + dx2, y + dy);
95
+ if (drawStyle === 'classic') {
96
+ g.fillStyle = grayColor;
97
+ g.fillText(grayPart, x + dx2, y + dy);
98
+ }
85
99
  }
86
100
 
87
-
88
- if (left || textSize.width > w) {
89
- draw(indent, indent + colorTextSize.width);
90
- return x + colorTextSize.width + g.measureText(grayPart).width;
101
+ if (left || textSize > w) {
102
+ draw(indent, indent + colorTextSize);
103
+ return x + colorTextSize + g.measureText(grayPart).width;
91
104
  } else {
92
- const dx = (w - textSize.width) / 2;
93
- draw(dx, dx + colorTextSize.width);
94
- return x + dx + colorTextSize.width;
105
+ const dx = (w - textSize) / 2;
106
+ draw(dx, dx + colorTextSize);
107
+ return x + dx + colorTextSize;
95
108
  }
96
109
  }
97
110
 
98
- function findMonomers(helmString: string) {
99
- //@ts-ignore
100
- const types = Object.keys(org.helm.webeditor.monomerTypeList());
101
- const monomers: any = [];
102
- const monomer_names: any = [];
103
- for (var i = 0; i < types.length; i++) {
104
- //@ts-ignore
105
- monomers.push(new scil.helm.Monomers.getMonomerSet(types[i]));
106
- Object.keys(monomers[i]).forEach(k => {
107
- monomer_names.push(monomers[i][k].id);
108
- });
109
- }
110
- const split_string = WebLogo.splitterAsHelm(helmString);
111
- return new Set(split_string.filter(val => !monomer_names.includes(val)));
112
- }
113
111
 
114
112
  export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
115
- get name(): string { return 'macromoleculeSequence'; }
113
+ get name(): string { return 'sequence'; }
116
114
 
117
- get cellType(): string { return C.SEM_TYPES.Macro_Molecule; }
115
+ get cellType(): string { return 'sequence'; }
118
116
 
119
117
  get defaultHeight(): number { return 30; }
120
118
 
@@ -139,87 +137,74 @@ export class MacromoleculeSequenceCellRenderer extends DG.GridCellRenderer {
139
137
  const grid = gridCell.gridRow !== -1 ? gridCell.grid : undefined;
140
138
  const cell = gridCell.cell;
141
139
  const tag = gridCell.cell.column.getTag(DG.TAGS.UNITS);
142
- if (tag === 'HELM') {
143
- const monomers = findMonomers(cell.value);
144
- if (monomers.size == 0) {
145
- const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
146
- host.setAttribute('dataformat', 'helm');
147
- host.setAttribute('data', gridCell.cell.value);
148
- gridCell.element = host;
149
- //@ts-ignore
150
- const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
151
- const formula = canvas.getFormula(true);
152
- if (!formula) {
153
- gridCell.element = ui.divText(gridCell.cell.value, {style: {color: 'red'}});
154
- }
155
- const molWeight = Math.round(canvas.getMolWeight() * 100) / 100;
156
- const coef = Math.round(canvas.getExtinctionCoefficient(true) * 100) / 100;
157
- const molfile = canvas.getMolfile();
158
- const result = formula + ', ' + molWeight + ', ' + coef + ', ' + molfile;
159
- lru.set(gridCell.cell.value, result);
160
- return;
161
- }
162
- if (monomers.size > 0) {
163
- w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
164
- g.save();
165
- g.beginPath();
166
- g.rect(x, y, w, h);
167
- g.clip();
168
- g.font = '12px monospace';
169
- g.textBaseline = 'top';
170
- let x1 = x;
171
- const s: string = cell.value ?? '';
172
- let subParts: string[] = WebLogo.splitterAsHelm(s);
140
+ const [type, subtype, paletteType] = gridCell.cell.column.getTag(DG.TAGS.UNITS).split(':');
141
+ w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
142
+ g.save();
143
+ g.beginPath();
144
+ g.rect(x, y, w, h);
145
+ g.clip();
146
+ g.font = '12px monospace';
147
+ g.textBaseline = 'top';
148
+ const s: string = cell.value ?? '';
149
+
150
+ //TODO: can this be replaced/merged with splitSequence?
151
+ const units = gridCell.cell.column.getTag(DG.TAGS.UNITS);
152
+
153
+ const palette = getPalleteByType(paletteType);
154
+
155
+ const separator = gridCell.cell.column.getTag('separator') ?? '';
156
+ const splitterFunc: SplitterFunc = WebLogo.getSplitter(units, gridCell.cell.column.getTag('separator'));
157
+
158
+ const columns = gridCell.cell.column.categories;
159
+ let monomerToShortFunction: (amino: string, maxLengthOfMonomer: number) => string = WebLogo.monomerToShort;
160
+ let maxLengthOfMonomer = 8;
161
+
162
+ let maxLengthWords = {};
163
+ // check if gridCell.cell.column.temp is array
164
+ if (gridCell.cell.column.getTag('.calculatedCellRender') !== 'exist') {
165
+ for (let i = 0; i < columns.length; i++) {
166
+ let subParts: string[] = splitterFunc(columns[i]);
173
167
  subParts.forEach((amino, index) => {
174
- let color = monomers.has(amino) ? 'red' : grayColor;
175
- g.fillStyle = undefinedColor;
176
- let last = index === subParts.length - 1;
177
- x1 = printLeftOrCentered(x1, y, w, h, g, amino, color, 0, true, 1.0, '/', last);
168
+ //@ts-ignore
169
+ let textSizeWidth = g.measureText(monomerToShortFunction(amino, maxLengthOfMonomer));
170
+ //@ts-ignore
171
+ if (textSizeWidth.width > (maxLengthWords[index] ?? 0)) {
172
+ //@ts-ignore
173
+ maxLengthWords[index] = textSizeWidth.width;
174
+ }
178
175
  });
179
- g.restore();
180
- return;
181
176
  }
177
+ gridCell.cell.column.temp = maxLengthWords;
178
+ gridCell.cell.column.setTag('.calculatedCellRender', 'exist');
182
179
  } else {
183
- const [type, subtype, paletteType] = gridCell.cell.column.getTag(DG.TAGS.UNITS).split(':');
184
- w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
185
- g.save();
186
- g.beginPath();
187
- g.rect(x, y, w, h);
188
- g.clip();
189
- g.font = '12px monospace';
190
- g.textBaseline = 'top';
191
- const s: string = cell.value ?? '';
192
-
193
- //TODO: can this be replaced/merged with splitSequence?
194
- const units = gridCell.cell.column.getTag(DG.TAGS.UNITS);
195
-
196
- const palette = getPalleteByType(paletteType);
197
-
198
- const separator = gridCell.cell.column.getTag('separator') ?? '';
199
- const splitterFunc: SplitterFunc = WebLogo.getSplitter(units, gridCell.cell.column.getTag('separator'));
200
-
201
- const subParts: string[] = splitterFunc(cell.value);
202
- // console.log(subParts);
203
- let x1 = x;
204
- let color = undefinedColor;
205
- subParts.forEach((amino, index) => {
206
- color = palette.get(amino);
207
- g.fillStyle = undefinedColor;
208
- let last = index === subParts.length - 1;
209
- x1 = printLeftOrCentered(x1, y, w, h, g, amino, color, 0, true, 1.0, separator, last);
210
- });
211
-
212
- g.restore();
213
- return;
180
+ maxLengthWords = gridCell.cell.column.temp;
214
181
  }
182
+
183
+ const subParts: string[] = splitterFunc(cell.value);
184
+ let x1 = x;
185
+ let color = undefinedColor;
186
+ // get max length word in subParts
187
+ let tagUnits = gridCell.cell.column.getTag(DG.TAGS.UNITS);
188
+ let drawStyle = 'classic';
189
+ if (tagUnits.includes('MSA')) {
190
+ drawStyle = 'msa';
191
+ }
192
+ subParts.forEach((amino, index) => {
193
+ color = palette.get(amino);
194
+ g.fillStyle = undefinedColor;
195
+ let last = index === subParts.length - 1;
196
+ x1 = printLeftOrCentered(x1, y, w, h, g, monomerToShortFunction(amino, maxLengthOfMonomer), color, 0, true, 1.0, separator, last, drawStyle, maxLengthWords, index, gridCell);
197
+ });
198
+
199
+ g.restore();
200
+ return;
215
201
  }
216
202
  }
217
203
 
204
+ export class MonomerCellRenderer extends DG.GridCellRenderer {
205
+ get name(): string {return 'MonomerCR';}
218
206
 
219
- export class AminoAcidsCellRenderer extends DG.GridCellRenderer {
220
- get name(): string {return 'aminoAcidsCR';}
221
-
222
- get cellType(): string {return C.SEM_TYPES.AMINO_ACIDS;}
207
+ get cellType(): string {return C.SEM_TYPES.MONOMER;}
223
208
 
224
209
  get defaultHeight(): number {return 15;}
225
210
 
@@ -256,10 +241,10 @@ export class AminoAcidsCellRenderer extends DG.GridCellRenderer {
256
241
  }
257
242
  }
258
243
 
259
- export class AlignedSequenceDifferenceCellRenderer extends DG.GridCellRenderer {
260
- get name(): string {return 'alignedSequenceDifferenceCR';}
244
+ export class MacromoleculeDifferenceCellRenderer extends DG.GridCellRenderer {
245
+ get name(): string {return 'MacromoleculeDifferenceCR';}
261
246
 
262
- get cellType(): string {return C.SEM_TYPES.ALIGNED_SEQUENCE_DIFFERENCE;}
247
+ get cellType(): string {return C.SEM_TYPES.MACROMOLECULE_DIFFERENCE;}
263
248
 
264
249
  get defaultHeight(): number {return 30;}
265
250
 
@@ -295,23 +280,28 @@ export class AlignedSequenceDifferenceCellRenderer extends DG.GridCellRenderer {
295
280
  //TODO: can this be replaced/merged with splitSequence?
296
281
  const [s1, s2] = s.split('#');
297
282
  const separator = gridCell.tableColumn!.tags[C.TAGS.SEPARATOR];
298
- const subParts1 = s1.split(separator);
299
- const subParts2 = s2.split(separator);
283
+ const units: string = gridCell.tableColumn!.tags[DG.TAGS.UNITS];
284
+ const splitter = WebLogo.getSplitter(units, separator);
285
+ const subParts1 = splitter(s1);
286
+ const subParts2 = splitter(s2);
300
287
  const [text] = processSequence(subParts1);
301
288
  const textSize = g.measureText(text.join(''));
302
289
  let updatedX = Math.max(x, x + (w - (textSize.width + subParts1.length * 4)) / 2);
303
290
  // 28 is the height of the two substitutions on top of each other + space
304
291
  const updatedY = Math.max(y, y + (h - 28) / 2);
305
292
 
306
- const palette = getPalleteByType(gridCell.tableColumn!.tags[C.TAGS.ALPHABET]);
293
+ let palette: SeqPalette = UnknownSeqPalettes.Color;
294
+ if (units != 'HELM')
295
+ palette = getPalleteByType(units.substring(units.length - 2));
296
+
297
+ const vShift = 7;
307
298
  for (let i = 0; i < subParts1.length; i++) {
308
299
  const amino1 = subParts1[i];
309
300
  const amino2 = subParts2[i];
310
301
  const color1 = palette.get(amino1);
311
- const color2 = palette.get(amino2);
312
302
 
313
303
  if (amino1 != amino2) {
314
- const vShift = 7;
304
+ const color2 = palette.get(amino2);
315
305
  const subX0 = printLeftOrCentered(updatedX, updatedY - vShift, w, h, g, amino1, color1, 0, true);
316
306
  const subX1 = printLeftOrCentered(updatedX, updatedY + vShift, w, h, g, amino2, color2, 0, true);
317
307
  updatedX = Math.max(subX1, subX0);
@@ -23,12 +23,11 @@ export enum TAGS {
23
23
  }
24
24
 
25
25
  export enum SEM_TYPES {
26
- AMINO_ACIDS = 'aminoAcids',
27
- ALIGNED_SEQUENCE = 'alignedSequence',
28
- ALIGNED_SEQUENCE_DIFFERENCE = 'alignedSequenceDifference',
26
+ MONOMER = 'Monomer',
27
+ MACROMOLECULE_DIFFERENCE = 'MacromoleculeDifference',
29
28
  ACTIVITY = 'activity',
30
29
  ACTIVITY_SCALED = 'activityScaled',
31
- Macro_Molecule = 'Macromolecule',
30
+ MACROMOLECULE = 'Macromolecule',
32
31
  }
33
32
 
34
33
  export const STATS = 'stats';
@@ -49,7 +49,17 @@ export function convert(col: DG.Column): void {
49
49
  if (convertDialog == null) {
50
50
  convertDialog = ui.dialog('Convert sequence notation')
51
51
  .add(ui.div([
52
- ui.h1('Current notation: ' + currentNotation),
52
+ ui.divText(
53
+ 'Current notation: ' + currentNotation,
54
+ {
55
+ style: {
56
+ 'text-align': 'center',
57
+ 'font-weight': 'bold',
58
+ 'font-size': '14px',
59
+ 'padding': '5px',
60
+ }
61
+ }
62
+ ),
53
63
  targetNotationInput.root,
54
64
  separatorInput.root
55
65
  ]))
@@ -59,7 +69,7 @@ export function convert(col: DG.Column): void {
59
69
 
60
70
  await convertDo(col, targetNotation, separator);
61
71
  })
62
- .show();
72
+ .show({x: 350, y: 100});
63
73
 
64
74
  convertDialogSubs.push(convertDialog.onClose.subscribe((value) => {
65
75
  convertDialogSubs.forEach((s) => { s.unsubscribe(); });
@@ -8,7 +8,7 @@ export async function sequenceGetSimilarities(col: DG.Column, seq: string): Prom
8
8
  const stringArray = col.toList();
9
9
  const distances = new Array(stringArray.length).fill(0.0);
10
10
  for (let i = 0; i < stringArray.length; ++i)
11
- distances[i] = getSimilarityFromDistance(AvailableMetrics['String']['Levenshtein'](stringArray[i], seq));
11
+ distances[i] = stringArray[i] ? getSimilarityFromDistance(AvailableMetrics['String']['Levenshtein'](stringArray[i], seq)) : 0;
12
12
  return DG.Column.fromList(DG.COLUMN_TYPE.FLOAT, 'distances', distances);
13
13
  }
14
14
 
@@ -50,6 +50,9 @@ export class VdRegionsViewer extends DG.JsViewer {
50
50
  public chains: string[];
51
51
  public sequenceColumnNamePostfix: string;
52
52
 
53
+ public skipEmptyPositions: boolean;
54
+
55
+
53
56
  public get df(): DG.DataFrame {
54
57
  return this.dataFrame;
55
58
  }
@@ -72,6 +75,8 @@ export class VdRegionsViewer extends DG.JsViewer {
72
75
  this.chains = this.stringList('chains', ['Heavy', 'Light'],
73
76
  {choices: ['Heavy', 'Light']});
74
77
  this.sequenceColumnNamePostfix = this.string('sequenceColumnNamePostfix', 'chain sequence');
78
+
79
+ this.skipEmptyPositions = this.bool('skipEmptyPositions', false);
75
80
  }
76
81
 
77
82
  public async init() {
@@ -119,6 +124,17 @@ export class VdRegionsViewer extends DG.JsViewer {
119
124
  break;
120
125
  case 'sequenceColumnNamePostfix':
121
126
  break;
127
+ case 'skipEmptyPositions':
128
+ // for (let orderI = 0; orderI < this.logos.length; orderI++) {
129
+ // for (let chainI = 0; chainI < this.chains.length; chainI++) {
130
+ // const chain: string = this.chains[chainI];
131
+ // this.logos[orderI][chain].setOptions({skipEmptyPositions: this.skipEmptyPositions});
132
+ // }
133
+ // }
134
+ // this.calcSize();
135
+ await this.destroyView();
136
+ await this.buildView();
137
+ break;
122
138
  }
123
139
  }
124
140
  }
@@ -188,6 +204,7 @@ export class VdRegionsViewer extends DG.JsViewer {
188
204
  startPositionName: region!.positionStartName,
189
205
  endPositionName: region!.positionEndName,
190
206
  fixWidth: true,
207
+ skipEmptyPositions: this.skipEmptyPositions,
191
208
  })) as unknown as WebLogo;
192
209
  }
193
210
  // WebLogo creation fires onRootSizeChanged event even before control being added to this.logos