In this article, we take advantage of the flexibility of D3.js to add some common features that d3.js does not support itself, but which we wanted: node deletion and scaling capabilities explored by Nebula Graph.

Nebula Graph: nebula-graph.com.cn/posts/d3-js…

preface

In the previous article (display optimization of d3.js force-directed graphs), we talked about d3.js’s advantages over other open source visualization libraries in terms of custom graphs, and how it works flexibly with the Document Object Model (DOM). Since D3.js is very flexible, does it do a lot of what we want to do? In this article, we will take advantage of the flexibility of D3.js to add some common features that d3.js does not support but we want.

Construct d3.js force guide diagram

Here we will not detail the d3-force particle physics motion module principle, interested students can look at the brief description of our last chapter, this practice we focus on the realization of visual operation function.

Ok, into our practice time, we still use d3.js force-directed graph to analyze the data relationship of graph database for the purpose of adding some functions we want.

First, we use the D3-Force guide diagram to construct a simple correlation

this.force = d3
        .forceSimulation()
        // Assign coordinates to nodes
        .nodes(data.vertexes)
        / / cable
        .force('link', linkForce)
        // The entire instance center
        .force('center', d3.forceCenter(width / 2, height / 2))
        / / gravity
        .force('charge', d3.forceManyBody().strength(- 20))
        // Collision forces prevent nodes from overlapping
        .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.

Nebula Graph Studio is the Nebula Graph visualization tool for the Nebula Graph database. With the Nebula Graph software suite, you can use the Nebula Graph software suite to create a series of images for the Nebula Graph database. For example, point 100 and point 200 in the figure above have a one-way follow relationship.

The amount of data in the figure above is not large. If we return a large amount of data during expansion or gradually add and display the data developed in multiple steps, there will be too many nodes and edges in the current view page, and the page will have a large amount of data information to present, and it is difficult to find a desired node. Well, a new scenario comes into play: users only want to analyze some node data in the graph, but not all node information. Delete Any selected feature is a good way to deal with the above scenario, removing unnecessary node information and leaving only the partial node data that you want to explore.

Support to delete any selected function

Before implementing this feature, I’ll start by introducing the d3.js built-in API. Yes, the same d3.js enter() and unmentioned exit()

From the description of the document:

Enter and exit are used to deal with data binding issues where DOM elements do not match the number of data elements. The Enter operation is used to add new DOM elements, and the exit operation is used to remove unwanted DOM elements. Use Enter if there are more data elements than DOM elements, and exit if there are fewer. There are three scenarios for data binding:

  • The number of data elements exceedsDOMNumber of elements
  • Data elements andDOMThe same number of elements
  • Less data elementsDOMNumber of elements

According to the documentation, it’s pretty easy to remove any selection, and the optimistic author took it for granted that you could just do it at the data level. Select (this.noderef).exit().remove() to remove unnecessary elements.

The unselected node is deleted, but the other nodes are displayed incorrectly. The color and properties of the node do not match the current DOM node. The author carefully looked at the above document description, a flash of light, come, first print the exit().remove() node, see what nodes it remove?

D3.js enter().exit() actually listens for changes in the number of elements. That is, if there are two missing elements, it does fire exit(), but the data it processes is not the actual data to delete, but the last two nodes of the current nodes data. To be clear, the triggering principle of Enter () and exit() is triggered by d3.js listening for changes in the length of the current data. However, after d3.js gets the change of data length, exit() is used as an example to process a single data by intercepting all elements in the range from the last N bits to the last bit in the data array position according to the length reduction N. On the contrary, Enter () will add N data after the last element in the array position.

Therefore, if you select the previously explored node (which is not the last element of the current location of the data array) and delete the data from our nodes, the existing view will delete the data. The d3.select(this.noderef).exit() method locates the last operation element, so the display is messy.

