Skip to content

Network Chart

A network graph is designed to allow the visualization of relational information. ChartFactor has two types of network charts: one that comes in the standard library called Graph and one more flexible and sofisticated called Network that comes in its own package.

Comparison between the Graph Chart and the Network Chart

Feature Graph Chart Network Chart
Simple Graph Visualization Yes Yes
Directed Graph No Yes
Nodes Custom Styles No Yes
Edges Custom Styles No Yes
Static layout (specific x and y) Yes Yes
Force Layout Yes Yes
Circle Layout No Yes
Grid Layout No Yes
CoSE Layout No Yes
Dagre Layout No Yes
Spread Layout No Yes
Concentric Layout No Yes
Custom event listeners No Yes

Creating a Basic Graph Chart with static data

In this example we will create a Graph chart and feed it with static data. Note that only edge information is required to render the graph.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var data = {
    'nodes': [
        {
            'color': '#4f19c7',
            'label': 'jquery',
            'y': -404.26147,
            'x': -739.36383,
            'id': 'jquery',
            'value': 4.7252817
        },
        ...
        {
            'color': '#c71969',
            'label': 'backbone',
            'y': -862.7517,
            'x': -134.2215,
            'id': 'backbone',
            'value': 6.1554675
    }],
    'edges': [
        {
            'source': 'jquery',
            'target': 'jsdom',
            'value': 1
        },
        ...
        {
            'source': 'jquery',
            'target': 'xmlhttprequest',
            'value': 1
    }]
}

var edgesInfo = {
    'width': 0.6,
    'curveness': 0.2,
    'opacity': 0.7
};
var nodesInfo = {
    'min': 0.1,
    'max': 60
};

var chart = cf
    .create()
    .graph('Graph')
    .element('chart')
    .set('layout', 'force')
    .set('loadIndicator', 'graph-loading')
    .set('layout', 'force')
    .set('edgesInfo', edgesInfo)
    .set('nodesInfo', nodesInfo)
    .data(data);

chart.execute();

The previous code will render the Graph Chart below:

SGC

Creating a Graph chart with a Elasticsearch datasource

It is recommended before reading this section, to read the documentation on Multiple Queries (click here). To make real queries in graphs, we have to make use of the chartfactor's multiple query capabilities as we show below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var edgesInfo = {
    width: 0.6,
    curveness: 0.2,
    opacity: 0.7
};
var nodesInfo = {
    min: 0.1,
    max: 60
};

let metric = cf.Metric('size', 'sum');
let metrics = [metric, cf.Metric('count')];
let grp = cf.Attribute('id').limit(1200).sort('asc', metrics[0]);

let nodesHandler = function (result) {
    result.data.nodes.forEach(n => {
        n.id = n.group[0];
        n.value = n.current.metrics[metric._name][metric._func];
    });
    result.keep = true;
    return result;
};

let edgesHandler = function (result) {
    result.data.edges.forEach(e => {
        e.source = e.group[0];
        e.target = e.group[1];
        e.value = e.current.metrics[metric._name][metric._func];
    });
    result.keep = true;
    return result;
};

let myChart = cf
    .create('nodes')
    .provider('Elasticsearch')
    .source('npmdepgraph-nodes')
    .processWith(nodesHandler.bind(this))
    .groupby(grp)
    .metrics(...metrics)
    .create('edges')
    .provider('Elasticsearch')
    .source('npmdepgraph-links')
    .processWith(edgesHandler.bind(this))
    .rows('source', 'target')
    .metrics(...metrics)
    .graph('Graph')
    .set('layout', 'force')
    .set('edgesInfo', edgesInfo)
    .set('nodesInfo', nodesInfo)
    .element('chart')
    .execute();

Note

It is mandatory that the query to retrieve the node is called "nodes" and the query to retrieve edges is called "edges". If you want to see an example of this code in action click here.

Creating a Basic Network Chart with static data

In this example we will create a Network chart and feed it with static data. Note that only edge information is required to render the network. Node information is optional.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    var data = {
        Edges: [ /* is mandatory call it "Edges" and each object have to have a source and a target. */
            {source: 'a', target: 'b'},
            {source: 'a', target: 'c'},
            {source: 'a', target: 'd'},
            {source: 'c', target: 'e'},
            {source: 'c', target: 'f'}
        ],
        Nodes: [] /* is mandatory call it "Nodes" and if no extra information is needed for the node (size, color, image background, etc) the array could by empty */
    }

    cf.create()
    .graph('Network')  // Setting the visual type
    .element('chart')  // Setting the id of the DOM element in where should be render the Network
    .set('layout', {name: 'dagre'}) // Setting the Network layout (see layout section)
    .data(data)        // Injecting static data
    .execute();        // Executing the visual

The previous code will render the Network Chart below:

NC1

Adding custom styles

