@dytsou/github-readme-stats 1.0.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/LICENSE +21 -0
- package/api/gist.js +98 -0
- package/api/index.js +146 -0
- package/api/pin.js +114 -0
- package/api/status/pat-info.js +193 -0
- package/api/status/up.js +129 -0
- package/api/top-langs.js +163 -0
- package/api/wakatime.js +129 -0
- package/package.json +102 -0
- package/readme.md +412 -0
- package/src/calculateRank.js +87 -0
- package/src/cards/gist.js +151 -0
- package/src/cards/index.js +4 -0
- package/src/cards/repo.js +199 -0
- package/src/cards/stats.js +607 -0
- package/src/cards/top-languages.js +968 -0
- package/src/cards/types.d.ts +67 -0
- package/src/cards/wakatime.js +482 -0
- package/src/common/Card.js +294 -0
- package/src/common/I18n.js +41 -0
- package/src/common/access.js +69 -0
- package/src/common/api-utils.js +221 -0
- package/src/common/blacklist.js +10 -0
- package/src/common/cache.js +153 -0
- package/src/common/color.js +194 -0
- package/src/common/envs.js +15 -0
- package/src/common/error.js +84 -0
- package/src/common/fmt.js +90 -0
- package/src/common/html.js +43 -0
- package/src/common/http.js +24 -0
- package/src/common/icons.js +63 -0
- package/src/common/index.js +13 -0
- package/src/common/languageColors.json +651 -0
- package/src/common/log.js +14 -0
- package/src/common/ops.js +124 -0
- package/src/common/render.js +261 -0
- package/src/common/retryer.js +97 -0
- package/src/common/worker-adapter.js +148 -0
- package/src/common/worker-env.js +48 -0
- package/src/fetchers/gist.js +114 -0
- package/src/fetchers/repo.js +118 -0
- package/src/fetchers/stats.js +350 -0
- package/src/fetchers/top-languages.js +192 -0
- package/src/fetchers/types.d.ts +118 -0
- package/src/fetchers/wakatime.js +109 -0
- package/src/index.js +2 -0
- package/src/translations.js +1105 -0
- package/src/worker.ts +116 -0
- package/themes/README.md +229 -0
- package/themes/index.js +467 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Card } from "../common/Card.js";
|
|
4
|
+
import { getCardColors } from "../common/color.js";
|
|
5
|
+
import { formatBytes } from "../common/fmt.js";
|
|
6
|
+
import { escapeCSSValue } from "../common/html.js";
|
|
7
|
+
import { I18n } from "../common/I18n.js";
|
|
8
|
+
import { chunkArray, clampValue, lowercaseTrim } from "../common/ops.js";
|
|
9
|
+
import {
|
|
10
|
+
createProgressNode,
|
|
11
|
+
flexLayout,
|
|
12
|
+
measureText,
|
|
13
|
+
} from "../common/render.js";
|
|
14
|
+
import { langCardLocales } from "../translations.js";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CARD_WIDTH = 300;
|
|
17
|
+
const MIN_CARD_WIDTH = 280;
|
|
18
|
+
const DEFAULT_LANG_COLOR = "#858585";
|
|
19
|
+
const CARD_PADDING = 25;
|
|
20
|
+
const COMPACT_LAYOUT_BASE_HEIGHT = 90;
|
|
21
|
+
const MAXIMUM_LANGS_COUNT = 20;
|
|
22
|
+
|
|
23
|
+
const NORMAL_LAYOUT_DEFAULT_LANGS_COUNT = 5;
|
|
24
|
+
const COMPACT_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
|
25
|
+
const DONUT_LAYOUT_DEFAULT_LANGS_COUNT = 5;
|
|
26
|
+
const PIE_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
|
27
|
+
const DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT = 6;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {import("../fetchers/types").Lang} Lang
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves the programming language whose name is the longest.
|
|
35
|
+
*
|
|
36
|
+
* @param {Lang[]} arr Array of programming languages.
|
|
37
|
+
* @returns {{ name: string, size: number, color: string }} Longest programming language object.
|
|
38
|
+
*/
|
|
39
|
+
const getLongestLang = (arr) =>
|
|
40
|
+
arr.reduce(
|
|
41
|
+
(savedLang, lang) =>
|
|
42
|
+
lang.name.length > savedLang.name.length ? lang : savedLang,
|
|
43
|
+
{ name: "", size: 0, color: "" },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert degrees to radians.
|
|
48
|
+
*
|
|
49
|
+
* @param {number} angleInDegrees Angle in degrees.
|
|
50
|
+
* @returns {number} Angle in radians.
|
|
51
|
+
*/
|
|
52
|
+
const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert radians to degrees.
|
|
56
|
+
*
|
|
57
|
+
* @param {number} angleInRadians Angle in radians.
|
|
58
|
+
* @returns {number} Angle in degrees.
|
|
59
|
+
*/
|
|
60
|
+
const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Convert polar coordinates to cartesian coordinates.
|
|
64
|
+
*
|
|
65
|
+
* @param {number} centerX Center x coordinate.
|
|
66
|
+
* @param {number} centerY Center y coordinate.
|
|
67
|
+
* @param {number} radius Radius of the circle.
|
|
68
|
+
* @param {number} angleInDegrees Angle in degrees.
|
|
69
|
+
* @returns {{x: number, y: number}} Cartesian coordinates.
|
|
70
|
+
*/
|
|
71
|
+
const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
|
|
72
|
+
const rads = degreesToRadians(angleInDegrees);
|
|
73
|
+
return {
|
|
74
|
+
x: centerX + radius * Math.cos(rads),
|
|
75
|
+
y: centerY + radius * Math.sin(rads),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convert cartesian coordinates to polar coordinates.
|
|
81
|
+
*
|
|
82
|
+
* @param {number} centerX Center x coordinate.
|
|
83
|
+
* @param {number} centerY Center y coordinate.
|
|
84
|
+
* @param {number} x Point x coordinate.
|
|
85
|
+
* @param {number} y Point y coordinate.
|
|
86
|
+
* @returns {{radius: number, angleInDegrees: number}} Polar coordinates.
|
|
87
|
+
*/
|
|
88
|
+
const cartesianToPolar = (centerX, centerY, x, y) => {
|
|
89
|
+
const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
|
|
90
|
+
let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX));
|
|
91
|
+
if (angleInDegrees < 0) {
|
|
92
|
+
angleInDegrees += 360;
|
|
93
|
+
}
|
|
94
|
+
return { radius, angleInDegrees };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Calculates length of circle.
|
|
99
|
+
*
|
|
100
|
+
* @param {number} radius Radius of the circle.
|
|
101
|
+
* @returns {number} The length of the circle.
|
|
102
|
+
*/
|
|
103
|
+
const getCircleLength = (radius) => {
|
|
104
|
+
return 2 * Math.PI * radius;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculates height for the compact layout.
|
|
109
|
+
*
|
|
110
|
+
* @param {number} totalLangs Total number of languages.
|
|
111
|
+
* @returns {number} Card height.
|
|
112
|
+
*/
|
|
113
|
+
const calculateCompactLayoutHeight = (totalLangs) => {
|
|
114
|
+
return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Calculates height for the normal layout.
|
|
119
|
+
*
|
|
120
|
+
* @param {number} totalLangs Total number of languages.
|
|
121
|
+
* @returns {number} Card height.
|
|
122
|
+
*/
|
|
123
|
+
const calculateNormalLayoutHeight = (totalLangs) => {
|
|
124
|
+
return 45 + (totalLangs + 1) * 40;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Calculates height for the donut layout.
|
|
129
|
+
*
|
|
130
|
+
* @param {number} totalLangs Total number of languages.
|
|
131
|
+
* @returns {number} Card height.
|
|
132
|
+
*/
|
|
133
|
+
const calculateDonutLayoutHeight = (totalLangs) => {
|
|
134
|
+
return 215 + Math.max(totalLangs - 5, 0) * 32;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Calculates height for the donut vertical layout.
|
|
139
|
+
*
|
|
140
|
+
* @param {number} totalLangs Total number of languages.
|
|
141
|
+
* @returns {number} Card height.
|
|
142
|
+
*/
|
|
143
|
+
const calculateDonutVerticalLayoutHeight = (totalLangs) => {
|
|
144
|
+
return 300 + Math.round(totalLangs / 2) * 25;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Calculates height for the pie layout.
|
|
149
|
+
*
|
|
150
|
+
* @param {number} totalLangs Total number of languages.
|
|
151
|
+
* @returns {number} Card height.
|
|
152
|
+
*/
|
|
153
|
+
const calculatePieLayoutHeight = (totalLangs) => {
|
|
154
|
+
return 300 + Math.round(totalLangs / 2) * 25;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Calculates the center translation needed to keep the donut chart centred.
|
|
159
|
+
* @param {number} totalLangs Total number of languages.
|
|
160
|
+
* @returns {number} Donut center translation.
|
|
161
|
+
*/
|
|
162
|
+
const donutCenterTranslation = (totalLangs) => {
|
|
163
|
+
return -45 + Math.max(totalLangs - 5, 0) * 16;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Trim top languages to lang_count while also hiding certain languages.
|
|
168
|
+
*
|
|
169
|
+
* @param {Record<string, Lang>} topLangs Top languages.
|
|
170
|
+
* @param {number} langs_count Number of languages to show.
|
|
171
|
+
* @param {string[]=} hide Languages to hide.
|
|
172
|
+
* @returns {{ langs: Lang[], totalLanguageSize: number }} Trimmed top languages and total size.
|
|
173
|
+
*/
|
|
174
|
+
const trimTopLanguages = (topLangs, langs_count, hide) => {
|
|
175
|
+
let langs = Object.values(topLangs);
|
|
176
|
+
let langsToHide = {};
|
|
177
|
+
let langsCount = clampValue(langs_count, 1, MAXIMUM_LANGS_COUNT);
|
|
178
|
+
|
|
179
|
+
// populate langsToHide map for quick lookup
|
|
180
|
+
// while filtering out
|
|
181
|
+
if (hide) {
|
|
182
|
+
hide.forEach((langName) => {
|
|
183
|
+
// @ts-ignore
|
|
184
|
+
langsToHide[lowercaseTrim(langName)] = true;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// filter out languages to be hidden
|
|
189
|
+
langs = langs
|
|
190
|
+
.sort((a, b) => b.size - a.size)
|
|
191
|
+
.filter((lang) => {
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
return !langsToHide[lowercaseTrim(lang.name)];
|
|
194
|
+
})
|
|
195
|
+
.slice(0, langsCount);
|
|
196
|
+
|
|
197
|
+
const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0);
|
|
198
|
+
|
|
199
|
+
return { langs, totalLanguageSize };
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get display value corresponding to the format.
|
|
204
|
+
*
|
|
205
|
+
* @param {number} size Bytes size.
|
|
206
|
+
* @param {number} percentages Percentage value.
|
|
207
|
+
* @param {string} format Format of the stats.
|
|
208
|
+
* @returns {string} Display value.
|
|
209
|
+
*/
|
|
210
|
+
const getDisplayValue = (size, percentages, format) => {
|
|
211
|
+
return format === "bytes" ? formatBytes(size) : `${percentages.toFixed(2)}%`;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create progress bar text item for a programming language.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} props Function properties.
|
|
218
|
+
* @param {number} props.width The card width
|
|
219
|
+
* @param {string} props.color Color of the programming language.
|
|
220
|
+
* @param {string} props.name Name of the programming language.
|
|
221
|
+
* @param {number} props.size Size of the programming language.
|
|
222
|
+
* @param {number} props.totalSize Total size of all languages.
|
|
223
|
+
* @param {string} props.statsFormat Stats format.
|
|
224
|
+
* @param {number} props.index Index of the programming language.
|
|
225
|
+
* @returns {string} Programming language SVG node.
|
|
226
|
+
*/
|
|
227
|
+
const createProgressTextNode = ({
|
|
228
|
+
width,
|
|
229
|
+
color,
|
|
230
|
+
name,
|
|
231
|
+
size,
|
|
232
|
+
totalSize,
|
|
233
|
+
statsFormat,
|
|
234
|
+
index,
|
|
235
|
+
}) => {
|
|
236
|
+
const staggerDelay = (index + 3) * 150;
|
|
237
|
+
const paddingRight = 95;
|
|
238
|
+
const progressTextX = width - paddingRight + 10;
|
|
239
|
+
const progressWidth = width - paddingRight;
|
|
240
|
+
|
|
241
|
+
const progress = (size / totalSize) * 100;
|
|
242
|
+
const displayValue = getDisplayValue(size, progress, statsFormat);
|
|
243
|
+
|
|
244
|
+
return `
|
|
245
|
+
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
|
|
246
|
+
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
|
|
247
|
+
<text x="${progressTextX}" y="34" class="lang-name">${displayValue}</text>
|
|
248
|
+
${createProgressNode({
|
|
249
|
+
x: 0,
|
|
250
|
+
y: 25,
|
|
251
|
+
color,
|
|
252
|
+
width: progressWidth,
|
|
253
|
+
progress,
|
|
254
|
+
progressBarBackgroundColor: "#ddd",
|
|
255
|
+
delay: staggerDelay + 300,
|
|
256
|
+
})}
|
|
257
|
+
</g>
|
|
258
|
+
`;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates compact text item for a programming language.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} props Function properties.
|
|
265
|
+
* @param {Lang} props.lang Programming language object.
|
|
266
|
+
* @param {number} props.totalSize Total size of all languages.
|
|
267
|
+
* @param {boolean=} props.hideProgress Whether to hide percentage.
|
|
268
|
+
* @param {string=} props.statsFormat Stats format
|
|
269
|
+
* @param {number} props.index Index of the programming language.
|
|
270
|
+
* @returns {string} Compact layout programming language SVG node.
|
|
271
|
+
*/
|
|
272
|
+
const createCompactLangNode = ({
|
|
273
|
+
lang,
|
|
274
|
+
totalSize,
|
|
275
|
+
hideProgress,
|
|
276
|
+
statsFormat = "percentages",
|
|
277
|
+
index,
|
|
278
|
+
}) => {
|
|
279
|
+
const percentages = (lang.size / totalSize) * 100;
|
|
280
|
+
const displayValue = getDisplayValue(lang.size, percentages, statsFormat);
|
|
281
|
+
|
|
282
|
+
const staggerDelay = (index + 3) * 150;
|
|
283
|
+
const color = lang.color || "#858585";
|
|
284
|
+
|
|
285
|
+
return `
|
|
286
|
+
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
|
|
287
|
+
<circle cx="5" cy="6" r="5" fill="${color}" />
|
|
288
|
+
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
|
289
|
+
${lang.name} ${hideProgress ? "" : displayValue}
|
|
290
|
+
</text>
|
|
291
|
+
</g>
|
|
292
|
+
`;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create compact languages text items for all programming languages.
|
|
297
|
+
*
|
|
298
|
+
* @param {object} props Function properties.
|
|
299
|
+
* @param {Lang[]} props.langs Array of programming languages.
|
|
300
|
+
* @param {number} props.totalSize Total size of all languages.
|
|
301
|
+
* @param {boolean=} props.hideProgress Whether to hide percentage.
|
|
302
|
+
* @param {string=} props.statsFormat Stats format
|
|
303
|
+
* @returns {string} Programming languages SVG node.
|
|
304
|
+
*/
|
|
305
|
+
const createLanguageTextNode = ({
|
|
306
|
+
langs,
|
|
307
|
+
totalSize,
|
|
308
|
+
hideProgress,
|
|
309
|
+
statsFormat,
|
|
310
|
+
}) => {
|
|
311
|
+
const longestLang = getLongestLang(langs);
|
|
312
|
+
const chunked = chunkArray(langs, langs.length / 2);
|
|
313
|
+
const layouts = chunked.map((array) => {
|
|
314
|
+
// @ts-ignore
|
|
315
|
+
const items = array.map((lang, index) =>
|
|
316
|
+
createCompactLangNode({
|
|
317
|
+
lang,
|
|
318
|
+
totalSize,
|
|
319
|
+
hideProgress,
|
|
320
|
+
statsFormat,
|
|
321
|
+
index,
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
return flexLayout({
|
|
325
|
+
items,
|
|
326
|
+
gap: 25,
|
|
327
|
+
direction: "column",
|
|
328
|
+
}).join("");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const percent = ((longestLang.size / totalSize) * 100).toFixed(2);
|
|
332
|
+
const minGap = 150;
|
|
333
|
+
const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11);
|
|
334
|
+
return flexLayout({
|
|
335
|
+
items: layouts,
|
|
336
|
+
gap: maxGap < minGap ? minGap : maxGap,
|
|
337
|
+
}).join("");
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Create donut languages text items for all programming languages.
|
|
342
|
+
*
|
|
343
|
+
* @param {object} props Function properties.
|
|
344
|
+
* @param {Lang[]} props.langs Array of programming languages.
|
|
345
|
+
* @param {number} props.totalSize Total size of all languages.
|
|
346
|
+
* @param {string} props.statsFormat Stats format
|
|
347
|
+
* @returns {string} Donut layout programming language SVG node.
|
|
348
|
+
*/
|
|
349
|
+
const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => {
|
|
350
|
+
return flexLayout({
|
|
351
|
+
items: langs.map((lang, index) => {
|
|
352
|
+
return createCompactLangNode({
|
|
353
|
+
lang,
|
|
354
|
+
totalSize,
|
|
355
|
+
hideProgress: false,
|
|
356
|
+
statsFormat,
|
|
357
|
+
index,
|
|
358
|
+
});
|
|
359
|
+
}),
|
|
360
|
+
gap: 32,
|
|
361
|
+
direction: "column",
|
|
362
|
+
}).join("");
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Renders the default language card layout.
|
|
367
|
+
*
|
|
368
|
+
* @param {Lang[]} langs Array of programming languages.
|
|
369
|
+
* @param {number} width Card width.
|
|
370
|
+
* @param {number} totalLanguageSize Total size of all languages.
|
|
371
|
+
* @param {string} statsFormat Stats format.
|
|
372
|
+
* @returns {string} Normal layout card SVG object.
|
|
373
|
+
*/
|
|
374
|
+
const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat) => {
|
|
375
|
+
return flexLayout({
|
|
376
|
+
items: langs.map((lang, index) => {
|
|
377
|
+
return createProgressTextNode({
|
|
378
|
+
width,
|
|
379
|
+
name: lang.name,
|
|
380
|
+
color: lang.color || DEFAULT_LANG_COLOR,
|
|
381
|
+
size: lang.size,
|
|
382
|
+
totalSize: totalLanguageSize,
|
|
383
|
+
statsFormat,
|
|
384
|
+
index,
|
|
385
|
+
});
|
|
386
|
+
}),
|
|
387
|
+
gap: 40,
|
|
388
|
+
direction: "column",
|
|
389
|
+
}).join("");
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Renders the compact language card layout.
|
|
394
|
+
*
|
|
395
|
+
* @param {Lang[]} langs Array of programming languages.
|
|
396
|
+
* @param {number} width Card width.
|
|
397
|
+
* @param {number} totalLanguageSize Total size of all languages.
|
|
398
|
+
* @param {boolean=} hideProgress Whether to hide progress bar.
|
|
399
|
+
* @param {string} statsFormat Stats format.
|
|
400
|
+
* @returns {string} Compact layout card SVG object.
|
|
401
|
+
*/
|
|
402
|
+
const renderCompactLayout = (
|
|
403
|
+
langs,
|
|
404
|
+
width,
|
|
405
|
+
totalLanguageSize,
|
|
406
|
+
hideProgress,
|
|
407
|
+
statsFormat = "percentages",
|
|
408
|
+
) => {
|
|
409
|
+
const paddingRight = 50;
|
|
410
|
+
const offsetWidth = width - paddingRight;
|
|
411
|
+
// progressOffset holds the previous language's width and used to offset the next language
|
|
412
|
+
// so that we can stack them one after another, like this: [--][----][---]
|
|
413
|
+
let progressOffset = 0;
|
|
414
|
+
const compactProgressBar = langs
|
|
415
|
+
.map((lang) => {
|
|
416
|
+
const percentage = parseFloat(
|
|
417
|
+
((lang.size / totalLanguageSize) * offsetWidth).toFixed(2),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const progress = percentage < 10 ? percentage + 10 : percentage;
|
|
421
|
+
|
|
422
|
+
const output = `
|
|
423
|
+
<rect
|
|
424
|
+
mask="url(#rect-mask)"
|
|
425
|
+
data-testid="lang-progress"
|
|
426
|
+
x="${progressOffset}"
|
|
427
|
+
y="0"
|
|
428
|
+
width="${progress}"
|
|
429
|
+
height="8"
|
|
430
|
+
fill="${lang.color || "#858585"}"
|
|
431
|
+
/>
|
|
432
|
+
`;
|
|
433
|
+
progressOffset += percentage;
|
|
434
|
+
return output;
|
|
435
|
+
})
|
|
436
|
+
.join("");
|
|
437
|
+
|
|
438
|
+
return `
|
|
439
|
+
${
|
|
440
|
+
hideProgress
|
|
441
|
+
? ""
|
|
442
|
+
: `
|
|
443
|
+
<mask id="rect-mask">
|
|
444
|
+
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5"/>
|
|
445
|
+
</mask>
|
|
446
|
+
${compactProgressBar}
|
|
447
|
+
`
|
|
448
|
+
}
|
|
449
|
+
<g transform="translate(0, ${hideProgress ? "0" : "25"})">
|
|
450
|
+
${createLanguageTextNode({
|
|
451
|
+
langs,
|
|
452
|
+
totalSize: totalLanguageSize,
|
|
453
|
+
hideProgress,
|
|
454
|
+
statsFormat,
|
|
455
|
+
})}
|
|
456
|
+
</g>
|
|
457
|
+
`;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Renders donut vertical layout to display user's most frequently used programming languages.
|
|
462
|
+
*
|
|
463
|
+
* @param {Lang[]} langs Array of programming languages.
|
|
464
|
+
* @param {number} totalLanguageSize Total size of all languages.
|
|
465
|
+
* @param {string} statsFormat Stats format.
|
|
466
|
+
* @returns {string} Compact layout card SVG object.
|
|
467
|
+
*/
|
|
468
|
+
const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => {
|
|
469
|
+
// Donut vertical chart radius and total length
|
|
470
|
+
const radius = 80;
|
|
471
|
+
const totalCircleLength = getCircleLength(radius);
|
|
472
|
+
|
|
473
|
+
// SVG circles
|
|
474
|
+
let circles = [];
|
|
475
|
+
|
|
476
|
+
// Start indent for donut vertical chart parts
|
|
477
|
+
let indent = 0;
|
|
478
|
+
|
|
479
|
+
// Start delay coefficient for donut vertical chart parts
|
|
480
|
+
let startDelayCoefficient = 1;
|
|
481
|
+
|
|
482
|
+
// Generate each donut vertical chart part
|
|
483
|
+
for (const lang of langs) {
|
|
484
|
+
const percentage = (lang.size / totalLanguageSize) * 100;
|
|
485
|
+
const circleLength = totalCircleLength * (percentage / 100);
|
|
486
|
+
const delay = startDelayCoefficient * 100;
|
|
487
|
+
|
|
488
|
+
circles.push(`
|
|
489
|
+
<g class="stagger" style="animation-delay: ${delay}ms">
|
|
490
|
+
<circle
|
|
491
|
+
cx="150"
|
|
492
|
+
cy="100"
|
|
493
|
+
r="${radius}"
|
|
494
|
+
fill="transparent"
|
|
495
|
+
stroke="${lang.color}"
|
|
496
|
+
stroke-width="25"
|
|
497
|
+
stroke-dasharray="${totalCircleLength}"
|
|
498
|
+
stroke-dashoffset="${indent}"
|
|
499
|
+
size="${percentage}"
|
|
500
|
+
data-testid="lang-donut"
|
|
501
|
+
/>
|
|
502
|
+
</g>
|
|
503
|
+
`);
|
|
504
|
+
|
|
505
|
+
// Update the indent for the next part
|
|
506
|
+
indent += circleLength;
|
|
507
|
+
// Update the start delay coefficient for the next part
|
|
508
|
+
startDelayCoefficient += 1;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return `
|
|
512
|
+
<svg data-testid="lang-items">
|
|
513
|
+
<g transform="translate(0, 0)">
|
|
514
|
+
<svg data-testid="donut">
|
|
515
|
+
${circles.join("")}
|
|
516
|
+
</svg>
|
|
517
|
+
</g>
|
|
518
|
+
<g transform="translate(0, 220)">
|
|
519
|
+
<svg data-testid="lang-names" x="${CARD_PADDING}">
|
|
520
|
+
${createLanguageTextNode({
|
|
521
|
+
langs,
|
|
522
|
+
totalSize: totalLanguageSize,
|
|
523
|
+
hideProgress: false,
|
|
524
|
+
statsFormat,
|
|
525
|
+
})}
|
|
526
|
+
</svg>
|
|
527
|
+
</g>
|
|
528
|
+
</svg>
|
|
529
|
+
`;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Renders pie layout to display user's most frequently used programming languages.
|
|
534
|
+
*
|
|
535
|
+
* @param {Lang[]} langs Array of programming languages.
|
|
536
|
+
* @param {number} totalLanguageSize Total size of all languages.
|
|
537
|
+
* @param {string} statsFormat Stats format.
|
|
538
|
+
* @returns {string} Compact layout card SVG object.
|
|
539
|
+
*/
|
|
540
|
+
const renderPieLayout = (langs, totalLanguageSize, statsFormat) => {
|
|
541
|
+
// Pie chart radius and center coordinates
|
|
542
|
+
const radius = 90;
|
|
543
|
+
const centerX = 150;
|
|
544
|
+
const centerY = 100;
|
|
545
|
+
|
|
546
|
+
// Start angle for the pie chart parts
|
|
547
|
+
let startAngle = 0;
|
|
548
|
+
|
|
549
|
+
// Start delay coefficient for the pie chart parts
|
|
550
|
+
let startDelayCoefficient = 1;
|
|
551
|
+
|
|
552
|
+
// SVG paths
|
|
553
|
+
const paths = [];
|
|
554
|
+
|
|
555
|
+
// Generate each pie chart part
|
|
556
|
+
for (const lang of langs) {
|
|
557
|
+
if (langs.length === 1) {
|
|
558
|
+
paths.push(`
|
|
559
|
+
<circle
|
|
560
|
+
cx="${centerX}"
|
|
561
|
+
cy="${centerY}"
|
|
562
|
+
r="${radius}"
|
|
563
|
+
stroke="none"
|
|
564
|
+
fill="${lang.color}"
|
|
565
|
+
data-testid="lang-pie"
|
|
566
|
+
size="100"
|
|
567
|
+
/>
|
|
568
|
+
`);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const langSizePart = lang.size / totalLanguageSize;
|
|
573
|
+
const percentage = langSizePart * 100;
|
|
574
|
+
// Calculate the angle for the current part
|
|
575
|
+
const angle = langSizePart * 360;
|
|
576
|
+
|
|
577
|
+
// Calculate the end angle
|
|
578
|
+
const endAngle = startAngle + angle;
|
|
579
|
+
|
|
580
|
+
// Calculate the coordinates of the start and end points of the arc
|
|
581
|
+
const startPoint = polarToCartesian(centerX, centerY, radius, startAngle);
|
|
582
|
+
const endPoint = polarToCartesian(centerX, centerY, radius, endAngle);
|
|
583
|
+
|
|
584
|
+
// Determine the large arc flag based on the angle
|
|
585
|
+
const largeArcFlag = angle > 180 ? 1 : 0;
|
|
586
|
+
|
|
587
|
+
// Calculate delay
|
|
588
|
+
const delay = startDelayCoefficient * 100;
|
|
589
|
+
|
|
590
|
+
// SVG arc markup
|
|
591
|
+
paths.push(`
|
|
592
|
+
<g class="stagger" style="animation-delay: ${delay}ms">
|
|
593
|
+
<path
|
|
594
|
+
data-testid="lang-pie"
|
|
595
|
+
size="${percentage}"
|
|
596
|
+
d="M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z"
|
|
597
|
+
fill="${lang.color}"
|
|
598
|
+
/>
|
|
599
|
+
</g>
|
|
600
|
+
`);
|
|
601
|
+
|
|
602
|
+
// Update the start angle for the next part
|
|
603
|
+
startAngle = endAngle;
|
|
604
|
+
// Update the start delay coefficient for the next part
|
|
605
|
+
startDelayCoefficient += 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return `
|
|
609
|
+
<svg data-testid="lang-items">
|
|
610
|
+
<g transform="translate(0, 0)">
|
|
611
|
+
<svg data-testid="pie">
|
|
612
|
+
${paths.join("")}
|
|
613
|
+
</svg>
|
|
614
|
+
</g>
|
|
615
|
+
<g transform="translate(0, 220)">
|
|
616
|
+
<svg data-testid="lang-names" x="${CARD_PADDING}">
|
|
617
|
+
${createLanguageTextNode({
|
|
618
|
+
langs,
|
|
619
|
+
totalSize: totalLanguageSize,
|
|
620
|
+
hideProgress: false,
|
|
621
|
+
statsFormat,
|
|
622
|
+
})}
|
|
623
|
+
</svg>
|
|
624
|
+
</g>
|
|
625
|
+
</svg>
|
|
626
|
+
`;
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Creates the SVG paths for the language donut chart.
|
|
631
|
+
*
|
|
632
|
+
* @param {number} cx Donut center x-position.
|
|
633
|
+
* @param {number} cy Donut center y-position.
|
|
634
|
+
* @param {number} radius Donut arc Radius.
|
|
635
|
+
* @param {number[]} percentages Array with donut section percentages.
|
|
636
|
+
* @returns {{d: string, percent: number}[]} Array of svg path elements
|
|
637
|
+
*/
|
|
638
|
+
const createDonutPaths = (cx, cy, radius, percentages) => {
|
|
639
|
+
const paths = [];
|
|
640
|
+
let startAngle = 0;
|
|
641
|
+
let endAngle = 0;
|
|
642
|
+
|
|
643
|
+
const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0);
|
|
644
|
+
for (let i = 0; i < percentages.length; i++) {
|
|
645
|
+
const tmpPath = {};
|
|
646
|
+
|
|
647
|
+
let percent = parseFloat(
|
|
648
|
+
((percentages[i] / totalPercent) * 100).toFixed(2),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
endAngle = 3.6 * percent + startAngle;
|
|
652
|
+
const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise.
|
|
653
|
+
const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise.
|
|
654
|
+
const largeArc = endAngle - startAngle <= 180 ? 0 : 1;
|
|
655
|
+
|
|
656
|
+
tmpPath.percent = percent;
|
|
657
|
+
tmpPath.d = `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 0 ${endPoint.x} ${endPoint.y}`;
|
|
658
|
+
|
|
659
|
+
paths.push(tmpPath);
|
|
660
|
+
startAngle = endAngle;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return paths;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Renders the donut language card layout.
|
|
668
|
+
*
|
|
669
|
+
* @param {Lang[]} langs Array of programming languages.
|
|
670
|
+
* @param {number} width Card width.
|
|
671
|
+
* @param {number} totalLanguageSize Total size of all languages.
|
|
672
|
+
* @param {string} statsFormat Stats format.
|
|
673
|
+
* @returns {string} Donut layout card SVG object.
|
|
674
|
+
*/
|
|
675
|
+
const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat) => {
|
|
676
|
+
const centerX = width / 3;
|
|
677
|
+
const centerY = width / 3;
|
|
678
|
+
const radius = centerX - 60;
|
|
679
|
+
const strokeWidth = 12;
|
|
680
|
+
|
|
681
|
+
const colors = langs.map((lang) => lang.color);
|
|
682
|
+
const langsPercents = langs.map((lang) =>
|
|
683
|
+
parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)),
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents);
|
|
687
|
+
|
|
688
|
+
const donutPaths =
|
|
689
|
+
langs.length === 1
|
|
690
|
+
? `<circle cx="${centerX}" cy="${centerY}" r="${radius}" stroke="${colors[0]}" fill="none" stroke-width="${strokeWidth}" data-testid="lang-donut" size="100"/>`
|
|
691
|
+
: langPaths
|
|
692
|
+
.map((section, index) => {
|
|
693
|
+
const staggerDelay = (index + 3) * 100;
|
|
694
|
+
const delay = staggerDelay + 300;
|
|
695
|
+
|
|
696
|
+
const output = `
|
|
697
|
+
<g class="stagger" style="animation-delay: ${delay}ms">
|
|
698
|
+
<path
|
|
699
|
+
data-testid="lang-donut"
|
|
700
|
+
size="${section.percent}"
|
|
701
|
+
d="${section.d}"
|
|
702
|
+
stroke="${colors[index]}"
|
|
703
|
+
fill="none"
|
|
704
|
+
stroke-width="${strokeWidth}">
|
|
705
|
+
</path>
|
|
706
|
+
</g>
|
|
707
|
+
`;
|
|
708
|
+
|
|
709
|
+
return output;
|
|
710
|
+
})
|
|
711
|
+
.join("");
|
|
712
|
+
|
|
713
|
+
const donut = `<svg width="${width}" height="${width}">${donutPaths}</svg>`;
|
|
714
|
+
|
|
715
|
+
return `
|
|
716
|
+
<g transform="translate(0, 0)">
|
|
717
|
+
<g transform="translate(0, 0)">
|
|
718
|
+
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, statsFormat })}
|
|
719
|
+
</g>
|
|
720
|
+
|
|
721
|
+
<g transform="translate(125, ${donutCenterTranslation(langs.length)})">
|
|
722
|
+
${donut}
|
|
723
|
+
</g>
|
|
724
|
+
</g>
|
|
725
|
+
`;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* @typedef {import("./types").TopLangOptions} TopLangOptions
|
|
730
|
+
* @typedef {TopLangOptions["layout"]} Layout
|
|
731
|
+
*/
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Creates the no languages data SVG node.
|
|
735
|
+
*
|
|
736
|
+
* @param {object} props Object with function properties.
|
|
737
|
+
* @param {string} props.color No languages data text color.
|
|
738
|
+
* @param {string} props.text No languages data translated text.
|
|
739
|
+
* @param {Layout | undefined} props.layout Card layout.
|
|
740
|
+
* @returns {string} No languages data SVG node string.
|
|
741
|
+
*/
|
|
742
|
+
const noLanguagesDataNode = ({ color, text, layout }) => {
|
|
743
|
+
return `
|
|
744
|
+
<text x="${
|
|
745
|
+
layout === "pie" || layout === "donut-vertical" ? CARD_PADDING : 0
|
|
746
|
+
}" y="11" class="stat bold" fill="${color}">${text}</text>
|
|
747
|
+
`;
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Get default languages count for provided card layout.
|
|
752
|
+
*
|
|
753
|
+
* @param {object} props Function properties.
|
|
754
|
+
* @param {Layout=} props.layout Input layout string.
|
|
755
|
+
* @param {boolean=} props.hide_progress Input hide_progress parameter value.
|
|
756
|
+
* @returns {number} Default languages count for input layout.
|
|
757
|
+
*/
|
|
758
|
+
const getDefaultLanguagesCountByLayout = ({ layout, hide_progress }) => {
|
|
759
|
+
if (layout === "compact" || hide_progress === true) {
|
|
760
|
+
return COMPACT_LAYOUT_DEFAULT_LANGS_COUNT;
|
|
761
|
+
} else if (layout === "donut") {
|
|
762
|
+
return DONUT_LAYOUT_DEFAULT_LANGS_COUNT;
|
|
763
|
+
} else if (layout === "donut-vertical") {
|
|
764
|
+
return DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT;
|
|
765
|
+
} else if (layout === "pie") {
|
|
766
|
+
return PIE_LAYOUT_DEFAULT_LANGS_COUNT;
|
|
767
|
+
} else {
|
|
768
|
+
return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* @typedef {import('../fetchers/types').TopLangData} TopLangData
|
|
774
|
+
*/
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Renders card that display user's most frequently used programming languages.
|
|
778
|
+
*
|
|
779
|
+
* @param {TopLangData} topLangs User's most frequently used programming languages.
|
|
780
|
+
* @param {Partial<TopLangOptions>} options Card options.
|
|
781
|
+
* @returns {string} Language card SVG object.
|
|
782
|
+
*/
|
|
783
|
+
const renderTopLanguages = (topLangs, options = {}) => {
|
|
784
|
+
const {
|
|
785
|
+
hide_title = false,
|
|
786
|
+
hide_border = false,
|
|
787
|
+
card_width,
|
|
788
|
+
title_color,
|
|
789
|
+
text_color,
|
|
790
|
+
bg_color,
|
|
791
|
+
hide,
|
|
792
|
+
hide_progress,
|
|
793
|
+
theme,
|
|
794
|
+
layout,
|
|
795
|
+
custom_title,
|
|
796
|
+
locale,
|
|
797
|
+
langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }),
|
|
798
|
+
border_radius,
|
|
799
|
+
border_color,
|
|
800
|
+
disable_animations,
|
|
801
|
+
stats_format = "percentages",
|
|
802
|
+
} = options;
|
|
803
|
+
|
|
804
|
+
const i18n = new I18n({
|
|
805
|
+
locale,
|
|
806
|
+
translations: langCardLocales,
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const { langs, totalLanguageSize } = trimTopLanguages(
|
|
810
|
+
topLangs,
|
|
811
|
+
langs_count,
|
|
812
|
+
hide,
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
let width = card_width
|
|
816
|
+
? isNaN(card_width)
|
|
817
|
+
? DEFAULT_CARD_WIDTH
|
|
818
|
+
: card_width < MIN_CARD_WIDTH
|
|
819
|
+
? MIN_CARD_WIDTH
|
|
820
|
+
: card_width
|
|
821
|
+
: DEFAULT_CARD_WIDTH;
|
|
822
|
+
let height = calculateNormalLayoutHeight(langs.length);
|
|
823
|
+
|
|
824
|
+
// returns theme based colors with proper overrides and defaults
|
|
825
|
+
const colors = getCardColors({
|
|
826
|
+
title_color,
|
|
827
|
+
text_color,
|
|
828
|
+
bg_color,
|
|
829
|
+
border_color,
|
|
830
|
+
theme,
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
let finalLayout = "";
|
|
834
|
+
if (langs.length === 0) {
|
|
835
|
+
height = COMPACT_LAYOUT_BASE_HEIGHT;
|
|
836
|
+
finalLayout = noLanguagesDataNode({
|
|
837
|
+
color: colors.textColor,
|
|
838
|
+
text: i18n.t("langcard.nodata"),
|
|
839
|
+
layout,
|
|
840
|
+
});
|
|
841
|
+
} else if (layout === "pie") {
|
|
842
|
+
height = calculatePieLayoutHeight(langs.length);
|
|
843
|
+
finalLayout = renderPieLayout(langs, totalLanguageSize, stats_format);
|
|
844
|
+
} else if (layout === "donut-vertical") {
|
|
845
|
+
height = calculateDonutVerticalLayoutHeight(langs.length);
|
|
846
|
+
finalLayout = renderDonutVerticalLayout(
|
|
847
|
+
langs,
|
|
848
|
+
totalLanguageSize,
|
|
849
|
+
stats_format,
|
|
850
|
+
);
|
|
851
|
+
} else if (layout === "compact" || hide_progress == true) {
|
|
852
|
+
height =
|
|
853
|
+
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
|
|
854
|
+
|
|
855
|
+
finalLayout = renderCompactLayout(
|
|
856
|
+
langs,
|
|
857
|
+
width,
|
|
858
|
+
totalLanguageSize,
|
|
859
|
+
hide_progress,
|
|
860
|
+
stats_format,
|
|
861
|
+
);
|
|
862
|
+
} else if (layout === "donut") {
|
|
863
|
+
height = calculateDonutLayoutHeight(langs.length);
|
|
864
|
+
width = width + 50; // padding
|
|
865
|
+
finalLayout = renderDonutLayout(
|
|
866
|
+
langs,
|
|
867
|
+
width,
|
|
868
|
+
totalLanguageSize,
|
|
869
|
+
stats_format,
|
|
870
|
+
);
|
|
871
|
+
} else {
|
|
872
|
+
finalLayout = renderNormalLayout(
|
|
873
|
+
langs,
|
|
874
|
+
width,
|
|
875
|
+
totalLanguageSize,
|
|
876
|
+
stats_format,
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const card = new Card({
|
|
881
|
+
customTitle: custom_title,
|
|
882
|
+
defaultTitle: i18n.t("langcard.title"),
|
|
883
|
+
width,
|
|
884
|
+
height,
|
|
885
|
+
border_radius,
|
|
886
|
+
colors,
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
if (disable_animations) {
|
|
890
|
+
card.disableAnimations();
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
card.setHideBorder(hide_border);
|
|
894
|
+
card.setHideTitle(hide_title);
|
|
895
|
+
// Sanitize color values to prevent XSS
|
|
896
|
+
const safeTextColor = escapeCSSValue(colors.textColor);
|
|
897
|
+
card.setCSS(
|
|
898
|
+
`
|
|
899
|
+
@keyframes slideInAnimation {
|
|
900
|
+
from {
|
|
901
|
+
width: 0;
|
|
902
|
+
}
|
|
903
|
+
to {
|
|
904
|
+
width: calc(100%-100px);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
@keyframes growWidthAnimation {
|
|
908
|
+
from {
|
|
909
|
+
width: 0;
|
|
910
|
+
}
|
|
911
|
+
to {
|
|
912
|
+
width: 100%;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
.stat {
|
|
916
|
+
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${safeTextColor};
|
|
917
|
+
}
|
|
918
|
+
@supports(-moz-appearance: auto) {
|
|
919
|
+
/* Selector detects Firefox */
|
|
920
|
+
.stat { font-size:12px; }
|
|
921
|
+
}
|
|
922
|
+
.bold { font-weight: 700 }
|
|
923
|
+
.lang-name {
|
|
924
|
+
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
|
|
925
|
+
fill: ${safeTextColor};
|
|
926
|
+
}
|
|
927
|
+
.stagger {
|
|
928
|
+
opacity: 0;
|
|
929
|
+
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
|
930
|
+
}
|
|
931
|
+
#rect-mask rect{
|
|
932
|
+
animation: slideInAnimation 1s ease-in-out forwards;
|
|
933
|
+
}
|
|
934
|
+
.lang-progress{
|
|
935
|
+
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
|
936
|
+
}
|
|
937
|
+
`,
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (layout === "pie" || layout === "donut-vertical") {
|
|
941
|
+
return card.render(finalLayout);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return card.render(`
|
|
945
|
+
<svg data-testid="lang-items" x="${CARD_PADDING}">
|
|
946
|
+
${finalLayout}
|
|
947
|
+
</svg>
|
|
948
|
+
`);
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
export {
|
|
952
|
+
getLongestLang,
|
|
953
|
+
degreesToRadians,
|
|
954
|
+
radiansToDegrees,
|
|
955
|
+
polarToCartesian,
|
|
956
|
+
cartesianToPolar,
|
|
957
|
+
getCircleLength,
|
|
958
|
+
calculateCompactLayoutHeight,
|
|
959
|
+
calculateNormalLayoutHeight,
|
|
960
|
+
calculateDonutLayoutHeight,
|
|
961
|
+
calculateDonutVerticalLayoutHeight,
|
|
962
|
+
calculatePieLayoutHeight,
|
|
963
|
+
donutCenterTranslation,
|
|
964
|
+
trimTopLanguages,
|
|
965
|
+
renderTopLanguages,
|
|
966
|
+
MIN_CARD_WIDTH,
|
|
967
|
+
getDefaultLanguagesCountByLayout,
|
|
968
|
+
};
|