@datagrok/sequence-translator 1.2.9 → 1.3.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 (34) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/package-test.js +1 -1
  3. package/dist/package-test.js.map +1 -1
  4. package/dist/package.js +1 -1
  5. package/dist/package.js.map +1 -1
  6. package/files/polytool-rules/rules_example.json +34 -0
  7. package/package.json +4 -3
  8. package/src/apps/pattern/model/event-bus.ts +23 -6
  9. package/src/apps/pattern/model/translator.ts +27 -1
  10. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +13 -3
  11. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +4 -3
  12. package/src/apps/pattern/view/components/load-block-controls.ts +4 -2
  13. package/src/apps/pattern/view/svg-utils/const.ts +15 -1
  14. package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
  15. package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
  16. package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
  17. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +4 -5
  18. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +16 -4
  19. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +32 -377
  20. package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
  21. package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
  22. package/src/apps/translator/view/ui.ts +1 -1
  23. package/src/package.ts +22 -6
  24. package/src/polytool/const.ts +3 -15
  25. package/src/polytool/pt-conversion.ts +307 -0
  26. package/src/polytool/pt-dialog.ts +115 -0
  27. package/src/polytool/pt-enumeration.ts +127 -0
  28. package/src/polytool/pt-rules.ts +73 -0
  29. package/src/polytool/utils.ts +7 -0
  30. package/src/tests/helm-to-nucleotides.ts +0 -5
  31. package/tsconfig.json +1 -1
  32. package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +0 -498
  33. package/src/polytool/transformation.ts +0 -326
  34. package/src/polytool/ui.ts +0 -59