The next example uses the same data as the previous example. In addition, it defines a style array to be assigned to the network chart. Each object in the style array must have a selector attribute (node, edge, or id) and a style attribute containing css-like styles to be applied to network chart.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    var style = [
        {selector: 'node',
            style: {
                'label': 'data(id)',
                'background-color': '#4292B3'
            }
        }, {selector: 'edge',
            style: {
                'line-color': '#4292B3'
            }
        }
    ];
    cf.create()
    .graph('Network')
    .element('chart')
    .set('layout', {name: 'dagre'})
    .set('style', style)  // Setting the style to the Network chart.
    .data(data)
    .execute();

The previous code will render the Network Chart below:

NC2

Using the data to style nodes and edges

We can use properties of the data to specify styles of nodes and edges. Use the syntax 'data(propertyName)' to assign the value of that property to a style object. Let's modify our previous example to use this feature:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    var style = [
        {selector: 'node',
            style: {
                'label': 'data(id)',  // Using the id of the node as label
                'background-color': 'data(color)', /* Using the color property of the data object like the background color of the node */
                'height': 'data(size)', // Using size for height
                'width': 'data(size)'   // Using size for width
            } 
        }, {selector: 'edge',
            style: {
                'line-color': 'data(color)',  // Using the property color of the edge data object as line-color
                'curve-style': 'unbundled-bezier'  // This make the line curved
            }
        }
    ];
    var data = {
        /* Now we will modify a little bit the edges data objects to store a color for each edge */
        Edges: [
            {source: 'a', target: 'b', color: 'red'},
            {source: 'a', target: 'c', color: 'red'},
            {source: 'a', target: 'd', color: 'red'},
            {source: 'c', target: 'e', color: 'green'},
            {source: 'c', target: 'f', color: 'green'}
        ],
        /* And modify the nodes data objects to store a color and size for each node */
        Nodes: [
            {id: 'a', size: 10, color: 'blue'},
            {id: 'b', size: 20, color: 'yellow'},
            {id: 'c', size: 15, color: 'black'},
            {id: 'd', size: 30, color: 'orange'},
            {id: 'e', size: 40, color: 'purple'},
            {id: 'f', size: 50, color: 'gray'}
        ]
    };

    cf.create()
    .graph('Network')
    .element('chart')
    .set('layout', {name: 'dagre'})
    .set('style', style)
    .data(data)
    .execute();

The previous code will render the Network Chart below:

NC3

Setting arrows to edges

To set arrows on the edges we can modify the properties \<position>-arrow-shape, \<position>-arrow-color, \<position>-arrow-fill and \<position>-arrow-scale.

Valid values for \<position> are:    source : Pointing towards the source node, at the end of the edge.    mid-source : Pointing towards the source node, at the middle of the edge.    target : Pointing towards the target node, at the end of the edge.    mid-target: Pointing towards the target node, at the middle of the edge.

Valid shapes are:    triangle    triangle-tee    triangle-cross    triangle-backcurve    vee    tee    square    circle    diamond    none

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    var style = [
        {selector: 'node',
            style: {
                'label': 'data(id)',
                'background-color': 'data(color)',
                'height': 'data(size)',
                'width': 'data(size)'
            }
        }, {selector: 'edge',
            style: {
                'line-color': 'data(color)',
                'curve-style': 'unbundled-bezier',
                'target-arrow-shape': 'triangle',
                'target-arrow-color': 'black'
            }
        }
    ];

NC4

Images in Nodes

To put an image in a node you must modify the properties 'background-image' and 'background-fit' in the style of the node as well as specify the path of the image in the data of the node or create a custom selector using the id of the node to specify the path of the background image as shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    var style = [
        {selector: 'node',
            style: {
                'label': 'data(label)',
                'background-color': 'data(color)',
                'height': 'data(size)',
                'width': 'data(size)',
                'background-image': 'data(backgroundImage)',
                'background-fit': 'cover',
                'border-color': '#000',
                'border-width': 1,
                'border-opacity': 0.5
            }
        }, {selector: '#a',
            style: {
                'background-image': 'https://farm8.staticflickr.com/7272/7633179468_3e19e45a0c_b.jpg'
            }
        }, {selector: 'edge',
            style: {
                'line-color': 'black',
                'target-arrow-shape': 'triangle',
                'target-arrow-color': 'black'
            }
        }
    ];
    var data = {
        Edges: [
            {source: 'a', target: 'b', color: 'red'},
            {source: 'a', target: 'c', color: 'red'}
        ],
        Nodes: [{
            id: 'a',
            label: 'bird',
            size: 60,
            color: 'blue'
        }, {
            id: 'b',
            size: 60,
            label: 'cat',
            color: 'yellow',
            backgroundImage: 'https://farm2.staticflickr.com/1261/1413379559_412a540d29_b.jpg'
        }, {
            id: 'c',
            size: 60,
            color: 'black',
            label: 'rose',
            backgroundImage: 'https://farm6.staticflickr.com/5109/5817854163_eaccd688f5_b.jpg'
        }
        ]
    };

NC5

Custom Tooltips

