Compare commits

..
8 Commits
Author SHA1 Message Date
François Rozet 1afe4189cb Add finer ranking levels 2023-06-01 20:24:21 +02:00
github-actions[bot]andrickstaa e0b3d833b0 refactor: update languages JSON (#2760)
Co-authored-by: rickstaa <rickstaa@users.noreply.github.com>
2023-05-31 13:24:11 +02:00
Alexandr Garbuzov 275c1fc1c7 Show notice about no languages data instead of empty card (#2755)
* Show message about no languages data instead of empty card

* dev

* dev

* dev

* dev

* dev

* dev
2023-05-29 08:45:19 +02:00
Alexandr Garbuzov 73b0a91b7b Improve ukrainian translation of langcard.title (#2756) 2023-05-29 08:43:37 +02:00
François RozetandRick Staa c96e84a9ae Ranking System v2 (#1186)
* Revise rank calculation

* Replace contributions by commits

* Lower average stats and S+ threshold

* Fix calculateRank.test.js

Missing key in dictionary constructor

Co-authored-by: Rick Staa <rick.staa@outlook.com>

* refactor: run prettier

* feat: change star weight to 0.75

* Separate PRs and issues

* Tweak weights

* Add count_private back

* fix: enable 'count_private' again

* test: fix tests

* refactor: improve code formatting

* Higher targets

---------

Co-authored-by: Rick Staa <rick.staa@outlook.com>
2023-05-26 15:39:35 +02:00
Alexandr Garbuzov ff2e02ba68 Docs: fix top languages card available layouts number (#2738) 2023-05-22 22:40:21 +02:00
Alexandr Garbuzov c8d5eda27c Docs (translations): add top languages card donut vertical layout (#2739) 2023-05-22 22:39:43 +02:00
Alexandr Garbuzov f9427b2a54 Top languages card donut vertical layout (#2701)
* Top languages card donut layout

* dev

* dev

* dev

* dev
2023-05-19 10:45:29 +02:00
22 changed files with 409 additions and 86 deletions
+1 -1
View File
@@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
- `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
- `hide_title` - _(boolean)_
- `layout` - 提供種佈局 `normal` & `compact` & `donut` & `pie` 间切换
- `layout` - 提供種佈局 `normal` & `compact` & `donut` & `donut-vertical` & `pie` 间切换
- `card_width` - 手动设置卡片的宽度 _(number)_
> :warning: **重要:**
+1 -1
View File
@@ -161,7 +161,7 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben,
- `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_
- `hide_title` - _(Boolean)_
- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie`
- `layout` - Wechseln Sie zwischen den fünf verfügbaren Layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_
> :warning: **Wichtig:**
+1 -1
View File
@@ -175,7 +175,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para
- `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_
- `hide_title` - _(booleano)_
- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie`
- `layout` - Cambiar entre los cinco diseños disponibles `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Establece el ancho de la tarjeta manualmente _(número)_
- `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_
- `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_
+1 -1
View File
@@ -159,7 +159,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
- `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_
- `hide_title` - Masquer le titre _(boolean)_
- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie`
- `layout` - Alterner entre 5 mise en page `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Fixer la largeur de la carte manuellement _(number)_
> :warning: **Important:**
+1 -1
View File
@@ -171,7 +171,7 @@ Puoi fornire valori separati da virgola nel parametro bg_color per creare un gra
- `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_
- `hide_title` - Nasconde il titolo _(booleano)_
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta)
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella), `donut-vertical` (ciambella verticale) e `pie` (torta)
- `card_width` - Specifica il valore della larghezza _(numero)_
> :warning: **Importante:**
+1 -1
View File
@@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ
- `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
- `hide_title` - _(boolean)_
- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える
- `layout` - `normal` & `compact` & `donut` & `donut-vertical` & `pie` のいずれかのレイアウトに切り替える
- `card_width` - カードの横幅 _(number)_
- `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_
- `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
+1 -1
View File
@@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
- `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
- `hide_title` - 타이틀 제외 _(boolean)_
- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택
- `layout` - 5가지 값 사용 가능, `normal` & `compact` & `donut` & `donut-vertical` & `pie` 중 표시 형태 선택
- `card_width` - 카드 너비 직접 설정 _(number)_
- `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
- `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
+1 -1
View File
@@ -176,7 +176,7 @@ Je kan meerdere komma verdeelde waarden in de bg_color optie geven om een kleure
- `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_
- `hide_title` - _(boolean)_
- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie`
- `layout` - Kies uit de vijf beschikbare lay-outs `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_
- `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_
- `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_
+1 -1
View File
@@ -174,7 +174,7 @@ You can provide multiple comma-separated values in bg_color option to render a g
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
- `hide_title` - _(boolean)_
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
- `card_width` - Set the card's width manually _(number)_
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
+1 -1
View File
@@ -164,7 +164,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej
- `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_
- `hide_title` - Oculta o título _(boolean)_
- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie`
- `layout` - Alternar entre os cinco layouts disponíveis `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Define a largura do cartão manualmente _(number)_
> :warning: **Importante:**
+1 -1
View File
@@ -176,7 +176,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül
- `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_
- `hide_title` - _(boolean)_
- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie`
- `layout` - Beş uygun tasarım / düzen arasında geçiş yapın `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `card_width` - Kartın genişliğini manuel olarak belirler _(number)_
- `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_
- `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_
+13 -1
View File
@@ -290,7 +290,7 @@ You can provide multiple comma-separated values in the bg_color option to render
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
- `hide_title` - _(boolean)_. Default: `false`.
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
- `card_width` - Set the card's width manually _(number)_. Default `300`.
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
@@ -418,6 +418,14 @@ You can use the `&layout=donut` option to change the card design.
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats)
```
### Donut Vertical Chart Language Card Layout
You can use the `&layout=donut-vertical` option to change the card design.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github.com/anuraghazra/github-readme-stats)
```
### Pie Chart Language Card Layout
You can use the `&layout=pie` option to change the card design.
@@ -446,6 +454,10 @@ 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&layout=donut)](https://github.com/anuraghazra/github-readme-stats)
- Donut Vertical Chart layout
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github.com/anuraghazra/github-readme-stats)
- Pie Chart layout
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats)
+25 -29
View File
@@ -1,5 +1,10 @@
function expsf(x, lambda = 1) {
return 2 ** (-lambda * x);
function exponential_cdf(x) {
return 1 - 2 ** -x;
}
function log_normal_cdf(x) {
// approximation
return x / (1 + x);
}
/**
@@ -13,7 +18,7 @@ function expsf(x, lambda = 1) {
* @param {number} params.repos Total number of repos.
* @param {number} params.stars The number of stars.
* @param {number} params.followers The number of followers.
* @returns {{level: string, score: number}}} The users rank.
* @returns {{level: string, percentile: number}}} The users rank.
*/
function calculateRank({
all_commits,
@@ -24,15 +29,15 @@ function calculateRank({
stars,
followers,
}) {
const COMMITS_MEAN = all_commits ? 1000 : 250,
const COMMITS_MEDIAN = all_commits ? 1000 : 250,
COMMITS_WEIGHT = 2;
const PRS_MEAN = 50,
const PRS_MEDIAN = 50,
PRS_WEIGHT = 3;
const ISSUES_MEAN = 25,
const ISSUES_MEDIAN = 25,
ISSUES_WEIGHT = 1;
const STARS_MEAN = 250,
const STARS_MEDIAN = 50,
STARS_WEIGHT = 4;
const FOLLOWERS_MEAN = 25,
const FOLLOWERS_MEDIAN = 10,
FOLLOWERS_WEIGHT = 1;
const TOTAL_WEIGHT =
@@ -42,30 +47,21 @@ function calculateRank({
STARS_WEIGHT +
FOLLOWERS_WEIGHT;
const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];
const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"];
const rank =
(COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) +
PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) +
ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) +
STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) +
FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) /
TOTAL_WEIGHT;
1 -
(COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) +
PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) +
ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) +
STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) +
FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) /
TOTAL_WEIGHT;
const RANK_S_PLUS = 0.025;
const RANK_S = 0.1;
const RANK_A_PLUS = 0.25;
const RANK_A = 0.5;
const RANK_B_PLUS = 0.75;
const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)];
const level = (() => {
if (rank <= RANK_S_PLUS) return "S+";
if (rank <= RANK_S) return "S";
if (rank <= RANK_A_PLUS) return "A+";
if (rank <= RANK_A) return "A";
if (rank <= RANK_B_PLUS) return "B+";
return "B";
})();
return { level, score: rank * 100 };
return { level: level, percentile: rank * 100 };
}
export { calculateRank };
+2 -3
View File
@@ -209,9 +209,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
hide_rank ? 0 : 150,
);
// the better user's score the the rank will be closer to zero so
// subtracting 100 to get the progress in 100%
const progress = 100 - rank.score;
// the lower the user's percentile the better
const progress = 100 - rank.percentile;
const cssStyles = getStyles({
titleColor,
ringColor,
+136 -12
View File
@@ -17,6 +17,7 @@ const MIN_CARD_WIDTH = 280;
const DEFAULT_LANGS_COUNT = 5;
const DEFAULT_LANG_COLOR = "#858585";
const CARD_PADDING = 25;
const COMPACT_LAYOUT_BASE_HEIGHT = 90;
/**
* @typedef {import("../fetchers/types").Lang} Lang
@@ -84,6 +85,16 @@ const cartesianToPolar = (centerX, centerY, x, y) => {
return { radius, angleInDegrees };
};
/**
* Calculates length of circle.
*
* @param {number} radius Radius of the circle.
* @returns {number} The length of the circle.
*/
const getCircleLength = (radius) => {
return 2 * Math.PI * radius;
};
/**
* Calculates height for the compact layout.
*
@@ -91,7 +102,7 @@ const cartesianToPolar = (centerX, centerY, x, y) => {
* @returns {number} Card height.
*/
const calculateCompactLayoutHeight = (totalLangs) => {
return 90 + Math.round(totalLangs / 2) * 25;
return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
};
/**
@@ -114,6 +125,16 @@ const calculateDonutLayoutHeight = (totalLangs) => {
return 215 + Math.max(totalLangs - 5, 0) * 32;
};
/**
* Calculates height for the donut vertical layout.
*
* @param {number} totalLangs Total number of languages.
* @returns {number} Card height.
*/
const calculateDonutVerticalLayoutHeight = (totalLangs) => {
return 300 + Math.round(totalLangs / 2) * 25;
};
/**
* Calculates height for the pie layout.
*
@@ -371,6 +392,76 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
`;
};
/**
* Renders donut vertical layout to display user's most frequently used programming languages.
*
* @param {Lang[]} langs Array of programming languages.
* @param {number} totalLanguageSize Total size of all languages.
* @returns {string} Compact layout card SVG object.
*/
const renderDonutVerticalLayout = (langs, totalLanguageSize) => {
// Donut vertical chart radius and total length
const radius = 80;
const totalCircleLength = getCircleLength(radius);
// SVG circles
let circles = [];
// Start indent for donut vertical chart parts
let indent = 0;
// Start delay coefficient for donut vertical chart parts
let startDelayCoefficient = 1;
// Generate each donut vertical chart part
for (const lang of langs) {
const percentage = (lang.size / totalLanguageSize) * 100;
const circleLength = totalCircleLength * (percentage / 100);
const delay = startDelayCoefficient * 100;
circles.push(`
<g class="stagger" style="animation-delay: ${delay}ms">
<circle
cx="150"
cy="100"
r="${radius}"
fill="transparent"
stroke="${lang.color}"
stroke-width="25"
stroke-dasharray="${totalCircleLength}"
stroke-dashoffset="${indent}"
size="${percentage}"
data-testid="lang-donut"
/>
</g>
`);
// Update the indent for the next part
indent += circleLength;
// Update the start delay coefficient for the next part
startDelayCoefficient += 1;
}
return `
<svg data-testid="lang-items">
<g transform="translate(0, 0)">
<svg data-testid="donut">
${circles.join("")}
</svg>
</g>
<g transform="translate(0, 220)">
<svg data-testid="lang-names" x="${CARD_PADDING}">
${createLanguageTextNode({
langs,
totalSize: totalLanguageSize,
hideProgress: false,
})}
</svg>
</g>
</svg>
`;
};
/**
* Renders pie layout to display user's most frequently used programming languages.
*
@@ -564,6 +655,19 @@ const renderDonutLayout = (langs, width, totalLanguageSize) => {
`;
};
/**
* Creates the no coding activity SVG node.
*
* @param {{color: string, text: string, layout: import("./types").TopLangOptions["layout"]}} The function prop
*/
const noLanguagesDataNode = ({ color, text, layout }) => {
return `
<text x="${
layout === "pie" || layout === "donut-vertical" ? CARD_PADDING : 0
}" y="11" class="stat bold" fill="${color}">${text}</text>
`;
};
/**
* Renders card that display user's most frequently used programming languages.
*
@@ -609,10 +713,29 @@ const renderTopLanguages = (topLangs, options = {}) => {
: card_width;
let height = calculateNormalLayoutHeight(langs.length);
// returns theme based colors with proper overrides and defaults
const colors = getCardColors({
title_color,
text_color,
bg_color,
border_color,
theme,
});
let finalLayout = "";
if (layout === "pie") {
if (langs.length === 0) {
height = COMPACT_LAYOUT_BASE_HEIGHT;
finalLayout = noLanguagesDataNode({
color: colors.textColor,
text: i18n.t("langcard.nodata"),
layout,
});
} else if (layout === "pie") {
height = calculatePieLayoutHeight(langs.length);
finalLayout = renderPieLayout(langs, totalLanguageSize);
} else if (layout === "donut-vertical") {
height = calculateDonutVerticalLayoutHeight(langs.length);
finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize);
} else if (layout === "compact" || hide_progress == true) {
height =
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
@@ -631,15 +754,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
}
// returns theme based colors with proper overrides and defaults
const colors = getCardColors({
title_color,
text_color,
bg_color,
border_color,
theme,
});
const card = new Card({
customTitle: custom_title,
defaultTitle: i18n.t("langcard.title"),
@@ -671,6 +785,14 @@ const renderTopLanguages = (topLangs, options = {}) => {
width: 100%;
}
}
.stat {
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${colors.textColor};
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.bold { font-weight: 700 }
.lang-name {
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
fill: ${colors.textColor};
@@ -688,7 +810,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
`,
);
if (layout === "pie") {
if (layout === "pie" || layout === "donut-vertical") {
return card.render(finalLayout);
}
@@ -705,9 +827,11 @@ export {
radiansToDegrees,
polarToCartesian,
cartesianToPolar,
getCircleLength,
calculateCompactLayoutHeight,
calculateNormalLayoutHeight,
calculateDonutLayoutHeight,
calculateDonutVerticalLayoutHeight,
calculatePieLayoutHeight,
donutCenterTranslation,
trimTopLanguages,
+1 -1
View File
@@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & {
hide_border: boolean;
card_width: number;
hide: string[];
layout: "compact" | "normal" | "donut" | "pie";
layout: "compact" | "normal" | "donut" | "donut-vertical" | "pie";
custom_title: string;
langs_count: number;
disable_animations: boolean;
+9 -1
View File
@@ -106,6 +106,7 @@
"Cypher": "#34c0eb",
"Cython": "#fedf5b",
"D": "#ba595e",
"D2": "#526ee8",
"DM": "#447265",
"Dafny": "#FFEC25",
"Darcs Patch": "#8eff23",
@@ -185,6 +186,7 @@
"Go": "#00ADD8",
"Go Checksums": "#00ADD8",
"Go Module": "#00ADD8",
"Go Workspace": "#00ADD8",
"Godot Resource": "#355570",
"Golo": "#88562A",
"Gosu": "#82937f",
@@ -284,6 +286,7 @@
"Lua": "#000080",
"MATLAB": "#e16737",
"MAXScript": "#00a6a6",
"MDX": "#fcb32c",
"MLIR": "#5EC8DB",
"MQL4": "#62A8D6",
"MQL5": "#4A76B8",
@@ -330,11 +333,12 @@
"Nu": "#c9df40",
"NumPy": "#9C8AF9",
"Nunjucks": "#3d8137",
"Nushell": "#4E9906",
"OASv2-json": "#85ea2d",
"OASv2-yaml": "#85ea2d",
"OASv3-json": "#85ea2d",
"OASv3-yaml": "#85ea2d",
"OCaml": "#3be133",
"OCaml": "#ef7a08",
"ObjectScript": "#424893",
"Objective-C": "#438eff",
"Objective-C++": "#6866fb",
@@ -360,6 +364,7 @@
"PLSQL": "#dad8d8",
"PLpgSQL": "#336790",
"POV-Ray SDL": "#6bac65",
"Pact": "#F7A8B8",
"Pan": "#cc0000",
"Papyrus": "#6600cc",
"Parrot": "#f3ca0a",
@@ -398,6 +403,7 @@
"Quake": "#882233",
"R": "#198CE7",
"RAML": "#77d9fb",
"RBS": "#701516",
"RDoc": "#701516",
"REXX": "#d90e09",
"RMarkdown": "#198ce7",
@@ -472,6 +478,7 @@
"Swift": "#F05138",
"SystemVerilog": "#DAE1C2",
"TI Program": "#A0AA87",
"TL-Verilog": "#C40023",
"TLA": "#4b0079",
"TOML": "#9c4221",
"TSQL": "#e38c00",
@@ -512,6 +519,7 @@
"Vyper": "#2980b9",
"Web Ontology Language": "#5b70bd",
"WebAssembly": "#04133b",
"WebAssembly Interface Type": "#6250e7",
"Whiley": "#d5c397",
"Wikitext": "#fc5757",
"Windows Registry Entries": "#52d5ff",
+1 -1
View File
@@ -192,7 +192,7 @@ const fetchStats = async (
totalIssues: 0,
totalStars: 0,
contributedTo: 0,
rank: { level: "B", score: 0 },
rank: { level: "C", percentile: 100 },
};
let res = await statsFetcher(username);
+1 -1
View File
@@ -22,7 +22,7 @@ export type StatsData = {
totalIssues: number;
totalStars: number;
contributedTo: number;
rank: { level: string; score: number };
rank: { level: string; percentile: number };
};
export type Lang = {
+31 -1
View File
@@ -283,7 +283,7 @@ const langCardLocales = {
np: "अधिक प्रयोग गरिएको भाषाहरू",
el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες",
ru: "Наиболее часто используемые языки",
"uk-ua": "Найбільш часто використовувані мови",
"uk-ua": "Найчастіше використовувані мови",
id: "Bahasa Yang Paling Banyak Digunakan",
ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ",
my: "Bahasa Paling Digunakan",
@@ -293,6 +293,36 @@ const langCardLocales = {
vi: "Ngôn Ngữ Thường Sử Dụng",
se: "Mest använda språken",
},
"langcard.nodata": {
ar: "لا توجد بيانات لغات.",
cn: "沒有語言數據。",
"zh-tw": "沒有語言數據。",
cs: "Žádné jazykové údaje.",
de: "Keine Sprachdaten.",
bn: "কোন ভাষার ডেটা নেই।",
en: "No languages data.",
es: "Sin datos de idiomas.",
fr: "Aucune donnée sur les langues.",
hu: "Nincsenek nyelvi adatok.",
it: "Nessun dato sulle lingue.",
ja: "言語データがありません。",
kr: "언어 데이터가 없습니다.",
nl: "Ingen sprogdata.",
"pt-pt": "Sem dados de idiomas.",
"pt-br": "Sem dados de idiomas.",
np: "कुनै भाषा डाटा छैन।",
el: "Δεν υπάρχουν δεδομένα γλωσσών.",
ru: "Нет данных о языках.",
"uk-ua": "Немає даних про мови.",
id: "Tidak ada data bahasa.",
ml: "ഭാഷാ ഡാറ്റയില്ല.",
my: "Tiada data bahasa.",
sk: "Žiadne údaje o jazykoch.",
tr: "Dil verisi yok.",
pl: "Brak danych dotyczących języków.",
vi: "Không có dữ liệu ngôn ngữ.",
se: "Inga språkdata.",
},
};
const wakatimeCardLocales = {
+39 -25
View File
@@ -2,7 +2,7 @@ import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js";
describe("Test calculateRank", () => {
it("new user gets B rank", () => {
it("new user gets C rank", () => {
expect(
calculateRank({
all_commits: false,
@@ -13,10 +13,24 @@ describe("Test calculateRank", () => {
stars: 0,
followers: 0,
}),
).toStrictEqual({ level: "B", score: 100 });
).toStrictEqual({ level: "C", percentile: 100 });
});
it("average user gets A rank", () => {
it("beginner user gets B- rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 125,
prs: 25,
issues: 10,
repos: 0,
stars: 25,
followers: 5,
}),
).toStrictEqual({ level: "B-", percentile: 69.333868386557 });
});
it("median user gets B+ rank", () => {
expect(
calculateRank({
all_commits: false,
@@ -24,13 +38,13 @@ describe("Test calculateRank", () => {
prs: 50,
issues: 25,
repos: 0,
stars: 250,
followers: 25,
stars: 50,
followers: 10,
}),
).toStrictEqual({ level: "A", score: 50 });
).toStrictEqual({ level: "B+", percentile: 50 });
});
it("average user gets A rank (include_all_commits)", () => {
it("average user gets B+ rank (include_all_commits)", () => {
expect(
calculateRank({
all_commits: true,
@@ -38,13 +52,13 @@ describe("Test calculateRank", () => {
prs: 50,
issues: 25,
repos: 0,
stars: 250,
followers: 25,
stars: 50,
followers: 10,
}),
).toStrictEqual({ level: "A", score: 50 });
).toStrictEqual({ level: "B+", percentile: 50 });
});
it("more than average user gets A+ rank", () => {
it("advanced user gets A rank", () => {
expect(
calculateRank({
all_commits: false,
@@ -52,13 +66,13 @@ describe("Test calculateRank", () => {
prs: 100,
issues: 50,
repos: 0,
stars: 500,
followers: 50,
stars: 200,
followers: 40,
}),
).toStrictEqual({ level: "A+", score: 25 });
).toStrictEqual({ level: "A", percentile: 22.72727272727273 });
});
it("expert user gets S rank", () => {
it("expert user gets A+ rank", () => {
expect(
calculateRank({
all_commits: false,
@@ -66,23 +80,23 @@ describe("Test calculateRank", () => {
prs: 200,
issues: 100,
repos: 0,
stars: 1000,
followers: 100,
stars: 800,
followers: 160,
}),
).toStrictEqual({ level: "S", score: 6.25 });
).toStrictEqual({ level: "A+", percentile: 6.082887700534744 });
});
it("ezyang gets S+ rank", () => {
it("sindresorhus gets S rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 1000,
prs: 4000,
issues: 2000,
commits: 1300,
prs: 1500,
issues: 4500,
repos: 0,
stars: 5000,
followers: 2000,
stars: 600000,
followers: 50000,
}),
).toStrictEqual({ level: "S+", score: 1.1363983154296875 });
).toStrictEqual({ level: "S", percentile: 0.49947889605312934 });
});
});
+140
View File
@@ -6,9 +6,11 @@ import {
radiansToDegrees,
polarToCartesian,
cartesianToPolar,
getCircleLength,
calculateCompactLayoutHeight,
calculateNormalLayoutHeight,
calculateDonutLayoutHeight,
calculateDonutVerticalLayoutHeight,
calculatePieLayoutHeight,
donutCenterTranslation,
trimTopLanguages,
@@ -70,6 +72,20 @@ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => {
return (endAngle - startAngle) / 3.6;
};
/**
* Calculate language percentage for donut vertical chart SVG.
*
* @param {number} partLength Length of current chart part..
* @param {number} totalCircleLength Total length of circle.
* @return {number} Chart part percentage.
*/
const langPercentFromDonutVerticalLayoutSvg = (
partLength,
totalCircleLength,
) => {
return (partLength / totalCircleLength) * 100;
};
/**
* Retrieve the language percentage from the pie chart SVG.
*
@@ -230,6 +246,20 @@ describe("Test renderTopLanguages helper functions", () => {
expect(calculateDonutLayoutHeight(10)).toBe(375);
});
it("calculateDonutVerticalLayoutHeight", () => {
expect(calculateDonutVerticalLayoutHeight(0)).toBe(300);
expect(calculateDonutVerticalLayoutHeight(1)).toBe(325);
expect(calculateDonutVerticalLayoutHeight(2)).toBe(325);
expect(calculateDonutVerticalLayoutHeight(3)).toBe(350);
expect(calculateDonutVerticalLayoutHeight(4)).toBe(350);
expect(calculateDonutVerticalLayoutHeight(5)).toBe(375);
expect(calculateDonutVerticalLayoutHeight(6)).toBe(375);
expect(calculateDonutVerticalLayoutHeight(7)).toBe(400);
expect(calculateDonutVerticalLayoutHeight(8)).toBe(400);
expect(calculateDonutVerticalLayoutHeight(9)).toBe(425);
expect(calculateDonutVerticalLayoutHeight(10)).toBe(425);
});
it("calculatePieLayoutHeight", () => {
expect(calculatePieLayoutHeight(0)).toBe(300);
expect(calculatePieLayoutHeight(1)).toBe(325);
@@ -258,6 +288,18 @@ describe("Test renderTopLanguages helper functions", () => {
expect(donutCenterTranslation(10)).toBe(35);
});
it("getCircleLength", () => {
expect(getCircleLength(20)).toBeCloseTo(125.663);
expect(getCircleLength(30)).toBeCloseTo(188.495);
expect(getCircleLength(40)).toBeCloseTo(251.327);
expect(getCircleLength(50)).toBeCloseTo(314.159);
expect(getCircleLength(60)).toBeCloseTo(376.991);
expect(getCircleLength(70)).toBeCloseTo(439.822);
expect(getCircleLength(80)).toBeCloseTo(502.654);
expect(getCircleLength(90)).toBeCloseTo(565.486);
expect(getCircleLength(100)).toBeCloseTo(628.318);
});
it("trimTopLanguages", () => {
expect(trimTopLanguages([])).toStrictEqual({
langs: [],
@@ -569,6 +611,104 @@ describe("Test renderTopLanguages", () => {
"circle",
);
});
it("should render with layout donut vertical", () => {
document.body.innerHTML = renderTopLanguages(langs, {
layout: "donut-vertical",
});
expect(queryByTestId(document.body, "header")).toHaveTextContent(
"Most Used Languages",
);
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
"HTML 40.00%",
);
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
"size",
"40",
);
const totalCircleLength = queryAllByTestId(
document.body,
"lang-donut",
)[0].getAttribute("stroke-dasharray");
const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
"stroke-dashoffset",
) -
queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
"stroke-dashoffset",
),
totalCircleLength,
);
expect(HTMLLangPercent).toBeCloseTo(40);
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
"javascript 40.00%",
);
expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute(
"size",
"40",
);
const javascriptLangPercent = langPercentFromDonutVerticalLayoutSvg(
queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
"stroke-dashoffset",
) -
queryAllByTestId(document.body, "lang-donut")[1].getAttribute(
"stroke-dashoffset",
),
totalCircleLength,
);
expect(javascriptLangPercent).toBeCloseTo(40);
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
"css 20.00%",
);
expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute(
"size",
"20",
);
const cssLangPercent = langPercentFromDonutVerticalLayoutSvg(
totalCircleLength -
queryAllByTestId(document.body, "lang-donut")[2].getAttribute(
"stroke-dashoffset",
),
totalCircleLength,
);
expect(cssLangPercent).toBeCloseTo(20);
expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);
});
it("should render with layout donut vertical full donut circle of one language is 100%", () => {
document.body.innerHTML = renderTopLanguages(
{ HTML: langs.HTML },
{ layout: "donut-vertical" },
);
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
"HTML 100.00%",
);
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
"size",
"100",
);
const totalCircleLength = queryAllByTestId(
document.body,
"lang-donut",
)[0].getAttribute("stroke-dasharray");
const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg(
totalCircleLength -
queryAllByTestId(document.body, "lang-donut")[0].getAttribute(
"stroke-dashoffset",
),
totalCircleLength,
);
expect(HTMLLangPercent).toBeCloseTo(100);
});
it("should render with layout pie", () => {
document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" });