D3.js

As a front-end, in addition to hearing the name of D3.js, common visualization libraries also include ECharts and chart.js. These two libraries also have powerful functions, but they have a common feature that the packaging level is high, leaving too little for developers to design and control. Compared with EChart and chart.js, d3. js** has much higher degree of freedom. Thanks to the support of event handler for SVG drawing in D3.js**, D3.js can bind arbitrary data to document object model (DOM). You can also manipulate the OBJECT model (DOM) directly to complete the W3C DOM API. D3.js is a great choice for developers who want to show off their graphics.

D3-force Force guide diagram

In order to realize a network, the D3-force guide diagram is the best choice. D3-force is a module of velocity Verlet numerical integrator implemented by D3.js to simulate particle physical motion, which can be used to control particle and edge order. In the force steering diagram, each node in D3-force can be regarded as a discharging particle, and there is a certain repulsion force (Coulomb repulsion) between the particles. At the same time, these particles are entangled by “edges” between them, creating traction.

However, under the action of repulsion and traction force, the particles in D3-Force constantly shift from the random disorder initial state and gradually tend to equilibrium order. The whole graph has only points/edges, and there are few examples of graphic implementation and most custom styles.

The diagram below is the simplest network diagram, to achieve the network diagram you want, or to implement a D3.js force guide diagram is the best.

Construct d3.js force guide diagram

In the practice here, we use d3.js force guide graph to analyze the data relationship of graph database, its nodes and relationship lines intuitively reflect the data relationship of graph database, and can also be associated with the corresponding graph database statement to complete the expansion query. Further, the data can be synchronized to the database through direct manipulation of the Document Object Model (DOM), which is of course more complex and is not covered in detail in 😂.

Below, we implement a simple force-directed diagram to get a glimpse of the role of D3.js in data analysis and some ideas for display optimization. First we create a force guide diagram:

This.force = d3.forcesimulation () // Assign coordinates to nodes.nodes(data.vertexes) // connect line.force ('link', linkForce) // the entire instance center.force('center'ForceCenter (width / 2, height / 2) //'charge', d3.forceManyBody(). Strength (-20)) // Force to prevent node overlap. Force ('collide',d3.forceCollide().radius(60).iterations(2));
Copy the code

From the above code, we can get a visual node and diagram like the one below.

To achieve extended query display optimization

Looking at the diagram (above), we see a new requirement: select the node and expand the query further. In order to achieve the expansion of the query, here I want to introduce the d3.js own API.

The d3.js Enter () API can perform separate logical processing for new nodes, so when a new node is pushed into the node array in the extended query, the existing node information (including x and Y coordinates) will not be changed, but will be rendered according to the coordinates assigned by the D3-Force instance. This is true from an API perspective, but the new node is more than a simple push for an existing instance of D3-Force. Because d3.forcesimulation () assigns position coordinates (x, y) to the current node to be random, so far there seems to be no problem, right?

Due to the random assignment of d3.forcesimulation ().node(), the location of the graph will appear randomly. In addition, the parameters set in the d3.force instance collide and links. Therefore, the nodes associated with the new node are influenced by the traction force to get closer to each other. If there are nodes in the force-map, these new nodes will keep colliding and pulling the force-map under the action of Collide and links until each node finds its proper location. That is, the collision force and traction force meet the requirements of the stop moving, look at the picture below, like the big Bang 🌞.

From the user’s point of view, every new node in the process of entropy reduction from disorder to order causes the whole force map to move all the time. In addition to a sense of convulsion, it is unacceptable to wait for a long time to stop the graph change. However, the d3.js API Enter () is defined in this way. Should the new node and the previous node be handled separately by the developer? If it is processed separately, each node rendering must be traversed to determine whether it is new or not. If there are more nodes, it will affect performance more. So how to optimize the new node presentation problem?

Most online solutions to the problem of new node presence are to reduce the d3-force instance Collide and increase the distance parameter value of links. In this way, nodes can quickly find a balance and the whole force map can be stabilized. This is indeed a good method. However, such a difference in the length of the line between nodes is obvious, and the overall graph is too large, which is not suitable for a case with a large amount of data.

Based on the above method, the author has another solution — to ensure that the new node is around the selected expansion node, that is, to directly set the coordinates of the new node to the same X and Y coordinates as the selected expansion node instead of d3.forcesimulation ().node() assignment. In this way, the node collision force of the instance d3-force is used to ensure that the appearance of new nodes will not be overwritten, and will eventually appear around the selected extension node. Although this treatment still has an impact on nodes within a small range of newly added nodes, relatively speaking, it will not significantly affect the trend of the whole relationship graph. The key codes are as follows:

