1. Introduction

I just wrote a blog about introducing G6 a few days ago. If you are interested, you can have a look. In retrospect, I found that all the space was spent on introducing the core concepts of G6 at that time, but the actual practice and the summary of the following problems were not perfect enough. So write another blog post to fill in the blanks.

2. Some implementation potholes

As mentioned in the previous article, some issues with rendering performance and layout were encountered during implementation. So let’s go step by step and start with performance.

2.1. Performance issues

2.1.1. First screen rendering

Since the requirement involves a large amount of data rendering, we simulated 1000+ data at the beginning of demo to test the rendering performance of G6 tree layout. The rendering of the first demo was pretty bad after it was written, and there was 9 seconds of white screen time when it started up. However, in a performance demo provided by the government, interaction lag only appeared when the number of nodes was 50000+, and the first screen rendering and network request time added up to only about 8s. So I open Performance to find out what the problem is.

    

Judging from the performance screenshot above, the pathFinder method took more than 6s. Repeated debugging found that this function took an average of 6 to 7 seconds. So our optimization is going to start with this function. This is a function under Antv G6/edge package, indicating that our time is related to the edge of the tree graph. Later, I found the answer by consulting the issue and official documents on Github. When our edge type is set to polyline, it will automatically generate polylines according to A* algorithm by default, which has A very complicated time. Let’s try toggling the Edge type to see what it looks like. The total time is about 1s.

      

Let’s get a better feel for the optimization with two GIFs.

Before optimization:

After the optimization:

However, when we really need a polyline style but don’t want to use a performance expensive polyline, we can use custom Edge to generate the polyline style, which is basically the same as the rendering performance of the built-in edge. Here briefly stick to the core code of custom polyline.

G6.registerEdge( "h-poly-line", { draw(cfg, group) { const { startPoint, endPoint } = cfg; const shape = group.addShape("path", { attrs: { stroke: "#333", path: X, startPoint.y], ["L", endpoint.x / 3 + (2/3) * startPoint.x, startPoint.y], // 1/3 ["L", X / 3 + (2/3) * startPoint.x, endpoint.y], // 2/3 ["L", endpoint.x, endpoint.y],],}, name: "h-poly-line", }); return shape; }, update: undefined // need to override update otherwise inherit line method}, "line");Copy the code

You only need to specify the type of edge in the configuration to instantiate Graph.

const graph = new G6.Graph({ ... DefaultEdge: {type: 'h-poly-line' // Specify the type of edge}... })Copy the code

Another way to optimize rendering is to use the Web-worker mechanism without blocking user interaction with other parts of the page. It is also easy to configure in config.

2.1.2. Interactive performance

In the same case with 1000+ data, our demo had a lag in drag and zoom interactions. As mentioned earlier, the official performance example does not have this problem, mainly because the official example uses built-in nodes, while the demo uses custom nodes to meet the need to display more race information. This caused us to spend more time re-rendering the entire node during the interaction. Officials have provided an optimized approach to this situation, with the following code. Shapes other than keyShape are not rendered during node interaction. Although it brings better performance, the user’s visual experience will be worse after this function is enabled. So think carefully about using this solution.

const graph = new G6.Graph({ ... Modes: {default: ["drag-canvas", {type: "zoom-canvas", enableOptimize: true}],},... })Copy the code

Another is to start from the node optimization, reduce the unnecessary Shape in the node, this needs to be analyzed according to the actual situation, not to expand here.

2.2. Layout issues

Another difficult problem to deal with is the layout problem, the built-in tree layout does not calculate the exact coordinates of the nodes, causing the nodes to overlap. Here is a compact tree layout with the default parameters.

            

At present, the layout is adjusted by actively setting the spacing and the width and height of the nodes, but I feel that this method is not particularly elegant. The configuration code is as follows:

const graph = new G6.Graph({ ... layout: { type: "compactBox", direction: Direction, / / the following four attributes configuration layout of nodes in the wide and high level interval / / official documents have the function of this a few properties | number / / / but in 4.3.4 version number getWidth complains / / parameter for the node information: ({type, size}) => { if(type === "race-node"){ return size[0] } return 200 }, getHeight: ({type, size}) => { if(type === "race-node"){ return size[1] } return 100 }, getHGap: ({type}) => type === 'race-node'? 25 : 10, getVGap: ({type}) => type === 'race-node'? 25:100}... })Copy the code

The effect is as follows:

    

3. Some potholes in use

Browsing through the documentation and implementation process found that the tree layout is still more limited. Tree layouts are not supported in subgraph layouts, nor are Web-worker optimizations supported. Since it is not consistent with the data structure of the general graph, the two can not be mixed on the artboard. In addition, you cannot use Combo(grouping multiple nodes into a group) in a tree layout. According to the official statement, none of these features will be supported anytime soon. In some cases, we may need to manipulate data at a certain level in the tree graph, which is difficult to achieve without Combo, so we have to use other layouts to simulate the tree graph, such as Darge layout. Unlike tree graph, Darge layout can generate node relationship according to data structure, which needs to be generated separately. Other uses of Darge layout are similar and performance is similar. Darge layout can basically restore the general tree graph layout.

// Const compactBoxData = [{id: '1', children: [{id: '2', children: []}] // Load data graph.data(compactBoxData) //Darge layout // Need to convert metadata to the following form const dargeData = {nodes: [{id:] '1' }, { id: '2' } ... ] Edges: [{source: '1', target: '2'}]} // Load data graph.data(dargeData)Copy the code

The complete code implementation can be seen in the official demo. In addition, I would like to make a joke that the jSX-like syntax to define nodes is not as good as the original addShapeAPI, and it cannot describe the relationship between shapes well. If the nesting level is too deep, there will be overlapping layout bugs.

4. To summarize

These are some of the pits encountered so far. Although there are some small problems, G6 is more suitable for the scene of graph visualization in the mainstream community, which is out of the box and has good scalability. The layout and interaction ability provided by G6 can basically cover common business requirements. The G6 is a good choice for situations that require rapid development.