The material, the translation of which we publish today, is devoted to the process of developing a visualization system for dynamic tree diagrams. For drawing cubic Bezier curves, SVG technology (Scalable Vector Graphics, scalable vector graphics) is used here. Reactive work with data is organized by Vue.js.
Here is a demo version of the system with which you can experiment.
Interactive Tree Chart
The combination of the powerful capabilities of SVG and the Vue.js framework allowed us to create a system for building diagrams that are data-based, interactive and customizable.
A diagram is a collection of cubic Bezier curves starting at one point. The curves end at different points equidistant from each other. Their final position depends on the data entered by the user. As a result, the chart is able to react reactively to data changes.
First, we will talk about how Bezier cubic curves are formed, then we will figure out how to represent them in the coordinate system of the
<svg>
element, and talk about creating masks for images.
The author of the material says that she prepared many illustrations for him, trying to make him understandable and interesting. The purpose of the material is to help everyone to get the knowledge and skills necessary to develop their own charting systems.
Svg
â–Ť How are cubic Bezier curves formed?
The curves that are used in this project are called Cubic Bezier Curve. The following figure shows the key elements of these curves.
Key Elements of a Bezier Cubic Curve
The curve is described by four pairs of coordinates. The first pair
(x0, y0)
is the starting anchor point of the curve. The last pair of coordinates
(x3, y3)
is the final reference point.
Between these points, you can see the so-called control points. This is the point
(x1, y1)
and the point
(x2, y2)
.
The location of the control points with respect to the control points determines the shape of the curve. If the curve were set only by the start and end points, the coordinates
(x0, y0)
and
(x3, y3)
, then this curve would look like a straight segment located diagonally.
Now we will use the coordinates of the four points described above to construct the curve using the SVG
<path>
element. Here is the syntax used in the
<path>
element to construct cubic Bezier curves:
<path D="M x0,y0 C x1,y1 x2,y2 x3,y3" />
The letter
, which can be seen in the code, is an abbreviation for Cubic Bezier Curve. Lower case letter (
c
) means the use of relative values, uppercase (
C
) means the use of absolute values. I use absolute values ​​to build the diagram, this is indicated by the capital letter used in the example.
â–ŤCreating a symmetric diagram
Symmetry is a key aspect of this project. To build a symmetrical diagram, I used only one variable, receiving on its basis such values ​​as the height, width or coordinates of the center of a certain object.
Let's name this variable
size
. Since the chart is oriented horizontally - the
size
variable can be considered as the entire horizontal space that is available to the chart.
Assign this variable a realistic value. We will use this value to calculate the coordinates of the chart elements.
size = 1000
Finding the coordinates of chart elements
Before we can find the coordinates needed to build the diagram, we need to deal with the SVG coordinate system.
â–ŤCoordinate system and viewBox
The attribute of the
<svg> viewBox
very important in our project. The fact is that it describes the user coordinate system of the SVG image. Simply put, the
viewBox
determines the position and dimensions of the space in which the SVG image visible on the screen will be created.
The
viewBox
attribute consists of four numbers that specify the parameters of the coordinate system and the following in this order:
min-x
,
min-y
,
width
,
height
. The
min-x
and
min-y
parameters set the origin of the user coordinate system, the
width
and
height
parameters set the width and height of the displayed image. Here's what the
viewBox
attribute might look like:
<svg viewBox="min-x min-y width height">...</svg>
The
size
variable that we described above will be used to control the
width
and
height
parameters of this coordinate system.
Later, in the section on Vue.js, we will bind the
viewBox
to a computed property to specify the
width
and
height
values. Moreover, in our project, the properties
min-x
and
min-y
will always be set to 0.
Note that we do not use the
height
and
width
attributes of the
<svg>
element itself. We will set them to
width: 100%
and
height: 100%
using CSS. This will allow us to create an SVG image that flexibly adjusts to the page size.
Now that the user coordinate system is ready to draw a chart, let's talk about using the
size
variable to calculate the coordinates of the chart elements.
â–Ť Invariable and dynamic coordinates
Chart concept
The circle in which the drawing is displayed is part of the diagram. That is why it is important to include it in the calculations from the very beginning. Let us, based on the above illustration, find out the coordinates for the circle and for one experimental curve.
The height of the chart is divided into two parts. These are
topHeight
(20% of
size
) and
bottomHeight
(the remaining 80% of
size
). The total width of the chart is divided into 2 parts - the length of each of them is 50% of the
size
.
This makes the conclusion of the circle parameters not requiring special explanations (here the
halfSize
and
topHeight
indicators are used). The
radius
parameter is set to half the value of
topHeight
. Thanks to this, the circle fits perfectly into the available space.
Now let's take a look at the coordinates of the curves.
- The coordinates
(x0, y0)
define the starting reference point of the curve. These coordinates remain constant all the time. The x0
coordinate is the center of the chart (half the size
), and y0
is the coordinate at which the bottom of the circle ends. Therefore, in the formula for calculating this coordinate, the radius of the circle is used. As a result, the coordinates of this point can be found by the following formula : (50% size, 20% size + radius)
. - The coordinates
(x1, y1)
are the first control point of the curve. It also remains unchanged for all curves. If we do not forget that the curves should be symmetrical, then it turns out that the values ​​of x1
and y1
always equal half the value of size
. Hence the formula for their calculation: (50% size, 50% size)
. - The coordinates
(x2, y2)
represent the second control point of the Bezier curve. Here, x2
indicates what shape the curve should be. This indicator is calculated dynamically for each curve. And the indicator y2
, as before, will be half of size
. Hence the following formula for calculating these coordinates: (x2, 50% size)
. - The coordinates
(x3, y3)
are the end reference point of the curve. This coordinate indicates where you want to finish drawing the line. Here, the value of x3
, like x2
, is calculated dynamically. And y3
takes a value equal to 80% of size
. As a result, we obtain the following formula: (x3, 80% size)
.
We rewrite, in general terms, the code for the
<path>
element, taking into account the formulas that we just derived. The percentages used above are presented here by dividing them by 100.
<path d="M size*0.5, (size*0.2) + radius C size*0.5, size*0.5 x2, size*0.5 x3, size*0.8" >
Please note that at first glance, the use of percentages in our formulas may seem optional, based only on my own opinion. However, these values ​​are not applied on a whim, but because their use helps to achieve symmetry and the correct proportions of the diagram. After you feel their role in charting, you can try your own percentage values ​​and examine the results obtained by applying them.
Now let's talk about how we will look for the coordinates
x2
and
x3
. They allow you to dynamically create many curves based on the
index
elements in the corresponding array.
Dividing the available horizontal space of the chart into equal parts is based on the number of elements in the array. As a result, each part receives the same space along the x axis.
The formula that we derive should subsequently work with any number of elements. But here we will experiment with an array containing 5 elements:
[0,1,2,3,4]
. Visualization of such an array means that it is necessary to draw 5 curves.
â–ŤFinding dynamic coordinates (x2 and x3)
First, I divided
size
by the number of elements, that is, by the length of the array. I called this variable
distance
. It represents the distance between two elements.
distance = size/arrayLength
Then I walked around the array and multiplied the index of each of its elements (
index
) by
distance
. For simplicity, I simply call
x
both the
x2
parameter and the
x3
parameter.
If you apply the obtained values ​​when building the diagram, that is, use the
x
value calculated above for both
x2
and
x3
, it will look a little strange.
The diagram is asymmetrical
As you can see, the elements are located in the area where they should be, but the diagram turned out to be asymmetric. It seems that in its left part there are more elements than in the right.
Now I need to make the
x3
value lie in the center of the corresponding segments, the length of which is set using the
distance
variable.
In order to bring the diagram to the form I need, I simply added to
x
half the value of
distance
.
x = index * distance + (distance * 0.5)
As a result, I found the center of the
distance
segment and placed the
x3
coordinate in it. In addition, I brought to the form we need the
x2
coordinate for curve No. 2.
Symmetric chart
Adding half the
distance
value to the
x2
and
x3
coordinates made the formula for calculating these coordinates suitable for visualizing arrays containing an even and odd number of elements.
â–Ť Image masking
We need a certain image to be displayed at the top of the diagram, within the circle. To solve this problem, I created a clipping mask containing a circle.
<defs> <mask id="svg-mask"> <circle :r="radius" :cx="halfSize" :cy="topHeight" fill="white"/> </mask> </defs>
Then, using the
<image>
tag of the
<image>
<svg>
<image>
element to display the image, I linked the image to the
<mask>
element created above using the
mask
attribute of the
<image>
element.
<image mask="url(#svg-mask)" :x="(halfSize-radius)" :y="(topHeight-radius)" ... > </image>
Since we are trying to fit a square image into a round “window”, I adjusted the position of the element by subtracting the
radius
parameter from the corresponding parameters. As a result, the image is visible through a mask made in the form of a circle.
Let's collect all that we talked about in one drawing. This will help us see the overall picture of the progress of work.
Data used in calculating chart parameters
Creating a dynamic SVG image using Vue.js
At this point, we figured out the cubic Bezier curves and performed the calculations necessary to form the diagram. As a result, we can now create static SVG diagrams. If we combine the capabilities of SVG and Vue.js, we can create data driven charts. Static charts will become dynamic.
In this section, we revise the SVG diagram, presenting it as a set of Vue components. We will also attach the SVG attributes to the calculated properties and make the chart respond to data changes.
In addition, at the end of the project, we will create a component that represents a configuration panel. This component will be used to enter data that will be transmitted to the chart.
â–ŤBinding data to viewBox parameters
Let's start by adjusting the coordinate system. Without doing this, we will not be able to draw SVG images. The computed
viewbox
property will return what we need using the
size
variable. There will be four values ​​separated by spaces. All this will become the value of the
viewBox
attribute of the
<svg>
element.
viewbox() { return "0 0 " + this.size + " " + this.size; }
In SVG, the name of the
viewBox
attribute
viewBox
already written using camel style.
<svg viewBox="0 0 1000 1000"> </svg>
Therefore, in order to correctly bind this attribute to the calculated property, I wrote down the attribute name in the kebab style and put the
.camel
modifier after it. With this approach, it is possible to "trick" HTML and correctly implement attribute binding.
<svg :view-box.camel="viewbox"> ... </svg>
Now when you change the
size
chart is reconfigured independently. We do not need to manually change the layout.
â–ŤCalculation of curve parameters
Since most of the values ​​needed to construct the curves are calculated on the basis of a single variable (
size
), I used the calculated properties to find all the fixed coordinates. What we call “fixed coordinates” here is calculated on the basis of
size
, and after that it does not change and does not depend on how many curves the diagram will include.
If you change the
size
- "fixed coordinates" will be recounted. After that, they will not change until the next
size
change. Given the above, here are five values ​​we need to draw Bezier curves:
-
topHeight — size * 0.2
-
bottomHeight — size * 0.8
-
width — size
-
halfSize — size * 0.5
-
distance — size/arrayLength
Now we have only two unknown values ​​left -
x2
and
x3
. The formula for calculating them we have already derived:
x = index * distance + (distance * 0.5)
To find specific values, we need to substitute the indices of the array elements in this formula.
Now let's ask ourselves if the computed property is right for us to find
x
. Briefly answer this question, then - no, it will not do.
The calculated property cannot be passed parameters. The fact is that this is a property, not a function. In addition, the need to use a parameter to calculate something means that there is no tangible advantage of using calculated properties in terms of caching.
Please note that there is an exception regarding the above principle. It's about Vuex. If you use Vuex getters that return functions, you can pass parameters to them.
In this case, we do not use Vuex. But even in this situation, we have a couple of ways to solve this problem.
â–Ť Option number 1
You can declare a function to which
index
passed as an argument, and which returns the result we need. This approach looks cleaner if we are going to use the value returned by a similar function in several 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" /> </g>
The
calculateXPos()
method will perform calculations every time it is called. This method takes as argument the index of the element -
i
.
<script> methods: { calculateXPos (i) { return distance * i + (distance * 0.5) } } </script>
Here is an example on CodePen that uses this solution.
Screen for the first application variant
â–Ť Option number 2
This option is better than the first. We can extract the small SVG markup needed to build the curve into a separate small child component and pass
index
to it as one of the properties.
With this approach, you can even use the computed property to find
x2
and
x3
.
<g v-for="(item, i) in items"> <cubic-bezier :index="i" :half-size="halfSize" :top-height="topHeight" :bottom-height="bottomHeight" :r="radius" :d="distance" > </cubic-bezier> </g>
This option gives us the opportunity to better organize the code. For example, we can create another child component for the mask:
<clip-mask :title="title" :half-size="halfSize" :top-height="topHeight" :r="radius"> </clip-mask>
â–ŤConfiguration panel
Configuration panel
You have probably already seen the configuration panel, called up by the button located in the upper left corner of the screen of the above
example . This panel makes it easy to add elements to the array and remove them from it. Following the ideas discussed in the "Option # 2" section, I created a child component for the configuration panel. Thanks to this, the top-level component is clean and well readable. As a result, our small, nice tree of Vue components looks something like the one below.
Project Component Tree
Want to take a look at the code that implements this version of the project? If so, take a look
here .
Screen for the second application variant
Project repository
Here is the GitHub repository of the project (“Option # 2” is implemented here). I believe it will be useful for you to look at it before you move on to the next section.
Homework
Try to create the same diagram that we described here, but make it oriented vertically. Take advantage of the ideas outlined in this article.
If you think that this is an easy task, that to build such a diagram it is enough to swap the
x
and
y
coordinates, then you are right. Considering that the project considered here was not created as universal, after changing the coordinates where you need it, you will also need to edit the code by renaming some variables and methods.
Thanks to Vue.js, our simple chart can be equipped with additional features. For example, the following:
- You can create a mechanism that allows you to switch between horizontal and vertical chart modes.
- Curves can try to animate. For example, using GSAP.
- You can adjust the properties of the curves (say - color and line width) from the configuration panel.
- You can use an external library to organize the saving of diagrams in any graphic format or as a PDF file. These materials can be allowed to download to those who work with the chart.
Try this homework. And if you have any problems - below will be given a link to its solution.
Summary
The
<path>
element is one of the powerful features of SVG. This element allows you to create various images with high accuracy. Here we figured out how the Bezier curves are structured, and how to put them into practice to create your own diagrams.
, , JavaScript-. Vue.js . , , , , DOM. , — .
, , , , , Vue.js SVG. —
, Vue.js.
— .
, - , , , — .
Dear readers! ?