# Coding time visualization

_How to build a live chart using WakaTime_

Author: Georg Ledermann  
Published: 2021-10-17  
Tags: JavaScript, Tailwind CSS, nginx  
Canonical: https://ledermann.dev/blog/2021/10/17/coding-time-visualization/

The language chart on my homepage is not a wish list. It comes straight out of WakaTime and shows what my editor really saw last week.

---

[WakaTime](https://wakatime.com/) is a time tracking app to build metrics about coding. From inside the code editor it sends **metadata** (coding time, language, project, operating system) to a WakaTime account. This way you can see how much time you spend programming.

I would like to demonstrate how to create a simple, but good-looking **live (!)** chart showing the languages I have been using recently. Let me show the result first:

      Languages I have used in the last 12 months

As you can see, at the time of writing I mainly use Ruby, TypeScript and Vue.js. I've added this chart to my [home page](/).

## How does this work?

A WakaTime plugin is installed in my code editor (Visual Studio Code) and tracks the coding time for me. In a private dashboard, I can see the time I spent coding in my projects, the languages I have used and some other metrics.

To share these statistics, WakaTime already offers embedding a chart in your web page. But for me, this is not perfect:

- I don't like the visual design of the diagrams. There are only standard column or pie charts that show far too much detail.
- I prefer not to send the IP address of my site's visitors to wakatime.com

Fortunately, `wakatime.com` also provides a [JSON endpoint](https://wakatime.com/developers#embedding) that allows building my own representation.

Ok, let's go and create a custom chart:

## 1. Protect visitor's IP address by setting up a proxy

Sending requests to `wakatime.com` directly from within the browser would be a privacy issue because the IP address of the visitor will be sent to `wakatime.com`. To fix this, I want to use a proxy installed on my own host.

Thanks to [nginx](https://nginx.org/), which I use to serve my website, setting up a proxy is easy. I add a new endpoint named [ledermann.dev/languages.json](https://ledermann.dev/languages.json), which returns the original JSON response. The result is being cached, so once a day the data is updated.

In my `nginx.conf`, the most important part for doing this is the `proxy_pass` which contains the URL I got from WakaTime. Replace `[username]` and `[uuid]` with your credentials.

```nginx
# ...
proxy_cache_path /var/cache/nginx/wakatime
                 keys_zone=wakatime:1m
                 use_temp_path=off;

server {
  # ...
  location /languages.json {
    proxy_pass https://wakatime.com/share/@[username]/[uuid].json;
    proxy_set_header Host $proxy_host;

    proxy_ignore_headers Cache-Control Set-Cookie;

    proxy_buffering on;
    proxy_cache wakatime;
    proxy_cache_valid 200 1d;
    proxy_cache_use_stale error timeout invalid_header updating
                          http_500 http_502 http_503 http_504;

    add_header Cache-Control "public, max-age=86400";
  }
}
```

## 2. Visual appearance

I want to show a simple stacked bar chart build with CSS only, based on the [idea of Marco Slooten](https://marcoslooten.com/blog/using-css-grid-to-create-a-bar-chart). Ok, because I'm using Tailwind CSS, there will be no CSS at all.

In the HTML code, I put a `<div>` as an anchor point, which will later be filled with content by JavaScript:

```html
<div id="wakatime" class="grid overflow-hidden rounded-full">
  <!-- JavaScript will add child divs here -->
</div>
```

## 3. Fetch and process JSON data

This is the main part. On the client side, the JSON data is fetched and for every language it contains, a child `<div>` is created inside the anchor point.

The original JSON response from WakaTime includes an _Other_ item (which represents unrecognized terminal/directory activity) and items with very low usage. I get more than 30 entries, which is far too much to display. So I remove the _Other_ category entirely and filter out languages below 1% of usage, then normalize the remaining percentages to 100%.

Here is the code:

```javascript
document.addEventListener("DOMContentLoaded", () => {
  const element = document.getElementById("wakatime");

  if (element)
    fetch("/languages.json")
      .then((response) => response.json())
      .then(({ data }) => drawChart(element, fixOther(data)));
});

/* Draw chart for the given data */
function drawChart(element, data) {
  data.forEach((item) => {
    const childElement = document.createElement("div");
    childElement.className = "flex justify-center py-1";
    childElement.style.backgroundColor = item.color;
    childElement.innerText = item.name;

    element.appendChild(childElement);
  });

  // Let the CSS grid do the magic, see the blog post
  // of Marco Slooten for details:
  // https://marcoslooten.com/blog/using-css-grid-to-create-a-bar-chart/
  element.style.gridTemplateColumns = data
    .map((item) => `${item.percent}fr`)
    .join(" ");
}

/* Remove "Other" and languages below 1%, normalize to 100% */
function fixOther(wakatimeData) {
  const filtered = wakatimeData.filter(
    (item) => item.name !== "Other" && item.percent >= 1,
  );

  const total = filtered.reduce((sum, item) => sum + item.percent, 0);
  return filtered.map((item) => ({
    ...item,
    percent: (item.percent / total) * 100,
  }));
}
```

That's it. In my actual implementation, I added tooltips and language logos. But to keep this blog post short, I left that out.
