feat: Added compact layout for top languages card (#179)

* compact layout for top langs card

* 🐞 FIX: most used lang should be first, apply correct colors, removed nested svgs and height set

* 🐞 FIX: conditionally rendering layout compact

* 🐞 FIX: border radius on lang progressbar

* refactor: refactored code & fixed bugs

* fix: toFixed

* chore: change string interpolation

* docs: updated readme

Co-authored-by: anuraghazra <hazru.anurag@gmail.com>
This commit is contained in:
Sagar 2020-07-26 21:45:23 +05:30 committed by GitHub
parent 3bdcf3d61f
commit b8330a88e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 19 deletions

View File

@ -20,6 +20,7 @@ module.exports = async (req, res) => {
bg_color,
theme,
cache_seconds,
layout
} = req.query;
let topLangs;
@ -49,6 +50,7 @@ module.exports = async (req, res) => {
text_color,
bg_color,
theme,
layout
})
);
};

View File

@ -125,6 +125,7 @@ Customization Options:
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default |
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' |
| count_private | boolean | counts private contributions too if enabled | false | N/A | N/A |
| layout | string | choose a layout option | N/A | N/A | "default" |
> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours
@ -176,10 +177,22 @@ You can use `?hide=language1,language2` parameter to hide individual languages.
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide=javascript,html)](https://github.com/anuraghazra/github-readme-stats)
```
### Compact Language Card Layout
You can use the `&layout=compact` option to change the card design.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats)
```
### Demo
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
- Compact layout
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats)
---
### All Demos

View File

@ -11,11 +11,11 @@ const createProgressNode = ({ width, color, name, progress }) => {
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
<svg width="${progressWidth}">
<rect rx="5" ry="5" x="0" y="25" width="${progressWidth}" height="8" fill="#ddd"></rect>
<rect
height="8"
<rect
height="8"
fill="${color}"
rx="5" ry="5" x="0" y="25"
data-testid="lang-progress"
rx="5" ry="5" x="0" y="25"
data-testid="lang-progress"
width="${progressPercentage}%"
>
</rect>
@ -23,6 +23,41 @@ const createProgressNode = ({ width, color, name, progress }) => {
`;
};
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const color = lang.color || "#858585";
return `
<g transform="translate(${x}, ${y})">
<circle cx="5" cy="6" r="5" fill="${color}" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
${lang.name} ${percentage}%
</text>
</g>
`;
};
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
return langs.map((lang, index) => {
if (index % 2 === 0) {
return createCompactLangNode({
lang,
x,
y: 12.5 * index + y,
totalSize,
index,
});
}
return createCompactLangNode({
lang,
x: 150,
y: 12.5 + 12.5 * index,
totalSize,
index,
});
});
};
const lowercaseTrim = (name) => name.toLowerCase().trim();
const renderTopLanguages = (topLangs, options = {}) => {
@ -34,6 +69,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
bg_color,
hide,
theme,
layout,
} = options;
let langs = Object.values(topLangs);
@ -54,7 +90,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
return !langsToHide[lowercaseTrim(lang.name)];
});
const totalSize = langs.reduce((acc, curr) => {
const totalLanguageSize = langs.reduce((acc, curr) => {
return acc + curr.size;
}, 0);
@ -66,12 +102,78 @@ const renderTopLanguages = (topLangs, options = {}) => {
theme,
});
const width = isNaN(card_width) ? 300 : card_width;
let width = isNaN(card_width) ? 300 : card_width;
let height = 45 + (langs.length + 1) * 40;
let finalLayout = "";
// RENDER COMPACT LAYOUT
if (layout === "compact") {
width = width + 50;
height = 30 + (langs.length / 2 + 1) * 40;
// progressOffset holds the previous language's width and used to offset the next language
// so that we can stack them one after another, like this: [--][----][---]
let progressOffset = 0;
const compactProgressBar = langs
.map((lang) => {
const percentage = (
(lang.size / totalLanguageSize) *
(width - 50)
).toFixed(2);
const progress =
percentage < 10 ? parseFloat(percentage) + 10 : percentage;
const output = `
<rect
mask="url(#rect-mask)"
data-testid="lang-progress"
x="${progressOffset}"
y="0"
width="${progress}"
height="8"
fill="${lang.color || "#858585"}"
/>
`;
progressOffset += parseFloat(percentage);
return output;
})
.join("");
finalLayout = `
<mask id="rect-mask">
<rect x="0" y="0" width="${
width - 50
}" height="8" fill="white" rx="5" />
</mask>
${compactProgressBar}
${createLanguageTextNode({
x: 0,
y: 25,
langs,
totalSize: totalLanguageSize,
}).join("")}
`;
} else {
finalLayout = FlexLayout({
items: langs.map((lang) => {
return createProgressNode({
width: width,
name: lang.name,
color: lang.color || "#858585",
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
});
}),
gap: 40,
direction: "column",
}).join("");
}
if (hide_title) {
height -= 30;
}
return `
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
@ -80,7 +182,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
</style>
<rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
${
hide_title
? ""
@ -88,18 +189,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
}
<svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
${FlexLayout({
items: langs.map((lang) => {
return createProgressNode({
width: width,
name: lang.name,
color: lang.color || "#858585",
progress: ((lang.size / totalSize) * 100).toFixed(2),
});
}),
gap: 40,
direction: "column",
}).join("")}
${finalLayout}
</svg>
</svg>
`;

View File

@ -207,4 +207,19 @@ describe("Test renderTopLanguages", () => {
);
});
});
it('should render with layout compact', () => {
document.body.innerHTML = renderTopLanguages(langs, {layout: 'compact'});
expect(queryByTestId(document.body, "header")).toHaveTextContent("Top Languages");
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent("HTML 40.00%");
expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute("width","120.00");
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent("javascript 40.00%");
expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute("width","120.00");
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent("css 20.00%");
expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute("width","60.00");
})
});