Development of dynamic tree diagrams using SVG and Vue.js

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.





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 // distance = 1000/5 = 200
      
      





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.



 //  x2  x3 x = index * distance
      
      





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:





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:





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! ?






All Articles