Compare commits

..
17 Commits
Author SHA1 Message Date
François Rozet 53005a5849 Higher targets 2023-05-19 08:50:18 +00:00
Rick Staa 8d8d972ec6 refactor: improve code formatting 2023-05-19 09:23:06 +02:00
Rick Staa edf00af449 Merge branch 'master' into patch-rank 2023-05-19 09:12:56 +02:00
Rick Staa 7164451e59 test: fix tests 2023-05-19 09:11:55 +02:00
Rick Staa 0917e03fbb fix: enable 'count_private' again 2023-05-19 08:51:58 +02:00
François Rozet c251dba21f Add count_private back 2023-05-18 11:17:30 +00:00
François Rozet 8e007fe6f0 Tweak weights 2023-05-01 10:04:20 +00:00
rickstaa 08e069e432 test: fix several broken tests 2023-05-01 09:02:43 +02:00
François Rozet 6fe9bb0e92 Separate PRs and issues 2023-04-29 11:24:28 +00:00
rickstaa 9900d74f3e feat: change star weight to 0.75 2023-04-08 09:42:52 +02:00
rickstaa 5a24ada1c3 Merge branch 'master' into patch-rank 2023-04-08 09:08:21 +02:00
rickstaa e10cae6df2 refactor: run prettier 2022-09-23 09:39:54 +02:00
François Rozet 273b28776f Merge branch 'master' into patch-rank 2022-09-22 08:56:53 +02:00
François RozetandRick Staa 7fd6551bbb Fix calculateRank.test.js
Missing key in dictionary constructor

