Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a display bytes option to top-languages card #3708

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
12 changes: 12 additions & 0 deletions api/top-langs.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default async (req, res) => {
border_color,
disable_animations,
hide_progress,
stats_format,
} = req.query;
res.setHeader("Content-Type", "image/svg+xml");

Expand Down Expand Up @@ -62,6 +63,16 @@ export default async (req, res) => {
);
}

if (
stats_format !== undefined &&
(typeof stats_format !== "string" ||
!["bytes", "percentages"].includes(stats_format))
) {
return res.send(
renderError("Something went wrong", "Incorrect stats_format input"),
);
}

try {
const topLangs = await fetchTopLanguages(
username,
Expand Down Expand Up @@ -104,6 +115,7 @@ export default async (req, res) => {
locale: locale ? locale.toLowerCase() : null,
disable_animations: parseBoolean(disable_animations),
hide_progress: parseBoolean(hide_progress),
stats_format,
}),
);
} catch (err) {
Expand Down
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of
- [Donut Vertical Chart Language Card Layout](#donut-vertical-chart-language-card-layout)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to add new query string parameter information in table on 404 line

Copy link
Author

@abap34 abap34 Apr 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I did not check enough.
I added it at 27da644.

- [Pie Chart Language Card Layout](#pie-chart-language-card-layout)
- [Hide Progress Bars](#hide-progress-bars)
- [Change format of language's stats](#change-format-of-languages-stats)
- [Demo](#demo-2)
- [WakaTime Stats Card](#wakatime-stats-card)
- [Demo](#demo-3)
Expand Down Expand Up @@ -592,6 +593,15 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)
```

### Change format of language's stats

Yout can use the `&stats_format=bytes` option to display the stats in bytes instead of percentage.

```md
![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&stats_format=bytes)
```


### Demo

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

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


* Display bytes instead of percentage

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

# WakaTime Stats Card

> [!WARNING]\
Expand Down
107 changes: 86 additions & 21 deletions src/cards/top-languages-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getCardColors,
lowercaseTrim,
measureText,
formatBytes,
} from "../common/utils.js";
import { langCardLocales } from "../translations.js";

Expand Down Expand Up @@ -203,20 +204,34 @@ const trimTopLanguages = (topLangs, langs_count, hide) => {
* @param {number} props.width The card width
* @param {string} props.color Color of the programming language.
* @param {string} props.name Name of the programming language.
* @param {number} props.progress Usage of the programming language in percentage.
* @param {number} props.size Size of the programming language.
* @param {number} props.totalSize Total size of all languages.
* @param {string} props.statsFormat Stats format.
* @param {number} props.index Index of the programming language.
* @returns {string} Programming language SVG node.
*/
const createProgressTextNode = ({ width, color, name, progress, index }) => {
const createProgressTextNode = ({
width,
color,
name,
size,
totalSize,
statsFormat,
index,
}) => {
const staggerDelay = (index + 3) * 150;
const paddingRight = 95;
const progressTextX = width - paddingRight + 10;
const progressWidth = width - paddingRight;

const progress = parseFloat(((size / totalSize) * 100).toFixed(2));
const bytes = formatBytes(size);
const showValue = statsFormat === "bytes" ? bytes : `${progress}%`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this file contains the almost same code on 266-269 lines. I think it will be better to avoid duplication of this logic and create separate function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer it that way too, modified at 0c90270.


return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
<text x="${progressTextX}" y="34" class="lang-name">${showValue}</text>
${createProgressNode({
x: 0,
y: 25,
Expand All @@ -237,19 +252,30 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => {
* @param {Lang} props.lang Programming language object.
* @param {number} props.totalSize Total size of all languages.
* @param {boolean=} props.hideProgress Whether to hide percentage.
* @param {string=} props.statsFormat Stats format
* @param {number} props.index Index of the programming language.
* @returns {string} Compact layout programming language SVG node.
*/
const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const createCompactLangNode = ({
lang,
totalSize,
hideProgress,
statsFormat,
index,
}) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2) + "%";
const bytes = formatBytes(lang.size);

const showValue = statsFormat === "bytes" ? bytes : percentage;

const staggerDelay = (index + 3) * 150;
const color = lang.color || "#858585";

return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
<circle cx="5" cy="6" r="5" fill="${color}" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
${lang.name} ${hideProgress ? "" : percentage + "%"}
${lang.name} ${hideProgress ? "" : showValue}
</text>
</g>
`;
Expand All @@ -262,9 +288,15 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => {
* @param {Lang[]} props.langs Array of programming languages.
* @param {number} props.totalSize Total size of all languages.
* @param {boolean=} props.hideProgress Whether to hide percentage.
* @param {string=} props.statsFormat Stats format
* @returns {string} Programming languages SVG node.
*/
const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
const createLanguageTextNode = ({
langs,
totalSize,
hideProgress,
statsFormat,
}) => {
const longestLang = getLongestLang(langs);
const chunked = chunkArray(langs, langs.length / 2);
const layouts = chunked.map((array) => {
Expand All @@ -274,6 +306,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
lang,
totalSize,
hideProgress,
statsFormat,
index,
}),
);
Expand All @@ -299,15 +332,17 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
* @param {object} props Function properties.
* @param {Lang[]} props.langs Array of programming languages.
* @param {number} props.totalSize Total size of all languages.
* @param {string} props.statsFormat Stats format
* @returns {string} Donut layout programming language SVG node.
*/
const createDonutLanguagesNode = ({ langs, totalSize }) => {
const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => {
return flexLayout({
items: langs.map((lang, index) => {
return createCompactLangNode({
lang,
totalSize,
hideProgress: false,
statsFormat,
index,
});
}),
Expand All @@ -322,18 +357,19 @@ const createDonutLanguagesNode = ({ langs, totalSize }) => {
* @param {Lang[]} langs Array of programming languages.
* @param {number} width Card width.
* @param {number} totalLanguageSize Total size of all languages.
* @param {string} statsFormat Stats format.
* @returns {string} Normal layout card SVG object.
*/
const renderNormalLayout = (langs, width, totalLanguageSize) => {
const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat) => {
return flexLayout({
items: langs.map((lang, index) => {
return createProgressTextNode({
width,
name: lang.name,
color: lang.color || DEFAULT_LANG_COLOR,
progress: parseFloat(
((lang.size / totalLanguageSize) * 100).toFixed(2),
),
size: lang.size,
totalSize: totalLanguageSize,
statsFormat,
index,
});
}),
Expand All @@ -349,9 +385,16 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
* @param {number} width Card width.
* @param {number} totalLanguageSize Total size of all languages.
* @param {boolean=} hideProgress Whether to hide progress bar.
* @param {string} statsFormat Stats format.
* @returns {string} Compact layout card SVG object.
*/
const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
const renderCompactLayout = (
langs,
width,
totalLanguageSize,
hideProgress,
statsFormat,
) => {
const paddingRight = 50;
const offsetWidth = width - paddingRight;
// progressOffset holds the previous language's width and used to offset the next language
Expand Down Expand Up @@ -397,6 +440,7 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
langs,
totalSize: totalLanguageSize,
hideProgress,
statsFormat,
})}
</g>
`;
Expand All @@ -407,9 +451,10 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
*
* @param {Lang[]} langs Array of programming languages.
* @param {number} totalLanguageSize Total size of all languages.
* @param {string} statsFormat Stats format.
* @returns {string} Compact layout card SVG object.
*/
const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => {
// Donut vertical chart radius and total length
const radius = 80;
const totalCircleLength = getCircleLength(radius);
Expand Down Expand Up @@ -465,6 +510,7 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
langs,
totalSize: totalLanguageSize,
hideProgress: false,
statsFormat,
})}
</svg>
</g>
Expand All @@ -477,9 +523,10 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
*
* @param {Lang[]} langs Array of programming languages.
* @param {number} totalLanguageSize Total size of all languages.
* @param {string} statsFormat Stats format.
* @returns {string} Compact layout card SVG object.
*/
const renderPieLayout = (langs, totalLanguageSize) => {
const renderPieLayout = (langs, totalLanguageSize, statsFormat) => {
// Pie chart radius and center coordinates
const radius = 90;
const centerX = 150;
Expand Down Expand Up @@ -560,6 +607,7 @@ const renderPieLayout = (langs, totalLanguageSize) => {
langs,
totalSize: totalLanguageSize,
hideProgress: false,
statsFormat,
})}
</svg>
</g>
Expand Down Expand Up @@ -610,9 +658,10 @@ const createDonutPaths = (cx, cy, radius, percentages) => {
* @param {Lang[]} langs Array of programming languages.
* @param {number} width Card width.
* @param {number} totalLanguageSize Total size of all languages.
* @param {string} statsFormat Stats format.
* @returns {string} Donut layout card SVG object.
*/
const renderDonutLayout = (langs, width, totalLanguageSize) => {
const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat) => {
const centerX = width / 3;
const centerY = width / 3;
const radius = centerX - 60;
Expand Down Expand Up @@ -655,7 +704,7 @@ const renderDonutLayout = (langs, width, totalLanguageSize) => {
return `
<g transform="translate(0, 0)">
<g transform="translate(0, 0)">
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })}
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, statsFormat })}
</g>

<g transform="translate(125, ${donutCenterTranslation(langs.length)})">
Expand Down Expand Up @@ -738,6 +787,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
border_radius,
border_color,
disable_animations,
stats_format = "percentages",
} = options;

const i18n = new I18n({
Expand Down Expand Up @@ -779,10 +829,14 @@ const renderTopLanguages = (topLangs, options = {}) => {
});
} else if (layout === "pie") {
height = calculatePieLayoutHeight(langs.length);
finalLayout = renderPieLayout(langs, totalLanguageSize);
finalLayout = renderPieLayout(langs, totalLanguageSize, stats_format);
} else if (layout === "donut-vertical") {
height = calculateDonutVerticalLayoutHeight(langs.length);
finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize);
finalLayout = renderDonutVerticalLayout(
langs,
totalLanguageSize,
stats_format,
);
} else if (layout === "compact" || hide_progress == true) {
height =
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
Expand All @@ -792,13 +846,24 @@ const renderTopLanguages = (topLangs, options = {}) => {
width,
totalLanguageSize,
hide_progress,
stats_format,
);
} else if (layout === "donut") {
height = calculateDonutLayoutHeight(langs.length);
width = width + 50; // padding
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
finalLayout = renderDonutLayout(
langs,
width,
totalLanguageSize,
stats_format,
);
} else {
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
finalLayout = renderNormalLayout(
langs,
width,
totalLanguageSize,
stats_format,
);
}

const card = new Card({
Expand Down
1 change: 1 addition & 0 deletions src/cards/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type TopLangOptions = CommonOptions & {
langs_count: number;
disable_animations: boolean;
hide_progress: boolean;
stats_format: "percentages" | "bytes";
};

export type WakaTimeOptions = CommonOptions & {
Expand Down