@datagrok/sequence-translator 1.2.6 → 1.2.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.
Files changed (102) hide show
  1. package/.eslintrc.json +5 -5
  2. package/CHANGELOG.md +12 -0
  3. package/dist/package-test.js +2 -1
  4. package/dist/package-test.js.LICENSE.txt +8 -0
  5. package/dist/package-test.js.map +1 -1
  6. package/dist/package.js +2 -1
  7. package/dist/package.js.LICENSE.txt +8 -0
  8. package/dist/package.js.map +1 -1
  9. package/files/pattern-app-data.json +80 -0
  10. package/package.json +21 -14
  11. package/src/{model → apps/common/model}/const.ts +1 -1
  12. package/src/{model/data-loading-utils → apps/common/model/data-loader}/const.ts +7 -2
  13. package/src/apps/common/model/data-loader/json-loader.ts +48 -0
  14. package/src/{model/data-loading-utils → apps/common/model/data-loader}/types.ts +13 -6
  15. package/src/{model → apps/common/model}/monomer-lib/lib-wrapper.ts +9 -12
  16. package/src/apps/common/model/oligo-toolkit-package.ts +30 -0
  17. package/src/{model → apps/common/model}/parsing-validation/format-detector.ts +5 -5
  18. package/src/{model → apps/common/model}/parsing-validation/format-handler.ts +18 -19
  19. package/src/{model → apps/common/model}/parsing-validation/sequence-validator.ts +1 -1
  20. package/src/apps/common/view/app-ui-base.ts +28 -0
  21. package/src/apps/common/view/combined-app-ui.ts +66 -0
  22. package/src/{view/utils → apps/common/view/components}/colored-input/colored-text-input.ts +1 -1
  23. package/src/{view/utils → apps/common/view/components}/draw-molecule.ts +1 -1
  24. package/src/{view/utils → apps/common/view/components}/molecule-img.ts +3 -3
  25. package/src/{view/const/ui.ts → apps/common/view/const.ts} +4 -4
  26. package/src/apps/common/view/isolated-app-ui.ts +43 -0
  27. package/src/{view/monomer-lib-viewer/viewer.ts → apps/common/view/monomer-lib-viewer.ts} +2 -2
  28. package/src/apps/common/view/utils.ts +29 -0
  29. package/src/apps/pattern/model/const.ts +121 -0
  30. package/src/apps/pattern/model/data-manager.ts +297 -0
  31. package/src/apps/pattern/model/event-bus.ts +470 -0
  32. package/src/apps/pattern/model/router.ts +46 -0
  33. package/src/apps/pattern/model/subscription-manager.ts +21 -0
  34. package/src/apps/pattern/model/translator.ts +68 -0
  35. package/src/apps/pattern/model/types.ts +52 -0
  36. package/src/apps/pattern/model/utils.ts +110 -0
  37. package/src/apps/pattern/view/components/bulk-convert/column-input.ts +69 -0
  38. package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +37 -0
  39. package/src/apps/pattern/view/components/bulk-convert/table-input.ts +95 -0
  40. package/src/apps/pattern/view/components/edit-block-controls.ts +196 -0
  41. package/src/apps/pattern/view/components/left-section.ts +44 -0
  42. package/src/apps/pattern/view/components/load-block-controls.ts +198 -0
  43. package/src/apps/pattern/view/components/numeric-label-visibility-controls.ts +69 -0
  44. package/src/apps/pattern/view/components/right-section.ts +148 -0
  45. package/src/apps/pattern/view/components/strand-editor/dialog.ts +79 -0
  46. package/src/apps/pattern/view/components/strand-editor/header-controls.ts +105 -0
  47. package/src/apps/pattern/view/components/strand-editor/strand-controls.ts +159 -0
  48. package/src/apps/pattern/view/components/terminal-modification-editor.ts +127 -0
  49. package/src/apps/pattern/view/components/translation-examples-block.ts +139 -0
  50. package/src/{view/style/pattern-app.css → apps/pattern/view/style.css} +4 -0
  51. package/src/apps/pattern/view/svg-utils/const.ts +63 -0
  52. package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +498 -0
  53. package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +45 -0
  54. package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +82 -0
  55. package/src/apps/pattern/view/svg-utils/svg-renderer.ts +396 -0
  56. package/src/apps/pattern/view/svg-utils/utils.ts +37 -0
  57. package/src/apps/pattern/view/types.ts +14 -0
  58. package/src/apps/pattern/view/ui.ts +61 -0
  59. package/src/{model/structure-app → apps/structure/model}/mol-transformations.ts +3 -3
  60. package/src/{model/structure-app → apps/structure/model}/monomer-code-parser.ts +9 -10
  61. package/src/{model/structure-app → apps/structure/model}/oligo-structure.ts +4 -4
  62. package/src/{model/structure-app → apps/structure/model}/sequence-to-molfile.ts +2 -2
  63. package/src/{view/apps/oligo-structure.ts → apps/structure/view/ui.ts} +31 -17
  64. package/src/{model/translator-app → apps/translator/model}/conversion-utils.ts +25 -7
  65. package/src/{model/translator-app → apps/translator/model}/format-converter.ts +7 -12
  66. package/src/{view/const/oligo-translator.ts → apps/translator/view/const.ts} +2 -0
  67. package/src/apps/translator/view/ui.ts +547 -0
  68. package/src/demo/demo-st-ui.ts +12 -32
  69. package/src/package.ts +76 -56
  70. package/src/plugins/mermade.ts +9 -9
  71. package/src/polytool/const.ts +40 -0
  72. package/src/polytool/csv-to-json-monomer-lib-converter.ts +40 -0
  73. package/src/polytool/cyclized.ts +56 -0
  74. package/src/polytool/monomer-lib-handler.ts +115 -0
  75. package/src/polytool/transformation.ts +326 -0
  76. package/src/polytool/ui.ts +59 -0
  77. package/src/polytool/utils.ts +20 -0
  78. package/src/tests/const.ts +5 -5
  79. package/src/tests/formats-support.ts +6 -6
  80. package/src/tests/formats-to-helm.ts +5 -5
  81. package/src/tests/helm-to-nucleotides.ts +5 -5
  82. package/tsconfig.json +4 -10
  83. package/webpack.config.js +3 -0
  84. package/files/axolabs-style.json +0 -97
  85. package/src/model/data-loading-utils/json-loader.ts +0 -38
  86. package/src/model/pattern-app/const.ts +0 -33
  87. package/src/model/pattern-app/draw-svg.ts +0 -193
  88. package/src/model/pattern-app/helpers.ts +0 -96
  89. package/src/model/pattern-app/oligo-pattern.ts +0 -111
  90. package/src/view/app-ui.ts +0 -193
  91. package/src/view/apps/oligo-pattern.ts +0 -759
  92. package/src/view/apps/oligo-translator.ts +0 -184
  93. /package/src/{model → apps/common/model}/helpers.ts +0 -0
  94. /package/src/{model → apps/common/model}/monomer-lib/const.ts +0 -0
  95. /package/src/{view/utils → apps/common/view/components}/app-info-dialog.ts +0 -0
  96. /package/src/{view/utils → apps/common/view/components}/colored-input/input-painters.ts +0 -0
  97. /package/src/{view/style/colored-text-input.css → apps/common/view/components/colored-input/style.css} +0 -0
  98. /package/src/{view/utils → apps/common/view/components}/router.ts +0 -0
  99. /package/src/{model/structure-app → apps/structure/model}/const.ts +0 -0
  100. /package/src/{view/style/structure-app.css → apps/structure/view/style.css} +0 -0
  101. /package/src/{model/translator-app → apps/translator/model}/const.ts +0 -0
  102. /package/src/{view/style/translator-app.css → apps/translator/view/style.css} +0 -0
