Categories
Vue and D3

Adding Graphics to a Vue App with D3 — TSV, Timer, and Bar Chart

Spread the love

D3 lets us add graphics to a front-end web app easily.

Vue is a popular front end web framework.

They work great together. In this article, we’ll look at how to add graphics to a Vue app with D3.

tsvFormatRows

We can call the tsvFormatRows method to convert a nested array into a tab-separated string.

For example, we can write:

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
const data = [
  [2011, 10],
  [2012, 20],
  [2013, 30],
];
export default {
  name: "App",
  mounted() {
    const string = d3.tsvFormatRows(data);
    console.log(string);
  },
};
</script>

Then string is:

2011 10
2012 20
2013 30

Timer

We can use timers that come with D3 to add animations to our Vue app.

For example, we can use the d3.timer function to create a timer by writing:

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";

export default {
  name: "App",
  mounted() {
    const timer = d3.timer(function (duration) {
      console.log(duration);
      if (duration > 150) {
        timer.stop();
      }
    }, 100);
  },
};
</script>

We log the duration and stop the timer if the duration is bigger than 150.

duration is in milliseconds.

Create a Bar Chart

We can create a bar chart with D3 in our Vue app by reading in the data, creating the axes, and adding the bars.

For example, we can write:

public/data.csv

year,population
2006,30
2008,35
2010,28
2012,31
2014,43
2016,56
2017,62

App.vue

<template>
  <div id="app">
    <svg width="500" height="500"></svg>
  </div>
</template>

<script>
import * as d3 from "d3";
import Vue from "vue";

export default {
  name: "App",
  mounted() {
    Vue.nextTick(async () => {
      const svg = d3.select("svg"),
        margin = 200,
        width = svg.attr("width") - margin,
        height = svg.attr("height") - margin;

        svg
        .append("text")
        .attr("transform", "translate(100,0)")
        .attr("x", 50)
        .attr("y", 50)
        .attr("font-size", "20px")
        .attr("class", "title")
        .text("Population bar chart");

      const x = d3.scaleBand().range([0, width]).padding(0.4),
        y = d3.scaleLinear().range([height, 0]);
      const g = svg.append("g").attr("transform", "translate(100, 100)");
      const data = await d3.csv("data.csv");

      x.domain(
        data.map(function (d) {
          return d.year;
        })
      );
      y.domain([
        0,
        d3.max(data, function (d) {
          return d.population;
        }),
      ]);

      g.append("g")
        .attr("transform", `translate(0, ${height})`)
        .call(d3.axisBottom(x))
        .append("text")
        .attr("y", height - 250)
        .attr("x", width - 100)
        .attr("text-anchor", "end")
        .attr("font-size", "18px")
        .attr("stroke", "blue")
        .text("year");

      g.append("g")
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "-5.1em")
        .attr("text-anchor", "end")
        .attr("font-size", "18px")
        .attr("stroke", "blue")
        .text("population");

      g.append("g").attr("transform", "translate(0, 0)").call(d3.axisLeft(y));
      g.selectAll(".bar")
        .data(data)
        .enter()
        .append("rect")
        .attr("class", "bar")
        .style("fill", "lightgreen")
        .on("mouseover", onMouseOver)
        .on("mouseout", onMouseOut)
        .attr("x", function (d) {
          return x(d.year);
        })
        .attr("y", function (d) {
          return y(d.population);
        })
        .attr("width", x.bandwidth())
        .transition()
        .ease(d3.easeLinear)
        .duration(200)
        .delay(function (d, i) {
          return i * 25;
        })
        .attr("height", function (d) {
          return height - y(d.population);
        });

      function onMouseOver(d, i) {
        d3.select(this).attr("class", "highlight");

        d3.select(this)
          .transition()
          .duration(200)
          .attr("width", x.bandwidth() + 5)
          .attr("y", function (d) {
            return y(d.population) - 10;
          })
          .attr("height", function (d) {
            return height - y(d.population) + 10;
          });

        g.append("text")
          .attr("class", "val")
          .attr("x", function () {
            return x(d.year);
          })
          .attr("y", function () {
            return y(d.value) - 10;
          });
      }

      function onMouseOut(d, i) {
        d3.select(this).attr("class", "bar");

        d3.select(this)
          .transition()
          .duration(200)
          .attr("width", x.bandwidth())
          .attr("y", function (d) {
            return y(d.population);
          })
          .attr("height", function (d) {
            return height - y(d.population);
          });

        d3.selectAll(".val").remove();
      }
    });
  },
};
</script>

We add the svg element in our template.

Then in the nextTick callback, we create the title by writing:

svg
  .append("text")
  .attr("transform", "translate(100,0)")
  .attr("x", 50)
  .attr("y", 50)
  .attr("font-size", "20px")
  .attr("class", "title")
  .text("Population bar chart");

x and y are the x and y coordinates of the text’s position.

transform transforms the text.

text has the text itself.

font-size sets the font size of the text.

Then we create the x and y ranges we use for the axes:

const x = d3.scaleBand().range([0, width]).padding(0.4),
  y = d3.scaleLinear().range([height, 0]);
const g = svg.append("g").attr("transform", "translate(100, 100)");
const data = await d3.csv("data.csv");

x.domain(
  data.map(function(d) {
    return d.year;
  })
);
y.domain([
  0,
  d3.max(data, function(d) {
    return d.population;
  }),
]);

We set the domain of x and y with the domain methods.

Next, we create the x-axis with the axisBottom method:

g.append("g")
  .attr("transform", `translate(0, ${height})`)
  .call(d3.axisBottom(x))
  .append("text")
  .attr("y", height - 250)
  .attr("x", width - 100)
  .attr("text-anchor", "end")
  .attr("font-size", "18px")
  .attr("stroke", "blue")
  .text("year");

We set the styles with attr calls.

The text call adds the label for the x-axis.

Then we add the label for the y-axis by writing:

g.append("g")
  .append("text")
  .attr("transform", "rotate(-90)")
  .attr("y", 6)
  .attr("dy", "-5.1em")
  .attr("text-anchor", "end")
  .attr("font-size", "18px")
  .attr("stroke", "blue")
  .text("population");

Then we add the y-axis itself by writing:

g.append("g").attr("transform", "translate(0, 0)").call(d3.axisLeft(y));

Then we add the bars by writing:

g.selectAll(".bar")
  .data(data)
  .enter()
  .append("rect")
  .attr("class", "bar")
  .style("fill", "lightgreen")
  .on("mouseover", onMouseOver)
  .on("mouseout", onMouseOut)
  .attr("x", function(d) {
    return x(d.year);
  })
  .attr("y", function(d) {
    return y(d.population);
  })
  .attr("width", x.bandwidth())
  .transition()
  .ease(d3.easeLinear)
  .duration(200)
  .delay(function(d, i) {
    return i * 25;
  })
  .attr("height", function(d) {
    return height - y(d.population);
  });

We add a mouseover event listener that expands the bar when we hover our mouse over it.

Also, we have a mouseout event listener to restore the bar to its original size when we move our mouse away from the bar.

We just set everything into its original size like we did when we load the graph.

We set the x and y attributes so that we can position it on the x-axis.

Also, we add some transition to it when it’s loading with the transition , ease , and duration calls.

We set the height of the bar to by setting the height attribute in the last attr call.

Conclusion

We can convert arrays to TSVs and create a bar chart with D3.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *