Skip to content

Commit 078040d

Browse files
abap34qwerty541Copilot
authored
feat: add bytes stats format option for top-languages card (#3708)
* feat: add `display_bytes` option to top-languages. * docs: add description about display bytes in top-languages * feat: add `stats_format` option instead of `display_bytes` * docs: add description about stats format in top-languages * refactor: rewrite with function to determine display value * docs: add `stats_format`to table of parameter * fix: remove unnecessary decimal part from format of bytes * tests: add tests of stats_format in top-langs card * Update tests/renderTopLanguagesCard.test.js Co-authored-by: Copilot <[email protected]> * Update readme.md Co-authored-by: Copilot <[email protected]> * prettier * jsdoc --------- Co-authored-by: Alexandr <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 485c224 commit 078040d

File tree

7 files changed

+203
-21
lines changed

7 files changed

+203
-21
lines changed

api/top-langs.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default async (req, res) => {
3434
border_color,
3535
disable_animations,
3636
hide_progress,
37+
stats_format,
3738
} = req.query;
3839
res.setHeader("Content-Type", "image/svg+xml");
3940

@@ -85,6 +86,16 @@ export default async (req, res) => {
8586
);
8687
}
8788

89+
if (
90+
stats_format !== undefined &&
91+
(typeof stats_format !== "string" ||
92+
!["bytes", "percentages"].includes(stats_format))
93+
) {
94+
return res.send(
95+
renderError("Something went wrong", "Incorrect stats_format input"),
96+
);
97+
}
98+
8899
try {
89100
const topLangs = await fetchTopLanguages(
90101
username,
@@ -125,6 +136,7 @@ export default async (req, res) => {
125136
locale: locale ? locale.toLowerCase() : null,
126137
disable_animations: parseBoolean(disable_animations),
127138
hide_progress: parseBoolean(hide_progress),
139+
stats_format,
128140
}),
129141
);
130142
} catch (err) {

readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
- [Donut Vertical Chart Language Card Layout](#donut-vertical-chart-language-card-layout)
7575
- [Pie Chart Language Card Layout](#pie-chart-language-card-layout)
7676
- [Hide Progress Bars](#hide-progress-bars)
77+
- [Change format of language's stats](#change-format-of-languages-stats)
7778
- [Demo](#demo-2)
7879
- [WakaTime Stats Card](#wakatime-stats-card)
7980
- [Options](#options-3)
@@ -468,6 +469,7 @@ You can customize the appearance and behavior of the top languages card using th
468469
| `hide_progress` | Uses the compact layout option, hides percentages, and removes the bars. | boolean | `false` |
469470
| `size_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `1` |
470471
| `count_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `0` |
472+
| `stats_format` | Switches between two available formats for language's stats `percentages` and `bytes`. | enum | `percentages` |
471473

472474
> [!WARNING]\
473475
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
@@ -556,6 +558,15 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
556558
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)
557559
```
558560

561+
### Change format of language's stats
562+
563+
You can use the `&stats_format=bytes` option to display the stats in bytes instead of percentage.
564+
565+
```md
566+
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&stats_format=bytes)
567+
```
568+
569+
559570
### Demo
560571

561572
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)
@@ -580,6 +591,11 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
580591

581592
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra\&hide_progress=true)
582593

594+
595+
* Display bytes instead of percentage
596+
597+
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra\&stats_format=bytes)
598+
583599
# WakaTime Stats Card
584600

585601
> [!WARNING]\

src/cards/top-languages.js

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getCardColors,
1010
lowercaseTrim,
1111
measureText,
12+
formatBytes,
1213
} from "../common/utils.js";
1314
import { langCardLocales } from "../translations.js";
1415

@@ -196,27 +197,52 @@ const trimTopLanguages = (topLangs, langs_count, hide) => {
196197
return { langs, totalLanguageSize };
197198
};
198199

200+
/**
201+
* Get display value corresponding to the format.
202+
*
203+
* @param {number} size Bytes size.
204+
* @param {number} percentages Percentage value.
205+
* @param {string} format Format of the stats.
206+
* @returns {string} Display value.
207+
*/
208+
const getDisplayValue = (size, percentages, format) => {
209+
return format === "bytes" ? formatBytes(size) : `${percentages.toFixed(2)}%`;
210+
};
211+
199212
/**
200213
* Create progress bar text item for a programming language.
201214
*
202215
* @param {object} props Function properties.
203216
* @param {number} props.width The card width
204217
* @param {string} props.color Color of the programming language.
205218
* @param {string} props.name Name of the programming language.
206-
* @param {number} props.progress Usage of the programming language in percentage.
219+
* @param {number} props.size Size of the programming language.
220+
* @param {number} props.totalSize Total size of all languages.
221+
* @param {string} props.statsFormat Stats format.
207222
* @param {number} props.index Index of the programming language.
208223
* @returns {string} Programming language SVG node.
209224
*/
210-
const createProgressTextNode = ({ width, color, name, progress, index }) => {
225+
const createProgressTextNode = ({
226+
width,
227+
color,
228+
name,
229+
size,
230+
totalSize,
231+
statsFormat,
232+
index,
233+
}) => {
211234
const staggerDelay = (index + 3) * 150;
212235
const paddingRight = 95;
213236
const progressTextX = width - paddingRight + 10;
214237
const progressWidth = width - paddingRight;
215238

239+
const progress = (size / totalSize) * 100;
240+
const displayValue = getDisplayValue(size, progress, statsFormat);
241+
216242
return `
217243
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
218244
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
219-
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
245+
<text x="${progressTextX}" y="34" class="lang-name">${displayValue}</text>
220246
${createProgressNode({
221247
x: 0,
222248
y: 25,
@@ -237,19 +263,28 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => {
237263
* @param {Lang} props.lang Programming language object.
238264
* @param {number} props.totalSize Total size of all languages.
239265
* @param {boolean=} props.hideProgress Whether to hide percentage.
266+
* @param {string=} props.statsFormat Stats format
240267
* @param {number} props.index Index of the programming language.
241268
* @returns {string} Compact layout programming language SVG node.
242269
*/
243-
const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => {
244-
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
270+
const createCompactLangNode = ({
271+
lang,
272+
totalSize,
273+
hideProgress,
274+
statsFormat = "percentages",
275+
index,
276+
}) => {
277+
const percentages = (lang.size / totalSize) * 100;
278+
const displayValue = getDisplayValue(lang.size, percentages, statsFormat);
279+
245280
const staggerDelay = (index + 3) * 150;
246281
const color = lang.color || "#858585";
247282

248283
return `
249284
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
250285
<circle cx="5" cy="6" r="5" fill="${color}" />
251286
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
252-
${lang.name} ${hideProgress ? "" : percentage + "%"}
287+
${lang.name} ${hideProgress ? "" : displayValue}
253288
</text>
254289
</g>
255290
`;
@@ -262,9 +297,15 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => {
262297
* @param {Lang[]} props.langs Array of programming languages.
263298
* @param {number} props.totalSize Total size of all languages.
264299
* @param {boolean=} props.hideProgress Whether to hide percentage.
300+
* @param {string=} props.statsFormat Stats format
265301
* @returns {string} Programming languages SVG node.
266302
*/
267-
const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
303+
const createLanguageTextNode = ({
304+
langs,
305+
totalSize,
306+
hideProgress,
307+
statsFormat,
308+
}) => {
268309
const longestLang = getLongestLang(langs);
269310
const chunked = chunkArray(langs, langs.length / 2);
270311
const layouts = chunked.map((array) => {
@@ -274,6 +315,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
274315
lang,
275316
totalSize,
276317
hideProgress,
318+
statsFormat,
277319
index,
278320
}),
279321
);
@@ -299,15 +341,17 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
299341
* @param {object} props Function properties.
300342
* @param {Lang[]} props.langs Array of programming languages.
301343
* @param {number} props.totalSize Total size of all languages.
344+
* @param {string} props.statsFormat Stats format
302345
* @returns {string} Donut layout programming language SVG node.
303346
*/
304-
const createDonutLanguagesNode = ({ langs, totalSize }) => {
347+
const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => {
305348
return flexLayout({
306349
items: langs.map((lang, index) => {
307350
return createCompactLangNode({
308351
lang,
309352
totalSize,
310353
hideProgress: false,
354+
statsFormat,
311355
index,
312356
});
313357
}),
@@ -322,18 +366,19 @@ const createDonutLanguagesNode = ({ langs, totalSize }) => {
322366
* @param {Lang[]} langs Array of programming languages.
323367
* @param {number} width Card width.
324368
* @param {number} totalLanguageSize Total size of all languages.
369+
* @param {string} statsFormat Stats format.
325370
* @returns {string} Normal layout card SVG object.
326371
*/
327-
const renderNormalLayout = (langs, width, totalLanguageSize) => {
372+
const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat) => {
328373
return flexLayout({
329374
items: langs.map((lang, index) => {
330375
return createProgressTextNode({
331376
width,
332377
name: lang.name,
333378
color: lang.color || DEFAULT_LANG_COLOR,
334-
progress: parseFloat(
335-
((lang.size / totalLanguageSize) * 100).toFixed(2),
336-
),
379+
size: lang.size,
380+
totalSize: totalLanguageSize,
381+
statsFormat,
337382
index,
338383
});
339384
}),
@@ -349,9 +394,16 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
349394
* @param {number} width Card width.
350395
* @param {number} totalLanguageSize Total size of all languages.
351396
* @param {boolean=} hideProgress Whether to hide progress bar.
397+
* @param {string} statsFormat Stats format.
352398
* @returns {string} Compact layout card SVG object.
353399
*/
354-
const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
400+
const renderCompactLayout = (
401+
langs,
402+
width,
403+
totalLanguageSize,
404+
hideProgress,
405+
statsFormat = "percentages",
406+
) => {
355407
const paddingRight = 50;
356408
const offsetWidth = width - paddingRight;
357409
// progressOffset holds the previous language's width and used to offset the next language
@@ -397,6 +449,7 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
397449
langs,
398450
totalSize: totalLanguageSize,
399451
hideProgress,
452+
statsFormat,
400453
})}
401454
</g>
402455
`;
@@ -407,9 +460,10 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
407460
*
408461
* @param {Lang[]} langs Array of programming languages.
409462
* @param {number} totalLanguageSize Total size of all languages.
463+
* @param {string} statsFormat Stats format.
410464
* @returns {string} Compact layout card SVG object.
411465
*/
412-
const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
466+
const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => {
413467
// Donut vertical chart radius and total length
414468
const radius = 80;
415469
const totalCircleLength = getCircleLength(radius);
@@ -465,6 +519,7 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
465519
langs,
466520
totalSize: totalLanguageSize,
467521
hideProgress: false,
522+
statsFormat,
468523
})}
469524
</svg>
470525
</g>
@@ -477,9 +532,10 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
477532
*
478533
* @param {Lang[]} langs Array of programming languages.
479534
* @param {number} totalLanguageSize Total size of all languages.
535+
* @param {string} statsFormat Stats format.
480536
* @returns {string} Compact layout card SVG object.
481537
*/
482-
const renderPieLayout = (langs, totalLanguageSize) => {
538+
const renderPieLayout = (langs, totalLanguageSize, statsFormat) => {
483539
// Pie chart radius and center coordinates
484540
const radius = 90;
485541
const centerX = 150;
@@ -560,6 +616,7 @@ const renderPieLayout = (langs, totalLanguageSize) => {
560616
langs,
561617
totalSize: totalLanguageSize,
562618
hideProgress: false,
619+
statsFormat,
563620
})}
564621
</svg>
565622
</g>
@@ -610,9 +667,10 @@ const createDonutPaths = (cx, cy, radius, percentages) => {
610667
* @param {Lang[]} langs Array of programming languages.
611668
* @param {number} width Card width.
612669
* @param {number} totalLanguageSize Total size of all languages.
670+
* @param {string} statsFormat Stats format.
613671
* @returns {string} Donut layout card SVG object.
614672
*/
615-
const renderDonutLayout = (langs, width, totalLanguageSize) => {
673+
const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat) => {
616674
const centerX = width / 3;
617675
const centerY = width / 3;
618676
const radius = centerX - 60;
@@ -655,7 +713,7 @@ const renderDonutLayout = (langs, width, totalLanguageSize) => {
655713
return `
656714
<g transform="translate(0, 0)">
657715
<g transform="translate(0, 0)">
658-
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })}
716+
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, statsFormat })}
659717
</g>
660718
661719
<g transform="translate(125, ${donutCenterTranslation(langs.length)})">
@@ -738,6 +796,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
738796
border_radius,
739797
border_color,
740798
disable_animations,
799+
stats_format = "percentages",
741800
} = options;
742801

743802
const i18n = new I18n({
@@ -779,10 +838,14 @@ const renderTopLanguages = (topLangs, options = {}) => {
779838
});
780839
} else if (layout === "pie") {
781840
height = calculatePieLayoutHeight(langs.length);
782-
finalLayout = renderPieLayout(langs, totalLanguageSize);
841+
finalLayout = renderPieLayout(langs, totalLanguageSize, stats_format);
783842
} else if (layout === "donut-vertical") {
784843
height = calculateDonutVerticalLayoutHeight(langs.length);
785-
finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize);
844+
finalLayout = renderDonutVerticalLayout(
845+
langs,
846+
totalLanguageSize,
847+
stats_format,
848+
);
786849
} else if (layout === "compact" || hide_progress == true) {
787850
height =
788851
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
@@ -792,13 +855,24 @@ const renderTopLanguages = (topLangs, options = {}) => {
792855
width,
793856
totalLanguageSize,
794857
hide_progress,
858+
stats_format,
795859
);
796860
} else if (layout === "donut") {
797861
height = calculateDonutLayoutHeight(langs.length);
798862
width = width + 50; // padding
799-
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
863+
finalLayout = renderDonutLayout(
864+
langs,
865+
width,
866+
totalLanguageSize,
867+
stats_format,
868+
);
800869
} else {
801-
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
870+
finalLayout = renderNormalLayout(
871+
langs,
872+
width,
873+
totalLanguageSize,
874+
stats_format,
875+
);
802876
}
803877

804878
const card = new Card({

src/cards/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type TopLangOptions = CommonOptions & {
4444
langs_count: number;
4545
disable_animations: boolean;
4646
hide_progress: boolean;
47+
stats_format: "percentages" | "bytes";
4748
};
4849

4950
export type WakaTimeOptions = CommonOptions & {

0 commit comments

Comments
 (0)