Co-authored-by: Rick Staa <rick.staa@outlook.com>
2021-11-09 11:04:17 +01:00
François Rozet 9c01a95a23 Lower average stats and S+ threshold 2021-07-18 15:57:47 +02:00
François Rozet 7a652bf144 Replace contributions by commits 2021-07-14 00:16:37 +02:00
François Rozet 2c4331d061 Revise rank calculation 2021-07-13 16:46:51 +02:00
20 changed files with 210 additions and 511 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` & `donut-vertical` & `pie` 间切换
- `layout` - 提供種佈局 `normal` & `compact` & `donut` & `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 fünf verfügbaren Layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `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 cinco diseños disponibles `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `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)_
+2 -14
View File
@@ -90,18 +90,6 @@ Pour masquer des statistiques spécifiques, vous pouvez passer un paramètre de
![Les Stats GitHub de Anurag](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,prs)
```
### Ajouter le compte des contributions privées au compte des commits totaux
Vous pouvez ajouter le compte de toutes vos contributions privées au compte total des engagements en utilisant le paramètre de requête `?count_private=true`.
_Note: Si vous déployez vous-même ce projet, les contributions privées seront comptées par défaut ; sinon, vous devez choisir de partager les comptes de vos contributions privées._
> Options: `&count_private=true`
```md
![Les Stats GitHub de Anurag](https://github-readme-stats.vercel.app/api?username=anuraghazra&count_private=true)
```
### Afficher les icônes
Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci :
@@ -160,7 +148,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
- `hide_rank` - Masquer le rang _(boolean)_
- `show_icons` - Afficher les icônes _(boolean)_
- `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_
- `count_private` - Compter les commits privés _(boolean)_
- `count_private` - Compter les contributions privées _(boolean)_
- `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_
#### Repo Card Exclusive Options:
@@ -171,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 5 mise en page `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `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), `donut-vertical` (ciambella verticale) e `pie` (torta)
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) 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` & `donut-vertical` & `pie` のいずれかのレイアウトに切り替える
- `layout` - `normal` & `compact` & `donut` & `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` - 5가지 값 사용 가능, `normal` & `compact` & `donut` & `donut-vertical` & `pie` 중 표시 형태 선택
- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `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` - Kies uit de vijf beschikbare lay-outs `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `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 five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `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` - Alternar entre os cinco layouts disponíveis `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `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` - Beş uygun tasarım / düzen arasında geçiş yapın `normal` & `compact` & `donut` & `donut-vertical` & `pie`
- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `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)_
+2 -27
View File
@@ -120,19 +120,6 @@ You can pass a query parameter `&hide=` to hide any specific stats with comma-se
![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,prs)
```
### Adding private contributions count to total commits count
You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`.
> **Note**
> If you are deploying this project yourself, the private contributions will be counted by default. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile).
> Options: `&count_private=true`
```md
![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&count_private=true)
```
### Showing icons
To enable icons, you can pass `&show_icons=true` in the query param, like so:
@@ -283,7 +270,7 @@ You can provide multiple comma-separated values in the bg_color option to render
- `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`.
- `show_icons` - _(boolean)_. Default: `false`.
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
- `count_private` - Count private commits _(boolean)_. Default: `false`.
- `count_private` - Count private contributions _(boolean)_. Default: `false`.
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
- `custom_title` - Sets a custom title for the card. Default: `<username> GitHub Stats`.
@@ -303,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 five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `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)`.
@@ -431,14 +418,6 @@ 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.
@@ -467,10 +446,6 @@ 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)
+51 -84
View File
@@ -1,105 +1,72 @@
/**
* Calculates the probability of x taking on x or a value less than x in a normal distribution
* with mean and standard deviation.
*
* @see https://stackoverflow.com/a/5263759/10629172
*
* @param {string} mean The mean of the normal distribution.
* @param {number} sigma The standard deviation of the normal distribution.
* @param {number} to The value to calculate the probability for.
* @returns {number} Probability.
*/
const normalcdf = (mean, sigma, to) => {
var z = (to - mean) / Math.sqrt(2 * sigma * sigma);
var t = 1 / (1 + 0.3275911 * Math.abs(z));
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var erf =
1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-z * z);
var sign = 1;
if (z < 0) {
sign = -1;
}
return (1 / 2) * (1 + sign * erf);
};
function expsf(x, lambda = 1) {
return 2 ** (-lambda * x);
}
/**
* Calculates the users rank.
*
* @param {object} params Parameters on which the user's rank depends.
* @param {number} params.totalRepos Total number of repos.
* @param {number} params.totalCommits Total number of commits.
* @param {number} params.contributions The number of contributions.
* @param {number} params.followers The number of followers.
* @param {boolean} params.all_commits Whether `include_all_commits` was used.
* @param {number} params.commits Number of commits.
* @param {number} params.prs The number of pull requests.
* @param {number} params.issues The number of issues.
* @param {number} params.stargazers The number of stars.
* @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.
*/
const calculateRank = ({
totalRepos,
totalCommits,
contributions,
followers,
function calculateRank({
all_commits,
commits,
prs,
issues,
stargazers,
}) => {
const COMMITS_OFFSET = 1.65;
const CONTRIBS_OFFSET = 1.65;
const ISSUES_OFFSET = 1;
const STARS_OFFSET = 0.75;
const PRS_OFFSET = 0.5;
const FOLLOWERS_OFFSET = 0.45;
const REPO_OFFSET = 1;
repos, // unused
stars,
followers,
}) {
const COMMITS_MEAN = all_commits ? 1000 : 250,
COMMITS_WEIGHT = 2;
const PRS_MEAN = 50,
PRS_WEIGHT = 3;
const ISSUES_MEAN = 25,
ISSUES_WEIGHT = 1;
const STARS_MEAN = 250,
STARS_WEIGHT = 4;
const FOLLOWERS_MEAN = 25,
FOLLOWERS_WEIGHT = 1;
const ALL_OFFSETS =
CONTRIBS_OFFSET +
ISSUES_OFFSET +
STARS_OFFSET +
PRS_OFFSET +
FOLLOWERS_OFFSET +
REPO_OFFSET;
const TOTAL_WEIGHT =
COMMITS_WEIGHT +
PRS_WEIGHT +
ISSUES_WEIGHT +
STARS_WEIGHT +
FOLLOWERS_WEIGHT;
const RANK_S_VALUE = 1;
const RANK_DOUBLE_A_VALUE = 25;
const RANK_A2_VALUE = 45;
const RANK_A3_VALUE = 60;
const RANK_B_VALUE = 100;
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;
const TOTAL_VALUES =
RANK_S_VALUE +
RANK_DOUBLE_A_VALUE +
RANK_A2_VALUE +
RANK_A3_VALUE +
RANK_B_VALUE;
// prettier-ignore
const score = (
totalCommits * COMMITS_OFFSET +
contributions * CONTRIBS_OFFSET +
issues * ISSUES_OFFSET +
stargazers * STARS_OFFSET +
prs * PRS_OFFSET +
followers * FOLLOWERS_OFFSET +
totalRepos * REPO_OFFSET
) / 100;
const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100;
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 = (() => {
if (normalizedScore < RANK_S_VALUE) return "S+";
if (normalizedScore < RANK_DOUBLE_A_VALUE) return "S";
if (normalizedScore < RANK_A2_VALUE) return "A++";
if (normalizedScore < RANK_A3_VALUE) return "A+";
return "B+";
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: normalizedScore };
};
return { level, score: rank * 100 };
}
export { calculateRank };
export default calculateRank;
+4 -102
View File
@@ -84,16 +84,6 @@ 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.
*
@@ -124,16 +114,6 @@ 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.
*
@@ -391,76 +371,6 @@ 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.
*
@@ -700,16 +610,10 @@ const renderTopLanguages = (topLangs, options = {}) => {
let height = calculateNormalLayoutHeight(langs.length);
let finalLayout = "";
const layoutParsed = Array.isArray(layout)
? layout[0]?.toLocaleLowerCase()
: layout?.toLocaleLowerCase();
if (layoutParsed === "pie") {
if (layout === "pie") {
height = calculatePieLayoutHeight(langs.length);
finalLayout = renderPieLayout(langs, totalLanguageSize);
} else if (layoutParsed === "donut-vertical") {
height = calculateDonutVerticalLayoutHeight(langs.length);
finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize);
} else if (layoutParsed === "compact" || hide_progress == true) {
} else if (layout === "compact" || hide_progress == true) {
height =
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
@@ -719,7 +623,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
totalLanguageSize,
hide_progress,
);
} else if (layoutParsed === "donut") {
} else if (layout?.toLowerCase() === "donut") {
height = calculateDonutLayoutHeight(langs.length);
width = width + 50; // padding
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
@@ -784,7 +688,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
`,
);
if (layoutParsed === "pie" || layoutParsed === "donut-vertical") {
if (layout === "pie") {
return card.render(finalLayout);
}
@@ -801,11 +705,9 @@ 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" | "donut-vertical" | "pie";
layout: "compact" | "normal" | "donut" | "pie";
custom_title: string;
langs_count: number;
disable_animations: boolean;
+15 -24
View File
@@ -192,7 +192,7 @@ const fetchStats = async (
totalIssues: 0,
totalStars: 0,
contributedTo: 0,
rank: { level: "C", score: 0 },
rank: { level: "B", score: 0 },
};
let res = await statsFetcher(username);
@@ -220,53 +220,44 @@ const fetchStats = async (
const user = res.data.data.user;
// populate repoToHide map for quick lookup
// while filtering out
let repoToHide = {};
if (exclude_repo) {
exclude_repo.forEach((repoName) => {
repoToHide[repoName] = true;
});
}
stats.name = user.name || user.login;
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
// normal commits
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
// if include_all_commits then just get that,
// since totalCommitsFetcher already sends totalCommits no need to +=
// if include_all_commits, fetch all commits using the REST API.
if (include_all_commits) {
stats.totalCommits = await totalCommitsFetcher(username);
} else {
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
}
// if count_private then add private commits to totalCommits so far.
// if count_private, add private contributions to totalCommits.
if (count_private) {
stats.totalCommits +=
user.contributionsCollection.restrictedContributionsCount;
}
stats.totalPRs = user.pullRequests.totalCount;
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
stats.contributedTo = user.repositoriesContributedTo.totalCount;
// Retrieve stars while filtering out repositories to be hidden
// Retrieve stars while filtering out repositories to be hidden.
let repoToHide = new Set(exclude_repo);
stats.totalStars = user.repositories.nodes
.filter((data) => {
return !repoToHide[data.name];
return !repoToHide.has(data.name);
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
stats.rank = calculateRank({
totalCommits: stats.totalCommits,
totalRepos: user.repositories.totalCount,
followers: user.followers.totalCount,
contributions: stats.contributedTo,
stargazers: stats.totalStars,
all_commits: include_all_commits,
commits: stats.totalCommits,
prs: stats.totalPRs,
issues: stats.totalIssues,
repos: user.repositories.totalCount,
stars: stats.totalStars,
followers: user.followers.totalCount,
});
return stats;
+7 -38
View File
@@ -12,17 +12,18 @@ const stats = {
totalCommits: 200,
totalIssues: 300,
totalPRs: 400,
contributedTo: 500,
contributedTo: 50,
rank: null,
};
stats.rank = calculateRank({
totalCommits: stats.totalCommits,
totalRepos: 1,
followers: 0,
contributions: stats.contributedTo,
stargazers: stats.totalStars,
all_commits: false,
commits: stats.totalCommits,
prs: stats.totalPRs,
issues: stats.totalIssues,
repos: 1,
stars: stats.totalStars,
followers: 0,
});
const data_stats = {
@@ -229,38 +230,6 @@ describe("Test /api/", () => {
}
});
it("should add private contributions", async () => {
const { req, res } = faker(
{
username: "anuraghazra",
count_private: true,
},
data_stats,
);
await api(req, res);
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
expect(res.send).toBeCalledWith(
renderStatsCard(
{
...stats,
totalCommits: stats.totalCommits + 100,
rank: calculateRank({
totalCommits: stats.totalCommits + 100,
totalRepos: 1,
followers: 0,
contributions: stats.contributedTo,
stargazers: stats.totalStars,
prs: stats.totalPRs,
issues: stats.totalIssues,
}),
},
{},
),
);
});
it("should allow changing ring_color", async () => {
const { req, res } = faker(
{
+79 -9
View File
@@ -2,17 +2,87 @@ import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js";
describe("Test calculateRank", () => {
it("should calculate rank correctly", () => {
it("new user gets B rank", () => {
expect(
calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 400,
prs: 300,
issues: 200,
all_commits: false,
commits: 0,
prs: 0,
issues: 0,
repos: 0,
stars: 0,
followers: 0,
}),
).toStrictEqual({ level: "A+", score: 49.25629684876535 });
).toStrictEqual({ level: "B", score: 100 });
});
it("average user gets A rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 250,
prs: 50,
issues: 25,
repos: 0,
stars: 250,
followers: 25,
}),
).toStrictEqual({ level: "A", score: 50 });
});
it("average user gets A rank (include_all_commits)", () => {
expect(
calculateRank({
all_commits: true,
commits: 1000,
prs: 50,
issues: 25,
repos: 0,
stars: 250,
followers: 25,
}),
).toStrictEqual({ level: "A", score: 50 });
});
it("more than average user gets A+ rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 500,
prs: 100,
issues: 50,
repos: 0,
stars: 500,
followers: 50,
}),
).toStrictEqual({ level: "A+", score: 25 });
});
it("expert user gets S rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 1000,
prs: 200,
issues: 100,
repos: 0,
stars: 1000,
followers: 100,
}),
).toStrictEqual({ level: "S", score: 6.25 });
});
it("ezyang gets S+ rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 1000,
prs: 4000,
issues: 2000,
repos: 0,
stars: 5000,
followers: 2000,
}),
).toStrictEqual({ level: "S+", score: 1.1363983154296875 });
});
});
+39 -62
View File
@@ -102,13 +102,13 @@ describe("Test fetchStats", () => {
it("should fetch correct stats", async () => {
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
all_commits: false,
commits: 100,
prs: 300,
issues: 200,
repos: 5,
stars: 300,
followers: 100,
});
expect(stats).toStrictEqual({
@@ -132,13 +132,13 @@ describe("Test fetchStats", () => {
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
all_commits: false,
commits: 100,
prs: 300,
issues: 200,
repos: 5,
stars: 300,
followers: 100,
});
expect(stats).toStrictEqual({
@@ -161,49 +161,26 @@ describe("Test fetchStats", () => {
);
});
it("should fetch and add private contributions", async () => {
let stats = await fetchStats("anuraghazra", true);
const rank = calculateRank({
totalCommits: 150,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 150,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
it("should fetch total commits", async () => {
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
let stats = await fetchStats("anuraghazra", true, true);
let stats = await fetchStats("anuraghazra", false, true);
const rank = calculateRank({
totalCommits: 1050,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
all_commits: true,
commits: 1000,
prs: 300,
issues: 200,
repos: 5,
stars: 300,
followers: 100,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 1050,
totalCommits: 1000,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
@@ -216,21 +193,21 @@ describe("Test fetchStats", () => {
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]);
let stats = await fetchStats("anuraghazra", false, true, ["test-repo-1"]);
const rank = calculateRank({
totalCommits: 1050,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 200,
all_commits: true,
commits: 1000,
prs: 300,
issues: 200,
repos: 5,
stars: 200,
followers: 100,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 1050,
totalCommits: 1000,
totalIssues: 200,
totalPRs: 300,
totalStars: 200,
@@ -243,13 +220,13 @@ describe("Test fetchStats", () => {
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 400,
all_commits: false,
commits: 100,
prs: 300,
issues: 200,
repos: 5,
stars: 400,
followers: 100,
});
expect(stats).toStrictEqual({
@@ -268,13 +245,13 @@ describe("Test fetchStats", () => {
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
all_commits: false,
commits: 100,
prs: 300,
issues: 200,
repos: 5,
stars: 300,
followers: 100,
});
expect(stats).toStrictEqual({
@@ -293,13 +270,13 @@ describe("Test fetchStats", () => {
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
all_commits: false,
commits: 100,
prs: 300,
issues: 200,
repos: 5,
stars: 300,
followers: 100,
});
expect(stats).toStrictEqual({
-140
View File
@@ -6,11 +6,9 @@ import {
radiansToDegrees,
polarToCartesian,
cartesianToPolar,
getCircleLength,
calculateCompactLayoutHeight,
calculateNormalLayoutHeight,
calculateDonutLayoutHeight,
calculateDonutVerticalLayoutHeight,
calculatePieLayoutHeight,
donutCenterTranslation,
trimTopLanguages,
@@ -72,20 +70,6 @@ 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.
*
@@ -246,20 +230,6 @@ 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);
@@ -288,18 +258,6 @@ 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: [],
@@ -611,104 +569,6 @@ 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" });