Repository: anuraghazra/github-readme-stats
Pull request #2701 (2023-05-07 07:18:02)

Top languages card donut vertical layout
![image](https://user-images.githubusercontent.com/53787217/236663388-5a29e6cf-86e9-4e78-8f42-cfc76a5eb2fa.png)

Preview deployed there: https://github-readme-stats-1svgrsiyn-qwerty541.vercel.app

Option: `https://github-readme-stats-1svgrsiyn-qwerty541.vercel.app/api/top-langs?username=qwerty541&layout=donut-vertical`

TODOS:

- [x] rebase master
- [x] refactoring
- [x] docs
- [x] tests
- [x] animations

vercel[bot]: @qwerty541 is attempting to deploy a commit to the **github readme stats** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=github%20readme%20stats&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%2218444a800ae0219ce4834ea08b8cf71786caee1c%22%7D%2C%22id%22%3A%22QmWKjq2PHcRy2rEM18Ts2AvFvQ2QWqEjfLQP6nhunGpAXN%22%2C%22org%22%3A%22anuraghazra%22%2C%22prId%22%3A2701%2C%22repo%22%3A%22github-readme-stats%22%7D).


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/2701?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) Report
Patch coverage: **`32.87`**% and project coverage change: **`-1.08`** :warning:
> Comparison is base [(`688f4e4`)](https://app.codecov.io/gh/anuraghazra/github-readme-stats/commit/688f4e497593d22d813423248bd96aaf0f0e495f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) 97.35% compared to head [(`18444a8`)](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/2701?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) 96.27%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2701      +/-   ##
==========================================
- Coverage   97.35%   96.27%   -1.08%     
==========================================
  Files          24       24              
  Lines        4310     4381      +71     
  Branches      393      395       +2     
==========================================
+ Hits         4196     4218      +22     
- Misses        112      161      +49     
  Partials        2        2              
```


| [Impacted Files](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/2701?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) | Coverage Δ | |
|---|---|---|
| [src/cards/top-languages-card.js](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/2701?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra#diff-c3JjL2NhcmRzL3RvcC1sYW5ndWFnZXMtY2FyZC5qcw==) | `89.52% <32.87%> (-10.48%)` | :arrow_down: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/2701?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).

rickstaa: Hey @qwerty541. I think it is wonderful that you have contributed so much to the project in the last days 🚀! I asked @anuraghazra to add you as a collaborator. You are welcome to accept or decline 👍🏻.

Sadly, the donut layout has already been implemented in https://github.com/anuraghazra/github-readme-stats/pull/2099 😅. I am planning to merge it this week when the @arndom implements the changes I requested. You can find the other top bugs, feature requests and pull requests in https://github.com/anuraghazra/github-readme-stats/issues/1935.
qwerty541: > Hey @qwerty541. I think it is wonderful that you have contributed so much to the project in the last days 🚀! I asked @anuraghazra to add you as a collaborator. You are welcome to accept or decline 👍🏻.
> 
> Sadly, the donut layout has already been implemented in #2099 😅. I am planning to merge it this week when the @arndom implements the changes I requested. You can find the other top bugs, feature requests and pull requests in #1935.

Hey @rickstaa. Thank you for the collaboration invitation, i am going to accept it when it will be sent.

I feel like a fool for not checking for open pull requests on the issue before starting to work. I'll try to be more careful in the future.

I want to draw your attention to what my pull request offers a DIFFERENT design of top languages card with donut than #2099. In my pull request, the objects are arranged vertically, the amount of empty space is minimized, the card turned out to be quite compact. In another pull request, the objects are arranged horizontally, the card turned out to be quite cumbersome, and it has an obvious disadvantage, which is that with a large number of different languages there will be a lot of empty space above and below the donut. For this reason, some users may prefer to use my design. What do you think about supporting both layouts like `donut-vertical` and `donut-horizontal`?
rickstaa: > > Hey @qwerty541. I think it is wonderful that you have contributed so much to the project in the last days rocket! I asked @anuraghazra to add you as a collaborator. You are welcome to accept or decline 👍🏻.
> > Sadly, the donut layout has already been implemented in #2099 sweat_smile. I am planning to merge it this week when the @arndom implements the changes I requested. You can find the other top bugs, feature requests and pull requests in #1935.
> 
> Hey @rickstaa. Thank you for the collaboration invitation, i am going to accept it when it will be sent.
> 
> I feel like a fool for not checking for open pull requests on the issue before starting to work. I'll try to be more careful in the future.
> 
> I want to draw your attention to what my pull request offers a DIFFERENT design of top languages card with donut than #2099. In my pull request, the objects are arranged vertically, the amount of empty space is minimized, the card turned out to be quite compact. In another pull request, the objects are arranged horizontally, the card turned out to be quite cumbersome, and it has an obvious disadvantage, which is that with a large number of different languages there will be a lot of empty space above and below the donut. For this reason, some users may prefer to use my design. What do you think about supporting both layouts like `donut-vertical` and `donut-horizontal`?

> Hey @rickstaa. Thank you for the collaboration invitation, i am going to accept it when it will be sent.

Amazing! It would be an honour to have you on the team 🚀.

> I feel like a fool for not checking for open pull requests on the issue before starting to work. I'll try to be more careful in the future.

No problem, it is easy to get lost in 180 open pull requests 😅. That is why merging https://github.com/anuraghazra/github-readme-stats/pull/2489 is a high priority since that will remove 36 translation repositories.

> I want to draw your attention to what my pull request offers a DIFFERENT design of top languages card with donut than #2099. In my pull request, the objects are arranged vertically, the amount of empty space is minimized, the card turned out to be quite compact. In another pull request, the objects are arranged horizontally, the card turned out to be quite cumbersome, and it has an obvious disadvantage, which is that with a large number of different languages there will be a lot of empty space above and below the donut. For this reason, some users may prefer to use my design. What do you think about supporting both layouts like `donut-vertical` and `donut-horizontal`?

I think having two donut layouts is great if duplicate code is kept to the minimum. Let's keep this PR open so that you can rebase it on top of the `master` branch when https://github.com/anuraghazra/github-readme-stats/pull/2099 is merged.

Patch:
diff --git a/docs/readme_cn.md b/docs/readme_cn.md
index d6487b6..3bd2c14 100644
--- a/docs/readme_cn.md
+++ b/docs/readme_cn.md
@@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
 
 - `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
 - `hide_title` - _(boolean)_
-- `layout` - 在两个可用布局 `default` & `compact` 间切换
+- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换
 - `card_width` - 手动设置卡片的宽度 _(number)_
 
 > :warning: **重要:**
diff --git a/docs/readme_de.md b/docs/readme_de.md
index 1ad0d26..a94e8c2 100644
--- a/docs/readme_de.md
+++ b/docs/readme_de.md
@@ -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` - Wechsel zwischen den zwei verfügbaren Layouts `default` & `compact`
+- `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:**
diff --git a/docs/readme_es.md b/docs/readme_es.md
index 5672027..a353502 100644
--- a/docs/readme_es.md
+++ b/docs/readme_es.md
@@ -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` - Cambia entre los dos diseños disponibles `default` & `compact`
+- `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)_
diff --git a/docs/readme_fr.md b/docs/readme_fr.md
index ce0d3d4..427f297 100644
--- a/docs/readme_fr.md
+++ b/docs/readme_fr.md
@@ -171,7 +171,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 2 mise en page `default` & `compact`
+-   `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie`
 -   `card_width` - Fixer la largeur de la carte manuellement _(number)_
 
 > :warning: **Important:**
