Unverified Commit b8330a88 authored by Sagar's avatar Sagar Committed by GitHub
Browse files

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

* compact layout for top langs card

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

* 🐞 FIX: conditionally rendering layout compact

* 🐞

 FIX: border radius on lang progressbar

* refactor: refactored code & fixed bugs

* fix: toFixed

* chore: change string interpolation

* docs: updated readme

Co-authored-by: default avataranuraghazra <hazru.anurag@gmail.com>
parent 3bdcf3d6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ module.exports = async (req, res) => {
    bg_color,
    theme,
    cache_seconds,
    layout
  } = req.query;
  let topLangs;

@@ -49,6 +50,7 @@ module.exports = async (req, res) => {
      text_color,
      bg_color,
      theme,
      layout
    })
  );
};
+13 −0
Original line number Diff line number Diff line
@@ -125,6 +125,7 @@ Customization Options:
| theme         | string    | sets inbuilt theme                          | 'default'            | 'default_repocard'  | 'default                |
| cache_seconds | number    | manually set custom cache control           | 1800                 | 1800                | '1800'                  |
| count_private | boolean   | counts private contributions too if enabled | false                | N/A                 | N/A                     |
| layout        | string    | choose a layout option                      | N/A                  | N/A                 | "default"               |

> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours

@@ -176,10 +177,22 @@ You can use `?hide=language1,language2` parameter to hide individual languages.
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide=javascript,html)](https://github.com/anuraghazra/github-readme-stats)
```

### Compact Language Card Layout

You can use the `&layout=compact` option to change the card design.

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

### Demo

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

- Compact layout

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

---

### All Demos
+109 −19
Original line number Diff line number Diff line
@@ -23,6 +23,41 @@ const createProgressNode = ({ width, color, name, progress }) => {
  `;
};

const createCompactLangNode = ({ lang, totalSize, x, y }) => {
  const percentage = ((lang.size / totalSize) * 100).toFixed(2);
  const color = lang.color || "#858585";

  return `
    <g transform="translate(${x}, ${y})">
      <circle cx="5" cy="6" r="5" fill="${color}" />
      <text data-testid="lang-name" x="15" y="10" class='lang-name'>
        ${lang.name} ${percentage}%
      </text>
    </g>
  `;
};

const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
  return langs.map((lang, index) => {
    if (index % 2 === 0) {
      return createCompactLangNode({
        lang,
        x,
        y: 12.5 * index + y,
        totalSize,
        index,
      });
    }
    return createCompactLangNode({
      lang,
      x: 150,
      y: 12.5 + 12.5 * index,
      totalSize,
      index,
    });
  });
};

const lowercaseTrim = (name) => name.toLowerCase().trim();

const renderTopLanguages = (topLangs, options = {}) => {
@@ -34,6 +69,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
    bg_color,
    hide,
    theme,
    layout,
  } = options;

  let langs = Object.values(topLangs);
@@ -54,7 +90,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
      return !langsToHide[lowercaseTrim(lang.name)];
    });

  const totalSize = langs.reduce((acc, curr) => {
  const totalLanguageSize = langs.reduce((acc, curr) => {
    return acc + curr.size;
  }, 0);

@@ -66,12 +102,78 @@ const renderTopLanguages = (topLangs, options = {}) => {
    theme,
  });

  const width = isNaN(card_width) ? 300 : card_width;
  let width = isNaN(card_width) ? 300 : card_width;
  let height = 45 + (langs.length + 1) * 40;

  let finalLayout = "";

  // RENDER COMPACT LAYOUT
  if (layout === "compact") {
    width = width + 50;
    height = 30 + (langs.length / 2 + 1) * 40;

    // progressOffset holds the previous language's width and used to offset the next language
    // so that we can stack them one after another, like this: [--][----][---]
    let progressOffset = 0;
    const compactProgressBar = langs
      .map((lang) => {
        const percentage = (
          (lang.size / totalLanguageSize) *
          (width - 50)
        ).toFixed(2);

        const progress =
          percentage < 10 ? parseFloat(percentage) + 10 : percentage;

        const output = `
          <rect
            mask="url(#rect-mask)" 
            data-testid="lang-progress"
            x="${progressOffset}" 
            y="0"
            width="${progress}" 
            height="8"
            fill="${lang.color || "#858585"}"
          />
        `;
        progressOffset += parseFloat(percentage);
        return output;
      })
      .join("");

    finalLayout = `
      <mask id="rect-mask">
        <rect x="0" y="0" width="${
          width - 50
        }" height="8" fill="white" rx="5" />
      </mask>
      ${compactProgressBar}
      ${createLanguageTextNode({
        x: 0,
        y: 25,
        langs,
        totalSize: totalLanguageSize,
      }).join("")}
    `;
  } else {
    finalLayout = FlexLayout({
      items: langs.map((lang) => {
        return createProgressNode({
          width: width,
          name: lang.name,
          color: lang.color || "#858585",
          progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
        });
      }),
      gap: 40,
      direction: "column",
    }).join("");
  }

  if (hide_title) {
    height -= 30;
  }

  return `
    <svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
      <style>
@@ -80,7 +182,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
      </style>
      <rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>

      
      ${
        hide_title
          ? ""
@@ -88,18 +189,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
      }

      <svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
      ${FlexLayout({
        items: langs.map((lang) => {
          return createProgressNode({
            width: width,
            name: lang.name,
            color: lang.color || "#858585",
            progress: ((lang.size / totalSize) * 100).toFixed(2),
          });
        }),
        gap: 40,
        direction: "column",
      }).join("")}
        ${finalLayout}
      </svg>
    </svg>
  `;
+15 −0
Original line number Diff line number Diff line
@@ -207,4 +207,19 @@ describe("Test renderTopLanguages", () => {
      );
    });
  });

  it('should render with layout compact', () => {
    document.body.innerHTML = renderTopLanguages(langs, {layout: 'compact'});

    expect(queryByTestId(document.body, "header")).toHaveTextContent("Top Languages");

    expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent("HTML 40.00%");
    expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute("width","120.00");

    expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent("javascript 40.00%");
    expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute("width","120.00");

    expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent("css 20.00%");
    expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute("width","60.00");
  })
});