@datagrok/sequence-translator 1.0.12 → 1.0.14

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.
@@ -1,253 +1,192 @@
1
- import {axolabsMap} from './constants';
2
-
3
- // https://uxdesign.cc/star-rating-make-svg-great-again-d4ce4731347e
4
- function getPointsToDrawStar(centerX: number, centerY: number): string {
5
- const innerCirclePoints = 5; // a 5 point star
6
- const innerRadius = 15 / innerCirclePoints;
7
- const innerOuterRadiusRatio = 2; // outter circle is x2 the inner
8
- const outerRadius = innerRadius * innerOuterRadiusRatio;
9
- const angle = Math.PI / innerCirclePoints;
10
- const angleOffsetToCenterStar = 60;
11
- const totalNumberOfPoints = innerCirclePoints * 2; // 10 in a 5-points star
12
-
13
- let points = '';
14
- for (let i = 0; i < totalNumberOfPoints; i++) {
15
- const r = (i % 2 == 0) ? outerRadius : innerRadius;
16
- const currentX = centerX + Math.cos(i * angle + angleOffsetToCenterStar) * r;
17
- const currentY = centerY + Math.sin(i * angle + angleOffsetToCenterStar) * r;
18
- points += currentX + ',' + currentY + ' ';
19
- }
20
- return points;
21
- }
22
-
23
- function countOverhangsOnTheRightEdge(modifications: string[]): number {
24
- let i = 0;
25
- while (i < modifications.length && modifications[i].slice(-3) == '(o)')
26
- i++;
27
- return (i == modifications.length - 1) ? 0 : i;
28
- }
29
-
30
- function getTextWidth(text: string, font: number): number {
31
- const context = document.createElement('canvas').getContext('2d');
32
- // @ts-ignore
33
- context.font = String(font);
34
- // @ts-ignore
35
- return 2 * context.measureText(text).width;
36
- }
37
-
38
- function getTextInsideCircle(bases: string[], index: number): string {
39
- return (bases[index].slice(-3) == '(o)' || !['A', 'G', 'C', 'U', 'T'].includes(bases[index])) ? '' : bases[index];
40
- }
41
-
42
- function getFontColorVisibleOnBackground(rgbString: string): string {
43
- const rgbIntList = rgbString.match(/\d+/g)!.map((e) => Number(e));
44
- return (rgbIntList[0] * 0.299 + rgbIntList[1] * 0.587 + rgbIntList[2] * 0.114) > 186 ? '#33333' : '#ffffff';
45
- }
46
-
47
- function getBaseColor(base: string): string {
48
- return axolabsMap[base]['color'];
49
- }
1
+ import {NUCLEOTIDES} from '../structures-works/map';
2
+ import {isOverhang, svg, textWidth, countOverhangsOnTheRightEdge, baseColor, textInsideCircle,
3
+ fontColorVisibleOnBackground, isOneDigitNumber} from './helpers';
4
+
5
+ const BASE_RADIUS = 15;
6
+ const BASE_DIAMETER = 2 * BASE_RADIUS;
7
+ const shiftToAlignTwoDigitNumberNearCircle = -10;
8
+ const shiftToAlignOneDigitNumberNearCircle = -5;
9
+ const LEGEND_RADIUS = 6;
10
+ const PS_LINKAGE_RADIUS = 5;
11
+ const BASE_FONT_SIZE = 17;
12
+ const LEGEND_FONT_SIZE = 14;
13
+ const PS_LINKAGE_COLOR = 'red';
14
+ const FONT_COLOR = 'var(--grey-6)';
15
+ const TITLE_FONT_COLOR = 'black';
16
+ const MODIFICATIONS_COLOR = 'red';
17
+ const SS_LEFT_TEXT = 'SS: 5\'';
18
+ const AS_LEFT_TEXT = 'AS: 3\'';
19
+ const SS_RIGHT_TEXT = '3\'';
20
+ const AS_RIGHT_TEXT = '5\'';
21
+
22
+ const WIDTH_OF_LEFT_TEXT = Math.max(
23
+ textWidth(SS_LEFT_TEXT, BASE_FONT_SIZE),
24
+ textWidth(AS_LEFT_TEXT, BASE_FONT_SIZE),
25
+ );
26
+
27
+ const WIDTH_OF_RIGHT_TEXT = Math.max(
28
+ textWidth(SS_RIGHT_TEXT, BASE_FONT_SIZE),
29
+ textWidth(AS_RIGHT_TEXT, BASE_FONT_SIZE),
30
+ );
31
+
32
+ const X = {
33
+ TITLE: BASE_RADIUS, // Math.round(width / 4),
34
+ LEFT_TEXTS: 0,
35
+ };
36
+ const X_OF_LEFT_MODIFICATIONS = X.LEFT_TEXTS + WIDTH_OF_LEFT_TEXT - 5;
37
+
38
+ const Y = {
39
+ TITLE: BASE_RADIUS,
40
+ SS_INDICES: 2 * BASE_RADIUS,
41
+ SS_CIRCLES: 3.5 * BASE_RADIUS,
42
+ SS_TEXTS: 4 * BASE_RADIUS,
43
+ AS_CIRCLES: 6.5 * BASE_RADIUS,
44
+ AS_TEXTS: 7 * BASE_RADIUS,
45
+ AS_INDICES: 8.5 * BASE_RADIUS,
46
+ comment: (asExists: boolean) => (asExists) ? 11 * BASE_RADIUS : 8.5 * BASE_RADIUS,
47
+ circlesInLegends: (asExists: boolean) => (asExists) ? 9.5 * BASE_RADIUS : 6 * BASE_RADIUS,
48
+ textLegend: (asExists: boolean) => (asExists) ? 10 * BASE_RADIUS - 3 : Y.AS_CIRCLES - 3,
49
+ svgHeight: (asExists: boolean) => (asExists) ? 11 * BASE_RADIUS : 9 * BASE_RADIUS,
50
+ };
50
51
 
