Building a Dynamic Tree Diagram with SVG and Vue.js
This article will take you through my learning journey into how I created a dynamic tree diagram that uses SVG (Scalable Vector Graphics) for drawing Cubic Bezier curve paths and Vue.js for data reactivity.
Before we start, take a look at the demo.
Combining the power of SVG and Vue.js framework allows creating diagrams and infographics that are data-driven, interactive and configurable.
This diagram is a collection of Cubic Bezier curves that start from a single point, but end on different points — at equal distance — based on user-provided data. So, the diagram is reactive to user input.
We’ll start by learning how the Cubic Bezier curve is formed and then try to find
y points in the coordinate system that’s available within
<svg> element followed by clipping mask.
I’ve used plenty of visuals on a way to keep things interesting. The key idea of this article is to help you come up with your own diagram concepts for similar projects.
How the Cubic Bezier curve is formed?
The type of curve you saw in the demo above, is called a Cubic Bezier curve. I have highlighted each part of this curve structure below.
It has total 4 pairs of coordinates. First pair —
(x0, y0)— is the starting anchor point, and last pair —
(x3, y3) — is ending anchor points that indicates where to finish the path.
Two pairs in the middle are,
- Bezier control point #1
- Bezier control point #2
And they’re responsible to achieve that smooth curve you see in a path. Without these control points, this path would just be a straight diagonal path!
Let’s put these four coordinates into SVG
// Cubic Bezier path syntax<path D="M x0,y0 C x1,y1 x2,y2 x3,y3" />
c seen in the syntax, stands for Cubic Bezier curve. Small
c indicates relative values, while the capital
C indicates absolute value. I have used absolute value —
C — to create this diagram.
Symmetry is the key aspect of this diagram. And to achieve that, I have used just one variable to derive values like height, width and mid-point.
Let’s call this variable, a
size. Since the orientation of this tree-diagram is horizontal, this
size variable can be viewed as an entire horizontal space that’s available for the diagram.
Let’s assign most realistic value to this variable. So that, you can also calculate the coordinates along the way.
size = 1000
Before we can find coordinates, we need a coordinate system in place!
Coordinate system & viewBox
viewBox attribute of an <svg> element is an important, because it defines a user coordinate system of an SVG. In simplified terms,
viewBox defines the position and dimension of the user-space to draw an SVG.
viewBox is made up of four numbers in this exact same order —
min-x, min-y, width, height.
<svg viewBox="min-x min-y width height">...</svg>
size variable we defined earlier will control the
height of this coordinate system.
Later in Vue.js section,
viewBox will be bound to computed property to populate the
min-y will always be zero in this instance.
Notice that, we’re not using
width attributes of SVG element itself. Because, we’ll set
width: 100% and
height: 100% of an
<svg> via CSS to make the viewport fluid.
Now that the user-space/coordinate system is ready for the diagram, let’s see how
size helps calculating coordinates just by using different
Constant & Dynamic Coordinates
Circle is part of the diagram. That’s why, it’s important to include it in the calculations from the beginning. As portrayed in the diagram above, let’s start deriving coordinate values for a circle and one sample path.
The vertical height is divided into two parts,
topHeight (20% of
bottomHeight (remaining 80% of
size). And the horizontal width is divided into two parts — 50% of
This makes the circle coordinates pretty self-explanatory,
(halfSize, topHeight). Circle
radius is set to half of
topHeight, so that it fits nicely in available space.
Now, let’s look at the path coordinates…
x0, y0— First pair of anchor points that stays constant at all the times. Here,
x0is the centre of the diagram
y0is vertical point where the circle stops (…hence the addition of a radius) and path begins.
(50% of size, 20% of size + radius)
x1, y1— Bezier control point one, which also stays constant for all paths. Keeping symmetry in mind,
y1are always half of the diagram
(50% of size, 50% of size)
x2, y2— Bezier control point two, where
x2directs which side to form the curve and is calculated dynamically for each paths. And again,
y2will be half the diagram
(x2, 50% of size)
x3, y3- Final pair of anchor points that indicates where to stop drawing the path. Here,
x3imitates the value of
x2, which is to be calculated dynamically. And
y3takes 80% of the
(x3, 80% of size)
See the generic path syntax below after punching in these calculations. To represent
%, I’ve simply divided the
% value by 100.
<path d="M size*0.5, (size*0.2) + radius
C size*0.5, size*0.5
Note: This whole
% business may seem opinionated at first, but it’s for achieving symmetry and right proportion. Once you understand its purpose in building this diagram, you may try out your own
% value & examine different results.
Next part focuses on finding the values of remaining coordinates,
x3 — which enables forming multiple curved paths dynamically based on their array
Depending on a number of items in an array, available horizontal space should be distributed into equal parts, so that each path gets same amount of space on
Formula should work for any number of items eventually, but for the purpose of this article, I’ve gone with 5 array items —
[0,1,2,3,4]. Meaning, I’ll draw 5 Bezier curves.
Finding Dynamic Coordinates (x2 and
First, I have divided the
size by the number of items, i.e. array length, and called this variable
distance — for being the distance between the two items.
distance = size/arrayLength
// distance = 1000/5 = 200
Then, I’ve looped through each item in array and multiplied their
index value with
distance. For simplicity in writing, I’ve referred to both
x3 — just
// value of x2 and x3
x = index * distance
The diagram looks bit odd when I used the value of
x as it is for both
As you can see, the placement of coordinates is correct, but not quite symmetrical. It looks as if left-side is getting more items than the right-side.
At this point, somehow I needed to place
x3 coordinate in the centre of the
distance, rather than at the start.
To fix this, let’s revisit
distance variable — which is 200 for the given scenario. I’ve simply added half-a-distance back into
x = index * distance + (distance * 0.5)
Meaning, I’ve found a mid-point of
distance and placed the final
x3 coordinate there and also adjusted
x2 of the Bezier curve #2.
Addition of half-a-distance into —
x3 — coordinate works for both odd and even number of array items.
To make the mask that’s shaped as a circle, I’ve defined a mask with circle in it.
<image> tag of
<svg> element as a content, I have bound image with a
<mask> (created above), using
Since we’re trying to fit square image into a circle, I’ve adjusted the image position by reducing the
radius of a circle to achieve full visibility of an image through the circular mask.
Let’s put together all the values visually in a diagram to help us see the bigger picture.
Dynamic SVG using Vue.js
So far we have understood how Bezier curve works and surrounding nitty gritty. As a result, we have a static SVG diagram concept. Using Vue.js with SVG, we’ll now make this diagram data-driven and transform it from static to dynamic.
In this section we will break down SVG diagram into Vue components and bind SVG attributes to computed properties and make it respond to data changes.
Finally, we will also look at a config panel component which is used to provide data to the dynamic SVG diagram.
We’ll learn about the following key topics in this section.
- Binding SVG viewBox
- Calculating SVG Path Coordinates
- Two options for Bezier curve path implementation
- Config Panel
- Homework ❤
Binding SVG viewBox
First and foremost, we need a coordinate system in place to be able to draw inside SVG. And computed property
viewbox will do just that using
size variable. It contains four values separated by a whitespace — which is fed into
viewBox attribute of an
return "0 0 " + this.size + " " + this.size;
viewBox attribute is written in camelCase already.
<svg viewBox="0 0 1000 1000">
Therefore, to correctly bind it to a computed property, I’ve used kebab-case version of attribute (below), followed by
.camel modifier. This way HTML is tricked into binding this attribute correctly.
Now, every time we change the
size, the diagram will adjust itself without us having to change the markup manually.
Calculating SVG Path Coordinates
Since most values are derived from a single variable,
size, I’ve used computed properties for all of the constant coordinates. Don’t be confused by the term constant here. These values are derived from the
size, but after that, they remain constant — no matter how many curved paths are created.
And if you change the size of an SVG, these values are computed again. With that in mind, here’s the list of five values required to draw Bezier curves.
size * 0.2
- bottomHeight —
size * 0.8
- width —
- halfSize —
size * 0.5
At this point, we are left with two unknown values,
x3 — and we do have a formula to find their values.
x = index * distance + (distance * 0.5)
x above, we need to feed
index into the formula for each path at once. So…
Is computed property an appropriate option here? The short answer is no.
We cannot pass a parameter to a computed property — because it’s a property, and not a function. Also, needing a parameter to calculate something means — there’s no clear caching benefit of using the computed property anyways.
Note: There’s an exception to above. Vuex. If we’re using Vuex Getters, then yes, we can pass parameter to getters by returning a function.
We’re not using Vuex in this instance. Even then, we’re left with couple of options.
We can define a function, where we pass an array
index as an argument and return the result. Bit cleaner option, if we’re to use this value at multiple places in the template.
<g v-for="(item, i) in itemArray">
<path :d="'M' + halfSize + ',' + (topHeight+r) +' '+
'C' + halfSize + ',' + halfSize +' '+
calculateXPos(i) + ',' + halfSize +' '+
calculateXPos(i) + ',' + bottomHeight"
calculateXPos() method will evaluate every time it’s called. And this method accepts index —
i — as an argument. (Below)
return distance * i + (distance * 0.5)
Here’s the CodePen that uses Option 1.
Better yet, we can extract this small SVG path markup into its own little child component and pass
index as a prop into it — along with other required props, of course.
In this instance, we can even use computed property to find
<g v-for="(item, i) in items">
This option gives us an opportunity to be more organised with the code. For example, we can create one more child component for the clipping-mask of a circle as below.
As you may have already seen the Config Panel on top-left corner in CodePen above, it facilitates adding and removing of items from array. Following Option 2, I have created a child component to accommodate the Config Panel as well, which leaves top-level Vue component clean and readable. As a result, sweet little Vue component tree will look something like below.
Wondering how the code would look like now? Here’s the CodePen using Option 2.
Finally, here’s the GitHub Repo for you to review the project (with Option 2) before you move onto the next section.
Try creating the same diagram in vertical mode based on a logic presented in this article.
If you think, it could be as easy as swapping the x and y coordinates, then you’re correct! Because the hard-work is done already, after swapping the coordinates where required, update the code with appropriate variables and method names.
With the help of Vue.js, this diagram can be extended further with more features, such as,
- Create a switch to toggle between horizontal and vertical mode
- Maybe use GSAP to animate the path
- Control path attributes (such as colour & stroke width) from config panel
- Use external library to save and download the diagram as an image/PDF
Now give it a try and if needed, there’s the answer-link to the Homework below.
<path> is one of the many powerful elements in SVG, as it allows you to create graphics and diagrams with precision. In this article, we understood how Bezier curve works and its application to create custom diagram.
I’ve realised that it took some of the simple concepts of both Vue.js and SVG to create this diagram that seems complex by the look of it. I encourage you to read about Building an Interactive Infographic with Vue.js, if you haven’t read already. It’d be good read after this one. ❤ And here’s an answer to the homework.
I hope you have learned something from this article and had as much fun reading it as I had while creating it.