diff --git a/docs/readme_it.md b/docs/readme_it.md
index e54af7d..4418c33 100644
--- a/docs/readme_it.md
+++ b/docs/readme_it.md
@@ -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` - Specifica il tipo di layout, `default` (esteso) o `compact` (compatto)
+- `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:**
diff --git a/docs/readme_ja.md b/docs/readme_ja.md
index 2c2def7..e6629ae 100644
--- a/docs/readme_ja.md
+++ b/docs/readme_ja.md
@@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ
 
 - `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
 - `hide_title` - _(boolean)_
-- `layout` - `default` か `compact` のいずれかのレイアウトに切り替える
+- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える
 - `card_width` - カードの横幅 _(number)_
 - `langs_count` - 表示される言語の数　_(1 ~ 10, 初期値 5)_
 - `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
diff --git a/docs/readme_kr.md b/docs/readme_kr.md
index 0d7bced..28207af 100644
--- a/docs/readme_kr.md
+++ b/docs/readme_kr.md
@@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
 
 - `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
 - `hide_title` - 타이틀 제외 _(boolean)_
-- `layout` - 사용 가능한 두 가지 값, `default` & `compact` 중 표시 형태 선택
+- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택
 - `card_width` - 카드 너비 직접 설정 _(number)_
 - `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
 - `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
diff --git a/docs/readme_nl.md b/docs/readme_nl.md
index f70828f..04ba9cf 100644
--- a/docs/readme_nl.md
+++ b/docs/readme_nl.md
@@ -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 voor de twee beschikbare layouts `default` & `compact`
+- `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)_
diff --git a/docs/readme_np.md b/docs/readme_np.md
index b322545..088039d 100644
--- a/docs/readme_np.md
+++ b/docs/readme_np.md
@@ -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 two available layouts `default` & `compact`
+- `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)_
diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md
index cf6eeed..bcb58f2 100644
--- a/docs/readme_pt-BR.md
+++ b/docs/readme_pt-BR.md
@@ -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 dois layouts disponíveis `default` & `compact`
+- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie`
 - `card_width` - Define a largura do cartão manualmente _(number)_
 
 > :warning: **Importante:**
