Network Visualization Javascript Library
This library combines a number of D3 and custom features into a dynamic graph visualization library. It allows you to work with collapsible clusters as a way of grouping nodes.
It is a plain JavaScript library. It can be included with a simple <script>
tag, with require.js or amd, or it can be used with Meteor.
and
The library works as a standalone Meteor package (installed with meteor add futurescaper:futuregrapher
) that allows for easy creation and manipulation of a D3 force-directed graph.
The package exists on Atmosphere at:
https://atmospherejs.com/futurescaper/futuregrapher
To use Futuregrapher, create an instance of the GraphVis
class. The constructor for this takes two parameters: renderer and options. The renderer needs to be an instance of a renderer. Currently only one renderer exists: SvgRenderer
.
The minimalist setup looks like this:
<div id="graph-vis-container"></div>
$(function () {
var svgRenderer = new SvgRenderer($("#graph-vis-container"), {});
var graphVis = new GraphVis(svgRenderer, {});
});
The renderer will create an svg element inside the graph-vis-container div. Nothing will show up, this is an empty graph. In order to show something, you need at least one node. To do this, you need to instantiate the class VisNode
. This class, along with VisLink
and VisCluster
form the elements that you supply to your GraphVis
instance to make something appear.
To add a node and show it, add the following two lines:
var node = new VisNode("myNode");
graphVis.update([node], [], []);
This will create a node with the id myNode. We then update the graph. The update function takes three arrays: visNodes
, visLinks
and visClusters
. In this example, we just give it our node.
Now, the visualization is boring at this stage. We haven't told our GraphVis anything about what we want our nodes to look like, so it will create our node in a random position, with the default radius and color (10 and grey). We can change that, but first let's create another node and link between them.
var node1 = new VisNode("node1");
var node2 = new VisNode("node2");
var link = new VisLink("node1", "node2");
graphVis.update([node1, node2], [link], []);
This should create a graph consisting of two nodes connected by a link. Still bland looking, but at least it's a small graph.
Typically we will want to map visual properties of nodes, links and clusters to values in our data, either directly or through some transformation.
Say you have some data that looks like this:
var people = [
{ name: "John", vehicle: "car", age: 45 },
{ name: "Lisa", vehicle: "car", age: 37 },
{ name: "Alice", vehicle: "bike", age: 39 }
];
You might want to visualize these people as nodes where the color represents the vehicle and the size (radius) represents the age. First we would have to create a VisNode for each person and attach the object somehow. Let's take a look at the VisNode constructor:
VisNode = function (id, data, clusterId)
The first parameter, id
, needs to be a unique identifier. The data
parameter can be anything we want, it allows us to attach an object to the node. We will use this to attach our person object to the nodes. We will ignore the clusterId
parameter for now.
Now, the data
property works in conjunction with a describer function. The describer function for nodes should follow this pattern:
function describeVisNode(visNode, radiusFactor) {
// Figure out what the node should look like here..
return {
color: ...,
radius: ...,
opacity: ...,
...
}
}
So the complete code to visualize our people structure would be like this:
var people = [
{ name: "John", vehicle: "car", age: 45 },
{ name: "Lisa", vehicle: "car", age: 37 },
{ name: "Alice", vehicle: "bike", age: 39 }
];
var vehicleColors = { car: "red", bike: "blue" };
function describeVisNode(visNode, radiusFactor) {
var color = vehicleColors[visNode.data.vehicle];
var radius = visNode.data.age / 2;
return {
color: color,
radius: radius
};
}
var svgRenderer = new SvgRenderer($("#graph-vis-container"), {});
var graphVis = new GraphVis(svgRenderer, { describeVisNode: describeVisNode });
var visNodes = _.map(people, function (person) { return new VisNode(person.name, person); });
graphVis.update(visNodes, [], []);
This should show us three dots on the screen now, two red ones and a blue one. They should have slightly different radii.
This library visualizes three types of data: nodes, links and clusters. The nodes are visualized as circles. A link indicates that two nodes are connected, and is represented by a line between the two circles.
Finally, there is a concept of clusters. Clusters are another way of indicating a relationship between a group of nodes. A node can belong to zero or one clusters. Clusters can be visualized as collapsed or expanded. Collapsed clusters are represented by a single circle. Expanded clusters show circles for all the member nodes, as well as a convex hull surrounding them to indicate the relationship. If a VisNode has a clusterId, a corresponding VisCluster must exist.
It's all well and good with static nodes, but you will probably want to apply the nice D3 force. Doing that is rather simple. You call GraphVis.startForce()
. This will start the physics simulation. It will cool down and eventually stop but you can keep it going using GraphVis.resumeForce()
.
The dynamics settings can be set in the options.forceParameters
, and they can be modified afterwards by calling GraphVis.updateForceDynamics
.
In order to make the visualization interactive, you can attach event handlers. The default event handlers allow you to zoom and pan, and they allow you to expand or collapse clusters. You can customize this behavior by setting specific event handlers in the options
parameter to the GraphVis
constructor.
GraphVis(renderer, options)
This is the constructor for the GraphVis
class. The renderer
parameter must be an instance of a renderer like SvgRenderer
.
###Options
The options
paramter is a plain JS object. If it is not supplied, or certain properties are undefined, default values will be used. The following options are allowed:
####General Settings
enableZoom
- Enable zooming (default: true)enablePan
- Enable panning (default: true)enableForce
- Enable physics simulation (Note: you still need to callstartForce()
to start it) (default: true)forceParameters
- object containing the parameters to d3.force. Details below.enableCollisionDetection
- Prevent nodes from colliding when force is applied (default: true)enableClusterForce
- Add gravity to clusters in an attempt to keep clusters separate (default: false)zoomExtent
- Allowed zoom range. (default: [0.25, 4])zoomDensityScale
- transformation applied to determine radii, font sizes etc.updateOnlyPositionsOnZoom
- set to false if you want your describer functions called when zooming (default: true)updateOnlyPositionsOnTick
- set to false if you want your describer functions called on force ticks (default: true)
The two last properties allow you to describe your graph to more detail. However, this will trigger an entire update()
call every time the user zooms/pans or when the force ticks. If your graph is large, this will perform poorly.
The default value for zoomDensityScale
is d3.scale.linear().domain([0.25, 4]).range([0.5, 2])
. This means that when the user zooms out to 0.25, radii will be 0.5 times the value from the description. You can supply any function that takes a zoom factor and returns a radius factor. This is the factor that will be applied to the "density" of the visual elements, such as radii, font size, link thickness and so on. If you set zoomDensityScale
to function () { return 1; }
, the density will remain constant and zooming will only cause elements to move closer to each other or further apart.
The forceParameters
property can have the following subproperties:
linkDistance
- force.linkDistance (defaut: 20)linkStrength
- force.linkStrength (default: 1)friction
- force.friction (default: 0.9)charge
- force.charge (default: -30)chargeDistance
- force.chargeDistance (default: Infinity)theta
- force.theta (default: 0.8)gravity
- force.gravity (default: 0.1)
####Event Handling
You can supply the following event handlers in options
:
onUpdatePreProcess
- called from update() before processing vis*-elementsonUpdatePreRender
- called from update() before renderingonClick
- event handler for when the user clicks the graphonMouseDown
- event handler for when the user presses the mouse buttononMouseUp
- event handler for when the user releases the mouse buttononMouseMove
- event handler for when the user moves the mouseonNodeClick
- event handler for node clicksonNodeDoubleClick
- event handler for node double-clicksonNodeMouseOver
- event handler for hovering over a nodeonNodeMouseOut
- event handler for when the mouse leaves a nodeonNodeDragStart
- event handler for when the user starts dragging a nodeonNodeDrag
- event handler called when the user is dragging a nodeonNodeDragEnd
- event handler for when the user drops a node somewhereonClusterNodeClick
- event handler for clicks on cluster placeholder-nodesonClusterNodeDoubleClick
- event handler for doubleclick on placeholder-node (default: expand cluster)onClusterNodeMouseOver
- event handler for hovering placeholder-nodesonClusterNodeMouseOut
- event handler for leaving placeholder-nodesonClusterNodeDragStart
- event handler for when the user starts dragging a placeholder-nodeonClusterNodeDrag
- event handler for dragging placeholder-nodesonClusterNodeDragEnd
- event handler for when the user drops a placeholder-nodeonLinkClick
- event handler for link clicksonLinkDoubleClick
- event handler for link double-clicksonLinkMouseOver
- event handler for hovering over a linkonLinkMouseOut
- event handler for when the mouse leaves a linkonClusterClick
- event handler for when the user clicks on a cluster hullonClusterDoubleClick
- event handler for cluster hull doubleclick (default: collapse cluster)onClusterMouseOver
- event handler for hovering a cluster hullonClusterMouseOut
- event handler for leaving a cluster hull
####Visual element describing
The options
object allows the following properties for describing visual elements (See the section below on describing visual elements):
defaultNodeDescription
- default description to use for nodesdescribeVisNode
- describer function to use for nodesdefaultLinkDescription
- default description for linksdescribeVisLink
- describer function for linksdefaultCollapsedClusterDescription
- default description for collapsed clustersdescribeCollapsedCluster
- describer function for collapsed clustersdefaultExpandedClusterDescription
- default description for expanded clustersdescribeExpandedCluster
- describer function for expanded clusters
#####describeVisNode
This function can determine visual properties for the NodeCircle
and LabelText
created from a VisNode.
The signature is: describeVisNode(visNode, radiusFactor)
If this function doesn't exist or returns null, the NodeCircle and LabelText will use the defaultNodeDescription
properties from options
.
The result from this function should be an object containing one or more of the following properties:
x
andy
- if you want to lock a node into a certain position, set these. They should be in the range [0..canvasWidth] and [0..canvasHeight], without any consideration for zoom or pan as that will be applied afterwards. Not that setting these will override force effects and potentially pull other nodes that connected to this node and force-controlled.radius
- the radius of the node, in pixels. Note that you should not apply theradiusFactor
here, it will be applied by the renderer. I.e. this function should return the same radius for the node regardless of zoom if you want it to stay the same semantically.color
- the fill color of the node. Can be astring
or ad3.rgb
instance.borderColor
- the border color. Can be astring
or ad3.rgb
instance.borderWith
- the width of the border, in pixels. Likeradius
, you should not applyradiusFactor
yourself.opacity
- the opacity ("reverse transparency") of the node. 0 means invisible, 0.5 means semi-transparent and 1 means opaque.hoverText
- the tooltip text that will appear when hovering the mouse over the node. Can be used to show the entire title text if it's too long for the label
In addition to these, you can specify the properties for the label. If you want the node to have a label, add a object called label
to your result, containing one or more of the following:
offsetX
andoffsetY
- the position of the label, relative to the node center.anchor
- specifies whether the text goes left or right of the node. Should be a string containing either"start"
,"end"
or"auto"
.auto
will make the label face away from the centroid of the graph, possibly making the graph easier to read.fontSize
- The font size of the label. Don't applyradiusFactor
, it will be done automatically.color
- the text color of the label. Can be astring
or ad3.rgb
instance.borderColor
- the border color. Can be astring
or ad3.rgb
instance.opacity
- the opacity ("reverse transparency") of the label. 0 means invisible, 0.5 means semi-transparent and 1 means opaque.hoverText
- the tooltip text that will appear when hovering the mouse over the label.
..so why is radiusFactor
included as a parameter if you are not supposed to use it?
Well, you can, if you want your labels or nodes to scale differently than with the zoom. Per default, radii, font sizes and the like are scaled with the zoomDensityScale
specified in the options. If you want to further scale or unscale, you can do that here. Or, you can use the factor to determine whether a label should be visible or not. This can be helpful to achieve a Google Maps-like feel where you see more details when zooming in. However, note that for this to work properly, you need to set updateOnlyPositionsOnZoom
to false (it defaults to true), which does have a rather significant performance penalty. It is not recommended for larger graphs.
#####describeVisLink
This function describes the LinkLine
that corresponds to a VisLink.
The signature is: describeVisLink(visLink, sourceNodeCircle, targetNodeCircle, radiusFactor)
If this function exists and returns non-null, the LinkLine that is sent to the renderer will contain the properties from the result and from defaultLinkDescription
when they are not specified.
The sourceNodeCircle
and targetNodeCircle
parameters can be used if you, say, want your link to be colored based on the two nodes it connects etc.
This result from this function should be an object containing one or more of the following properties:
width
- the width, in pixels, of the link line.color
- the text color of the link. Can be astring
or ad3.rgb
instance.opacity
- the opacity ("reverse transparency") of the link. 0 means invisible, 0.5 means semi-transparent and 1 means opaque.marker
- (boolean) show an arrowhead on the link to indicate directionality?curvature
- (boolean) make the link curve?dashPattern
- will become the SVGstroke-dasharray
string, e.g. "5, 5". See more herehoverText
- the tooltip text that will appear when hovering the mouse over the link.
#####describeCollapsedCluster
This function describes the NodeCircle
that represents a collapsed cluster.
The signature is: describeCollapsedCluster(visCluster, clusterVisNodes, radiusFactor)
If this function exists and returns non-null, the NodeCircle
that the renderer will receive for this cluster when it's collapsed will contain the properties from the result and from defaultCollapsedClusterDescription
when they are not specified.
The result has the same format as describeVisNode
, see the description of that above for details.
#####describeExpandedCluster
This function describes the ClusterHull
that represents an expanded cluster.
If this function exists and returns non-null, the ClusterHull
that the renderer will receive for this cluster when it's expanded will contain the properties from the result and from defaultExpandedClusterDescription
when they are specified.
The result from this function should be an object containing one or more of the following properties:
color
- the fill color of the hull. Can be astring
or ad3.rgb
instance.borderColor
- the border color. Can be astring
or ad3.rgb
instance.opacity
- the opacity ("reverse transparency") of the hull. 0 means invisible, 0.5 means semi-transparent and 1 means opaque.hoverText
- the tooltip text that will appear when hovering the mouse over the hull.
GraphVis.unscaleCoords = function (screenCoords)
This function takes a set of screen coordinates relative to the container div and scales them into the coordinate system of the visualisation. The argument screenCoords
should be an array of coordinates [x, y]
. The return value has the same format.
Use this is you want to position something under the mouse etc.
GraphVis.update = function (newVisNodes, newVisLinks, newVisClusters, transitionDuration, updateType)
The update function causes the entire graph to be updated.
If arguments are supplied for newVisNodes
, newVisLinks
and newVisClusters
, these will be used. Otherwise, the ones supplied from the last update()
call will be reused.
Specifically, update
will turn VisNode
's into NodeCircle
's, VisLink
's into LinkLine
's and VisCluster
's into ClusterHull
's. However, clusters that have isCollapsed
set to true will not create a cluster hull but rather a placeholder-node that represents the given cluster.
If no transitionDuration
is specified, it defaults to 250 (ms). The updateType
argument will be passed on to the onUpdatePreProcess
and onUpdatePreRender
eventhandlers, if they exist. If it's not set, it defaults to "update", but it might be called internally from zoom
or tick
in which case updateType
will be set to "zoom" or "tick" respectively,
GraphVis.updatePositions = function (updateType)
This function updates positions and density-properties of the graph (i.e. radii, widths of links, font size etc.).
The updateType
argument will be passed on to the onUpdatePreRender
eventhandlers, if it exists.
GraphVis.updateForceDynamics(newForceParameters)
This function allows you to dynamically change the parameteres of the d3.force instance. The newForceParameters
can contain one or multiple of the options in available in the options.forceParameters
property in the GraphVis
constructor. Only properties that are set will have an effect.
This function also updates the internal options
variable so it will work even if you haven't yet called GraphVis.startForce()
.
GraphVis.zoomPan = function (scale, translate, transitionDuration)
This function allows you to zoom/pan to a specific location.
VisNode = function (id, data, clusterId)
This constructs a VisNode
. They are what you feed to GraphVis.update()
The id
parameter must be set to a unique identifier. If the model that you are representing nodes with has an ID already, it is recommended that you use this.
The data
parameter can contain any object you want to attach to your node.
The clusterId
parameter can be null or it can be the ID of a cluster. If set, a corresponding VisCluster
with the same ID must exist and be supplied to GraphVis.update
.
VisLink = funciotn (sourceNodeId, targetNodeId, data)
This constructs a VisLink
.
The sourceNodeId
and targetNodeId
must be ID's of VisNode
's supplied in the same update() call.
The data
parameter is optional. Use it to attach custom data to your link.
VisCluster = function (id, data, isCollapsed)
This constructs a VisCluster
.
The id
parameter must be a unique identifier for the cluster. Any VisNode
that belongs to a cluster must set its clusterId
property to something that matches one of these.
The data
property is optional. You can use it to attach additional data to the cluster.
The isCollapsed
property defines whether the cluster is collapsed or not. When collapsed, the a single NodeCircle
will be drawn in stead of all of the nodes inside the cluster. If it's not collapsed, all the nodes belonging to the cluster will be represented individually and a convex hull ("cluster hull") will be drawn around them to indicate their relationship.
SvgRenderer(containerElement, options)
This is the constructor for the SvgRenderer
class. The containerElement
parameter should be a jQuery object of the DOM element where the graph should be rendered.
At the time of writing, the only option allowed in the options
parameter is layerIds
. This is an array of strings, indicating which layers the renderer should create. The order is significant, as the first layer will be first in the DOM and hence will be the "bottom" layer. The default setting for this property is ["clusters", "links", "nodes", "labels", "ui"]
. The first four of these must exist, the "ui" layer is added as a way to add UI-elements on top of the visualization.
SvgRenderer.getLayer = function (name)
This method returns the D3 selection of the layer specified. This means that if you have a layer called "ui", you can apply D3 selection actions on it like this: svgRenderer.getLayer("ui").append("svg:g")
.
SvgRenderer.update = function (clusterHulls, linkLines, nodeCircles, labelTexts, xScale, yScale, radiusFactor, transitionDuration
This function updates the DOM with visual elements. It uses D3 to map data to the DOM. The data supplied should be directly mappable. The only transformation applied is the scales and radiusFactor
. Coordinates are transformed with the scales. Radii, link widths, font sizes and the like are multiplied with the radiusFactor before applied.
update
will add, update and remove elements in order to make the DOM match the data supplied. If transitionDuration
is specified, the transition will take that number of milliseconds. Otherwise, it will default to 250ms.
SvgRenderer.updatePositions = function (clusterHulls, linkLines, nodeCircles, labelTexts, xScale, yScale, radiusFactor)
This function is an optimized version of update
that doesn't add or remove elements, and only updates certain properties like positions and sizes. This function is typically used when zooming or in a force tick function.
SvgRenderer.containerElement = function ()
This function returns the jQuery object passed in the constructor.
SvgRenderer.width = function ()
Gets the width of the container element.
SvgRenderer.height = function ()
Gets the height of the container element.