Using the same data from the previous example we can develop a small example of how to implement custom tooltips that show the size in addition to the node identifier.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    var tooltipBuilder = function (info) {
        var result = '';
        switch (info.type) {
            case 'node' : {
                result = `<b>Node:</b> ${info.data.id}<br />`;
                result += `<b>Size:</b> ${info.data.size}<br />`;
                break;
            }
            case 'edge' : {
                result = `<b>Edge:</b> ${info.data.source} > ${info.data.target}<br />`;
            }
        }
        return result;
    };
    cf.create()
    .graph('Network')
    .element('chart')
    .set('layout', {name: 'dagre'})
    .set('style', style)
    .set('tooltipBuilder', tooltipBuilder)
    .data(data)
    .execute();

The previous code will render the Tooltips below:

NC6

Available layouts

Layout Name Node Position Config. Variables Best Scenarios
preset preset on data none when you have the positions that each node will have
random random none if is a small graph
force calculated Edge length, Node spacing good to detect association and orphan nodes
circle on the perimeter of a circle none if is a small graph
grid on a grid spacingFactor a graph without many connections
cose calculated idealEdgeLength, nodeOverlap, nodeRepulsion, edgeElasticity, gravity, etc. a graph without many connections
dagre calculated none non-cyclic graph
spread calculated minDist a graph without many connections
concentric calculated concentric (func), levelWidth (func) graphs in which few nodes have relationships with many nodes

In this demonstration you can try each of the layouts. (click here)

Registering a click listener

Setting a listener to an event in a graph is done in a similar way as to any other chartfactor visualization. Here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    var clickListener = function (event) {
        alert(event.data.id);
    };
    var chart = cf.create()
    .graph('Network')
    .element('chart')
    .set('layout', {name: 'dagre'})
    .set('style', style)
    .data(data);

    chart.on('click', clickListener);
    chart.execute();

Consulting data providers:

It is recommended before reading this section, to read the documentation on Multiple Queries (click here). To make real queries in graphs, we have to make use of the chartfactor's multiple query capabilities as we show below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    var nodesHandler = function (result) {
        // The callback to be used when executing the query to get the nodes

        // In this example we set custom color for all nodes.
        result.data.Nodes.forEach(x => {
            x.color = '#4292B3';
        });

        // The data should be persistent (important)
        result.keep = true;
        return result;
    };

    var edgesHandler = function (result) {
        // The callback to be used when executing the query to get the edges

        // In this example we set a filter for get only the nodes info that is needed, 
        // and a customs color for the edge line.
        let nodes = [];
        result.data.Edges.forEach(x => {
            if (nodes.indexOf(x.source) === -1) nodes.push(x.source);
            if (nodes.indexOf(x.target) === -1) nodes.push(x.target);
            x.color = '#4292B3';
        });

        result.filters = (nodes && nodes.length > 0) ? [cf.Filter('id').values(nodes)] : [];

        return result;
    };

    cf.create('Edges')        // Query to retrieve the edges info.
    .provider('ProviderName')
    .source('edgesSourceName')
    .fields('source', 'target')
    .processWith(edgesHandler.bind(this))
    .create('Nodes')          // Query to retrieve the nodes info.
    .provider('ProviderName')   // Could be a different provider.
    .source('nodesSourceName')
    .fields('id', 'value', 'y', 'x', 'label', 'color')    // All this attributes will be added to the node data.
    .processWith(nodesHandler.bind(this))
    .element('chart')
    .graph('Network')

Note

It is mandatory that the query to retrieve the node is called "Nodes" and the query to retrieve edges is called "Edges". If you want to see an example of this code in action click here.

Inject urls of dependency libraries:

In order to not increase the size of the library with the dependencies of an uncommon visualization, by default, ChartFactor does not load the dependencies of the network graph. These are loaded when the graph is instantiated and are left in cache. These are loaded from the official CDN but if you are behind a corporate firewall or for reasons of speed you want to serve them on your own servers you can inject the addresses of these libraries and they will be loaded in the following way. Example:

1
2
3
4
    <script>
        window.colaUrl = 'https://chartfactor.com/wp-content/lib/cola.min.js';
    </script>
    <script src="../../lib/CFToolkit.min.js"></script>

Note

It is recommended to define global variables to inject the dependencies before loading the library. The following table shows all the dependencies that can be injected

Vairable Default Url
colaUrl http://marvl.infotech.monash.edu/webcola/cola.min.js
cyColaUrl https://cdn.jsdelivr.net/npm/cytoscape-cola@2.1.0/cytoscape-cola.min.js
cytoscapeUrl https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.2.8/cytoscape.min.js
dagreUrl https://cdn.rawgit.com/cpettitt/dagre/v0.7.4/dist/dagre.min.js
cyDagreUrl https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js
weaverUrl https://cdn.rawgit.com/maxkfranz/weaver/v1.2.0/dist/weaver.min.js
cySpreadUrl https://cdn.rawgit.com/cytoscape/cytoscape.js-spread/1.3.1/cytoscape-spread.js
cyCanvasUrl https://unpkg.com/cytoscape-canvas/dist/cytoscape-canvas.js

Or you can inject a bundle with all dependencies minified:

Vairable Default Url
graphBundleUrl https://chartfactor.com/wp-content/lib/cf_graph_bundle.min.js