@@ -0,0 +1,335 @@
1
+ import {NUCLEOTIDES} from '../../../common/model/const';
2
+ import {STRAND, STRANDS, TERMINUS} from '../../model/const';
3
+ import {PatternConfiguration} from '../../model/types';
4
+ import {isOverhangNucleotide} from '../../model/utils';
5
+ import {SVG_CIRCLE_SIZES, SVG_ELEMENT_COLORS, SVG_TEXT_FONT_SIZES} from './const';
6
+ import {SVGBlockBase} from './svg-block-base';
7
+ import {SVGElementFactory} from './svg-element-factory';
8
+ import {TextDimensionsCalculator} from './text-dimensions-calculator';
9
+ import {computeTextColorForNucleobaseLabel, getNucleobaseColorFromStyleMap, getNucleobaseLabelForCircle} from './utils';
10
+
11
+ const NUMERIC_LABEL_PADDING = 5;
12
+ const SENSE_STRAND_HEIGHT = SVG_TEXT_FONT_SIZES.NUCLEOBASE +
13
+ NUMERIC_LABEL_PADDING + SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER;
14
+ const SENSE_STRAND_PADDING = 10;
15
+ const LEFT_LABEL_WIDTH = 55;
16
+ const SENSE_STRAND_HORIZONTAL_SHIFT = SENSE_STRAND_PADDING + LEFT_LABEL_WIDTH;
17
+ const RIGHT_LABEL_WIDTH = 20;
18
+
19
+ export class StrandsBlock extends SVGBlockBase {
20
+ private strands: SVGBlockBase[];
21
+ private labels: SVGBlockBase[];
22
+ constructor(
23
+ svgElementFactory: SVGElementFactory,
24
+ config: PatternConfiguration,
25
+ yShift: number
26
+ ) {
27
+ super(svgElementFactory, config, yShift);
28
+ const strandTypes = STRANDS.filter((strandType) => config.nucleotideSequences[strandType].length > 0);
29
+
30
+ this.strands = strandTypes
31
+ .map((strand) => new SingleStrandBlock(this.svgElementFactory, config, yShift, strand));
32
+
33
+ this.labels = strandTypes.map(
34
+ (strandType, idx) =>
35
+ new StrandLabel(this.svgElementFactory, config, yShift, strandType, this.strands[idx] as SingleStrandBlock)
36
+ );
37
+ }
38
+
39
+ get svgElements(): SVGElement[] {
40
+ const elements = [
41
+ ...this.strands,
42
+ ...this.labels
43
+ ].map((block) => block.svgElements).flat();
44
+ return elements;
45
+ }
46
+
47
+ getContentWidth(): number {
48
+ return Math.max(...this.labels.map((labelBlock) => labelBlock.getContentWidth()));
49
+ }
50
+
51
+ getContentHeight(): number {
52
+ const result = this.strands
53
+ .reduce((acc, strand) => acc + strand.getContentHeight(), 0);
54
+ return result;
55
+ }
56
+ }
57
+
58
+ class SingleStrandBlock extends SVGBlockBase {
59
+ private _svgElements: SVGElement[];
60
+ private nucleotideNumericLabels: (number | null)[];
61
+ constructor(
62
+ protected svgElementFactory: SVGElementFactory,
63
+ protected config: PatternConfiguration,
64
+ protected yShift: number,
65
+ private strand: STRAND
66
+ ) {
67
+ super(svgElementFactory, config, yShift);
68
+
69
+ // WARNING: should be computed before creating circles
70
+ this.nucleotideNumericLabels = this.computeNucleotideNumericLabels();
71
+
72
+ if (this.strand === STRAND.ANTISENSE) {
73
+ this.config.phosphorothioateLinkageFlags[this.strand].reverse();
74
+ this.config.nucleotideSequences[this.strand].reverse();
75
+ this.nucleotideNumericLabels.reverse();
76
+ }
77
+
78
+ this._svgElements = [
79
+ this.createStrandCircles(),
80
+ this.createPTOLinkageStars(),
81
+ ].flat();
82
+ }
83
+
84
+ private computeNucleotideNumericLabels(): (number | null)[] {
85
+ let index = 0;
86
+ const nucleotides = this.config.nucleotideSequences[this.strand];
87
+ const indices = nucleotides.map((nucleotide) => {
88
+ if (isOverhangNucleotide(nucleotide)) return null;
89
+ index++;
90
+ return index;
91
+ });
92
+ return indices;
93
+ }
94
+
95
+ get svgElements(): SVGElement[] {
96
+ return this._svgElements;
97
+ }
98
+
99
+ private getStrandCircleYShift(): number {
100
+ return getStrandCircleYShift(this.strand, this.yShift);
101
+ }
102
+
103
+ private createStrandCircles(): SVGElement[] {
104
+ const defaultShift = {
105
+ x: SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS + SENSE_STRAND_HORIZONTAL_SHIFT,
106
+ y: this.getStrandCircleYShift()
107
+ };
108
+
109
+ const nucleotides = this.config.nucleotideSequences[this.strand];
110
+
111
+ const elements = nucleotides
112
+ .map((nucleotide, index) => this.createNucleotideElementGroup(nucleotide, index, defaultShift)).flat();
113
+
114
+ return elements;
115
+ }
116
+
117
+ private createNucleotideElementGroup(
118
+ nucleotide: string,
119
+ index: number,
120
+ defaultShift: {x: number, y: number}
121
+ ): SVGElement[] {
122
+ const circleElements = this.createNucleotideCircleElements(nucleotide, index, defaultShift);
123
+ const numericLabel = this.config.nucleotidesWithNumericLabels.includes(nucleotide) ?
124
+ this.createNucleotideNumericLabel(index, defaultShift) : null;
125
+
126
+ return [...circleElements, numericLabel].filter((element) => element !== null) as SVGElement[];
127
+ }
128
+
129
+ private createNucleotideCircleElements(
130
+ nucleotide: string,
131
+ index: number,
132
+ defaultShift: {x: number, y: number}
133
+ ): (SVGElement | null)[] {
134
+ const color = getNucleobaseColorFromStyleMap(nucleotide);
135
+ const centerPosition = {...defaultShift, x: defaultShift.x + index * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER};
136
+
137
+ const circle = this.svgElementFactory
138
+ .createCircleElement(centerPosition, SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS, color);
139
+
140
+ const nonModifiedNucleotideLetterLabel = this.createNucleotideLetterLabel(index, defaultShift, nucleotide);
141
+
142
+ return [circle, nonModifiedNucleotideLetterLabel];
143
+ }
144
+
145
+ private getNucleotideCircleCenterPosition(
146
+ index: number,
147
+ defaultShift: {x: number, y: number}
148
+ ): {x: number, y: number} {
149
+ return {
150
+ x: defaultShift.x + index * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER,
151
+ y: defaultShift.y
152
+ };
153
+ }
154
+
155
+ private createNucleotideLetterLabel(
156
+ index: number,
157
+ defaultShift: {x: number, y: number},
158
+ nucleobase: string
159
+ ): SVGElement | null {
160
+ if (!NUCLEOTIDES.includes(nucleobase))
161
+ return null;
162
+
163
+ const text = getNucleobaseLabelForCircle(nucleobase);
164
+ const color = computeTextColorForNucleobaseLabel(nucleobase);
165
+ // position at the very center of the circle
166
+ const position = this.getPositionForNucleotideLabel(index, defaultShift);
167
+ return this.svgElementFactory.createTextElement(
168
+ text,
169
+ position,
170
+ SVG_TEXT_FONT_SIZES.NUCLEOBASE,
171
+ color
172
+ );
173
+ }
174
+
175
+ /** Returns the position for the letter with its center being at the center of the circle */
176
+ private getPositionForNucleotideLabel(
177
+ index: number,
178
+ defaultShift: {x: number, y: number}
179
+ ): {x: number, y: number} {
180
+ const circleCenter = this.getNucleotideCircleCenterPosition(index, defaultShift);
181
+ const textDimensions = TextDimensionsCalculator.getTextDimensions('A', SVG_TEXT_FONT_SIZES.NUCLEOBASE);
182
+ return {
183
+ x: circleCenter.x - textDimensions.width / 2,
184
+ // the coefficient 1/3 is fine-tuned to make the text look centered
185
+ y: circleCenter.y + textDimensions.height / 3
186
+ };
187
+ }
188
+
189
+ private getNumericLabelYShift(
190
+ defaultShift: {x: number, y: number}
191
+ ): number {
192
+ return this.strand === STRAND.SENSE ?
193
+ defaultShift.y - (SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS + NUMERIC_LABEL_PADDING) :
194
+ defaultShift.y + SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS + NUMERIC_LABEL_PADDING + SVG_TEXT_FONT_SIZES.COMMENT;
195
+ }
196
+
197
+ private createNucleotideNumericLabel(
198
+ index: number,
199
+ defaultShift: {x: number, y: number}
200
+ ): SVGElement | null {
201
+ const label = this.nucleotideNumericLabels[index];
202
+ if (label === null) return null;
203
+
204
+ const width = TextDimensionsCalculator.getTextDimensions(label.toString(), SVG_TEXT_FONT_SIZES.COMMENT).width;
205
+
206
+ const position = {
207
+ x: defaultShift.x + index * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER - width / 2,
208
+ y: this.getNumericLabelYShift(defaultShift)
209
+ };
210
+
211
+ return this.svgElementFactory.createTextElement(
212
+ label.toString(),
213
+ position,
214
+ SVG_TEXT_FONT_SIZES.COMMENT,
215
+ SVG_ELEMENT_COLORS.TEXT
216
+ );
217
+ }
218
+
219
+ private createPTOLinkageStars(): SVGElement[] {
220
+ const ptoFlags = this.config.phosphorothioateLinkageFlags[this.strand];
221
+
222
+ const yShift = this.getStrandCircleYShift() + SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS * 0.8;
223
+
224
+ const elements = ptoFlags
225
+ .map((ptoFlag, index) => {
226
+ if (!ptoFlag) return null;
227
+
228
+ const centerPosition = {
229
+ x: SENSE_STRAND_HORIZONTAL_SHIFT + index * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER,
230
+ y: yShift
231
+ };
232
+
233
+ const color = SVG_ELEMENT_COLORS.LINKAGE_STAR;
234
+ return this.svgElementFactory.createStarElement(centerPosition, color);
235
+ })
236
+ .filter((element) => element !== null) as SVGElement[];
237
+
238
+ return elements;
239
+ }
240
+
241
+ getContentWidth(): number {
242
+ const numberOfMonomers = this.config.nucleotideSequences[this.strand].length;
243
+ return numberOfMonomers * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER;
244
+ }
245
+
246
+ getContentHeight(): number {
247
+ return SENSE_STRAND_HEIGHT + SENSE_STRAND_PADDING;
248
+ }
249
+ }
250
+
251
+ class StrandLabel extends SVGBlockBase {
252
+ private _svgElements: SVGElement[];
253
+ constructor(
254
+ protected svgElementFactory: SVGElementFactory,
255
+ protected config: PatternConfiguration,
256
+ protected yShift: number,
257
+ private strand: STRAND,
258
+ private strandSvgWrapper: SingleStrandBlock
259
+ ) {
260
+ super(svgElementFactory, config, yShift);
261
+ this._svgElements = this.createSVGElements();
262
+ // this.strandSvgWrapper.shiftElements({x: this.getLeftLabelWidth(), y: 0});
263
+ }
264
+
265
+ private createSVGElements(): SVGElement[] {
266
+ const elements = [
267
+ this.createLeftLabel(),
268
+ this.createRightLabel()
269
+ ];
270
+ return elements;
271
+ }
272
+
273
+ private getLeftLabelWidth(): number {
274
+ return LEFT_LABEL_WIDTH;
275
+ }
276
+
277
+ private getRightLabelWidth(): number {
278
+ return RIGHT_LABEL_WIDTH;
279
+ }
280
+
281
+ private createLeftLabel(): SVGTextElement {
282
+ const terminus = this.strand === STRAND.SENSE ? TERMINUS.FIVE_PRIME : TERMINUS.THREE_PRIME;
283
+ const text = `${this.strand}: ${terminus} `;
284
+ const textDimensions = TextDimensionsCalculator.getTextDimensions(text, SVG_TEXT_FONT_SIZES.NUCLEOBASE);
285
+ const position = {
286
+ x: SENSE_STRAND_PADDING,
287
+ y: getStrandCircleYShift(this.strand, this.yShift) + textDimensions.height / 3
288
+ };
289
+
290
+ return this.svgElementFactory.createTextElement(
291
+ text,
292
+ position,
293
+ SVG_TEXT_FONT_SIZES.NUCLEOBASE,
294
+ SVG_ELEMENT_COLORS.TEXT
295
+ );
296
+ }
297
+
298
+ private createRightLabel(): SVGTextElement {
299
+ const terminus = this.strand === STRAND.SENSE ? TERMINUS.THREE_PRIME : TERMINUS.FIVE_PRIME;
300
+ const text = ` ${terminus}`;
301
+ const textDimensions = TextDimensionsCalculator.getTextDimensions(text, SVG_TEXT_FONT_SIZES.NUCLEOBASE);
302
+ const position = {
303
+ x: SENSE_STRAND_HORIZONTAL_SHIFT + this.strandSvgWrapper.getContentWidth() + 5,
304
+ y: getStrandCircleYShift(this.strand, this.yShift) + textDimensions.height / 3
305
+ };
306
+
307
+ return this.svgElementFactory.createTextElement(
308
+ text,
309
+ position,
310
+ SVG_TEXT_FONT_SIZES.NUCLEOBASE,
311
+ SVG_ELEMENT_COLORS.TEXT
312
+ );
313
+ }
314
+
315
+ get svgElements(): SVGElement[] {
316
+ return this._svgElements;
317
+ }
318
+
319
+ getContentWidth(): number {
320
+ return this.strandSvgWrapper.getContentWidth() + this.getLeftLabelWidth() + this.getRightLabelWidth() + SENSE_STRAND_PADDING;
321
+ }
322
+
323
+ getContentHeight(): number {
324
+ return this.strandSvgWrapper.getContentHeight();
325
+ }
326
+ }
327
+
328
+ function getStrandCircleYShift(
329
+ strand: STRAND,
330
+ defaultYShift: number
331
+ ): number {
332
+ return strand === STRAND.SENSE ?
333
+ defaultYShift + NUMERIC_LABEL_PADDING + SVG_TEXT_FONT_SIZES.COMMENT + SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS :
334
+ defaultYShift + SENSE_STRAND_HEIGHT + SENSE_STRAND_PADDING + SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
335
+ }
@@ -0,0 +1,37 @@
1
+ import {PatternConfiguration} from '../../model/types';
2
+ import {SVGElementFactory} from './svg-element-factory';
3
+
4
+ /** Horizontal block within SVG: title, strands, legend */
5
+ export abstract class SVGBlockBase {
6
+ // protected svgElements: SVGElement[] = [];
7
+ constructor(
8
+ protected svgElementFactory: SVGElementFactory,
9
+ protected config: PatternConfiguration,
10
+ protected yShift: number
11
+ ) {}
12
+
13
+ abstract get svgElements(): SVGElement[];
14
+
15
+ abstract getContentHeight(): number;
16
+
17
+ shiftElements(shift: {x: number, y: number}): void {
18
+ this.svgElements.forEach((element) => {
19
+ const transform = element.getAttribute('transform') || '';
20
+ const match = transform.match(/translate\(([^,]+),([^,]+)\)/);
21
+ const x = match ? parseFloat(match[1]) : 0;
22
+ const y = match ? parseFloat(match[2]) : 0;
23
+ const newTransform = `translate(${x + shift.x},${y + shift.y})`;
24
+ element.setAttribute('transform', `${transform} ${newTransform}`);
25
+ });
26
+ }
27
+
28
+ adjustContentWithinGlobalContainer(globalWidth: number): void {
29
+ const contentWidth = this.getContentWidth();
30
+ if (contentWidth < globalWidth) {
31
+ const shift = (globalWidth - contentWidth) / 2;
32
+ this.shiftElements({x: shift, y: 0});
33
+ }
34
+ }
35
+
36
+ abstract getContentWidth(): number;
37
+ }
@@ -1,13 +1,11 @@
1
1
  /* Do not change these import lines to match external modules in webpack configuration */
