The graph visualization in this article is supported by G6.


G6 — Professional graph visualization engine. Make relational data simple and transparent to give users Insight.


G6 website:
antv.alipay.com/zh-cn/g…. Welcome to follow.


Making:
github.com/antvis/g6. Welcome to Star.

Most graph data is presented as a node-link Diagram when visualized. Point-plot is especially suitable for the display of relational data such as traffic network maps, the nodes of which usually have geographical location information, such as migration maps, migration maps, route maps, etc.



(left) Figure 1. Route map of France. (right) Chart 2. Us route chart.



(left) Figure 3. World Network IXP peer diagram. (Right) Figure 4. Immigration to the United States.

The problem

While point-and-line graphs provide intuitive visualizations, Visual Clutter can quickly become a serious problem when data has lots of nodes and edges. Visual confusion in point-graph is usually the direct result of edge congestion. However, in data such as traffic network, node position usually has a clearly defined meaning, and it is not always possible to modify node position to reduce visual confusion, as shown in FIG. 1-4. Therefore, many researchers in the academic world have designed various methods to reduce the above visual chaos through Edge optimization, among which Edge Bundling is widely studied and applied.

For example, in the following complex US route data set, nodes represent US cities with coordinates and latitude and longitude information; An edge represents a course:

{
    "nodes": [{
        "x": 922.24444,"y": 347.29444."id": "0"."lon": 92.224444,"lat": 34.729444}, {"x": 922.24444,"y": 347.29444."id": "1"."lon": 92.224444,"lat": 34.729444},... ] ."edges": [{"source": "0"."target": "21"."id": "e0"
      }, {
        "source": "2"."target": "13"."id": "e1"},... ] }Copy the code


If you simply rendered nodes and edges using G6, you would get something like this:

FIG. 5.G6 Render raw data results

We found that simply rendering the data resulted in a visually confusing crisscrossing route through a dense city, making it difficult to see details or the overall trend of the route.


Looking forward to effect

We hope to reduce the visual confusion of Figure 5 by edge binding, so as to clear the overall trend and structure of the figure, highlight cities with frequent air routes, which may be important transportation hubs, and display more statistical information for the observer to analyze. With the HELP of G6, we can achieve the following effects. With edge binding, the edge confusion is reduced and the color maps the flight direction (departure (orange-red) and landing (cyan)). The node size represents the total number of flights arriving and leaving the city, and each node uses a pie chart to show the proportion of flights arriving (orange-red) and departing (cyan). And added hover interaction, using tooltip to show the latitude and longitude of each city.

Looking forward to the renderings and tooltip effects.


Implementation steps

Statistical essential information

First of all, we use simple JS to calculate the total degree (degree, namely the total number of routes in and out of the city), outDegree (namely, the number of routes flying out of the city) and inbound degree (inDegree, namely, the number of routes flying into the city) of each node according to the data, so as to prepare for the subsequent mapping to the node.

const nodes = data.nodes;
const edges = data.edges;
nodes.forEach(n => {
  n.y = -n.y;
  n.degree = 0;
  n.inDegree = 0;
  n.outDegree = 0;
});
// compute the degree of each node
const nodeIdMap = new Map();
nodes.forEach(node => {
  nodeIdMap.set(node.id, node);
});
edges.forEach(e => {
  const source = nodeIdMap.get(e.source);
  const target = nodeIdMap.get(e.target);
  source.outDegree++;
  target.inDegree++;
  source.degree++;
  target.degree++;
});
let maxDegree = -9999, minDegree = 9999;
nodes.forEach(n => {
  if (maxDegree < n.degree) maxDegree = n.degree;
  if(minDegree > n.degree) minDegree = n.degree; }); const sizeRange = [1, 20]; const degreeDataRange = [minDegree, maxDegree]; // After the degree property is mapped to the scope sizeRange, write it to the 'size' property of the elements in Nodes. ScaleNodeProp (Nodes, degreeDataRange,'size'.'degree', degreeDataRange, sizeRange);Copy the code



The scaleNodeProp() method maps the specified refPropName node property outRange according to the given value range to another property propName:

/** * Mapping property * @param {array} Nodes object array * @param {string} propName Specifies the property name written * @param {string} refPropName Specifies the normalized property name * @param {array} dataRange Normalized attribute value range [min, Max] * @param {array} outRange Normalized attribute value range [min, Max] */function scaleNodeProp(nodes, propName, refPropName, dataRange, outRange) {
  const outLength = outRange[1] - outRange[0];
  const dataLength = dataRange[1] - dataRange[0];
  nodes.forEach(n => {
    n[propName] = (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0];
  });
}Copy the code

Through the above two codes, we have mapped the normalized degree to the node size.


Instantiate the edge binding plug-in

The Edge binding plug-in provided in G6 is based on the implementation of FEDB (force-directed Edge Bundling for Graph Visualization). You can adjust the effect of edge binding by adjusting the parameters.