diff --git a/docs/readme_tr.md b/docs/readme_tr.md
index fe5aff7..d08a00c 100644
--- a/docs/readme_tr.md
+++ b/docs/readme_tr.md
@@ -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` - Uygun olan iki tasarım / layout arasında değişiklik yapar `default` & `compact`
+- `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)_
diff --git a/readme.md b/readme.md
index f59342f..0de7caa 100644
--- a/readme.md
+++ b/readme.md
@@ -303,7 +303,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 four 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)`.
@@ -431,6 +431,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.
@@ -459,6 +467,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)
diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js
index e03e8bc..c6cedb1 100644
--- a/src/cards/top-languages-card.js
+++ b/src/cards/top-languages-card.js
@@ -84,6 +84,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.
  *
@@ -114,6 +124,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 +391,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.
  *
@@ -613,6 +703,9 @@ const renderTopLanguages = (topLangs, options = {}) => {
   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);
@@ -688,7 +781,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
     `,
   );
 
-  if (layout === "pie") {
+  if (layout === "pie" || layout === "donut-vertical") {
     return card.render(finalLayout);
   }
 
@@ -705,9 +798,11 @@ export {
   radiansToDegrees,
   polarToCartesian,
   cartesianToPolar,
+  getCircleLength,
   calculateCompactLayoutHeight,
   calculateNormalLayoutHeight,
   calculateDonutLayoutHeight,
+  calculateDonutVerticalLayoutHeight,
   calculatePieLayoutHeight,
   donutCenterTranslation,
   trimTopLanguages,
diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts
index 7945118..d6a1de0 100644
--- a/src/cards/types.d.ts
+++ b/src/cards/types.d.ts
@@ -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;


Test patch:
diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js
index a8bc873..ed3bd3d 100644
--- a/tests/renderTopLanguages.test.js
+++ b/tests/renderTopLanguages.test.js
@@ -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" });