timp / d3-graphviz

Graphviz DOT rendering and animated transitions using D3

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

d3-graphviz

Renders SVG from graphs described in the DOT language using the Viz.js port of Graphviz and does animated transitions between graphs.

Build Status codecov npm version unpkg unpkg min

Features

  • Rendering of SVG graphs from DOT source
  • Animated transition of one graph into another
  • Edge path tweening
  • Node shape tweening
  • Fade-in and fade-out of entering and exiting nodes and edges
  • Animated growth of entering edges
  • Panning & zooming of the generated graph

Graphviz methods all return the graphviz renderer instance, allowing the concise application of multiple operations on a given graph renderer instance via method chaining.

To render a graph, select an element, call selection.graphviz, and then render from a DOT source string. For example:

d3.select("#graph")
  .graphviz()
    .renderDot('digraph {a -> b}');

It is also possible to call d3.graphviz with a selector as the argument like so:

d3.graphviz("#graph")
    .renderDot('digraph {a -> b}');

This basic example can also bee seen here.

A more colorful demo can be seen here.

Installing

If you use NPM, npm install d3-graphviz. Otherwise, download the latest release.

Principles of Operation

Uses Viz.js to do a layout of a graph specified in the DOT language and generates an SVG text representation, which is analyzed and converted into a data representation. Then D3 is used to join this data with a selected DOM element, render the SVG graph on that element and to animate transitioning of one graph into another.

Contents

API Reference

Creating a Graphviz Renderer

Creating a Graphviz Renderer on an Existing Selection

# selection.graphviz() <>

Returns a new graphviz renderer instance on the given selection.

Creating a Graphviz Renderer Using a Selector String or a Node

# d3.graphviz(selector) <>

Creates a new graphviz renderer instance on the first element matching the given selector string. If the selecor is not a string, instead creates a new graphviz renderer instance on the specified node.

Rendering

# graphviz.renderDot(dotSrc[, callback]) <>

Starts rendering of an SVG graph from the specified dotSrc string and appends it to the selection the grapviz renderer instance was generated on. graphviz.renderDot returns "immediately", while the rendering is performed in the backgound. The layout stage is performed by a web worker. If callback is specified and not null, it is called with the this context as the graphviz instance, when the graphviz renderer has finished all actions.

It is also possible to do the Graphviz layout in a first separate stage and do the actual rendering of the SVG as a second step like so:

d3.select("#graph")
  .graphviz()
    .dot('digraph {a -> b}')
    .render();

This enables doing the computational intensive layout stages for multiple graphs before doing the potentially synchronized rendering of all the graphs simultaneously.

# graphviz.dot(dotSrc[, callback]) <>

Starts computation of the layout of a graph from the specified dotSrc string and saves the data for rendering the SVG with graphviz.render at a later stage. graphviz.dot returns "immediately", while the layout is performed by a web worker in the backgound. If callback is specified and not null, it is called with the this context as the graphviz instance, when the layout, data extraction and data processing has been finished.

# graphviz.render([callback]) <>

Starts rendering of an SVG graph from data saved by graphviz.dot and appends it to the selection the grapviz renderer instance was generated on. graphviz.render returns "immediately", while the rendering is performed in the backgound. If computation of a layout, started with the graphviz.dot method has not yet finsihed, the rendering task is placed in a queue and will commence when the layout is ready. If callback is specified and not null, it is called with the this context as the graphviz instance, when the graphviz renderer has finished all actions.

# graphviz.engine(engine) <>

Sets the Graphviz layout engine name to the specified engine string. The engine name must be set before attaching the DOT source. If it is changed after this, an eror is thrown. Supports all engines that Viz.js supports. Currently these are:

  • circo
  • dot (default)
  • fdp
  • neato (not supported with viz-lite.js)
  • osage
  • patchwork
  • twopi

sfdp is currently not supported.

# graphviz.onerror(callback) <>

If callback is specified and not null, it is called with the this context as the graphviz instance and the error message as the first argument, if the layout computation encounters an error. If callback is null, removes any previously registered callback.

Images

# graphviz.addImage(path,width,height) <>

Add image references as dictated by viz.js, must be done before renderDot() or dot().
addImage can be called multiple times.

path may be a filename ("example.png"), relative or absolute path ("/images/example.png"), or a URL ("http://example.com/image.png")
Dimensions(width,height) may be specified with units: in, px, pc, pt, cm, or mm. If no units are given or dimensions are given as numbers, points (pt) are used.

Graphviz does not actually load image data when this option is used — images are referenced with the dimensions given, eg, in SVG by an <image> element with width and height attributes.

d3.graphviz("#graph")
    .addImage("images/first.png", "400px", "300px")
    .addImage("images/second.png", "400px", "300px")
    .renderDot('digraph { a[image="images/first.png"]; b[image="images/second.png"]; a -> b }');

Creating Transitions

# graphviz.transition([name]) <>

Applies the specified transition name to subsequent SVG rendering. Accepts the same arguments as selection.transition or a function, but returns the graph renderer instance, not the transition. If name is a function, it is taken to be a transition factory. A transition factory is a function that returns a transition; when the rendering starts, the factory is evaluated once, with the this context as the graphviz instance. To attach a preconfigured transition, first create a transition intance with d3.transition, configure it and attach it with graphviz.transition like so:

var t = d3.transition()
    .duration(750)
    .ease(d3.easeLinear);

d3.select("#graph").graphviz()
    .transition(t)
    .renderDot('digraph {a -> b}');

A transition is scheduled when it is created. The above example will schedule the transition before the layout is computed, i.e. synchronously. But if, instead, a transition factory is used, the transition will be scheduled after the layout is computed, i.e. asynchronously.

NOTE: Transitions should be named if zooming is enabled. Transitions using the null name will be interrupted by the zoom behavior, causing the graph to be rendered incorrectly.

# graphviz.active([name]) <>

Returns the active transition on the generated graph's top level svg with the specified name, if any. If no name is specified, null is used. Returns null if there is no such active transition on the top level svg node. This method is useful for creating chained transitions.

Control Flow

For advanced usage, the grahviz renderer provides methods for custom control flow.

# graphviz.on(typenames[, listener]) <>

Adds or removes a listener to the graphviz renderer instance for the specified event typenames. The typenames is one of the following string event types:

  • initEnd - when the graphviz renderer has finished initialization.
  • start - when analysis of the DOT source starts.
  • layoutStart - when the layout of the DOT source starts.
  • layoutEnd - when the layout of the DOT source ends.
  • dataExtractEnd - when the extraction of data from the SVG text representation ends.
  • dataProcessPass1End - when the first pass of the processing of the extracted data ends.
  • dataProcessPass2End - when the second pass of the processing of the extracted data ends.
  • dataProcessEnd - when the processing of the extracted data ends.
  • renderStart - when the rendering preparation starts, which is just before an eventual transition factory is called.
  • renderEnd - when the rendering preparation ends.
  • transitionStart - when the anmiated transition starts.
  • transitionEnd - when the anmiated transition ends.
  • restoreEnd - when possibly converted paths and shapes have been restored after the transition.
  • end - when the graphviz renderer has finished all actions.

Note that these are not native DOM events as implemented by selection.on and selection.dispatch, but graphviz events!

The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered to receive events of the same type, such as start.foo and start.bar. To specify multiple typenames, separate typenames with spaces, such as interrupt end or start.foo start.bar.

When a specified graphviz event is dispatched, the specified listener will be invoked with the this context as the graphviz instance.

If an event listener was previously registered for the same typename on a selected element, the old listener is removed before the new listener is added. To remove a listener, pass null as the listener. To remove all listeners for a given name, pass null as the listener and .foo as the typename, where foo is the name; to remove all listeners with no name, specify . as the typename.

# graphviz.logEvents(enable) <>

If enable is true (default), adds event listeners, uing the name "log", for all available events. When invoked, each listener will print a one-line summary containing the event number and type, the time since the previous event and the time since the "start" event to the console log. For some events, additionally calculated times are printed. This method can be used for debugging or for tuning transition delay and duration. If enable is false, removes all event listeners named "log".

Controlling Fade-In & Fade-Out

# graphviz.fade(enable) <>

If enable is true (default), enables fade-in and fade-out of nodes and shapes, else disables fade-in and fade-out.

Controlling Animated Growth of Entering Edges

# graphviz.growEnteringEdges(enable) <>

If enable is true (default), enables animated growth of entering edges, else disables animated growth of entering edges.

A demo of animated growth of entering edges can be seen here

Controlling Path Tweening

# graphviz.tweenPaths(enable) <>

If enable is true (default), enables path tweening, else disables path tweening.

# graphviz.tweenPrecision(precision) <>

Sets the precision used during path tweening to precision pixels. Default is 1.

Controlling Shape Tweening

# graphviz.tweenShapes(enable) <>

If enable is true (default), enables shape tweening during transitions, else disables shape tweening. If enable is true, also enables path tweening since shape tweening currently is performed by converting SVG ellipses and polygons to SVG paths and do path tweening on them. At the end of the transition the original SVG shape element is restored.

# graphviz.convertEqualSidedPolygons(enable) <>

If enable is true (default), enables conversion of polygons with equal number of sides during shape tweening, else disables conversion. Not applicable when shape tweening is disabled. At the end of the transition the original SVG shape element is restored.

A demo of shape tweening can be seen here.

Controlling Panning & Zooming

# graphviz.zoom(enable) <>

If enable is true (default), enables panning and zooming, else disables panning and zooming. The zoom behavior is applied to the graph's top level svg element.

# graphviz.resetZoom([transition]) <>

Restores the original graph by resetting the transformation made by panning and zooming. If transition is specified and not null, it is taken to be a transition instance which is applied during zoom reset.

Maintaining Object Constancy