I’m going to share my method directly here, which is simple and crude but effective — obviously exit() does not meet the business requirement of removing the selected node, so let’s deal with the nodes that need to be removed separately. For this purpose, we need to bind an ID to each node during rendering, and then iterate to find the DOM corresponding to these nodes to be deleted according to the deleted node data. The following is our processing code:

  componentDidUpdate(prevProps) {
    const { nodes } = this.props;
    if (nodes.length < prevProps.nodes.length) {
      const removeNodes = _.differenceBy(
        prevProps.nodes,
        nodes,
        (v: any) => v.name,
      );
      removeNodes.forEach(removeNode= > {
        d3.select('#name_' + removeNode.name).remove();
      });
    } else {
      this.labelRender(this.props.nodes); }}Copy the code

In fact, what we need to deal with here is not only locating the DOM of the actual deleted node, but also deleting its associated edges and display text. Because there is no starting/ending edge, it is meaningless. The processing method of edge and copy is similar to the logic of point deletion, which will not be described here. If you have any questions, please go to our project address: github.com/vesoft-inc/… Communicate.

Support button zoom function

After deleting the selected point, zoom operation is also a common function in the visual view. D3.zoom () in D3.js is used to realize the zoom function, and this method is relatively mature and stable after the business test of other factories, so why do we have to do it by ourselves? (what bike 😂).

The zoom function is purely a function at the interactive change level. With the scroll wheel zoom scheme, this hidden operation is difficult for users not familiar with The Nebula Graph Studio to detect, and the scroll control zoom cannot control the specific scale of the zoom. For example, the user wants to zoom by 30% / 50%, for which the scroll control zoom cannot be used. In addition, in the process of implementing scroll wheel scaling, the author found that scroll scaling would affect the position offset of nodes and edges. What is the reason for this?

By looking at the d3.zoom() code, we find that the essence of D3.js is to obtain the scale value of d3.event in the event and then modify the transform attribute value for the whole canvas. However, the x and Y coordinates of nodes and edge elements in SVG are not changed in this way. As a result, when d3.zoom() is used to zoom in, the view will be shifted to the upper left (because the canvas itself is smaller compared to the x and Y coordinates of the side elements in the view), and the view will be shifted to the lower right when the canvas is shrunk.

Finding the cause of the problem is the first step to solving the problem. Let’s solve the problem by adding logic to handle the change of nodes and edges relative to the offset of canvas size when scaling. Ok, let’s do that.

We first make a slider control to provide users with manual control of the scale of the canvas, directly use antD slider, according to its sliding value to control the entire canvas scale, the following direct paste code

 <svg
  width={width}
  viewBox={`0 0 ${width * (1 + scale)}  ${height * (1 + scale)}`}
  height={height}
  >
 {/*****/}
</svg>
Copy the code

The scale parameter in the code above is generated from the scale value in the control’s scroll bar, which we need to record to enlarge the canvas (SVG element) and to make the view shrink.

In addition, we also need the scale value when dealing with the node and edge offsets mentioned above, because we need to set the nodes and edges to a reverse offset. Simply put, when the canvas is scaled up, the x and Y positions of the nodes and edges should also be offset by the current scale times of the canvas. This will keep the positions of the nodes and edges unchanged when the canvas is scaled up. Here is the key code for handling offsets during node scaling

 const { width, height } = this.props;
    const scale = (100 - zoomSize) / 100;
    const offsetX = width * (scale / 2);
    const offsetY = height * (scale / 2);
    // Handle the offset of the parent element DOM 
      
    d3.select(this.circleRef).attr(
      'transform'.`translate(${offsetX} ${offsetY}) `,);Copy the code

conclusion

Well, the above is the author using d3.js force guide diagram to realize the network of ideas and methods in the process of user-defined functions. I have to say, d3.js ** ** has a high degree of freedom, we can freely open our imagination to achieve the functions we want.

In this sharing, the author shared two practical and frequently used functions in the graph database visualization business: randomly select and delete nodes, customize zoom and optimize view offset functions. There’s a lot to consider when it comes to visualizing a complex web of relationships, and there are a lot of interactions and displays that need to be optimized, which we’ll continue to optimize as we update the D3.js optimization series. Subscribe to the Nebula Graph blog.

Like this article? GitHub: 🙇♂️🙇♀️

Ac graph database technology? NebulaGraphbot takes you into the NebulaGraphbot community

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