2
- import * as grok from 'datagrok-api/grok';
3
2
  import * as ui from 'datagrok-api/ui';
4
- import * as DG from 'datagrok-api/dg';
5
3
 
6
- import {PatternConfiguration, StrandType} from '../../model/types';
7
4
  import {EventBus} from '../../model/event-bus';
8
- import {NucleotidePatternSVGRenderer} from './svg-renderer';
5
+ import {PatternConfiguration} from '../../model/types';
9
6
  //@ts-ignore
10
7
  import * as svgExport from 'save-svg-as-png';
8
+ import {NucleotidePatternSVGRenderer} from './svg-renderer';
11
9
 
12
10
  export class SvgDisplayManager {
13
11
  private svgDisplayDiv = ui.div([]);
@@ -16,7 +14,7 @@ export class SvgDisplayManager {
16
14
  private constructor(
17
15
  private eventBus: EventBus
18
16
  ) {
19
- eventBus.patternStateChanged$.subscribe(() => this.updateSvgContainer());
17
+ eventBus.updateSvgContainer$.subscribe(() => this.updateSvgContainer());
20
18
  eventBus.svgSaveRequested$.subscribe(() => this.saveSvgAsPng());
21
19
  }
22
20
 
@@ -33,6 +31,7 @@ export class SvgDisplayManager {
33
31
  }
34
32
 
35
33
  private createSvg(patternConfig: PatternConfiguration) {
34
+ // const renderer = new NucleotidePatternSVGRenderer(patternConfig);
36
35
  const renderer = new NucleotidePatternSVGRenderer(patternConfig);
37
36
  const svg = renderer.renderPattern();
38
37
  return svg;
@@ -13,7 +13,7 @@ export class SVGElementFactory {
13
13
  });
14
14
  }
15
15
 
16
- public createCanvas(width: number, height: number): SVGElement {
16
+ createCanvas(width: number, height: number): SVGElement {
17
17
  const svgElement = this.createElement('svg') as SVGElement;
18
18
  this.setAttributes(svgElement, {
19
19
  id: 'mySvg',
@@ -23,7 +23,7 @@ export class SVGElementFactory {
23
23
  return svgElement;
24
24
  }
25
25
 
26
- public createCircleElement(centerPosition: Position, radius: number, color: string): SVGCircleElement {
26
+ createCircleElement(centerPosition: Position, radius: number, color: string): SVGCircleElement {
27
27
  const circle = this.createElement('circle') as SVGCircleElement;
28
28
  this.setAttributes(circle, {
29
29
  cx: centerPosition.x,
@@ -34,7 +34,7 @@ export class SVGElementFactory {
34
34
  return circle;
35
35
  }
36
36
 
37
- public createTextElement(textContent: string, position: Position, fontSize: number, color: string): SVGTextElement {
37
+ createTextElement(textContent: string, position: Position, fontSize: number, color: string): SVGTextElement {
38
38
  const textElement = this.createElement('text') as SVGTextElement;
39
39
  this.setAttributes(textElement, {
40
40
  'x': position.x,
@@ -48,7 +48,7 @@ export class SVGElementFactory {
48
48
  return textElement;
49
49
  }
50
50
 
51
- public createStarElement(centerPosition: Position, color: string): SVGPolygonElement {
51
+ createStarElement(centerPosition: Position, color: string): SVGPolygonElement {
52
52
  const star = this.createElement('polygon') as SVGPolygonElement;
53
53
  const points = this.computeStarVertexCoordinates(centerPosition);
54
54
  const pointsAttribute = points.map((point) => point.join(',')).join(' ');
@@ -79,4 +79,16 @@ export class SVGElementFactory {
79
79
 
80
80
  return points;
81
81
  }
82
+
83
+ createRectangleElement(topLeftCorner: Position, width: number, height: number, color: string): SVGRectElement {
84
+ const rectangle = this.createElement('rect') as SVGRectElement;
85
+ this.setAttributes(rectangle, {
86
+ x: topLeftCorner.x,
87
+ y: topLeftCorner.y,
88
+ width,
89
+ height,
90
+ fill: color,
91
+ });
92
+ return rectangle;
93
+ }
82
94
  }