In order to acheive meaningful transitions, the D3 default join-by-index key function is not sufficient. Four different key modes are available that may be useful in different situations:

  • title (default) - Uses the text of the SVG title element for each node and edge g element as generated by Graphviz. For nodes, this is "node_id" (not to be confused with the node attribute id) and for edges it is "node_id edgeop node_id", e.g. "a -> b". For node and edge sub-elements, the tag-index key mode is used, see below.
  • id - Uses the id attribute of the node and edge SVG g elements as generated by Graphviz. Note that unless the graph author specifies id attributes for nodes and edges, Graphviz generates a unique internal id that is unpredictable by the graph writer, making the id key mode not very useful. For node and edge sub-elements, the tag-index key mode is used, see below.
  • tag-index - Uses a key composed of the SVG element tag, followed by a dash (-) and the relative index of that element within all sibling elements with the same tag. For example: ellipse-0. Normally not very useful for other than static graphs, since all nodes and edges are siblings and are generated as SVG g elements.
  • index - Uses the D3 default join-by-index key function. Not useful for other than static graphs.

# graphviz.keyMode(mode) <>

Sets the key mode to the specified mode string. If mode is not one of the defined key modes above, an error is thrown. The key mode must be set before attaching the DOT source. If it is changed after this, an eror is thrown.

Customizing Graph Attributes

# graphviz.attributer(function) <>

If the function is a function, it is evaluated for each SVG element, before applying attributes and transitions, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). If function is null, removes the attributer. For example, to set the fill color of ellipses to yellow and fade to red during transition:

var t = d3.transition()
    .duration(2000)
    .ease(d3.easeLinear);

d3.select("#graph").graphviz()
    .transition(t)
    .attributer(function(d) {
        if (d.tag == "ellipse") {
            d3.select(this)
                .attr("fill", "yellow");
            d.attributes.fill = "red";
        }
    })
    .renderDot('digraph {a -> b}');

Accessing Elements of the Generated Graph

# selection.selectWithoutDataPropagation() <>

For each selected element, selects the first descendant element that matches the specified selector string in the same ways as selection.select, but does not propagate any associated data from the current element to the corresponding selected element.

Large Graphs

For very large graphs it might be necessary to increase the amount of memory available to Viz.js.

# graphviz.totalMemory(size) <>

Sets the total memory available to Viz.js to size bytes, which should be a power of 2. See the Viz.js API for details.

Examples

Building Applications with d3-graphviz

SVG structure

The generated SVG graph has exactly the same structure as the SVG generated by Viz.js, so applications utilizing knowledge about this structure should be able to use d3-graphviz without adaptations. If path tweening or shape tweening is used, some SVG elements may be converted during transitions, but they are restored to the original shape after the transition.

See this example application.

NOTE: avoid selection.select

When selecting elements within the graph, selection.select must not be used if additional rendering is going to be performed on the same graph renderer instance. This is due to the fact that selection.select propagates data from the elements in the selection to the corresponding selected elements, causing already bound data to be overwritten with incorrect data and subsequent errors. Use the selection.selectWithoutDataPropagation() (a d3-graphviz extension of d3-selection) or selection.selectAll, which do not propagate data or selection.node and querySelector. For example, to select the first g element within the first svg element within a specified div element:

var div = d3.select("#graph");
var svg = d3.select(div.node().querySelector("svg"));
var g = d3.select(svg.node().querySelector("g"));

For more, read this issue and this Stack Overflow post.

Data Format

The data bound to each DOM node is an object containing the following fields:

  • tag - The DOM node tag.
  • attributes - An object containing attributes as properties.
  • children - An array of data for the node's children.
  • key - The key used when binding data to nodes with the key function. See graphviz.keyMode for more.
  • text - Contains the text if the DOM node is a Text node. A text node has the tag "#text", not to be confused with the tag "text", which is an SVG 'text' element.
  • comment - Contains the comment if the DOM node is a Comment node. A comment node has the tag "#comment".

Other fields are used internally, but may be subject to change between releases and should not by used an external application.

To inspect data:

d3.select("#graph").graphviz()
    .renderDot('digraph  {a -> b}');
console.log(JSON.stringify(d3.select("svg").datum(), null, 4));

Performance

The shape- and path-tweening operations are quite computational intensive and can be disabled with graphviz.tweenShapes and graphviz.tweenPaths to improve performance if they are not needed. Even if enabled, performance gains can be made by turning off conversion of equally sided polygons with graphviz.convertEqualSidedPolygons or by reducing tween precision by setting a larger value with graphviz.tweenPrecision.

In order for animated transitions to be smooth, special considerations has been made to do the computational intensive operations before transitions start. Use transition.delay to reserve time for those computations.

Since the author is new to both Javascipt and D3, there are probably a lot of things that can be improved. Suggestions are welcome.

Requirements

d3-graphviz uses a few ES6 language features, so it must be used with a modern browser.

Support

When asking for help, please include a link to a live example that demonstrates the issue, preferably on JSFiddle. It is often impossible to debug from code snippets alone. Isolate the issue and reduce your code as much as possible before asking for help. The less code you post, the easier it is for someone to debug, and the more likely you are to get a helpful response.

Be notified of updates

By clicking the Watch button, you will stay tuned for updates to the library.

Getting help

Reporting bugs

If you think you have found a bug in d3-graphviz itself, please file an issue.

Development

In order to run the tests you need Node.js 6.x or later.

Credits

About

Graphviz DOT rendering and animated transitions using D3

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:JavaScript 100.0%