Skip to content

Futurescaper/Futuregrapher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Futuregrapher Build Status

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.

Examples

Inline Javascript example

and

AMD/RequireJS example

Meteor

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

Usage

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.

Customizing appearance

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.

Nodes, Links and clusters

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.

Force

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.

Events

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.

Reference

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 call startForce() 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:

####Event Handling You can supply the following event handlers in options:

  • onUpdatePreProcess - called from update() before processing vis*-elements
  • onUpdatePreRender - called from update() before rendering
  • onClick - event handler for when the user clicks the graph
  • onMouseDown - event handler for when the user presses the mouse button
  • onMouseUp - event handler for when the user releases the mouse button
  • onMouseMove - event handler for when the user moves the mouse
  • onNodeClick - event handler for node clicks
  • onNodeDoubleClick - event handler for node double-clicks
  • onNodeMouseOver - event handler for hovering over a node
  • onNodeMouseOut - event handler for when the mouse leaves a node
  • onNodeDragStart - event handler for when the user starts dragging a node
  • onNodeDrag - event handler called when the user is dragging a node
  • onNodeDragEnd - event handler for when the user drops a node somewhere
  • onClusterNodeClick - event handler for clicks on cluster placeholder-nodes
  • onClusterNodeDoubleClick - event handler for doubleclick on placeholder-node (default: expand cluster)
  • onClusterNodeMouseOver - event handler for hovering placeholder-nodes
  • onClusterNodeMouseOut - event handler for leaving placeholder-nodes
  • onClusterNodeDragStart - event handler for when the user starts dragging a placeholder-node
  • onClusterNodeDrag - event handler for dragging placeholder-nodes
  • onClusterNodeDragEnd - event handler for when the user drops a placeholder-node
  • onLinkClick - event handler for link clicks
  • onLinkDoubleClick - event handler for link double-clicks
  • onLinkMouseOver - event handler for hovering over a link
  • onLinkMouseOut - event handler for when the mouse leaves a link
  • onClusterClick - event handler for when the user clicks on a cluster hull
  • onClusterDoubleClick - event handler for cluster hull doubleclick (default: collapse cluster)
  • onClusterMouseOver - event handler for hovering a cluster hull
  • onClusterMouseOut - 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 nodes
  • describeVisNode - describer function to use for nodes
  • defaultLinkDescription - default description for links
  • describeVisLink - describer function for links
  • defaultCollapsedClusterDescription - default description for collapsed clusters
  • describeCollapsedCluster - describer function for collapsed clusters
  • defaultExpandedClusterDescription - default description for expanded clusters
  • describeExpandedCluster - 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 and y - 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 the radiusFactor 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 a string or a d3.rgb instance.
  • borderColor - the border color. Can be a string or a d3.rgb instance.
  • borderWith - the width of the border, in pixels. Like radius, you should not apply radiusFactor 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 and offsetY - 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 apply radiusFactor, it will be done automatically.
  • color - the text color of the label. Can be a string or a d3.rgb instance.
  • borderColor - the border color. Can be a string or a d3.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 a string or a d3.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 SVG stroke-dasharray string, e.g. "5, 5". See more here
  • hoverText - 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 a string or a d3.rgb instance.
  • borderColor - the border color. Can be a string or a d3.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

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

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

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

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

GraphVis.zoomPan = function (scale, translate, transitionDuration)

This function allows you to zoom/pan to a specific location.

VisNode constructor

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 constructor

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 constructor

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 constructor

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

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

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

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

SvgRenderer.containerElement = function ()

This function returns the jQuery object passed in the constructor.

SvgRenderer.width

SvgRenderer.width = function ()

Gets the width of the container element.

SvgRenderer.height

SvgRenderer.height = function ()

Gets the height of the container element.