# Set the new coordinates to expand the starting center or the entire image center
addVertexes.map(d => {
  d.x = _.meanBy(selectVertexes, 'x') || svg.style('width') / 2;
  d.y = _.meanBy(selectVertexes, 'y') || svg.style('heigth') / 2;
});
Copy the code

If the node is not selected (i.e. the starting point is added), the coordinate position of the starting point will be in the center of the graph. For existing nodes, the influence will be much smaller. This is still a good idea, and this solution can be recommended.

In addition to the presentation of new nodes, there is another problem in the presentation of the whole graph: multilateral optimization of display processing between two points.

Two points between multilateral optimization display processing

When there are multiple edges between two nodes, the default connection line is a straight line. So the curve connection becomes another problem that we need to solve. How do you define the curvature of a curve so that multiple lines between two points don’t overlap each other? In the case of multi-line bending, how to average semi-arc bending to avoid all running to a semi-arc? Define the arc direction of the curve?

The above problems are the next step to solve the problem, in fact, there are many solutions to the problem. At present, the author firstly calculates the number of lines between two points, and then allocates these connecting lines to a map. The name field of two nodes is spliced to form key, so as to calculate the total number of connecting lines between two points.

Then, the traversal lines in the same map are divided into forward and reverse groups according to the direction. For forward traversal, a Linknum number is appended to each line. Similarly, for reverse traversal, a -Linknum number is appended to each line. There are many ways to judge forward and reverse. The author compares the source. Name and target. There are too many ways to define the positive and negative directions of a connection line, just use any fixed field comparison between two points, so I won’t go into details here. The value of Linknum is set to determine the curvature and direction of the arc.

const linkGroup = {}; // The line between two edges is the same key according to the name attribute of the two points, and is added to the linkGroup. any) => { const key = link.source.name < link.target.name ? link.source.name +':' + link.target.name
        : link.target.name + ':' + link.source.name;
    if(! linkGroup.hasOwnProperty(key)) { linkGroup[key] = []; } linkGroup[key].push(link); }); // Iterate over each group to callsetLinkNumbers to allocate the linkum edges. ForEach ((link: any) => {const key =setLinkName(link);
    link.size = linkGroup[key].length;
    const group = linkGroup[key];
    const keyPair = key.split(':');
    let type = 'noself';
    if (keyPair[0] === keyPair[1]) {
      type = 'self';
    }
    setLinkNumbers(group, type);
  });
Copy the code
According to different directions, it is divided into linkA and linkB arrays, and two linknum arrays are allocated respectively to control the upper and lower elliptic arcs
export function setLinkNumbers(group) {
  const len = group.length;
  const linksA: any = [];
  const linksB: any = [];
  for (let i = 0; i < len; i++) {
    const link = group[i];
    if (link.source.name < link.target.name) {
      linksA.push(link);
    } else{ linksB.push(link); }}let startLinkANumber = 1;
  linksA.forEach(linkA=> {
    linkA.linknum = startLinkANumber++;
  }
  letstartLinkBNumber = -1; linksB.forEach(linkB=> { linkB.linknum = startLinkBNumber--; }}Copy the code

After assigning a linknum value to each connection line, determine whether linknum is positive or negative in the tick event function that monitors the connection line, and set the curvature and direction of the path. The final result is shown below

conclusion

Well, the above is the author using d3.js force guide diagram to realize the optimization of the network of ideas and methods. There’s a lot to consider and a lot to tune out when building a complex network. Today, I’m going to share with you two of the most common problems with new node rendering and multi-processing as we continue our series of d3.js optimizations. Subscribe to the Nebula Graph blog.

Finally, you can see how d3.js presents relationships by visiting the Nebula Graph Studio: Nebula-Graph-Studio database. Any errors or omissions in this article are welcome to GitHub: github.com/vesoft-inc/… Submit an issue to us in the Issue section or submit a suggestion under the suggestion feedback section of the official forum: discuss.nebula-graph.com.cn/ 👏; NebulaGraphbot to join the NebulaGraph Networking group, contact NebulaGraphbot, the NebulaGraph’s official assistant account

Hi, I’m Nico, a Nebula Graph front-end engineer with a lot of interest in data visualization. I’d like to share with you some of my own hands-on experience with Nebula Graph