@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.
- package/CHANGELOG.md +13 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/polytool-rules/rules_example.json +34 -0
- package/package.json +4 -3
- package/src/apps/pattern/model/event-bus.ts +23 -6
- package/src/apps/pattern/model/translator.ts +27 -1
- package/src/apps/pattern/view/components/bulk-convert/column-input.ts +13 -3
- package/src/apps/pattern/view/components/bulk-convert/table-controls.ts +4 -3
- package/src/apps/pattern/view/components/load-block-controls.ts +4 -2
- package/src/apps/pattern/view/svg-utils/const.ts +15 -1
- package/src/apps/pattern/view/svg-utils/legend-block.ts +92 -0
- package/src/apps/pattern/view/svg-utils/strands-block.ts +335 -0
- package/src/apps/pattern/view/svg-utils/svg-block-base.ts +37 -0
- package/src/apps/pattern/view/svg-utils/svg-display-manager.ts +4 -5
- package/src/apps/pattern/view/svg-utils/svg-element-factory.ts +16 -4
- package/src/apps/pattern/view/svg-utils/svg-renderer.ts +32 -377
- package/src/apps/pattern/view/svg-utils/text-dimensions-calculator.ts +29 -0
- package/src/apps/pattern/view/svg-utils/title-block.ts +53 -0
- package/src/apps/translator/view/ui.ts +1 -1
- package/src/package.ts +22 -6
- package/src/polytool/const.ts +3 -15
- package/src/polytool/pt-conversion.ts +307 -0
- package/src/polytool/pt-dialog.ts +115 -0
- package/src/polytool/pt-enumeration.ts +127 -0
- package/src/polytool/pt-rules.ts +73 -0
- package/src/polytool/utils.ts +7 -0
- package/src/tests/helm-to-nucleotides.ts +0 -5
- package/tsconfig.json +1 -1
- package/src/apps/pattern/view/svg-utils/dimensions-calculator.ts +0 -498
- package/src/polytool/transformation.ts +0 -326
- package/src/polytool/ui.ts +0 -59
|
@@ -1,396 +1,51 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import {PatternConfiguration} from '../../model/types';
|
|
3
|
+
import {StrandsBlock} from './strands-block';
|
|
4
|
+
import {SVGBlockBase} from './svg-block-base';
|
|
1
5
|
import {SVGElementFactory} from './svg-element-factory';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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';
|
|
6
|
+
import {TitleBlock} from './title-block';
|
|
7
|
+
import {LegendBlock} from './legend-block';
|
|
8
|
+
import {LEGEND_PADDING, TITLE_SHIFT} from './const';
|
|
13
9
|
|
|
14
10
|
export class NucleotidePatternSVGRenderer {
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private
|
|
18
|
-
private
|
|
19
|
-
private legendBuilder: LegendBuilder;
|
|
11
|
+
private title: TitleBlock;
|
|
12
|
+
private strands: StrandsBlock;
|
|
13
|
+
private legend: LegendBlock;
|
|
14
|
+
private svgElementFactory = new SVGElementFactory();
|
|
20
15
|
|
|
21
16
|
constructor(patternConfig: PatternConfiguration) {
|
|
22
|
-
|
|
17
|
+
const config = _.cloneDeep(patternConfig);
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
this.patternDimensionsCalculator = new PatternSVGDimensionsCalculator(this.config);
|
|
19
|
+
let heightShift = TITLE_SHIFT;
|
|
26
20
|
|
|
27
|
-
this.
|
|
21
|
+
this.title = new TitleBlock(this.svgElementFactory, config, heightShift);
|
|
22
|
+
heightShift += this.title.getContentHeight();
|
|
28
23
|
|
|
29
|
-
this.
|
|
30
|
-
|
|
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
|
-
}
|
|
24
|
+
this.strands = new StrandsBlock(this.svgElementFactory, config, heightShift);
|
|
25
|
+
heightShift += this.strands.getContentHeight() + LEGEND_PADDING;
|
|
185
26
|
|
|
186
|
-
|
|
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
|
-
);
|
|
27
|
+
this.legend = new LegendBlock(this.svgElementFactory, config, heightShift);
|
|
198
28
|
}
|
|
199
29
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
}
|
|
30
|
+
renderPattern(): SVGElement {
|
|
31
|
+
const width = this.getGlobalWidth();
|
|
32
|
+
const height = this.getGlobalHeight();
|
|
33
|
+
const canvas = this.svgElementFactory.createCanvas(width, height);
|
|
321
34
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const color = getNucleobaseColorFromStyleMap(nucleobaseType);
|
|
35
|
+
const elements = [this.title, this.strands, this.legend].map((block) => block.svgElements).flat();
|
|
36
|
+
canvas.append(...elements);
|
|
325
37
|
|
|
326
|
-
|
|
327
|
-
return this.svgElementFactory.createCircleElement(centerPosition, radius, color);
|
|
38
|
+
return canvas;
|
|
328
39
|
}
|
|
329
40
|
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
return
|
|
41
|
+
private getGlobalWidth(): number {
|
|
42
|
+
const blocks = [this.title, this.strands, this.legend] as SVGBlockBase[];
|
|
43
|
+
return Math.max(...blocks.map((block) => block.getContentWidth()));
|
|
333
44
|
}
|
|
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
45
|
|
|
394
|
-
|
|
46
|
+
private getGlobalHeight(): number {
|
|
47
|
+
const blocks = [this.title, this.strands, this.legend];
|
|
48
|
+
const height = blocks.reduce((acc, block) => acc + block.getContentHeight(), TITLE_SHIFT);
|
|
49
|
+
return height;
|
|
395
50
|
}
|
|
396
51
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class TextDimensionsCalculator {
|
|
2
|
+
private static instance: TextDimensionsCalculator;
|
|
3
|
+
private canvas: HTMLCanvasElement;
|
|
4
|
+
private constructor() { }
|
|
5
|
+
|
|
6
|
+
private static getInstance(): TextDimensionsCalculator {
|
|
7
|
+
if (!TextDimensionsCalculator.instance) {
|
|
8
|
+
TextDimensionsCalculator.instance = new TextDimensionsCalculator();
|
|
9
|
+
TextDimensionsCalculator.instance.canvas = document.createElement('canvas');
|
|
10
|
+
}
|
|
11
|
+
return TextDimensionsCalculator.instance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static getTextDimensions(text: string, fontSize: number): {width: number, height: number} {
|
|
15
|
+
const canvas = TextDimensionsCalculator.getInstance().canvas;
|
|
16
|
+
const context = canvas.getContext('2d');
|
|
17
|
+
if (!context)
|
|
18
|
+
throw new Error('Canvas 2D context is not available');
|
|
19
|
+
|
|
20
|
+
context.font = `${fontSize}px Arial`;
|
|
21
|
+
const scaleFactor = 1.1;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
width: context.measureText(text).width * scaleFactor,
|
|
25
|
+
height: fontSize * scaleFactor
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {STRAND} from '../../model/const';
|
|
2
|
+
import {PatternConfiguration} from '../../model/types';
|
|
3
|
+
import {FONT_SIZE} from './const';
|
|
4
|
+
import {SVGBlockBase} from './svg-block-base';
|
|
5
|
+
import {SVGElementFactory} from './svg-element-factory';
|
|
6
|
+
import {TextDimensionsCalculator} from './text-dimensions-calculator';
|
|
7
|
+
|
|
8
|
+
const TITLE_LEFT_PADDING = 15;
|
|
9
|
+
|
|
10
|
+
export class TitleBlock extends SVGBlockBase {
|
|
11
|
+
private titleText: string;
|
|
12
|
+
private _svgElements: SVGElement[] = [];
|
|
13
|
+
constructor(
|
|
14
|
+
protected svgElementFactory: SVGElementFactory,
|
|
15
|
+
protected config: PatternConfiguration,
|
|
16
|
+
protected yShift: number
|
|
17
|
+
) {
|
|
18
|
+
super(svgElementFactory, config, yShift);
|
|
19
|
+
this.titleText = this.getTitleText();
|
|
20
|
+
this._svgElements = [this.getTitle()];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get svgElements(): SVGElement[] {
|
|
24
|
+
return this._svgElements;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private getTitle(): SVGElement {
|
|
28
|
+
return this.svgElementFactory.createTextElement(
|
|
29
|
+
this.titleText,
|
|
30
|
+
{x: TITLE_LEFT_PADDING, y: this.yShift + FONT_SIZE.TITLE},
|
|
31
|
+
FONT_SIZE.TITLE,
|
|
32
|
+
'black'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getContentWidth(): number {
|
|
37
|
+
return TextDimensionsCalculator.getTextDimensions(this.titleText, FONT_SIZE.TITLE).width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getContentHeight(): number {
|
|
41
|
+
return TextDimensionsCalculator.getTextDimensions(this.titleText, FONT_SIZE.TITLE).height;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private getTitleText(): string {
|
|
45
|
+
const senseStrandLength = `${this.config.nucleotideSequences[STRAND.SENSE].length}`;
|
|
46
|
+
const antisenseStrandLength = this.config.isAntisenseStrandIncluded ?
|
|
47
|
+
`/${this.config.nucleotideSequences[STRAND.ANTISENSE].length}` : '';
|
|
48
|
+
const titleText = `${this.config.patternName} for ${senseStrandLength}${antisenseStrandLength}-mer`;
|
|
49
|
+
|
|
50
|
+
return titleText;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -165,7 +165,7 @@ class TranslatorAppLayout {
|
|
|
165
165
|
translatedColumn.semType = DG.SEMTYPE.MACROMOLECULE;
|
|
166
166
|
const units = outputFormat == NUCLEOTIDES_FORMAT ? NOTATION.FASTA : NOTATION.HELM;
|
|
167
167
|
translatedColumn.setTag(DG.TAGS.UNITS, units);
|
|
168
|
-
const seqHandler = SeqHandler.forColumn(translatedColumn);
|
|
168
|
+
const seqHandler = SeqHandler.forColumn(translatedColumn as DG.Column<string>);
|
|
169
169
|
const setUnits = outputFormat == NUCLEOTIDES_FORMAT ? SeqHandler.setUnitsToFastaColumn :
|
|
170
170
|
SeqHandler.setUnitsToHelmColumn;
|
|
171
171
|
setUnits(seqHandler);
|
package/src/package.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import DG from 'datagrok-api/dg';
|
|
2
2
|
import * as grok from 'datagrok-api/grok';
|
|
3
|
+
|
|
3
4
|
import {loadJsonData} from './apps/common/model/data-loader/json-loader';
|
|
4
5
|
import {MonomerLibWrapper} from './apps/common/model/monomer-lib/lib-wrapper';
|
|
5
6
|
import {OligoToolkitPackage} from './apps/common/model/oligo-toolkit-package';
|
|
@@ -14,10 +15,12 @@ import {FormatConverter} from './apps/translator/model/format-converter';
|
|
|
14
15
|
import {demoOligoPatternUI, demoOligoStructureUI, demoOligoTranslatorUI} from './demo/demo-st-ui';
|
|
15
16
|
import {getExternalAppViewFactories} from './plugins/mermade';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
//polytool specific
|
|
19
|
+
import {getPolyToolConversionDialog, getPolyToolEnumerationDialog} from './polytool/pt-dialog';
|
|
18
20
|
import {_setPeptideColumn} from './polytool/utils';
|
|
19
21
|
import {PolyToolCsvLibHandler} from './polytool/csv-to-json-monomer-lib-converter';
|
|
20
22
|
|
|
23
|
+
|
|
21
24
|
export const _package: OligoToolkitPackage = new OligoToolkitPackage();
|
|
22
25
|
|
|
23
26
|
//name: Oligo Toolkit
|
|
@@ -145,16 +148,29 @@ async function getSpecifiedAppView(appName: string): Promise<DG.ViewBase> {
|
|
|
145
148
|
return view;
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
//top-menu: Bio | Convert | PolyTool
|
|
149
|
-
//name:
|
|
151
|
+
//top-menu: Bio | Convert | PolyTool-Convert
|
|
152
|
+
//name: polyToolConvert
|
|
153
|
+
//description: Perform cyclization of polymers
|
|
154
|
+
export async function polyToolConvert(): Promise<void> {
|
|
155
|
+
let dialog: DG.Dialog;
|
|
156
|
+
try {
|
|
157
|
+
dialog = await getPolyToolConversionDialog();
|
|
158
|
+
dialog.show();
|
|
159
|
+
} catch (err: any) {
|
|
160
|
+
grok.shell.warning('To run PolyTool Conversion, open a dataframe with macromolecules');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//top-menu: Bio | Convert | PolyTool-Enumerate
|
|
165
|
+
//name: polyToolEnumerate
|
|
150
166
|
//description: Perform cyclization of polymers
|
|
151
|
-
export async function
|
|
167
|
+
export async function polyToolEnumerate(): Promise<void> {
|
|
152
168
|
let dialog: DG.Dialog;
|
|
153
169
|
try {
|
|
154
|
-
dialog = await
|
|
170
|
+
dialog = await getPolyToolEnumerationDialog();
|
|
155
171
|
dialog.show();
|
|
156
172
|
} catch (err: any) {
|
|
157
|
-
grok.shell.warning('To run PolyTool,
|
|
173
|
+
grok.shell.warning('To run PolyTool Enumeration, sketch the macromolecule and select monomers to vary');
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
|
package/src/polytool/const.ts
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import {HELM_REQUIRED_FIELD} from '@datagrok-libraries/bio/src/utils/const';
|
|
2
2
|
|
|
3
|
-
export const ALL_MONOMERS = '<All>';
|
|
4
|
-
|
|
5
|
-
export const enum TRANSFORMATION_TYPE {
|
|
6
|
-
CYCLIZATION = 'Cyclization',
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const enum CYCLIZATION_TYPE {
|
|
10
|
-
NO = 'N-O',
|
|
11
|
-
NCys = 'N-Cys',
|
|
12
|
-
R3 = 'R3-R3',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
3
|
export const helmFieldsToPolyToolInputFields = {
|
|
16
4
|
[HELM_REQUIRED_FIELD.SYMBOL]: 'Short Name',
|
|
17
5
|
[HELM_REQUIRED_FIELD.NAME]: 'Medium Name',
|
|
@@ -20,19 +8,19 @@ export const helmFieldsToPolyToolInputFields = {
|
|
|
20
8
|
|
|
21
9
|
export const R_GROUP_BLOCK_DUMMY = [
|
|
22
10
|
{
|
|
23
|
-
'
|
|
11
|
+
'capGroupSmiles': '[*:1][H]',
|
|
24
12
|
'alternateId': 'R1-H',
|
|
25
13
|
'capGroupName': 'H',
|
|
26
14
|
'label': 'R1'
|
|
27
15
|
},
|
|
28
16
|
{
|
|
29
|
-
'
|
|
17
|
+
'capGroupSmiles': 'O[*:2]',
|
|
30
18
|
'alternateId': 'R2-OH',
|
|
31
19
|
'capGroupName': 'OH',
|
|
32
20
|
'label': 'R2'
|
|
33
21
|
},
|
|
34
22
|
{
|
|
35
|
-
'
|
|
23
|
+
'capGroupSmiles': '[*:3][H]',
|
|
36
24
|
'alternateId': 'R3-H',
|
|
37
25
|
'capGroupName': 'H',
|
|
38
26
|
'label': 'R3'
|