ZoomCharts Documentation
Switch to Version 2.0 (BETA)
Details of the Focusnodes navigation algorithm
Overview
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:
- 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. - The first node (most recently focused) will get assigned a value of
max(
focusNodeExpansionRadius
+ 1, 1)
. - The last node (least recently focused) will get assigned the value of
max(
focusNodeTailExpansionRadius
+ 1, 1)
. - The rest of the nodes between the first and the last will receive values between these two, distributed linearly.
- 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 minimumBase 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; } }