@@ -0,0 +1,396 @@
1
+ import {SVGElementFactory} from './svg-element-factory';
2
+ import {isOverhangNucleotide} from '../../model/utils';
3
+ import {STRAND, STRANDS, STRAND_END, STRAND_ENDS, TERMINUS, TERMINI} from '../../model/const';
4
+ import {PatternConfiguration, StrandType, TerminalType} from '../../model/types';
5
+ import {SVG_CIRCLE_SIZES, SVG_TEXT_FONT_SIZES, SVG_ELEMENT_COLORS, STRAND_END_LABEL_TEXT} from './const';
6
+ import {StrandToNumberMap, StrandEndToSVGElementsMap, TerminusToSVGElementMap} from '../types';
7
+ import {
8
+ getNucleobaseLabelForCircle,
9
+ computeTextColorForNucleobaseLabel,
10
+ getNucleobaseColorFromStyleMap,
11
+ } from './utils';
12
+ import {PatternSVGDimensionsCalculator} from './dimensions-calculator';
13
+
14
+ export class NucleotidePatternSVGRenderer {
15
+ private config: PatternConfiguration;
16
+ private patternDimensionsCalculator: PatternSVGDimensionsCalculator;
17
+ private svgFactory: SVGElementFactoryWrapper;
18
+ private strandElementManager: StrandElementBuilder;
19
+ private legendBuilder: LegendBuilder;
20
+
21
+ constructor(patternConfig: PatternConfiguration) {
22
+ this.setupPatternConfig(patternConfig);
23
+
24
+ // todo: prefer dependency injection for all these properties
25
+ this.patternDimensionsCalculator = new PatternSVGDimensionsCalculator(this.config);
26
+
27
+ this.svgFactory = new SVGElementFactoryWrapper(new SVGElementFactory(), this.config, this.patternDimensionsCalculator);
28
+
29
+ this.strandElementManager = new StrandElementBuilder(this.svgFactory, this.config);
30
+ this.legendBuilder = new LegendBuilder(this.svgFactory, this.config);
31
+ }
32
+
33
+ renderPattern(): SVGElement {
34
+ const labelElements = this.createLabelElements();
35
+
36
+ const countOfNucleotidesExcludingOverhangs = this.countNucleotidesExcludingOverhangs();
37
+ const strandElements = this.strandElementManager.createStrandElements(countOfNucleotidesExcludingOverhangs);
38
+ const titleElement = this.svgFactory.createTitleElement(this.config.patternName, countOfNucleotidesExcludingOverhangs, this.config.isAntisenseStrandIncluded);
39
+
40
+ const legend = this.legendBuilder.getLegendItems();
41
+
42
+ const patternSVGCanvas = this.svgFactory.createCanvas();
43
+ patternSVGCanvas.append(...labelElements, ...strandElements, titleElement, ...legend);
44
+
45
+ return patternSVGCanvas;
46
+ }
47
+
48
+ private countNucleotidesExcludingOverhangs(): StrandToNumberMap {
49
+ return STRANDS.reduce((acc, strand) => {
50
+ acc[strand] = this.config.nucleotideSequences[strand].filter((value) => !isOverhangNucleotide(value)).length;
51
+ return acc;
52
+ }, {} as StrandToNumberMap);
53
+ }
54
+
55
+ private createLabelElements(): SVGElement[] {
56
+ const strandEndLabels = this.getStrandEndLabels();
57
+ const labelsTerminusModification = this.getTerminusModificationLabels();
58
+
59
+ const labelElements = [
60
+ ...STRANDS.flatMap((strand) => [
61
+ ...Object.values(strandEndLabels[strand]),
62
+ ...Object.values(labelsTerminusModification[strand])
63
+ ]),
64
+ ].filter((element) => element !== null) as SVGElement[];
65
+
66
+ return labelElements;
67
+ }
68
+
69
+ private setupPatternConfig(patternConfig: PatternConfiguration): void {
70
+ // WARNING: to ensure immutability, we need to deep copy the config object
71
+ this.config = JSON.parse(JSON.stringify(patternConfig)) as PatternConfiguration;
72
+
73
+ this.config.nucleotideSequences[STRAND.SENSE].reverse();
74
+ this.config.phosphorothioateLinkageFlags[STRAND.SENSE].reverse();
75
+ }
76
+
77
+ private getStrandEndLabels() {
78
+ const strandEndLabels = STRANDS.reduce((acc, strand) => {
79
+ acc[strand] = STRAND_ENDS.reduce((endAcc, end) => {
80
+ endAcc[end] = this.svgFactory.createLabelForStrandEnd(strand, end);
81
+ return endAcc;
82
+ }, {} as StrandEndToSVGElementsMap);
83
+ return acc;
84
+ }, {} as Record<typeof STRANDS[number], StrandEndToSVGElementsMap>);
85
+
86
+ return strandEndLabels;
87
+ }
88
+
89
+ private getTerminusModificationLabels() {
90
+ const labelsTerminusModification = STRANDS.reduce((acc, strand) => {
91
+ acc[strand] = TERMINI.reduce((terminiAcc, terminus) => {
92
+ terminiAcc[terminus] = this.svgFactory.createTerminusModificationLabel(strand, terminus);
93
+ return terminiAcc;
94
+ }, {} as TerminusToSVGElementMap);
95
+ return acc;
96
+ }, {} as Record<typeof STRANDS[number], TerminusToSVGElementMap>);
97
+
98
+ return labelsTerminusModification;
99
+ }
100
+ }
101
+
102
+ class LegendBuilder {
103
+ private containsPhosphorothioateLinkages: boolean;
104
+ constructor(
105
+ private svgFactory: SVGElementFactoryWrapper,
106
+ private config: PatternConfiguration
107
+ ) {
108
+ this.containsPhosphorothioateLinkages = this.checkAnyPhosphorothioateLinkages();
109
+ }
110
+
111
+ getLegendItems(): SVGElement[] {
112
+ const commentLabel = this.svgFactory.createCommentLabel();
113
+ const nucleotideLegendItems = this.createLegendItemsForNucleotideTypes();
114
+ const phosphorothioateLinkageLegendItem = this.createLegendItemForPhosphorothioateLinkage(this.containsPhosphorothioateLinkages);
115
+
116
+ return [commentLabel, ...nucleotideLegendItems, ...phosphorothioateLinkageLegendItem];
117
+ }
118
+
119
+ private createLegendItemsForNucleotideTypes(): SVGElement[] {
120
+ const distinctNucleobaseTypes = this.extractNucleotideTypes();
121
+ const svgElements = [] as SVGElement[];
122
+ distinctNucleobaseTypes.forEach((nucleobaseType, index) => {
123
+ const legendCircle = this.svgFactory.createLegendCircle(nucleobaseType, index, distinctNucleobaseTypes, this.containsPhosphorothioateLinkages);
124
+ const legendText = this.svgFactory.createLegendText(nucleobaseType, index, distinctNucleobaseTypes, this.containsPhosphorothioateLinkages);
125
+ svgElements.push(legendCircle, legendText);
126
+ });
127
+ return svgElements;
128
+ }
129
+
130
+ private createLegendItemForPhosphorothioateLinkage(containsPhosphorothioateLinkages: boolean): SVGElement[] {
131
+ const starLinkageLegendLabel = this.svgFactory.createLinkageStarLegendLabel(containsPhosphorothioateLinkages);
132
+ const phosphorothioateLinkageLabel = this.svgFactory.createPhosphorothioateLinkageLabel(containsPhosphorothioateLinkages);
133
+
134
+ return [starLinkageLegendLabel, phosphorothioateLinkageLabel].filter((element) => element !== null) as SVGElement[];
135
+ }
136
+
137
+ private extractNucleotideTypes(): string[] {
138
+ const distinctNucleotides = [...new Set(
139
+ this.config.nucleotideSequences[STRAND.SENSE].concat(
140
+ this.config.isAntisenseStrandIncluded ? this.config.nucleotideSequences[STRAND.ANTISENSE] : []
141
+ )
142
+ )];
143
+
144
+ return distinctNucleotides;
145
+ }
146
+
147
+ private checkAnyPhosphorothioateLinkages(): boolean {
148
+ return [...this.config.phosphorothioateLinkageFlags[STRAND.SENSE],
149
+ ...(this.config.isAntisenseStrandIncluded ? this.config.phosphorothioateLinkageFlags[STRAND.ANTISENSE] : [])
150
+ ].some((linkage) => linkage);
151
+ }
152
+ }
153
+
154
+ class NucleotideCountTracker {
155
+ private nucleotideCounts: StrandToNumberMap;
156
+ constructor(initialNucleotideCounts: StrandToNumberMap) {
157
+ // WARNING: to ensure immutability, we need to deep copy the object
158
+ this.nucleotideCounts = JSON.parse(JSON.stringify(initialNucleotideCounts)) as StrandToNumberMap;
159
+ }
160
+
161
+ decrementIfNotOverhang(isOverhang: boolean, strand: STRAND): void {
162
+ if (!isOverhang)
163
+ this.nucleotideCounts[strand]--;
164
+ }
165
+
166
+ getCurrentCount(strand: STRAND): number {
167
+ return this.nucleotideCounts[strand];
168
+ }
169
+ }
170
+
171
+ class SVGElementFactoryWrapper {
172
+ constructor(
173
+ private svgElementFactory: SVGElementFactory,
174
+ private config: PatternConfiguration,
175
+ private dimensionsCalculator: PatternSVGDimensionsCalculator
176
+ ) { }
177
+
178
+ createCanvas(): SVGElement {
179
+ const canvasWidth = this.dimensionsCalculator.getCanvasWidth();
180
+ const canvasHeight = this.dimensionsCalculator.getCanvasHeight();
181
+ const svgCanvas = this.svgElementFactory.createCanvas(canvasWidth, canvasHeight);
182
+
183
+ return svgCanvas;
184
+ }
185
+
186
+ createTitleElement(patternName: string,
187
+ numberOfNucleotides: StrandToNumberMap,
188
+ isAntisenseStrandActive: boolean
189
+ ) {
190
+ const titleText = this.getTitleText(patternName, numberOfNucleotides, isAntisenseStrandActive);
191
+ const titleTextPosition = this.dimensionsCalculator.getTitleTextPosition();
192
+ return this.svgElementFactory.createTextElement(
193
+ titleText,
194
+ titleTextPosition,
195
+ SVG_TEXT_FONT_SIZES.NUCLEOBASE,
196
+ SVG_ELEMENT_COLORS.TITLE_TEXT
197
+ );
198
+ }
199
+
200
+ private getTitleText(
201
+ patternName: string,
202
+ numberOfNucleotides: StrandToNumberMap,
203
+ isAntisenseStrandActive: boolean
204
+ ): string {
205
+ const senseStrandLength = `${numberOfNucleotides[STRAND.SENSE]}`;
206
+ const antisenseStrandLength = isAntisenseStrandActive ? `/${numberOfNucleotides[STRAND.ANTISENSE]}` : '';
207
+ const titleText = `${patternName} for ${senseStrandLength}${antisenseStrandLength}-mer`;
208
+
209
+ return titleText;
210
+ }
211
+
212
+ createPhosphorothioateLinkageStar(strand: StrandType, index: number): SVGElement | null {
213
+ const isActive = this.config.phosphorothioateLinkageFlags[strand][index];
214
+ if (!isActive)
215
+ return null;
216
+
217
+ const centerPosition = this.dimensionsCalculator.getCenterPositionOfLinkageStar(index, strand);
218
+ const color = SVG_ELEMENT_COLORS.LINKAGE_STAR;
219
+ const starElement = this.svgElementFactory.createStarElement(centerPosition, color);
220
+
221
+ return starElement;
222
+ }
223
+
224
+ createNucleotideNumericLabel(
225
+ indexOfNucleotide: number,
226
+ strand: STRAND,
227
+ displayedNucleotideNumber: number
228
+ ): SVGElement {
229
+ const nucleotide = this.config.nucleotideSequences[strand][indexOfNucleotide];
230
+ const isOverhang = isOverhangNucleotide(nucleotide);
231
+ const labelPosition = this.dimensionsCalculator.getNumericLabelPosition(indexOfNucleotide, strand, displayedNucleotideNumber);
232
+
233
+ const labelText = (!isOverhang && this.config.nucleotidesWithNumericLabels.includes(nucleotide)) ?
234
+ String(displayedNucleotideNumber) :
235
+ '';
236
+
237
+ const nucleotideNumericLabel = this.svgElementFactory.createTextElement(
238
+ labelText, labelPosition, SVG_TEXT_FONT_SIZES.COMMENT, SVG_ELEMENT_COLORS.TEXT
239
+ );
240
+
241
+ return nucleotideNumericLabel;
242
+ }
243
+
244
+ createNucleotideCircle(indexOfNucleotide: number, strand: STRAND): SVGCircleElement {
245
+ const nucleotide = this.config.nucleotideSequences[strand][indexOfNucleotide];
246
+ const nucleotideCirclePosition = this.dimensionsCalculator.getNucleotideCirclePosition(indexOfNucleotide, strand);
247
+ const nucleotideCircle = this.svgElementFactory.createCircleElement(
248
+ nucleotideCirclePosition, SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS, getNucleobaseColorFromStyleMap(nucleotide)
249
+ );
250
+ return nucleotideCircle;
251
+ }
252
+
253
+ createNucleotideNameLabel(indexOfNucleotide: number, strand: STRAND): SVGTextElement {
254
+ const nucleotide = this.config.nucleotideSequences[strand][indexOfNucleotide];
255
+ const nucleobaseLabelTextPosition = this.dimensionsCalculator.getNucleotideLabelTextPosition(indexOfNucleotide, strand);
256
+ const nucleotideLabelText = this.svgElementFactory.createTextElement(
257
+ getNucleobaseLabelForCircle(nucleotide), nucleobaseLabelTextPosition, SVG_TEXT_FONT_SIZES.NUCLEOBASE, computeTextColorForNucleobaseLabel(nucleotide)
258
+ );
259
+ return nucleotideLabelText;
260
+ }
261
+
262
+ createCommentLabel(): SVGElement {
263
+ const commentLabelPosition = this.dimensionsCalculator.getCommentLabelPosition();
264
+
265
+ const commentLabel = this.svgElementFactory.createTextElement(
266
+ this.config.patternComment,
267
+ commentLabelPosition,
268
+ SVG_TEXT_FONT_SIZES.COMMENT,
269
+ SVG_ELEMENT_COLORS.TEXT
270
+ );
271
+
272
+ return commentLabel;
273
+ }
274
+
275
+ createLinkageStarLegendLabel(isPhosphorothioateLinkageActive: boolean): SVGElement | null {
276
+ const starLabelPosition = this.dimensionsCalculator.getStarLabelPosition();
277
+ const starLabel = isPhosphorothioateLinkageActive ?
278
+ this.svgElementFactory.createStarElement(starLabelPosition, SVG_ELEMENT_COLORS.LINKAGE_STAR) :
279
+ null;
280
+
281
+ return starLabel;
282
+ }
283
+
284
+ createPhosphorothioateLinkageLabel(isPhosphorothioateLinkageActive: boolean): SVGElement | null {
285
+ const position = this.dimensionsCalculator.getPhosphorothioateLinkageLabelPosition();
286
+ const phosphorothioateLinkageLabel = isPhosphorothioateLinkageActive ?
287
+ this.svgElementFactory.createTextElement(
288
+ 'ps linkage',
289
+ position,
290
+ SVG_TEXT_FONT_SIZES.COMMENT,
291
+ SVG_ELEMENT_COLORS.TEXT) :
292
+ null;
293
+
294
+ return phosphorothioateLinkageLabel;
295
+ };
296
+
297
+ createLabelForStrandEnd(strand: StrandType, end: STRAND_END): SVGElement | null {
298
+ const isLabelActive = (strand === STRAND.SENSE) || (strand === STRAND.ANTISENSE && this.config.isAntisenseStrandIncluded);
299
+ if (!isLabelActive)
300
+ return null;
301
+
302
+
303
+ const labelText = STRAND_END_LABEL_TEXT[end][strand];
304
+ const labelPosition = this.dimensionsCalculator.getStrandEndLabelPosition(strand, end);
305
+
306
+ return this.svgElementFactory.createTextElement(labelText, labelPosition, SVG_TEXT_FONT_SIZES.NUCLEOBASE, SVG_ELEMENT_COLORS.TEXT);
307
+ }
308
+
309
+ createTerminusModificationLabel(strand: StrandType, terminus: TerminalType): SVGElement | null {
310
+ if (strand === STRAND.ANTISENSE && !this.config.isAntisenseStrandIncluded)
311
+ return null;
312
+
313
+
314
+ const end = (strand === STRAND.SENSE && terminus === TERMINUS.FIVE_PRIME) ||
315
+ (strand === STRAND.ANTISENSE && terminus === TERMINUS.THREE_PRIME) ? STRAND_END.LEFT : STRAND_END.RIGHT;
316
+
317
+ const labelText = this.config.strandTerminusModifications[strand][terminus];
318
+ const labelPosition = this.dimensionsCalculator.getTerminusLabelPosition(strand, end);
319
+ return this.svgElementFactory.createTextElement(labelText, labelPosition, SVG_TEXT_FONT_SIZES.NUCLEOBASE, SVG_ELEMENT_COLORS.MODIFICATION_TEXT);
320
+ }
321
+
322
+ createLegendCircle(nucleobaseType: string, index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): SVGCircleElement {
323
+ const radius = SVG_CIRCLE_SIZES.LEGEND_RADIUS;
324
+ const color = getNucleobaseColorFromStyleMap(nucleobaseType);
325
+
326
+ const centerPosition = this.dimensionsCalculator.getLegendCirclePosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages);
327
+ return this.svgElementFactory.createCircleElement(centerPosition, radius, color);
328
+ }
329
+
330
+ createLegendText(nucleobaseType: string, index: number, distinctNucleobaseTypes: string[], containsPhosphorothioateLinkages: boolean): SVGTextElement {
331
+ const legendPosition = this.dimensionsCalculator.getLegendTextPosition(index, distinctNucleobaseTypes, containsPhosphorothioateLinkages);
332
+ return this.svgElementFactory.createTextElement(nucleobaseType, legendPosition, SVG_TEXT_FONT_SIZES.COMMENT, SVG_ELEMENT_COLORS.TEXT);
333
+ }
334
+ }
335
+
336
+ class StrandElementBuilder {
337
+ constructor(
338
+ private svgFactory: SVGElementFactoryWrapper,
339
+ private config: PatternConfiguration
340
+ ) { }
341
+
342
+ createStrandElements(countOfNucleotidesExcludingOverhangs: StrandToNumberMap): SVGElement[] {
343
+ const svgElements = [] as SVGElement[];
344
+
345
+ const nucleotideCounter = new NucleotideCountTracker(countOfNucleotidesExcludingOverhangs);
346
+
347
+ STRANDS.forEach((strand) => {
348
+ const criterion = strand === STRAND.SENSE || (strand === STRAND.ANTISENSE && this.config.isAntisenseStrandIncluded);
349
+ if (!criterion)
350
+ return;
351
+
352
+ this.config.nucleotideSequences[strand].forEach((_, index) => {
353
+ const elements = this.createElementsForNucleotide(index, strand, nucleotideCounter, countOfNucleotidesExcludingOverhangs);
354
+ svgElements.push(...elements);
355
+ });
356
+ });
357
+
358
+ return svgElements;
359
+ }
360
+
361
+ // todo reduce the # of args
362
+ private createElementsForNucleotide(
363
+ indexOfNucleotide: number,
364
+ strand: STRAND,
365
+ counter: NucleotideCountTracker,
366
+ countOfNucleotidesExcludingOverhangs: StrandToNumberMap
367
+ ): SVGElement[] {
368
+ const nucleotide = this.config.nucleotideSequences[strand][indexOfNucleotide];
369
+ const isOverhang = isOverhangNucleotide(nucleotide);
370
+
371
+ counter.decrementIfNotOverhang(isOverhang, strand);
372
+ const displayedNucleotideNumber = strand === STRAND.SENSE ?
373
+ counter.getCurrentCount(strand) + 1 :
374
+ countOfNucleotidesExcludingOverhangs[strand] - counter.getCurrentCount(strand);
375
+
376
+
377
+ const nucleotideNumericLabel = this.svgFactory.createNucleotideNumericLabel(indexOfNucleotide, strand, displayedNucleotideNumber);
378
+ const nucleotideCircle = this.svgFactory. createNucleotideCircle(indexOfNucleotide, strand);
379
+ const nucleotideNameLabel = this.svgFactory. createNucleotideNameLabel(indexOfNucleotide, strand);
380
+
381
+ const phosphorothioateLinkageStar = this.svgFactory.createPhosphorothioateLinkageStar(strand, indexOfNucleotide);
382
+
383
+ const lastNucleotideIndex = this.config.nucleotideSequences[strand].length;
384
+ const lastStar = this.svgFactory.createPhosphorothioateLinkageStar(strand, lastNucleotideIndex);
385
+
386
+ const nucleotideSvgElements = [
387
+ nucleotideNumericLabel,
388
+ nucleotideCircle,
389
+ nucleotideNameLabel,
390
+ phosphorothioateLinkageStar,
391
+ lastStar,
392
+ ].filter((element) => element !== null) as SVGElement[];
393
+
394
+ return nucleotideSvgElements;
395
+ }
396
+ }
@@ -0,0 +1,37 @@
1
+ import {NUCLEOTIDES} from '../../../common/model/const';
2
+ import {PATTERN_APP_DATA} from '../../../common/model/data-loader/json-loader';
3
+ import {LUMINANCE_COEFFICIENTS, TEXT_COLOR, SVG_CIRCLE_SIZES} from './const';
4
+ import {isOverhangNucleotide} from '../../model/utils';
5
+
6
+ export function computeLegendCircleYPosition(isAntisenseStrandActive: boolean): number {
7
+ return (isAntisenseStrandActive ? 9.5 : 6) * SVG_CIRCLE_SIZES.NUCLEOBASE_RADIUS;
8
+ }
9
+
10
+ export function getNucleobaseLabelForCircle(nucleobase: string): string {
11
+ const criterion = !isOverhangNucleotide(nucleobase) && NUCLEOTIDES.includes(nucleobase);
12
+
13
+ return criterion ? nucleobase : '';
14
+ }
15
+
16
+ export function computeTextColorForNucleobaseLabel(nucleobase: string): string {
17
+ const nucleobaseColor = getNucleobaseColorFromStyleMap(nucleobase);
18
+
19
+ const rgbValues = nucleobaseColor.match(/\d+/g)?.map(Number);
20
+ if (!rgbValues || rgbValues.length < 3)
21
+ return TEXT_COLOR.LIGHT;
22
+
23
+
24
+ const [r, g, b] = rgbValues;
25
+ const luminance = r * LUMINANCE_COEFFICIENTS.RED + g * LUMINANCE_COEFFICIENTS.GREEN + b * LUMINANCE_COEFFICIENTS.BLUE;
26
+ return luminance > LUMINANCE_COEFFICIENTS.THRESHOLD ? TEXT_COLOR.DARK : TEXT_COLOR.LIGHT;
27
+ }
28
+
29
+ export function getNucleobaseColorFromStyleMap(nucleobase: string): string {
30
+ // todo: optimize
31
+ const format = Object.keys(PATTERN_APP_DATA)[0];
32
+ if (!format)
33
+ throw new Error('No format found in PATTERN_APP_DATA');
34
+
35
+ const styleMap = PATTERN_APP_DATA[format];
36
+ return styleMap[nucleobase].color || '';
37
+ }
@@ -0,0 +1,14 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ import {STRANDS, STRAND_ENDS, TERMINI} from '../model/const';
4
+
5
+ export type BooleanInput = DG.InputBase<boolean | null>;
6
+ export type StringInput = DG.InputBase<string | null>;
7
+ export type NumberInput = DG.InputBase<number | null>;
8
+
9
+ export type Position = { x: number, y: number };
10
+
11
+ export type StrandEndToNumberMap = Record<typeof STRAND_ENDS[number], number>;
12
+ export type StrandToNumberMap = Record<typeof STRANDS[number], number>;
13
+ export type StrandEndToSVGElementsMap = Record<typeof STRAND_ENDS[number], SVGElement | null>;
14
+ export type TerminusToSVGElementMap = Record<typeof TERMINI[number], SVGElement | null>
@@ -0,0 +1,61 @@
1
+ import * as ui from 'datagrok-api/ui';
2
+
3
+ import {APP_NAME} from '../../common/view/const';
4
+ import {IsolatedAppUIBase} from '../../common/view/isolated-app-ui';
5
+ import {DataManager} from '../model/data-manager';
6
+ import {EventBus} from '../model/event-bus';
7
+ import {URLRouter} from '../model/router';
8
+ import {PatternAppLeftSection} from './components/left-section';
9
+ import {PatternAppRightSection} from './components/right-section';
10
+ import {PatternConfigRecord} from '../model/types';
11
+
12
+
13
+ export class OligoPatternUI extends IsolatedAppUIBase {
14
+ constructor() {
15
+ super(APP_NAME.PATTERN);
16
+ }
17
+
18
+ protected getContent(): Promise<HTMLDivElement> {
19
+ return getContent();
20
+ }
21
+ }
22
+
23
+
24
+ async function getContent(): Promise<HTMLDivElement> {
25
+ const dataManager = await DataManager.getInstance();
26
+ const urlRouter = new URLRouter();
27
+
28
+ const initialPatternRecord = await getInitialPatternRecord(dataManager, urlRouter);
29
+ const eventBus = new EventBus(dataManager, initialPatternRecord);
30
+ urlRouter.subscribeToObservables(eventBus);
31
+
32
+ const leftSection = new PatternAppLeftSection(eventBus, dataManager).getLayout();
33
+ const rightSection = new PatternAppRightSection(eventBus, dataManager).getLayout();
34
+
35
+ const isResizeable = true;
36
+
37
+ const layout = ui.splitH([
38
+ leftSection,
39
+ rightSection,
40
+ ], {}, isResizeable);
41
+
42
+ return layout;
43
+ }
44
+
45
+ async function getInitialPatternRecord(
46
+ dataManager: DataManager,
47
+ urlRouter: URLRouter
48
+ ): Promise<PatternConfigRecord> {
49
+ const patternHash = urlRouter.getPatternHash();
50
+ if (!patternHash) {
51
+ urlRouter.clearPatternURL();
52
+ return dataManager.getDefaultPatternRecord();
53
+ }
54
+
55
+ let initialPatternRecord = await dataManager.getPatternRecord(patternHash);
56
+ if (!initialPatternRecord) {
57
+ urlRouter.clearPatternURL();
58
+ initialPatternRecord = dataManager.getDefaultPatternRecord();
59
+ }
60
+ return initialPatternRecord;
61
+ }
@@ -36,7 +36,7 @@ export function linkStrandsV3000(
36
36
 
37
37
  let inverted = false;
38
38
  const molBlocks = strands.senseStrands.concat(strands.antiStrands);
39
- /** Minimal value of AS and AS2 shift */
39
+ /** Minimal value of ANTISENSE_STRAND and AS2 shift */
40
40
  let ssYShift = 0;
41
41
 
42
42
  for (let i = 0; i < molBlocks.length; i++) {
@@ -47,12 +47,12 @@ export function linkStrandsV3000(
47
47
 
48
48
  if (i >= strands.senseStrands.length) {
49
49
  if (inverted === false) {
50
- // AS strand
50
+ // ANTISENSE_STRAND strand
51
51
  inverted = true;
52
52
  xShift = 0;
53
53
  }
54
54
  } else {
55
- // SS strands
55
+ // SENSE_STRAND strands
56
56
  ssYShift = Math.min(ssYShift, Math.min(
57
57
  ...coordinates.y.filter((item) => item < 0)
58
58
  ));
@@ -4,15 +4,15 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {PHOSPHATE_SYMBOL} from './const';
7
- import {sortByReverseLength} from '../helpers';
8
- import {MonomerLibWrapper} from '../monomer-lib/lib-wrapper';
9
- import {monomersWithPhosphateLinkers} from '../data-loading-utils/json-loader';
7
+ import {sortByReverseLength} from '../../common/model/helpers';
8
+ import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
9
+ import {MONOMERS_WITH_PHOSPHATE} from '../../common/model/data-loader/json-loader';
10
10
 
11
11
  /** Wrapper for parsing a strand and getting a sequence of monomer IDs (with
12
12
  * omitted linkers, if needed) */
13
13
  export class MonomerSequenceParser {
14
14
  constructor(
15
- private sequence: string,
15
+ private sequence: string,
16
16
  // todo: remove from the list of parameters
17
17
  private codeMap: Map<string, string>
18
18
  ) {
@@ -39,9 +39,8 @@ export class MonomerSequenceParser {
39
39
  const nextMonomerIsPhosphate = (i + 1 < parsedRawCodes.length && monomerIsPhosphateLinker(this.getSymbolForCode(parsedRawCodes[i + 1])));
40
40
 
41
41
  // todo: refactor as molfile-specific
42
- if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer) {
42
+ if (!isPhosphate && !monomerHasRightPhosphateLinker(monomerSymbol) && !nextMonomerIsPhosphate && !lastMonomer)
43
43
  monomerSymbolSequence.push(PHOSPHATE_SYMBOL);
44
- }
45
44
  });
46
45
  return monomerSymbolSequence;
47
46
  }
@@ -70,20 +69,20 @@ export class MonomerSequenceParser {
70
69
 
71
70
  // todo: port to monomer handler
72
71
  private getAllCodesOfFormat(): string[] {
73
- let allCodesInTheFormat = Array.from(this.codeMap.keys());
72
+ const allCodesInTheFormat = Array.from(this.codeMap.keys());
74
73
  return sortByReverseLength(allCodesInTheFormat);
75
74
  }
76
75
  }
77
76
 
78
77
  // todo: to be eliminated after full helm support
79
78
  function monomerHasLeftPhosphateLinker(monomerSymbol: string): boolean {
80
- return monomersWithPhosphateLinkers['left'].includes(monomerSymbol);
79
+ return MONOMERS_WITH_PHOSPHATE['left'].includes(monomerSymbol);
81
80
  }
82
81
 
83
82
  function monomerHasRightPhosphateLinker(monomerSymbol: string): boolean {
84
- return monomersWithPhosphateLinkers['right'].includes(monomerSymbol);
83
+ return MONOMERS_WITH_PHOSPHATE['right'].includes(monomerSymbol);
85
84
  }
86
85
 
87
86
  function monomerIsPhosphateLinker(monomerSymbol: string): boolean {
88
- return monomersWithPhosphateLinkers['phosphate'].includes(monomerSymbol);
87
+ return MONOMERS_WITH_PHOSPHATE['phosphate'].includes(monomerSymbol);
89
88
  }
@@ -4,11 +4,11 @@ import * as DG from 'datagrok-api/dg';
4
4
 
5
5
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
6
6
 
7
- import {download} from '../helpers';
7
+ import {download} from '../../common/model/helpers';
8
8
  import {SequenceToMolfileConverter} from './sequence-to-molfile';
9
9
  import {linkStrandsV3000} from './mol-transformations';
10
- import {DEFAULT_FORMATS} from '../const';
11
- import {FormatDetector} from '../parsing-validation/format-detector';
10
+ import {DEFAULT_FORMATS} from '../../common/model/const';
11
+ import {FormatDetector} from '../../common/model/parsing-validation/format-detector';
12
12
 
13
13
  export type StrandData = {
14
14
  strand: string,
@@ -62,7 +62,7 @@ export function saveSdf(
62
62
  nonEmptyStrands.length === 0 ||
63
63
  nonEmptyStrands.length === 1 && ss.strand === ''
64
64
  ) {
65
- grok.shell.warning('Enter SS and optionally AS/AS2 to save SDF');
65
+ grok.shell.warning('Enter SENSE_STRAND and optionally ANTISENSE_STRAND/AS2 to save SDF');
66
66
  } else {
67
67
  let result: string;
68
68
  if (oneEntity) {
@@ -4,7 +4,7 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {MonomerSequenceParser} from './monomer-code-parser';
7
- import {MonomerLibWrapper} from '../monomer-lib/lib-wrapper';
7
+ import {MonomerLibWrapper} from '../../common/model/monomer-lib/lib-wrapper';
8
8
 
9
9
  export class SequenceToMolfileConverter {
10
10
  constructor(
@@ -24,7 +24,7 @@ export class SequenceToMolfileConverter {
24
24
  parsedSequence.forEach((monomerSymbol, idx) => {
25
25
  const monomerMolfile = this.getMonomerMolfile(monomerSymbol, idx);
26
26
  monomerMolfiles.push(monomerMolfile);
27
- })
27
+ });
28
28
  let molfile = this.getPolymerMolfile(monomerMolfiles);
29
29
  if (this.invert) {
30
30
  molfile = this.reflect(molfile);