Const edgeBundling = new Bundling({bundleThreshold: 0.6, // The lower the value, the higher the similarity of the edges bound together, that is, the fewer edges bound together. K: 100 // binding strength});Copy the code


Customize pie chart nodes

In the first step, we have mapped the total degree of each node for the node size size. To show in more detail the proportion of inbound and outbound flights in each city, we want to display a pie chart for each node. For example,The orange sector represents the proportion of flights coming into the city and the cyan represents the proportion of flights coming out of the city. G6’s native circle, rect and other node shapes do not meet this requirement, but G6 provides an extension mechanism for nodes. With the following code snippet, you can register a custom node in G6:

const lightBlue = 'rgb(119, 243, 252)';
const lightOrange = 'rgb(230, 100, 64)'; // Register a custom node type named pie-node g6.registerNode ('pie-node', { drawShape: (cfg, group) => { const radius = cfg.size / 2; // Node radius constinPercentage = cfg.inDegree / cfg.degree; // Degree to total degree constinAngle = inPercentage * Math.PI * 2; // outAngle = math.pi * 2 - const outAngle = math.pi * 2 -inAngle; // The degree in the pie chart is constinArcEnd = [radius * Math.cos(inAngle), radius * Math.sin(inAngle)]; // Enter the end position of the pie chart arclet isInBigArc = 1, isOutBigArc = 0;
    if (inAngle > Math.PI) { isInBigArc = 0; isOutBigArc = 1; } const fanIn = group.addShape(const fanIn = group.addShape('path', {
      attrs: {
        path: [
          [ 'M', radius, 0 ],
          [ 'A', radius, radius, 0, isInBigArc, 0, inArcEnd[0], inArcEnd[1] ],
          [ 'L', 0, 0], ['B'] ], lineWidth: 0, fill: lightOrange } }); Const fanOut = group. AddShape (const fanOut = group.'path', {
      attrs: {
        path: [
          [ 'M'.inArcEnd[0], inArcEnd[1] ],
          [ 'A', radius, radius, 0, isOutBigArc, 0, radius, 0 ],
          [ 'L', 0, 0], ['B'] ], lineWidth: 0, fill: lightBlue } }); / / return keyshapereturnfanIn; }},"single-shape"
);Copy the code

Thus, we registered a node type named pie-node in G6.


Instantiate the figure

In this step, we specify the edge binding plug-in, node type (the pie-Node we just customized), node style, and edge style (gradient) when we instantiate the diagram.

Const edgeBundling = new Bundling({bundleThreshold: 0.6, // The lower the value, the higher the similarity of the edges bound together, that is, the fewer edges bound together. K: 100 // binding strength}); const graph = new G6.Graph({ container:'mountNode', width: 1000, height: 800, plugins: [edgeBundling],true,
   defaultNode: {
     size: 3,
     color: 'steelblue',
     fill: 'steelblue'
   },
   nodeStyle: {
     default: {
       lineWidth: 0,
       fill: 'steelblue'}}, edgeStyle: {default: {lineWidth: 0.7, strokeOpacity: 0.1,'l(0) 0:' + llightBlue16 + '1' + llightOrange16
    }
  }
 });Copy the code


Here, the starting color is llightBlue16, and the ending color is LlightTorange16:

const llightBlue16 = '#C8FDFC';
const llightOrange16 = '#FFAA86';Copy the code


To match the node and edge colors, set the body color of the page to black:

<style>
  body{
    background: rgb(0, 0, 0);
  }
</style>Copy the code

Perform binding and rendering

With the graph and edgeBundling instances in place, we execute the following code to bind and read and render the graph:

edgeBundling.bundling(data); // Execute the binding operation graph.data(data); graph.render();Copy the code


Set up tooltips and interactions

Using tooltip, you can display other attribute values of a node when the mouse hover over that node. Start by styling the tooltip in the HTML:

<style>
  .g6-tooltip {
    border: 1px solid #e2e2e2;
    border-radius: 4px;
    font-size: 12px;
    color: # 545454;Background: rgba(255, 255, 255, 0.9); padding: 10px 8px; box-shadow: rgb(174, 174, 174) 0px 0px 10px; } </style>Copy the code


Then, when you instantiate the graph in the previous step, add a modes configuration to the parameters, as follows: drag-canvas drag, Zoom-canvas zoom, and toolTip. The formatText function specifies the text content to be displayed in tooltip:

 modes: {
   default: [ 'drag-canvas'.'zoom-canvas', {
     type: 'tooltip',
     formatText(model) {
       const text = 'Longitude: ' + model.lon + '\n Latitude: ' + model.lat;
       return text;
     },
     shouldUpdate: e => {
       return true; }}}]Copy the code


This way, when the mouse moves over the node, tooltips with latitude and longitude information will appear:

tooltip


At the same time, you can drag and drop the canvas:

Scale and drag the canvas.


Analysis of the

Final rendering. The node size represents the total number of air routes flying into and out of the city. The node pie chart shows the proportion of flying out and flying in routes (salmon-red for flying in, cyan for flying out). The gradient of the edge represents the direction of the flight. Beginning: cyan; End: orange red.


Finally, let’s analyze the following information from the final result graph:

  • The big nodes are mainly concentrated in the Middle East. According to their latitude and longitude, it can be inferred that these cities are: Atlanta, New York, Chicago, Houston, Kansas, etc. These cities are important transportation hubs in the United States.
  • The eastern part of the United States is mostly orange, indicating that there are more inbound flights in eastern cities.
  • On the contrary, there are more outbound flights in western cities;
  • Overall flight direction from east to west;
  • Routes in the east were also more dense and frequent than in the west;
  • The West Coast has more flights from Seattle and Portland to Los Angeles.

This finding is easy to explain: the Eastern United States is the economic and political center of the United States.


The complete code

🔗 Full code

AntV G6 also allows for even more cool graphical visualizations.

Stay tuned and follow star at our Github: github.com/antvis/G6