@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
@@ -1,498 +0,0 @@
1
- import {NUCLEOTIDES} from '../../../common/model/const';
2
- import {STRAND, STRANDS, STRAND_END, STRAND_TO_END_TERMINUS_MAP, STRAND_ENDS} from '../../model/const';
3
- import {PatternConfiguration} from '../../model/types';
4
- import {SVG_CIRCLE_SIZES, SVG_TEXT_FONT_SIZES, NUMERIC_LABEL_POSITION_OFFSET, DEFAULT_FONT_FAMILY, Y_POSITIONS_FOR_STRAND_ELEMENTS, STRAND_END_LABEL_TEXT} from './const';
5
- import {Position, StrandToNumberMap, StrandEndToNumberMap} from '../types';
6
- import {isOverhangNucleotide} from '../../model/utils';
7
-
8
- export class PatternSVGDimensionsCalculator {
9
- private canvasDimensions: CanvasDimensionCalculator;
10
- private nucleotidePositionCalculator: NucleotidePositionCalculator;
11
- private legendPositionCalculator: LegendPositionCalculator;
12
- private labelPositionCalculator: LabelPositionCalculator;
13
- private linkageStarPositionCalculator: LinkageStarPositionCalculator;
14
-
15
- constructor(
16
- private config: PatternConfiguration
17
- ) {
18
- const rightOverhangNucleotideCounts = this.computeRightOverhangNucleotideCounts();
19
- const maxEffectiveStrandLength = this.computeMaxEffectiveStrandLength(rightOverhangNucleotideCounts);
20
- const maxWidthOfRightOverhangs = this.computeMaxWidthOfRightOverhangs(rightOverhangNucleotideCounts);
21
- const strandLabelWidth = this.computeStrandLabelWidth();
22
- const maxTerminusLabelWidthByEnd = this.computeMaxWidthOfTerminusLabels();
23
-
24
- this.initializeCalculators(
25
- rightOverhangNucleotideCounts,
26
- maxEffectiveStrandLength,
27
- maxWidthOfRightOverhangs,
28
- strandLabelWidth,
29
- maxTerminusLabelWidthByEnd
30
- );
31
- }
32
-
33
- private initializeCalculators(
34
- rightOverhangNucleotideCounts: StrandToNumberMap,
35
- maxEffectiveStrandLength: number,
36
- maxWidthOfRightOverhangs: number,
37
- strandLabelWidth: StrandEndToNumberMap,
38
- maxTerminusLabelWidthByEnd: StrandEndToNumberMap
39
- ): void {
40
- this.canvasDimensions = new CanvasDimensionCalculator(
41
- this.config,
42
- maxEffectiveStrandLength,
43
- maxWidthOfRightOverhangs,
44
- strandLabelWidth,
45
- maxTerminusLabelWidthByEnd
46
- );
47
-
48
- this.nucleotidePositionCalculator = new NucleotidePositionCalculator(
49
- this.config,
50
- maxEffectiveStrandLength,
51
- rightOverhangNucleotideCounts,
52
- maxTerminusLabelWidthByEnd
53
- );
54
-
55
- this.legendPositionCalculator = new LegendPositionCalculator(
56
- this.config,
57
- this.canvasDimensions
58
- );
59
-
60
- this.labelPositionCalculator = new LabelPositionCalculator(
61
- this.config,
62
- maxWidthOfRightOverhangs,
63
- maxTerminusLabelWidthByEnd,
64
- strandLabelWidth,
65
- rightOverhangNucleotideCounts,
66
- this.nucleotidePositionCalculator
67
- );
68
-
69
- this.linkageStarPositionCalculator = new LinkageStarPositionCalculator(
70
- this.nucleotidePositionCalculator,
71
- rightOverhangNucleotideCounts
72
- );
73
- }
74
-
75
- private computeRightOverhangNucleotideCounts(): StrandToNumberMap {
76
- return STRANDS.reduce((overhangCounts, strand) => {
77
- overhangCounts[strand] = this.countOverhangNucleotidesAtStartOfStrand(strand);
78
- return overhangCounts;
79
- }, {} as StrandToNumberMap);
80
- }
81
-
82
- private computeMaxEffectiveStrandLength(rightOverhangNucleotideCounts: StrandToNumberMap): number {
83
- return Math.max(...STRANDS.map((strand) => this.config.nucleotideSequences[strand].length - rightOverhangNucleotideCounts[strand]));
84
- }
85
-
86
- private computeMaxWidthOfRightOverhangs(rightOverhangNucleotideCounts: StrandToNumberMap): number {
87
- return Math.max(rightOverhangNucleotideCounts[STRAND.SENSE], rightOverhangNucleotideCounts[STRAND.ANTISENSE]);
88
- }
89
-
90
- getCanvasWidth(): number {
91
- return this.canvasDimensions.getCanvasWidth();
92
- }
93
-
94
- getCanvasHeight(): number {
95
- return this.canvasDimensions.getCanvasHeight();
96
- }
97
-
98
- getNucleotideCirclePosition(indexOfNucleotide: number, strand: STRAND): Position {
99
- return this.nucleotidePositionCalculator.getNucleotideCirclePosition(indexOfNucleotide, strand);
100
- };
101
-
102
- getNucleotideLabelTextPosition(indexOfNucleotide: number, strand: STRAND): Position {
103
- return this.nucleotidePositionCalculator.getNucleotideLabelTextPosition(indexOfNucleotide, strand);
104
- }
105
-
106
- getNumericLabelPosition(
107
- indexOfNucleotide: number,
108
- strand: STRAND,
109
- displayedNucleotideNumber: number
110
- ): Position {
111
- return this.nucleotidePositionCalculator.getNumericLabelPosition(indexOfNucleotide, strand, displayedNucleotideNumber);
112
- }
113
-
114
-
115
- getLegendCirclePosition(index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): Position {
116
- return this.legendPositionCalculator.getLegendCirclePosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages);
117
- }
118
-
119
- getLegendTextPosition(index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): Position {
120
- return this.legendPositionCalculator.getLegendTextPosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages);
121
- }
122
-
123
- getStarLabelPosition(): Position {
124
- return this.legendPositionCalculator.getStarLabelPosition();
125
- }
126
-
127
- getPhosphorothioateLinkageLabelPosition(): Position {
128
- return this.legendPositionCalculator.getPhosphorothioateLinkageLabelPosition();
129
- }
130
-
131
- getTerminusLabelPosition(strand: STRAND, end: STRAND_END): Position {
132
- return this.labelPositionCalculator.getTerminusLabelPosition(strand, end);
133
- }
134
-
135
- getStrandEndLabelPosition(strand: STRAND, end: STRAND_END): Position {
136
- return this.labelPositionCalculator.getStrandEndLabelPosition(strand, end);
137
- }
138
-
139
- getTitleTextPosition(): Position {
140
- return this.labelPositionCalculator.getTitleTextPosition();
141
- }
142
-
143
- getCommentLabelPosition(): Position {
144
- return this.labelPositionCalculator.getCommentLabelPosition();
145
- }
146
-
147
- getCenterPositionOfLinkageStar(index: number, strand: STRAND): Position {
148
- return this.linkageStarPositionCalculator.getCenterPositionOfLinkageStar(index, strand);
149
- }
150
-
151
- private computeStrandLabelWidth(): StrandEndToNumberMap {
152
- const widthOfStrandLabel = Object.fromEntries(
153
- STRAND_ENDS.map(
154
- (strandEnd) => [strandEnd, this.getMaxWidthStrandEndLabelsByEnd(strandEnd)]
155
- )
156
- ) as StrandEndToNumberMap;
157
-
158
- return widthOfStrandLabel;
159
- }
160
-
161
- private computeMaxWidthOfTerminusLabels(): StrandEndToNumberMap {
162
- const maxWidthOfTerminusLabelsByEnd = STRAND_ENDS.reduce((maxWidthMap, end) => {
163
- maxWidthMap[end] = this.getMaxWidthOfTerminusLabelsByEnd(end);
164
- return maxWidthMap;
165
- }, {} as StrandEndToNumberMap);
166
-
167
- return maxWidthOfTerminusLabelsByEnd;
168
- }
169
-
170
- private getMaxWidthOfTerminusLabelsByEnd(end: STRAND_END): number {
171
- return this.calculateMaxWidthOfStrandEndLabel(
172
- (strand) => this.config.strandTerminusModifications[strand][STRAND_TO_END_TERMINUS_MAP[strand][end]]
173
- );
174
- }
175
-
176
- private getMaxWidthStrandEndLabelsByEnd(strandEnd: STRAND_END): number {
177
- return this.calculateMaxWidthOfStrandEndLabel(
178
- (strand) => STRAND_END_LABEL_TEXT[strandEnd][strand]
179
- );
180
- }
181
-
182
- private calculateMaxWidthOfStrandEndLabel(getLabelText: (strand: STRAND) => string): number {
183
- const textWidthCalculator = TextWidthCalculator.getInstance();
184
- return Math.max(
185
- ...STRANDS.map((strand) =>
186
- textWidthCalculator.computeTextWidth(
187
- getLabelText(strand),
188
- SVG_TEXT_FONT_SIZES.NUCLEOBASE,
189
- DEFAULT_FONT_FAMILY
190
- )
191
- )
192
- );
193
- }
194
-
195
- private countOverhangNucleotidesAtStartOfStrand(strand: STRAND): number {
196
- const nucleotides = this.config.nucleotideSequences[strand];
197
-
198
- let overhangNucleotidesCount = 0;
199
- for (const nucleotide of nucleotides) {
200
- if (!isOverhangNucleotide(nucleotide))
201
- break;
202
- overhangNucleotidesCount++;
203
- }
204
-
205
- return overhangNucleotidesCount;
206
- }
207
- }
208
-
209
- class TextWidthCalculator {
210
- private static instance: TextWidthCalculator;
211
- private canvas: HTMLCanvasElement;
212
- private context: CanvasRenderingContext2D | null;
213
- private pixelRatio: number;
214
-
215
- // WARNING: singleton used to avoid creating canvas element on every call
216
- private constructor() {
217
- this.canvas = document.createElement('canvas');
218
- this.context = this.canvas.getContext('2d');
219
- this.pixelRatio = window.devicePixelRatio || 1;
220
-
221
- this.canvas.width *= this.pixelRatio;
222
- this.canvas.height *= this.pixelRatio;
223
- }
224
-
225
- public static getInstance(): TextWidthCalculator {
226
- if (!TextWidthCalculator.instance)
227
- TextWidthCalculator.instance = new TextWidthCalculator();
228
-
229
- return TextWidthCalculator.instance;
230
- }
231
-
232
- public computeTextWidth(text: string, fontSize: number, fontFamily: string): number {
233
- if (this.context) {
234
- this.context.font = `${fontSize * this.pixelRatio}px ${fontFamily}`;
235
- const metrics = this.context.measureText(text);
236
- return metrics.width / this.pixelRatio;
237
- }
238
-
239
- return 0;
240
- }
241
- }
242
-
243
- class CanvasDimensionCalculator {
244
- constructor(
245
- private config: PatternConfiguration,
246
- private maxEffectiveStrandLength: number,
247
- private maxWidthOfRightOverhangs: number,
248
- private strandLabelWidth: StrandEndToNumberMap,
249
- private maxTerminusWidthByEnd: StrandEndToNumberMap
250
- ) {}
251
-
252
- getCanvasWidth(): number {
253
- const widthOfNucleobases = SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER * (this.maxEffectiveStrandLength + this.maxWidthOfRightOverhangs);
254
-
255
- const canvasWidth = STRAND_ENDS.reduce((totalWidth, end) => {
256
- totalWidth += this.strandLabelWidth[end] + this.maxTerminusWidthByEnd[end];
257
- return totalWidth;
258
- }, 0) + widthOfNucleobases + SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER;
259
-
260
- return canvasWidth;
261
- }
262
-
263
- getCanvasHeight(): number {
264
- return (this.config.isAntisenseStrandIncluded ? 11 : 9) * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
265
- }
266
- }
267
-
268
- class NucleotidePositionCalculator {
269
- constructor(
270
- private config: PatternConfiguration,
271
- private maxEffectiveStrandLength: number,
272
- private rightOverhangNucleotideCounts: StrandToNumberMap,
273
- private maxTerminusWidthByEnd: StrandEndToNumberMap
274
- ) {}
275
-
276
- getNucleotideCirclePosition(indexOfNucleotide: number, strand: STRAND): Position {
277
- return {
278
- x: this.computeNucleotideCircleXPositionByStrand(indexOfNucleotide, strand),
279
- y: Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUCLEOBASE_CIRCLE,
280
- };
281
- };
282
-
283
- getNucleotideLabelTextPosition(indexOfNucleotide: number, strand: STRAND): Position {
284
- return {
285
- // todo: refactor legacy dependency on one-digit parameter
286
- x: this.computeNucleotideCircleXPositionByStrand(indexOfNucleotide, strand) + NUMERIC_LABEL_POSITION_OFFSET.ONE_DIGIT,
287
- y: Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUCLEOBASE_LABEL,
288
- };
289
- }
290
-
291
- // todo: cleanup legacy logic
292
- private computeNucleotideCircleXPositionByStrand(index: number, strand: STRAND): number {
293
- const rightOverhangCount = this.rightOverhangNucleotideCounts[strand];
294
- return this.computeNucleobaseCircleXPosition(index, rightOverhangCount);
295
- }
296
-
297
- computeNucleobaseCircleXPosition(index: number, rightOverhangCount: number): number {
298
- const rightModificationOffset = this.maxTerminusWidthByEnd[STRAND_END.RIGHT];
299
- const positionalIndex = this.maxEffectiveStrandLength - index + rightOverhangCount + 1;
300
- const xPosition = positionalIndex * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER;
301
- const finalPosition = rightModificationOffset + xPosition;
302
- return finalPosition;
303
- }
304
-
305
- getNumericLabelPosition(
306
- indexOfNucleotide: number,
307
- strand: STRAND,
308
- displayedNucleotideNumber: number
309
- ): Position {
310
- const indexForVisualStrand = this.getVisualStrandIndex(indexOfNucleotide, strand);
311
-
312
- const numericLabelOffset = this.computeNumericLabelXOffset(
313
- this.config.nucleotideSequences[strand],
314
- indexForVisualStrand,
315
- displayedNucleotideNumber
316
- );
317
-
318
- const numericLabelXPosition = this.computeNucleotideCircleXPositionByStrand(indexOfNucleotide, strand) + numericLabelOffset;
319
-
320
- return {
321
- x: numericLabelXPosition,
322
- y: Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUMERIC_LABEL,
323
- };
324
- }
325
-
326
- private computeNumericLabelXOffset(bases: string[], nucleobaseIndex: number, nucleotideNumericLabel: number): number {
327
- const isSingleDigitLabel = nucleotideNumericLabel >= 0 && nucleotideNumericLabel < 10;
328
-
329
- const criterion = isSingleDigitLabel || NUCLEOTIDES.includes(bases[nucleobaseIndex]);
330
-
331
- return criterion ?
332
- NUMERIC_LABEL_POSITION_OFFSET.ONE_DIGIT :
333
- NUMERIC_LABEL_POSITION_OFFSET.TWO_DIGIT;
334
- }
335
-
336
- /** Inverses index for antisense strand image */
337
- private getVisualStrandIndex(indexOfNucleotide: number, strand: STRAND): number {
338
- return strand === STRAND.SENSE ?
339
- indexOfNucleotide :
340
- this.config.nucleotideSequences[strand].length - indexOfNucleotide;
341
- }
342
- }
343
-
344
- class LegendPositionCalculator {
345
- constructor(
346
- private config: PatternConfiguration,
347
- private canvasDimensionCalculator: CanvasDimensionCalculator
348
- ) {}
349
-
350
- getLegendCirclePosition(index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): Position {
351
- const centerPosition = {
352
- x: this.computeLegendCircleXPosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages),
353
- y: this.getLegendVerticalPosition(),
354
- };
355
- return centerPosition;
356
- }
357
-
358
- getLegendTextPosition(index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): Position {
359
- const legendPosition = {
360
- x: this.computeLegendCircleXPosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages) + SVG_CIRCLE_SIZES.LEGEND_RADIUS + 4,
361
- y: this.getLegendTextVerticalPosition(),
362
- };
363
- return legendPosition;
364
- }
365
-
366
- private computeLegendCircleXPosition(index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): number {
367
- const legendStartIndex = containsPhosphorothioateLinkages ? 1 : 0;
368
- const totalPositions = distinctNucleobaseTypes.length + legendStartIndex;
369
- const width = this.canvasDimensionCalculator.getCanvasWidth();
370
- const spacingUnit = width / totalPositions;
371
- const position = (index + legendStartIndex) * spacingUnit;
372
- const adjustedPosition = position + SVG_CIRCLE_SIZES.LEGEND_RADIUS;
373
- return Math.round(adjustedPosition);
374
- }
375
-
376
- getStarLabelPosition(): Position {
377
- return {
378
- x: SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS,
379
- y: this.getLegendVerticalPosition(),
380
- };
381
- }
382
-
383
- getPhosphorothioateLinkageLabelPosition(): Position {
384
- return {
385
- x: 2 * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS - 8,
386
- y: this.getLegendTextVerticalPosition(),
387
- };
388
- }
389
-
390
- private getLegendVerticalPosition(): number {
391
- return (this.config.isAntisenseStrandIncluded ? 9.5 : 6) * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
392
- }
393
-
394
- private getLegendTextVerticalPosition(): number {
395
- const position = this.config.isAntisenseStrandIncluded ? 10 * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS : Y_POSITIONS_FOR_STRAND_ELEMENTS[STRAND.ANTISENSE].NUCLEOBASE_CIRCLE;
396
- return position - 3;
397
- }
398
- }
399
-
400
- class LabelPositionCalculator {
401
- private xPositionOfTerminusModifications: Record<typeof STRAND_ENDS[number], StrandToNumberMap>;
402
- constructor(
403
- private config: PatternConfiguration,
404
- private maxWidthOfRightOverhangs: number,
405
- private maxTerminusWidthByEnd: StrandEndToNumberMap,
406
- private strandLabelWidth: StrandEndToNumberMap,
407
- private rightOverhangNucleotideCounts: StrandToNumberMap,
408
- private nucleotidePositionCalculator: NucleotidePositionCalculator
409
- ) {
410
- this.xPositionOfTerminusModifications = this.computeXPositionOfTerminusModifications();
411
- }
412
-
413
- getTerminusLabelPosition(strand: STRAND, end: STRAND_END): Position {
414
- return {
415
- x: this.xPositionOfTerminusModifications[end][strand],
416
- y: Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUCLEOBASE_LABEL,
417
- };
418
- }
419
-
420
- getStrandEndLabelPosition(strand: STRAND, end: STRAND_END): Position {
421
- const xPosition = this.getXPositionOfStrandLabels()[end];
422
- return {
423
- x: xPosition,
424
- // todo: remove legacy grouping by y positions
425
- y: Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUCLEOBASE_LABEL,
426
- };
427
- }
428
-
429
- getTitleTextPosition(): Position {
430
- return {
431
- x: SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS,
432
- y: SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS,
433
- };
434
- }
435
-
436
- getCommentLabelPosition(): Position {
437
- const y = (this.config.isAntisenseStrandIncluded ? 11 : 8.5) * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
438
-
439
- const x = this.getXPositionOfStrandLabels()[STRAND_END.LEFT];
440
- return {x, y};
441
- }
442
-
443
- private getXPositionOfStrandLabels(): StrandEndToNumberMap {
444
- const maxRightTerminusModificationShift = Math.max(
445
- ...STRANDS.map((strand) => this.xPositionOfTerminusModifications[STRAND_END.RIGHT][strand])
446
- );
447
-
448
- const rightEndXPosition = maxRightTerminusModificationShift +
449
- this.maxTerminusWidthByEnd[STRAND_END.LEFT] +
450
- SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER * this.maxWidthOfRightOverhangs;
451
-
452
- return {
453
- [STRAND_END.LEFT]: 0,
454
- [STRAND_END.RIGHT]: rightEndXPosition,
455
- };
456
- }
457
-
458
- private computeXPositionOfTerminusModifications(): Record<typeof STRAND_ENDS[number], StrandToNumberMap> {
459
- const xPositionOfTerminusModifications = Object.fromEntries(
460
- STRAND_ENDS.map((end) => [
461
- end,
462
- Object.fromEntries(
463
- STRANDS.map((strand) => [
464
- strand,
465
- end === STRAND_END.LEFT ?
466
- this.strandLabelWidth[STRAND_END.LEFT] - 5 :
467
- this.rightOverhangNucleotideCounts[strand] * SVG_CIRCLE_SIZES.NUCLEOBASE_DIAMETER + this.nucleotidePositionCalculator.computeNucleobaseCircleXPosition(-0.5, 0)
468
- ])
469
- )
470
- ])
471
- ) as Record<typeof STRAND_ENDS[number], StrandToNumberMap>;
472
-
473
- return xPositionOfTerminusModifications;
474
- }
475
- }
476
-
477
- class LinkageStarPositionCalculator {
478
- constructor(
479
- private nucleotidePositionCalculator: NucleotidePositionCalculator,
480
- private rightOverhangNucleotideCounts: StrandToNumberMap
481
- ) {}
482
-
483
- getCenterPositionOfLinkageStar(index: number, strand: STRAND): Position {
484
- return {
485
- x: this.getXPositionOfLinkageStar(index, strand),
486
- y: this.getYPositionOfLinkageStar(strand),
487
- };
488
- }
489
-
490
- // todo: remove legacy division of x-y coordinates throughout the code
491
- private getXPositionOfLinkageStar(index: number, strand: STRAND): number {
492
- return this.nucleotidePositionCalculator.computeNucleobaseCircleXPosition(index, this.rightOverhangNucleotideCounts[strand]) + SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
493
- }
494
-
495
- private getYPositionOfLinkageStar(strand: STRAND): number {
496
- return Y_POSITIONS_FOR_STRAND_ELEMENTS[strand].NUCLEOBASE_LABEL + SVG_CIRCLE_SIZES.LINKAGE_STAR_RADIUS;
497
- }
498
- }