DEV Community

Cover image for Building charts in Vue with D3
Jakub Andrzejewski
Jakub Andrzejewski

Posted on

Building charts in Vue with D3

Creating data visualizations in modern web apps often requires both flexibility and reactivity. Vue.js, a progressive JavaScript framework, is excellent for managing reactive UIs, while D3.js is one of the most powerful libraries for data-driven document manipulation and visualization.

In this guide, we’ll explore how to combine Vue and D3 to build dynamic, interactive charts.

Enjoy!

🤔 Why using D3 for Vue?

Vue handles the DOM reactively using a component-based structure, which is great for app state management. D3, on the other hand, directly manipulates the DOM to render SVG elements based on data. While that might seem like a conflict, with proper structure, they can complement each other well:

  • Vue manages application state and layout.
  • D3 handles drawing and updating the visualization.

Check examples of data visualization with D3.

🟢 Building a basic chart component

Let's create a simple bar chart component.

1.Create a new file BarChart.vue:

<template>
  <svg ref="svg"></svg>
</template>

<script setup>
import { onMounted, ref, watch } from 'vue'
import * as d3 from 'd3'

const props = defineProps({
  data: {
    type: Array,
    required: true
  },
  width: {
    type: Number,
    default: 400
  },
  height: {
    type: Number,
    default: 200
  }
})

const svg = ref(null)

const drawChart = () => {
  const svgEl = d3.select(svg.value)
  svgEl.selectAll('*').remove() // Clear previous renders

  svgEl.attr('width', props.width).attr('height', props.height)

  const x = d3
    .scaleBand()
    .domain(props.data.map((d, i) => i))
    .range([0, props.width])
    .padding(0.1)

  const y = d3
    .scaleLinear()
    .domain([0, d3.max(props.data)])
    .nice()
    .range([props.height, 0])

  svgEl
    .selectAll('rect')
    .data(props.data)
    .join('rect')
    .attr('x', (_, i) => x(i))
    .attr('y', d => y(d))
    .attr('width', x.bandwidth())
    .attr('height', d => props.height - y(d))
    .attr('fill', 'steelblue')
}

onMounted(drawChart)
watch(() => props.data, drawChart)
</script>
Enter fullscreen mode Exit fullscreen mode

Let's stop for a second here to explain each part of the drawChart method:

const drawChart = () => {
  const svgEl = d3.select(svg.value)
  svgEl.selectAll('*').remove() // Clear previous renders
Enter fullscreen mode Exit fullscreen mode
  • d3.select(svg.value): Selects the actual <svg> element from the DOM.
  • selectAll('*').remove(): Clears any previously drawn chart elements to avoid overlap when re-rendering.
  svgEl.attr('width', props.width).attr('height', props.height)
Enter fullscreen mode Exit fullscreen mode
  • Sets the SVG canvas dimensions.
  const x = d3
    .scaleBand()
    .domain(props.data.map((d, i) => i))
    .range([0, props.width])
    .padding(0.1)
Enter fullscreen mode Exit fullscreen mode
  • d3.scaleBand(): Creates a scale for discrete bands (bars).
  • domain: Maps indices of data to bands.
  • range: Spans the scale from 0 to the total width.
  • padding(0.1): Adds spacing between bars.
  const y = d3
    .scaleLinear()
    .domain([0, d3.max(props.data)])
    .nice()
    .range([props.height, 0])
Enter fullscreen mode Exit fullscreen mode
  • d3.scaleLinear(): Linear scale for bar heights.
  • domain: Goes from 0 to the max value in the data.
  • nice(): Rounds the domain to nice round numbers.
  • range: From SVG height (bottom) to 0 (top) because SVG Y-coordinates increase downward.
  svgEl
    .selectAll('rect')
    .data(props.data)
    .join('rect')
    .attr('x', (_, i) => x(i))
    .attr('y', d => y(d))
    .attr('width', x.bandwidth())
    .attr('height', d => props.height - y(d))
    .attr('fill', 'steelblue')
Enter fullscreen mode Exit fullscreen mode
  • .selectAll('rect'): Prepares to bind data to <rect> elements (bars).
  • .data(props.data): Binds the array of values.
  • .join('rect'): Efficiently adds/removes <rect> elements as needed.
  • .attr('x', ...): Sets the x-position for each bar.
  • .attr('y', ...): Sets the y-position (top) of each bar.
  • .attr('width', ...): Sets bar width based on the scale’s bandwidth.
  • .attr('height', ...): Calculates bar height from value.
  • .attr('fill', ...): Sets bar color.

2.Use the BarChart component in your main app file:

<template>
  <div>
    <BarChart :data="chartData" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import BarChart from './components/BarChart.vue'

const chartData = ref([10, 15, 20, 25, 30])
</script>
Enter fullscreen mode Exit fullscreen mode

The chart should look more or less like this:

D3 Chart

To scale up your Vue and D3 app, make sure to utilize following approaches:

  • Encapsulate D3 logic in composables if complexity grows.
  • Use transitions from D3 for smooth updates.
  • Consider watching window size to make charts responsive.
  • For advanced use cases, look into D3 modules like d3-axis, d3-shape, and d3-brush.

📖 Learn more

If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below:

Vue School Link

It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉

✅ Summary

By combining Vue’s reactive data handling with D3’s powerful visualization tools, you can build highly interactive, performant data visualizations. This approach is perfect for dashboards, real-time analytics, and more.

Take care and see you next time!

And happy coding as always 🖥️

Top comments (0)