51
52
  export function drawAxolabsPattern(
52
53
  patternName: string, asExists: boolean, ssBases: string[],
53
54
  asBases: string[], ssPtoStatuses: boolean[], asPtoStatuses: boolean[],
54
- ssThreeModification: string, ssFiveModification: string,
55
- asThreeModification: string, asFiveModification: string, comment: string,
56
- enumerateModifications: string[]) {
57
- function getEquidistantXForLegend(index: number): number {
58
- return Math.round((index + startFrom) * width / (uniqueBases.length + startFrom) + legendRadius);
55
+ ss3Modification: string, ss5Modification: string,
56
+ as3Modification: string, as5Modification: string, comment: string,
57
+ enumerateModifications: string[],
58
+ ): Element {
59
+ function equidistantXForLegend(index: number): number {
60
+ return Math.round((index + startFrom) * width / (uniqueBases.length + startFrom) + LEGEND_RADIUS);
59
61
  }
60
62
 
61
- function getXOfBaseCircles(index: number, rightOverhangs: number): number {
63
+ function xOfBaseCircles(index: number, rightOverhangs: number): number {
62
64
  return widthOfRightModification +
63
- (resultingNumberOfNucleotidesInStrands - index + rightOverhangs + 1) * baseDiameter;
65
+ (resultingNumberOfNucleotidesInStrands - index + rightOverhangs + 1) * BASE_DIAMETER;
64
66
  }
65
67
 
66
- function getShiftToAlignNumberInsideCircle(bases: string[], generalIndex: number, nucleotideIndex: number): number {
67
- return (nucleotideIndex < 10 || ['A', 'G', 'C', 'U', 'T'].
68
- includes(bases[generalIndex])) ? shiftToAlignOneDigitNumberInsideCircle : shiftToAlignTwoDigitNumberInsideCircle;
68
+ function shiftToAlignNumberNearCircle(bases: string[], generalIndex: number, nucleotideIndex: number): number {
69
+ return (isOneDigitNumber(nucleotideIndex) || NUCLEOTIDES.includes(bases[generalIndex])) ?
70
+ shiftToAlignOneDigitNumberNearCircle : shiftToAlignTwoDigitNumberNearCircle;
69
71
  }
70
72
 
71
- const svg = {
72
- xmlns: 'http://www.w3.org/2000/svg',
73
- render: function(width: number, height: number) {
74
- const e = document.createElementNS(this.xmlns, 'svg');
75
- e.setAttribute('id', 'mySvg');
76
- e.setAttribute('width', String(width));
77
- e.setAttribute('height', String(height));
78
- return e;
79
- },
80
- circle: function(x: number, y: number, radius: number, color: string) {
81
- const e = document.createElementNS(this.xmlns, 'circle');
82
- e.setAttribute('cx', String(x));
83
- e.setAttribute('cy', String(y));
84
- e.setAttribute('r', String(radius));
85
- e.setAttribute('fill', color);
86
- return e;
87
- },
88
- text: function(text: string, x: number, y: number, fontSize: number, color: string) {
89
- const e = document.createElementNS(this.xmlns, 'text');
90
- e.setAttribute('x', String(x));
91
- e.setAttribute('y', String(y));
92
- e.setAttribute('font-size', String(fontSize));
93
- e.setAttribute('font-weight', 'normal');
94
- e.setAttribute('font-family', 'Arial');
95
- e.setAttribute('fill', color);
96
- e.innerHTML = text;
97
- return e;
98
- },
99
- star: function(x: number, y: number, fill: string) {
100
- const e = document.createElementNS(this.xmlns, 'polygon');
101
- e.setAttribute('points', getPointsToDrawStar(x, y));
102
- e.setAttribute('fill', fill);
103
- return e;
104
- },
105
- };
106
-
107
73
  ssBases = ssBases.reverse();
108
74
  ssPtoStatuses = ssPtoStatuses.reverse();
109
75
 
110
- const baseRadius = 15;
111
- const shiftToAlignTwoDigitNumberInsideCircle = -10;
112
- const shiftToAlignOneDigitNumberInsideCircle = -5;
113
- const legendRadius = 6;
114
- const psLinkageRadius = 5;
115
- const baseFontSize = 17;
116
- const legendFontSize = 14;
117
- const psLinkageColor = 'red';
118
- const fontColor = 'var(--grey-6)';
119
- const titleFontColor = 'black';
120
- const modificationsColor = 'red';
121
- const ssLeftText = 'SS: 5\'';
122
- const asLeftText = 'AS: 3\'';
123
- const ssRightText = '3\'';
124
- const asRightText = '5\'';
125
-
126
76
  const ssRightOverhangs = countOverhangsOnTheRightEdge(ssBases);
127
77
  const asRightOverhangs = countOverhangsOnTheRightEdge(asBases);
128
- const resultingNumberOfNucleotidesInStrands =
129
- Math.max(ssBases.length - ssRightOverhangs, asBases.length - asRightOverhangs);
130
- const baseDiameter = 2 * baseRadius;
131
- const widthOfBases =
132
- baseDiameter * (resultingNumberOfNucleotidesInStrands + Math.max(ssRightOverhangs, asRightOverhangs));
133
- const widthOfLeftModification =
134
- Math.max(getTextWidth(ssThreeModification, baseFontSize), getTextWidth(asFiveModification, baseFontSize));
135
- const widthOfRightModification =
136
- Math.max(getTextWidth(ssFiveModification, baseFontSize), getTextWidth(asThreeModification, baseFontSize));
137
- const widthOfLeftText = Math.max(getTextWidth(ssLeftText, baseFontSize), getTextWidth(asLeftText, baseFontSize));
138
- const widthOfRightText = Math.max(getTextWidth(ssRightText, baseFontSize), getTextWidth(asRightText, baseFontSize));
139
- const width =
140
- widthOfLeftText + widthOfLeftModification + widthOfBases +
141
- widthOfRightModification + widthOfRightText + baseDiameter;
142
- const height = asExists ? 11 * baseRadius : 9 * baseRadius;
143
- const xOfTitle = baseRadius; // Math.round(width / 4),
144
- const uniqueBases = asExists ? [...new Set(ssBases.concat(asBases))] : [...new Set(ssBases)];
145
- const isPtoExist =
146
- asExists ? [...new Set(ssPtoStatuses.concat(asPtoStatuses))].includes(true) :
147
- [...new Set(ssPtoStatuses)].includes(true);
78
+
79
+ const resultingNumberOfNucleotidesInStrands = Math.max(
80
+ ssBases.length - ssRightOverhangs,
81
+ asBases.length - asRightOverhangs,
82
+ );
83
+
84
+ const widthOfRightOverhangs = Math.max(ssRightOverhangs, asRightOverhangs);
85
+ const widthOfBases = BASE_DIAMETER * (resultingNumberOfNucleotidesInStrands + widthOfRightOverhangs);
86
+
87
+ const widthOfLeftModification = Math.max(
88
+ textWidth(ss3Modification, BASE_FONT_SIZE),
89
+ textWidth(as5Modification, BASE_FONT_SIZE),
90
+ );
91
+
92
+ const widthOfRightModification = Math.max(
93
+ textWidth(ss5Modification, BASE_FONT_SIZE),
94
+ textWidth(as3Modification, BASE_FONT_SIZE),
95
+ );
96
+
97
+ const uniqueBases = asExists ?
98
+ [...new Set(ssBases.concat(asBases))] :
99
+ [...new Set(ssBases)];
100
+
101
+ const isPtoExist = asExists ?
102
+ ssPtoStatuses.concat(asPtoStatuses).includes(true) :
103
+ ssPtoStatuses.includes(true);
104
+
148
105
  const startFrom = isPtoExist ? 1 : 0;
149
- const xOfLeftTexts = 0;
150
- const xOfLeftModifications = xOfLeftTexts + widthOfLeftText - 5;
151
- const xOfSsRightModifications = ssRightOverhangs * baseDiameter + getXOfBaseCircles(-0.5, 0);
152
- const xOfAsRightModifications = asRightOverhangs * baseDiameter + getXOfBaseCircles(-0.5, 0);
153
- const xOfRightTexts =
154
- Math.max(xOfSsRightModifications, xOfAsRightModifications) +
155
- widthOfLeftModification + baseDiameter * (Math.max(ssRightOverhangs, asRightOverhangs));
156
- const yOfTitle = baseRadius;
157
- const yOfSsNumbers = 2 * baseRadius;
158
- const yOfAsNumbers = 8.5 * baseRadius;
159
- const yOfSsTexts = 4 * baseRadius;
160
- const yOfAsTexts = 7 * baseRadius;
161
- const yOfComment = asExists ? 11 * baseRadius : 8.5 * baseRadius;
162
- const yOfSsCircles = 3.5 * baseRadius;
163
- const yOfAsCircles = 6.5 * baseRadius;
164
- const yOfCirclesInLegends = asExists ? 9.5 * baseRadius : 6 * baseRadius;
165
- const yOfTextLegend = asExists ? 10 * baseRadius - 3 : yOfAsCircles - 3;
166
-
167
- const image = svg.render(width, height);
106
+
107
+ const xOfSsRightModifications = ssRightOverhangs * BASE_DIAMETER + xOfBaseCircles(-0.5, 0);
108
+ const xOfAsRightModifications = asRightOverhangs * BASE_DIAMETER + xOfBaseCircles(-0.5, 0);
109
+
110
+ const xOfRightTexts = Math.max(xOfSsRightModifications, xOfAsRightModifications) + widthOfLeftModification +
111
+ BASE_DIAMETER * widthOfRightOverhangs;
112
+
113
+ const width = WIDTH_OF_LEFT_TEXT + widthOfLeftModification + widthOfBases + widthOfRightModification +
114
+ WIDTH_OF_RIGHT_TEXT + BASE_DIAMETER;
115
+ const image = svg.render(width, Y.svgHeight(asExists));
168
116
 
169
117
  image.append(
170
- svg.text(ssLeftText, xOfLeftTexts, yOfSsTexts, baseFontSize, fontColor),
171
- asExists ? svg.text(asLeftText, xOfLeftTexts, yOfAsTexts, baseFontSize, fontColor) : '',
172
- svg.text(ssRightText, xOfRightTexts, yOfSsTexts, baseFontSize, fontColor),
173
- asExists ? svg.text(asRightText, xOfRightTexts, yOfAsTexts, baseFontSize, fontColor) : '',
174
- svg.text(ssFiveModification, xOfLeftModifications, yOfSsTexts, baseFontSize, modificationsColor),
175
- asExists ? svg.text(asThreeModification, xOfLeftModifications, yOfAsTexts, baseFontSize, modificationsColor) : '',
176
- svg.text(ssThreeModification, xOfSsRightModifications, yOfSsTexts, baseFontSize, modificationsColor),
177
- asExists ? svg.text(asFiveModification, xOfAsRightModifications, yOfAsTexts, baseFontSize, modificationsColor) : '',
178
- svg.text(comment, xOfLeftTexts, yOfComment, legendFontSize, fontColor),
179
- isPtoExist ? svg.star(baseRadius, yOfCirclesInLegends, psLinkageColor) : '',
180
- isPtoExist ? svg.text('ps linkage', 2 * baseRadius - 8, yOfTextLegend, legendFontSize, fontColor) : '',
118
+ svg.text(SS_LEFT_TEXT, X.LEFT_TEXTS, Y.SS_TEXTS, BASE_FONT_SIZE, FONT_COLOR),
119
+ asExists ? svg.text(AS_LEFT_TEXT, X.LEFT_TEXTS, Y.AS_TEXTS, BASE_FONT_SIZE, FONT_COLOR) : '',
120
+ svg.text(SS_RIGHT_TEXT, xOfRightTexts, Y.SS_TEXTS, BASE_FONT_SIZE, FONT_COLOR),
121
+ asExists ? svg.text(AS_RIGHT_TEXT, xOfRightTexts, Y.AS_TEXTS, BASE_FONT_SIZE, FONT_COLOR) : '',
122
+ svg.text(ss5Modification, X_OF_LEFT_MODIFICATIONS, Y.SS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR),
123
+ asExists ? svg.text(as3Modification, X_OF_LEFT_MODIFICATIONS, Y.AS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR) : '',
124
+ svg.text(ss3Modification, xOfSsRightModifications, Y.SS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR),
125
+ asExists ? svg.text(as5Modification, xOfAsRightModifications, Y.AS_TEXTS, BASE_FONT_SIZE, MODIFICATIONS_COLOR) : '',
126
+ svg.text(comment, X.LEFT_TEXTS, Y.comment(asExists), LEGEND_FONT_SIZE, FONT_COLOR),
127
+ isPtoExist ? svg.star(BASE_RADIUS, Y.circlesInLegends(asExists), PS_LINKAGE_COLOR) : '',
128
+ isPtoExist ? svg.text('ps linkage', 2 * BASE_RADIUS - 8, Y.textLegend(asExists), LEGEND_FONT_SIZE, FONT_COLOR) : '',
181
129
  );
182
130
 
183
- let numberOfSsNucleotides = 0;
184
- for (let i = 0; i < ssBases.length; i++) {
185
- if (ssBases[i].slice(-3) != '(o)')
186
- numberOfSsNucleotides++;
187
- }
131
+ const numberOfSsNucleotides = ssBases.filter((value) => !isOverhang(value)).length;
188
132
  let nucleotideCounter = numberOfSsNucleotides;
189
133
  for (let i = ssBases.length - 1; i > -1; i--) {
190
- const xOfNumbers = getXOfBaseCircles(i, ssRightOverhangs) +
191
- getShiftToAlignNumberInsideCircle(ssBases, ssBases.length - i, numberOfSsNucleotides - nucleotideCounter);
192
- if (ssBases[i].slice(-3) != '(o)')
134
+ const xOfNumbers = xOfBaseCircles(i, ssRightOverhangs) +
135
+ shiftToAlignNumberNearCircle(ssBases, ssBases.length - i, numberOfSsNucleotides - nucleotideCounter);
136
+ if (!isOverhang(ssBases[i]))
193
137
  nucleotideCounter--;
194
- const n = (ssBases[i].slice(-3) != '(o)' && enumerateModifications.includes(ssBases[i])) ?
138
+ const n = (!isOverhang(ssBases[i]) && enumerateModifications.includes(ssBases[i])) ?
195
139
  String(numberOfSsNucleotides - nucleotideCounter) : '';
196
140
  image.append(
197
- svg.text(n, xOfNumbers, yOfSsNumbers, legendFontSize, fontColor),
198
- svg.circle(getXOfBaseCircles(i, ssRightOverhangs), yOfSsCircles, baseRadius, getBaseColor(ssBases[i])),
199
- svg.text(getTextInsideCircle(ssBases, i), xOfNumbers, yOfSsTexts, baseFontSize,
200
- getFontColorVisibleOnBackground(getBaseColor(ssBases[i]))),
141
+ svg.text(n, xOfNumbers, Y.SS_INDICES, LEGEND_FONT_SIZE, FONT_COLOR),
142
+ svg.circle(xOfBaseCircles(i, ssRightOverhangs), Y.SS_CIRCLES, BASE_RADIUS, baseColor(ssBases[i])),
143
+ svg.text(textInsideCircle(ssBases, i), xOfNumbers, Y.SS_TEXTS, BASE_FONT_SIZE,
144
+ fontColorVisibleOnBackground(ssBases[i])),
201
145
  ssPtoStatuses[i] ?
202
- svg.star(getXOfBaseCircles(i, ssRightOverhangs) + baseRadius, yOfSsTexts + psLinkageRadius, psLinkageColor) :
146
+ svg.star(xOfBaseCircles(i, ssRightOverhangs) + BASE_RADIUS, Y.SS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) :
203
147
  '',
204
148
  );
205
149
  }
206
150
  image.append(
207
151
  ssPtoStatuses[ssBases.length] ?
208
- svg.star(getXOfBaseCircles(ssBases.length, ssRightOverhangs) +
209
- baseRadius, yOfSsTexts + psLinkageRadius, psLinkageColor) : '',
152
+ svg.star(xOfBaseCircles(ssBases.length, ssRightOverhangs) +
153
+ BASE_RADIUS, Y.SS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) : '',
210
154
  );
211
155
 
212
- let numberOfAsNucleotides = 0;
213
- for (let i = 0; i < asBases.length; i++) {
214
- if (asBases[i].slice(-3) != '(o)')
215
- numberOfAsNucleotides++;
216
- }
156
+ const numberOfAsNucleotides = asBases.filter((value) => !isOverhang(value)).length;
217
157
  if (asExists) {
218
158
  let nucleotideCounter = numberOfAsNucleotides;
219
159
  for (let i = asBases.length - 1; i > -1; i--) {
220
- if (asBases[i].slice(-3) != '(o)')
160
+ if (!isOverhang(asBases[i]))
221
161
  nucleotideCounter--;
222
- const xOfNumbers = getXOfBaseCircles(i, asRightOverhangs) +
223
- getShiftToAlignNumberInsideCircle(asBases, i, nucleotideCounter + 1);
224
- const n = (asBases[i].slice(-3) != '(o)' && enumerateModifications.includes(asBases[i])) ?
162
+ const xOfNumbers = xOfBaseCircles(i, asRightOverhangs) +
163
+ shiftToAlignNumberNearCircle(asBases, i, nucleotideCounter + 1);
164
+ const n = (!isOverhang(asBases[i]) && enumerateModifications.includes(asBases[i])) ?
225
165
  String(nucleotideCounter + 1) : '';
226
166
  image.append(
227
- svg.text(n, xOfNumbers, yOfAsNumbers, legendFontSize, fontColor),
228
- svg.circle(getXOfBaseCircles(i, asRightOverhangs), yOfAsCircles, baseRadius, getBaseColor(asBases[i])),
229
- svg.text(getTextInsideCircle(asBases, i),
230
- getXOfBaseCircles(i, asRightOverhangs) + getShiftToAlignNumberInsideCircle(asBases, i, nucleotideCounter + 1),
231
- yOfAsTexts, baseFontSize, getFontColorVisibleOnBackground(getBaseColor(asBases[i]))),
232
- asPtoStatuses[i] ? svg.star(getXOfBaseCircles(i, asRightOverhangs) +
233
- baseRadius, yOfAsTexts + psLinkageRadius, psLinkageColor) : '',
167
+ svg.text(n, xOfNumbers, Y.AS_INDICES, LEGEND_FONT_SIZE, FONT_COLOR),
168
+ svg.circle(xOfBaseCircles(i, asRightOverhangs), Y.AS_CIRCLES, BASE_RADIUS, baseColor(asBases[i])),
169
+ svg.text(textInsideCircle(asBases, i),
170
+ xOfBaseCircles(i, asRightOverhangs) + shiftToAlignNumberNearCircle(asBases, i, nucleotideCounter + 1),
171
+ Y.AS_TEXTS, BASE_FONT_SIZE, fontColorVisibleOnBackground(asBases[i])),
172
+ asPtoStatuses[i] ? svg.star(xOfBaseCircles(i, asRightOverhangs) +
173
+ BASE_RADIUS, Y.AS_TEXTS + PS_LINKAGE_RADIUS, PS_LINKAGE_COLOR) : '',
234
174
  );
235
175
  }
236
176
  image.append(
237
177
  asPtoStatuses[asBases.length] ?
238
- svg.star(getXOfBaseCircles(asBases.length, asRightOverhangs) +
239
- baseRadius, yOfAsTexts + psLinkageRadius, psLinkageColor) : '',
178
+ svg.star(xOfBaseCircles(asBases.length, asRightOverhangs) + BASE_RADIUS, Y.AS_TEXTS + PS_LINKAGE_RADIUS,
179
+ PS_LINKAGE_COLOR) : '',
240
180
  );
241
181
  }
242
182
 
243
- const title = patternName + ' for ' +
244
- String(numberOfSsNucleotides) + (asExists ? '/' + String(numberOfAsNucleotides) : '') + 'mer';
245
- image.append(svg.text(title, xOfTitle, yOfTitle, baseFontSize, titleFontColor));
183
+ const title = `${patternName} for ${numberOfSsNucleotides}${(asExists ? `/${numberOfAsNucleotides}` : '')}mer`;
184
+ image.append(svg.text(title, X.TITLE, Y.TITLE, BASE_FONT_SIZE, TITLE_FONT_COLOR));
246
185
  for (let i = 0; i < uniqueBases.length; i++) {
247
186
  image.append(
248
- svg.circle(getEquidistantXForLegend(i), yOfCirclesInLegends, legendRadius, getBaseColor(uniqueBases[i])),
249
- svg.text(uniqueBases[i], getEquidistantXForLegend(i) +
250
- legendRadius + 4, yOfTextLegend, legendFontSize, fontColor),
187
+ svg.circle(equidistantXForLegend(i), Y.circlesInLegends(asExists), LEGEND_RADIUS, baseColor(uniqueBases[i])),
188
+ svg.text(uniqueBases[i], equidistantXForLegend(i) + LEGEND_RADIUS + 4, Y.textLegend(asExists), LEGEND_FONT_SIZE,
189
+ FONT_COLOR),
251
190
  );
252
191
  }
253
192
  return image;
@@ -0,0 +1,94 @@
1
+ import {AXOLABS_MAP} from './constants';
2
+ import {NUCLEOTIDES} from '../structures-works/map';
3
+
4
+ export function isOverhang(modification: string): boolean {
5
+ return modification.slice(-3) == '(o)';
6
+ }
7
+
8
+ export function isOneDigitNumber(n: number): boolean {
9
+ return n < 10;
10
+ }
11
+
12
+ // https://uxdesign.cc/star-rating-make-svg-great-again-d4ce4731347e
13
+ export function getPointsToDrawStar(centerX: number, centerY: number): string {
14
+ const innerCirclePoints = 5; // a 5 point star
15
+ const innerRadius = 15 / innerCirclePoints;
16
+ const innerOuterRadiusRatio = 2; // outter circle is x2 the inner
17
+ const outerRadius = innerRadius * innerOuterRadiusRatio;
18
+ const angle = Math.PI / innerCirclePoints;
19
+ const angleOffsetToCenterStar = 60;
20
+ const totalNumberOfPoints = innerCirclePoints * 2; // 10 in a 5-points star
21
+
22
+ let points = '';
23
+ for (let i = 0; i < totalNumberOfPoints; i++) {
24
+ const r = (i % 2 == 0) ? outerRadius : innerRadius;
25
+ const currentX = centerX + Math.cos(i * angle + angleOffsetToCenterStar) * r;
26
+ const currentY = centerY + Math.sin(i * angle + angleOffsetToCenterStar) * r;
27
+ points += `${currentX},${currentY} `;
28
+ }
29
+ return points;
30
+ }
31
+
32
+ export function countOverhangsOnTheRightEdge(modifications: string[]): number {
33
+ let i = 0;
34
+ while (i < modifications.length && isOverhang(modifications[i]))
35
+ i++;
36
+ return (i == modifications.length - 1) ? 0 : i;
37
+ }
38
+
39
+ export function textWidth(text: string, font: number): number {
40
+ const context = document.createElement('canvas').getContext('2d');
41
+ // @ts-ignore
42
+ context.font = String(font);
43
+ // @ts-ignore
44
+ return 2 * context.measureText(text).width;
45
+ }
46
+
47
+ export function textInsideCircle(bases: string[], index: number): string {
48
+ return (isOverhang(bases[index]) || !NUCLEOTIDES.includes(bases[index])) ? '' : bases[index];
49
+ }
50
+
51
+ export function fontColorVisibleOnBackground(base: string): string {
52
+ const rgbIntList = AXOLABS_MAP[base].color.match(/\d+/g)!.map((e) => Number(e));
53
+ return (rgbIntList[0] * 0.299 + rgbIntList[1] * 0.587 + rgbIntList[2] * 0.114) > 186 ? '#33333' : '#ffffff';
54
+ }
55
+
56
+ export function baseColor(base: string): string {
57
+ return AXOLABS_MAP[base].color;
58
+ }
59
+
60
+ export const svg = {
61
+ xmlns: 'http://www.w3.org/2000/svg',
62
+ render: function(width: number, height: number): Element {
63
+ const e = document.createElementNS(this.xmlns, 'svg');
64
+ e.setAttribute('id', 'mySvg');
65
+ e.setAttribute('width', String(width));
66
+ e.setAttribute('height', String(height));
67
+ return e;
68
+ },
69
+ circle: function(x: number, y: number, radius: number, color: string): Element {
70
+ const e = document.createElementNS(this.xmlns, 'circle');
71
+ e.setAttribute('cx', String(x));
72
+ e.setAttribute('cy', String(y));
73
+ e.setAttribute('r', String(radius));
74
+ e.setAttribute('fill', color);
75
+ return e;
76
+ },
77
+ text: function(text: string, x: number, y: number, fontSize: number, color: string): Element {
78
+ const e = document.createElementNS(this.xmlns, 'text');
79
+ e.setAttribute('x', String(x));
80
+ e.setAttribute('y', String(y));
81
+ e.setAttribute('font-size', String(fontSize));
82
+ e.setAttribute('font-weight', 'normal');
83
+ e.setAttribute('font-family', 'Arial');
84
+ e.setAttribute('fill', color);
85
+ e.innerHTML = text;
86
+ return e;
87
+ },
88
+ star: function(x: number, y: number, fill: string): Element {
89
+ const e = document.createElementNS(this.xmlns, 'polygon');
90
+ e.setAttribute('points', getPointsToDrawStar(x, y));
91
+ e.setAttribute('fill', fill);
92
+ return e;
93
+ },
94
+ };
package/src/helpers.ts ADDED
@@ -0,0 +1,28 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+
3
+ export function sortByStringLengthInDescendingOrder(array: string[]): string[] {
4
+ return array.sort(function(a, b) {return b.length - a.length;});
5
+ }
6
+
7
+ export function stringify(items: string[]): string {
8
+ return '["' + items.join('", "') + '"]';
9
+ }
10
+
11
+ export function download(name: string, href: string): void {
12
+ const element = document.createElement('a');
13
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + href);
14
+ element.setAttribute('download', name);
15
+ element.click();
16
+ }
17
+
18
+ export function removeEmptyRows(t: DG.DataFrame, colToCheck: DG.Column): DG.DataFrame {
19
+ for (let i = t.rowCount - 1; i > -1; i--) {
20
+ if (colToCheck.getString(i) == '')
21
+ t.rows.removeAt(i, 1, false);
22
+ }
23
+ return t;
24
+ }
25
+
26
+ export function differenceOfTwoArrays(a: string[], b: string[]): string[] {
27
+ return a.filter((x) => !b.includes(x));
28
+ }