DEV Community

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

Posted on

11 1

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 🖥️

ACI image

ACI.dev: Best Open-Source Composio Alternative (AI Agent Tooling)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Star our GitHub!

Top comments (0)