ZoomCharts Documentation

Details of the Focusnodes navigation algorithm


The "focusnodes" navigation mode is the most advanced mode of ZoomCharts. Its goal is to make it easier for the user to explore large graphs of data. The way this is done is by designating some of the nodes as "focus nodes". Other nodes are then automatically expanded around these focus nodes. The user can then expand even more nodes, or change the focus nodes and start browsing some other part of the graph. Only nodes that are visibly connected to a focus node will be shown. Thus, disjoint subgraphs are possible, but each will have at least 1 focus node in it.

To further improve the appearance of the graph and make the navigation more intuitive, each visible node and link also gets a non-negative "relevance" value. In general, relevance is the largest for the focus nodes, and then gets smaller for the nodes which are further away from them. However when a user manually expands a node, that node and its neighbors also get a small boost in relevance. Link relevance is calculated as the smallest relevance from either end node. This can be used for example to alter the size, color, or other visual attributes of nodes and links. Out of the box NetChart has the option to make the nodes and links on the edges of the visible graph smaller and faded out, however other effects can be achieved through nodeStyleFunction and linkStyleFunction. The built in effect can be enabled by setting focusAutoFadeout setting to true.

Algorithm details

First the relevance, because visibility depends on it. Each node has 3 attributes which are used to calculate the final relevance value: Base relevance, Expanded flag, Hidden flag. None of these values are accessible through the API and the flags are mutually exclusive - only one (or none) can be set at any time.

The Hidden flag gets set when the user either explicitly hides a node, or collapses a neighboring node. Nodes with the Hidden flag are not visible and do not participate in any relevance calculations. Hiding a focus node also automatically unfocuses it. Thus it is possible to hide a focus node only if there are more than minNumberOfFocusNodes focused nodes. The Hidden flag will get cleared when the user expands a neighbor node and makes this node visible again.

The Expanded flag gets set when the user expands a node, and its Base relevance is less than 2. The Expanded flag is cleared when the node is hidden, collapsed, or its Base relevance increases to at least 2. Expanding a node is only possible if it has hidden neighbors.

Collapsing a node hides all neighbor nodes with Base relevance less than the node's own Base relevance. Thus it is only possible if there are any such neighbor nodes.

Both Hidden flag and Expanded flag will get cleared when the node no longer has a visible path to any focus node. This will also hide the node.

Base relevance recalculation happens whenever there are changes which could affect it. This includes things like focusing and unfocusing nodes; collapsing, expanding and hiding the nodes; new data arriving or old data being removed; and chart initialization. At the start, all nodes are assigned a Base relevance value of -Infinity. Then the focus nodes are assigned a Base relevance value in the following manner:

  1. All focus nodes are sorted chronologically by the time they were focused. Initial nodes will keep the order which was specified in the initialNodes setting.
  2. The first node (most recently focused) will get assigned a value of max(focusNodeExpansionRadius+ 1, 1).
  3. The last node (least recently focused) will get assigned the value of max(focusNodeTailExpansionRadius+ 1, 1).
  4. The rest of the nodes between the first and the last will receive values between these two, distributed linearly.
  5. Finally, if any node had an explicitly specified relevance value from an addFocusNode() API call, that value will override the calculated value, but only if it is not less than 1. In other words, every focus node is guaranteed to have a minimum Base relevance value of 1.

After the focus nodes have been assigned their Base relevance values, the Base relevance values for all the other nodes are calculated. The calculation is recursive and ensures that each node has a Base relevance value which is the maximum Base relevance value of its neighbors minus 1. It can also affect focus nodes which already have an assigned Base relevance value. If a focus node has a neighbor node with a Base relevance value which is greater than the focus node's own Base relevance value plus 1, then the focus node's Base relevance value is increased. A focus node's Base relevance will never drop below the value which was calculated for it at the start.

The calculation will affect all nodes which are visible. That means that some nodes may have negative Base relevance values. Also, if there are any nodes which are hidden, don't have the Hidden flag set, and their Base relevance would be positive, they get shown. On the other hand, if the Base relevance of a node is negative, and it neither has the Expanded flag set, nor has a neighbor with the Expanded flag, it will be hidden. Nodes with the Hidden flag set are never shown and don't participate in the recursive calculations.

Finally, the actual relevance value for a node will be the same as Base relevance, except that nodes with the Expanded flag set will have a minimum relevance value of 2, and their neighbors will have a minimum relevance value of 1. This ensures that all nodes which are visible will have a positive relevance and that manually expanded nodes will have a greater relevance than their expanded neighbors.

Relevance demo

This demo shows the relevance calculation results and allows to experiment with it. The node label shows its relevance and the node radius also reflects it. The initialization parameters are:

navigation: {
	mode: "focusnodes",
	focusNodeExpansionRadius: 3,
	focusNodeTailExpansionRadius: 0.3,
	numberOfFocusNodes: 3,
	initialNodes: ["1886"]
style: {
	nodeStyleFunction: function (node) {
		node.label = node.relevance.toString();
		node.radius *= node.relevance;