Making charts with VueJS and no tooling

Static files all the way

Published February 19, 2020 #howto #vuejs #buildless #onefilecoding

I learned most of what I know about coding by looking at source code – especially view source on a web browser. Lets see if we can bring that era back a bit by using ES modules and eschewing webpack and other bundling systems. We will use the amazing unpkg.com CDN to get our building blocks to assemble together.

First get vue working

From unpkg we will link to tailwindcss in the head tag, and then import vuejs from using a script type="module" tag.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
    <title>Hello, World</title>
    <link href="https://unpkg.com/tailwindcss@^1.2.0/dist/tailwind.min.css" rel="stylesheet">
  </head>
  <body>
    <h1 id="title" class="text-xl">{{ message }}</h1>

    <script type="module">
      import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js'

      var app = new Vue({
        el: '#title',
        data: {
          message: 'Hello from Vue!'
        }
      })
    </script>
  </body>
</html>

Using import we are pulling the vue.esm.browser.js build straight from the CDN! So easy.

Add some charts

chart.xkcd is a nifty JavaScript library that lets you make charts like the stupendious xkcd. We’ll use the chart.xkcd-vue wrapper.

pika.dev is an index of packages that you can import as ES modules. That’s a great place to search for packages that can be imported directly. So lets import chartXkcd and wire it up with some sample data.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
    <title>Hello, World</title>
    <link href="https://unpkg.com/tailwindcss@^1.2.0/dist/tailwind.min.css" rel="stylesheet">
  </head>
  <body>
    <div id="app" class="min-h-screen">
      <chartxkcd-line :config="config"></chartxkcd-line>
    </div>

    <script type="module">
      import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js';
      import chartXkcdVue from 'https://cdn.pika.dev/chart.xkcd-vue@^1.1.0';
     
      Vue.use(chartXkcdVue);
      
      var vm = new Vue({
        el: "#app",
        data: {
          config: {
            title: 'Monthly income of an indie developer',
            xLabel: 'Month',
            yLabel: '$ Dollors',
            data: {
              labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
              datasets: [
                {
                  label: 'Plan',
                  data: [30, 70, 200, 300, 500, 800, 1500, 2900, 5000, 8000]
                },
                {
                  label: 'Reality',
                  data: [0, 1, 30, 70, 80, 100, 50, 80, 40, 150]
                }
              ]
           }
        }
      }
      });
    </script>
  </body>
</html>

Which should generate the following image:

Pulling in data from a file

We can shift the code to pulling out the data from a seperate json file. I’m using a trick here to wrap everything in an anonymous function so I can use async and await to load stuff from the network. (I’m not a JavaScript developer so there could be a more awesome way to do this.

(async () => {
  const result = await fetch( './dataset.json' )
  const data = await result.json()
  
  ... old code using the data loaded from a file ...
  
})()

Consuming files that are easier to make with shell scripts

Lets write a couple bash commands to look at the number of commits in a repo organized by month.

  1. Create a file with a header.
  2. Get the dates of all of the commits in the log using git log and the --pretty=format command since a year ago.
  3. Get rid of all the time after the month using sed. (The three characters before the T is the - and the two digit day.)
  4. Run it through uniq to get the count
  5. Format the output with awk, print a header using BEGIN and switch the order of the data and put a comma between it.

Is this the right way to make a CSV file? No. But it’s easy to copy and paste.

git log --reverse --pretty='format:%aI' --since="1 year ago"|\
sed 's/...T.*//'|uniq -c|\
awk 'BEGIN {print "Month,Commits"} // {print $2 "," $1}' > commits_by_month.csv

That gives us a csv file like this:

Month,Commits
2019-03,8
2019-04,43
2019-05,3
2019-07,15
2019-08,2
2019-09,3
2019-12,17
2020-01,11
2020-02,18

And now we can hack together some JavaScript to parse this file and create a data driven dataSet that we’ll pass off to the charting library.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
    <title>Hello, World</title>
    <link href="https://unpkg.com/tailwindcss@^1.2.0/dist/tailwind.min.css" rel="stylesheet">
  </head>
  <body>
    <div id="app" class="min-h-screen">
      <chartxkcd-line :config="config"></chartxkcd-line>
    </div>

    <script type="module">
      import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js';
      import chartXkcdVue from 'https://cdn.pika.dev/chart.xkcd-vue@^1.1.0';
     
      Vue.use(chartXkcdVue);

      (async () => {
      const result = await fetch( './commits_by_month.csv' );
      const data = await result.text();

      // "Parse" the csv
      const lines = data.split( "\n" );
      const header = lines.shift();
      const [xLabel,yLabel] = header.split( "," );

      // Map out the structure of the graph data
      const dataSet = { labels: [], datasets: [ { label: yLabel, data: [] } ] };
      lines.forEach( (line) => {
        if( line != "" ) { // Skip over the last line
          const [x,y] = line.split( "," );
          dataSet.labels.push(x);
          dataSet.datasets[0].data.push(parseInt(y));
        }
      } );

      var vm = new Vue({
        el: "#app",
        data: {
          config: {
            title: 'Monthly commits',
            xLabel: xLabel,
            yLabel: yLabel,
            data: dataSet
           }
        }
      });
      })()
    </script>
  </body>
</html>

Parsing a csv file using split and not bothering to do any error checking for parseInt is serious cowboy style coding, but it is something that we can hack together quickly. Just expect to throw it out after you get it working.

Next steps

With this basic framework in place, you create simple visualizors of data in a self container file. Write some scripts that output JSON, and then move this HTML file around to parse and display them.

Everything that you need is in one file, there’s no build process, you can copy and tweak it around. This is code that is meant to be deployed in on off situations, and when it break you throw it away and write it again.

Read next

See also

Leveraging disposability for exploration

how to play around without leaving a mess

I play around with a lot of technology to see what’s out there and keep myself current, especially trying to find simpler ways to do our work. One of the issues is that this can leave lots of stuff laying which can be messy. Here are some techniques I use to keep things under control. There are two complementary principle’s that I focus on. One is reproducibility, and the other is disposibility.

Read more

Playing with tailwind

An excersize in minimilism

We are going to use tailwindcss to build a site. We will start with a blank folder and a static HTML file to work on the designs, and then slowly bring in other tools when needed to add in functionality. The site that we are going to build is a company directory that pulls in data from Google Apps Suite. The goal here is to build the simplest thing possible and not get lost in the tooling.

Read more

Styling Hugo Diffs

Showing just what you changed

I often want to show small changes I’m making to a file and it would be nice for hugo to support styling patches directly. Lets see what we can do to make this process easier. Lets take the example of create node package.json file and add the following scripts worflow. How can we say this different than “copy this into your package.json file”? Create sample steps Lets first create the file using npm init -y and then immediately cp package.

Read more