Building a Replacement for Google Image Charts
Tags: webdev, visualization, api
Published: 2019-03-15 21:18:13

---
title: Building a Replacement for Google Image Charts
published: true
description: An open-source web API that generates rich charts from a single URL
tags: webdev, visualization, api
---

A couple weeks ago I learned that [Google Image Charts](https://developers.google.com/chart/image/) is shutting down, leaving a lot of people in the lurch.  Although the API has been deprecated for many years, many developers had not made the switch.

Why do *Image Charts* matter?  There are plenty of graphing libraries like Chart.js, plotly, d3, and so on.  The difference is that these libraries require Javascript, but Image Charts are necessary for embeddable, non-Javascript environments such as email, SMS, and certain types of reports.

This post describes the process of creating an open source image chart offering called QuickChart.  

**If you don't want to read the whole thing, skip to the final result: the [image chart web service](https://quickchart.io/) (called QuickChart) and [source code](https://github.com/typpo/quickchart).**

### Step 1: What's the best way to define a chart?

The old Google Image Charts API was arcane, built for a time when browsers couldn't support long URLs.  

This meant that you had to sacrifice readability and also learn a custom format.  For example, the following URL:
```
https://chart.googleapis.com/chart?cht=bvg&chs=250x150&chd=t:5,30,30,50,80,200&chxt=x,y&chxs=0,ff0000,12,0,lt|1,0000ff,10,1,lt
```

Produces this graph:
![Google Image Charts graph example](https://i.imgur.com/Tz1Mstw.png)

It gets more complicated from here.  The API supported a "simple encoding format" that allowed users to encode integer values between 0 and 61 as single characters.  Characters A-Z would encode integers 0 through 25, a-z encodes 26 through 51, and the digits 0-9 encode the remaining values through 61.  You'd wind up with URLs like `?chd=s:BTb19_,Mn5tzb` that encode a handful of two-digit numbers.

There is an "extended encoding format" that include punctuation.  For example, `-.` is equivalent to the number 4,031.  Easy, right?

I decided this was bad.  Imagine coming across this URL in code and trying to figure out what it meant.  Imagine writing your own number encoding script, only to find that your data contains a number larger than 61 and having to rewrite your encoder.

The Image Charts API was built in the early 2000s.  Nowadays browsers can do much better.  For this reason I decided to use [Chart.js](https://www.chartjs.org/) as inspiration.  Chart.js is very popular and has a clear API for defining charts:

```
{
  type: 'bar',
  data: {
    labels: [0,1,2,3,4,5],
    datasets: [{
      data: [5,30,30,50,80,200]
    }]
  }
}
```

### Step 2: Putting it together

We're using Chart.js, but that library produces charts dynamically via Javascript instead of images.  That's a showstopper because most users will want to embed static images in email.

The good news is that Chart.js uses HTML5 Canvas, and canvas can be converted into an image.  

The invention of Node made it much easier to run Javascript on the server side.  A server side canvas implementation called [node-canvas](https://github.com/Automattic/node-canvas) followed.  I evaluated a couple server-side Chart.js wrapper that used node-canvas to render the image and settled on [chartjs-node-canvas](https://github.com/SeanSobey/ChartjsNodeCanvas) with some modifications to make it more memory efficient.

This meant that I could take a Chart.js config like the one above and render it on the server side.

### Step 3: Building the web service

The web service is fairly straightforward.  It's just an `express` app that hooks into my patched `chartjs-node-canvas` library.

Upon implementation, I realized there were some default Chart.js options that I would like to apply.  For example, line and bar chart axes should start at 0 by default.  I also added a nice color palette and a Chart.js plugin that added the option for data labels.

**The result is a single endpoint, `https://quickchart.io/chart?c=`, that takes a Chart.js configuration and renders it as a PNG image.**

Let's take our simple chart JSON above and send it into the endpoint:

[https://quickchart.io/chart?c={type:'bar',data:{labels:[0,1,2,3,4,5],datasets:[{data:[5,30,30,50,80,200]}]}}](https://quickchart.io/chart?c={type:'bar',data:{labels:[0,1,2,3,4,5],datasets:[{data:[5,30,30,50,80,200]}]}})

This URL loads the following image:

![QuickChart image chart example](https://i.imgur.com/PczlN2E.png)

Perfect!

### Step 4: Getting more creative

It's relatively straightforward to build customized charts based on the Chart.js documentation:

![Customized image chart with QuickChart](https://i.imgur.com/6lPTkra.png)

At this point we're limited only by the Chart.js API, which is very flexible. 
 This chart uses custom background colors, title styling, legend position, stacked series, axes labels, and data labels:

```
{
  type: 'bar',
  data: {
    labels: ['January', 'February', 'March', 'April', 'May'],
    datasets: [{
      label: 'Dogs',
      backgroundColor: 'chartreuse',
      data: [ 50, 60, 70, 180, 190 ]
    }, {
      label: 'Cats',
      backgroundColor: 'gold',
      data: [ 100, 200, 300, 400, 500 ]
    }]
  },
  options: {
    title: {
      display: true,
      text: 'Total Revenue (billions)',
      fontColor: 'hotpink',
      fontSize: 24,
    },
    legend: {
      position: 'bottom',
    },
    scales: {
      xAxes: [{stacked: true}],
      yAxes: [{stacked: true}],
    },
    plugins: {
      datalabels: {
        display: true,
        font: {
          style: 'bold',
        },
      },
    },
  },
}
```

That's quite a bit more JSON than our simple example, but the specification is [well-documented](https://www.chartjs.org/docs/latest/configuration/) and uses a widespread standard.  Note that for more complex configurations, we need to URL-encode the JSON object before passing it to the API (e.g. by using Javascript's `encodeURIComponent`).

We can also use any of the chart types provided by Chart.js.  For example:

[https://quickchart.io/chart?c={type:'pie',data:{labels:['January','February','March','April', 'May'], datasets:[{data:[50,60,70,180,190]}]}}](https://quickchart.io/chart?c={type:'pie',data:{labels:['January','February','March','April', 'May'], datasets:[{data:[50,60,70,180,190]}]}})

The above URL yields:

![Pie image chart](https://quickchart.io/chart?bkg=white&c={type:%27pie%27,data:{labels:[%27January%27,%27February%27,%27March%27,%27April%27,%20%27May%27],datasets:[{data:[50,60,70,180,190]}]}})


### Step 5: Making it open source

I named this web service QuickChart and put it online at [QuickChart.io](https://quickchart.io/).  It's very cheap to run so I've made the image chart generation service available for free with no limitations.

Separately, I've open-sourced the project over on [Github](https://github.com/typpo/quickchart).  I would welcome involvement and contributions from the rest of the community!

Here are some thoughts on where we can take this project:

  - Create a library of useful chart templates
  - Create a "short url" feature that maps complex configurations to a simple URL
  - Support other kinds of charts - perhaps even maps or other more advanced visualizations

This is a strong base for a flexible and reliable image chart generation service. 
 There are lots of possibilities and I'm excited to continue building!