Giter VIP home page Giter VIP logo

d3-graphviz's Introduction

d3-graphviz

Renders SVG from graphs described in the DOT language using the @hpcc-js/wasm port of Graphviz and does animated transitions between graphs.

Node.js CI codecov npm version unpkg unpkg min Gitter

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 typically 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 be seen here.

A more colorful demo can be seen here.

Installing

The easiest way to use the library in your own application is to install it with NPM:

npm install d3-graphviz

If you don't use npm, you can download the latest release.

Building the library

You normally don't need to do this, but if you prefer, you can clone from github and build your own copy of the library with:

git clone https://github.com/magjac/d3-graphviz.git
cd d3-graphviz
npm install
npm run build

The built library will then be in build/d3-graphviz.js

Principles of Operation

Uses @hpcc-js/wasm 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

Defining the @hpcc-js/wasm Script Tag

The "@hpcc-js/wasm" script provides functions to do Graphviz layouts. If a web worker is used, these functions are called from the web worker which then loads and compiles the "@hpcc-js/wasm" script explicitly. In this case, it's unnecessary to let the browser also load and compile the script. This is accomplished by using the script tag "javascript/worker" which the browser does not identify to be Javascript and therefore does not compile. However, there are two d3-graphviz functions, drawNode and drawEdge that calls the layout functions directly and if they are going to be used, the script type must be "application/javascript" or "text/javascript".

Examples:

<script src="https://unpkg.com/@hpcc-js/wasm/dist/graphviz.umd.js" type="application/javascript/"></script>

This will always work, but will not be optimal if the script is used in a web worker only.

<script src="https://unpkg.com/@hpcc-js/wasm/dist/graphviz.umd.js" type="javascript/worker"></script>

This will work if a web worker is used and the drawNode and drawEdge functions are not used and will give shorter page load time.

The following table summarizes the recommended script type:

useWorker = true (default) or
useSharedWorker = true
useWorker = false and
useSharedWorker = false
drawNode()/drawEdge() is not used javascript/worker application/javascript
drawNode()/drawEdge() is used application/javascript application/javascript

Creating a Graphviz Renderer

Creating a Graphviz Renderer on an Existing Selection

# selection.graphviz([options]) <>

Returns a new graphviz renderer instance on the first element in the given selection. If a graphviz renderer instance already exists on that element, instead returns the existing graphviz renderer instance. If options is specified and is an object, its properties are taken to be options to the graphviz renderer. All options except the useWorker and useSharedWorker options can also be changed later, using individual methods or the graphviz.options method, see below.

Supported options
Option Default value
convertEqualSidedPolygons true
engine 'dot'
fade true
fit false
growEnteringEdges true
height null
keyMode 'title'
scale 1
tweenPaths true
tweenPrecision 1
tweenShapes true
useWorker¹ true
useSharedWorker¹ false
width null
zoom true
zoomScaleExtent [0.1, 10]
zoomTranslateExtent [[-∞, -∞], [+∞, +∞]]

¹ Only has effect when the graphviz renderer instance is created.

All options are described below. Only the specified options will be changed. The others will keep their current values. If options is a boolean it is taken to be the useWorker option (for backwards compatibility).

The useWorker and useSharedWorker options

If the useSharedWorker option is truthy, a shared web worker will be used for the layout stage. Otherwise, if useWorker is truthy, a dedicated web worker will be used. If both are falsey, no web worker will be used.

Creating a Graphviz Renderer Using a Selector String or a Node

# d3.graphviz(selector[, options]) <>

Creates a new graphviz renderer instance on the first element matching the given selector string. If the selector is not a string, instead creates a new graphviz renderer instance on the specified node. If a graphviz renderer instance already exists on that element, instead returns the existing graphviz renderer instance. See selection.graphviz for a description of the options argument.

Setting and Getting Options

# graphviz.options([options]) <>

If options is specified it is taken to be an object whose properties are used to set options to the graphviz renderer. See selection.graphviz for a list of supported options. Most options can also be changed by individual methods which are described separately. If options is not specified, a copy of the currently set options are returned as an object.

Getting the Graphviz version

# graphviz.graphvizVersion() <>

Returns the Graphviz version.

Rendering

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

Starts rendering of an SVG graph from the specified dotSrc string and appends it to the selection the Graphviz renderer instance was generated on. graphviz.renderDot returns "immediately", while the rendering is performed in the background. The layout stage is performed by a web worker (unless the use of a web worker was disabled when the renderer instance was created).

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 backrgound. 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 Graphviz renderer instance was generated on. graphviz.render returns "immediately", while the rendering is performed in the backrgound. If computation of a layout, started with the graphviz.dot method has not yet finished, 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. In order to have effect, the engine must be set before calling graphviz.dot or graphviz.renderDot. Supports all engines that @hpcc-js/wasm supports. Currently these are:

  • circo
  • dot (default)
  • fdp
  • sfdp
  • neato
  • osage
  • patchwork
  • twopi

# 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, e.g. 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 instance 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.

Controlling SVG Size and Graph Size

The SVG size determines the area in which the graph can be panned and zoomed, while the graph size determines the area that the graph occupies before it is panned or zoomed. The default is that these two areas are the same.

The size of the graph is determined in three optional steps:

  1. The SVG size can be set with graphviz.width and/or graphviz.height.
  2. The graph can be scaled to fit the SVG size with graphviz.fit or maintain its original size.
  3. The graph can be additionally scaled with a scaling factor with graphviz.scale.

# graphviz.width(width) <>

The SVG width attribute is set to width pixels.

# graphviz.height(height) <>

The SVG height attribute is set to height pixels.

# graphviz.fit(fit) <>

If fit is falsey (default), the viewBox attribute of the SVG is set so that the graph size (before scaling with graphviz.scale) is its orignal size, i.e. unaffected by a possible SVG size change with graphviz.width or graphviz.height. If fit is truthy, the viewBox attribute of the SVG is unchanged, which causes the graph size (before scaling with graphviz.scale) to fit the SVG size. Note that unless the SVG size has been changed, this options has no effect.

# graphviz.scale(scale) <>

The viewBox attribute of the SVG is set so that the graph size (after a possible fit to the SVG size with graphviz.fit) is scaled with scale. For example: If scale is 0.5, then if fit is truthy, the graph width and height is half the width and height of the SVG, while if fit is falsey, the graph width and height is half of its original width and height.

Control Flow

For advanced usage, the Graphviz 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 animated transition starts.
  • transitionEnd - when the animated transition ends.
  • restoreEnd - when possibly converted paths and shapes have been restored after the transition.
  • end - when the Graphviz renderer has finished all actions.
  • zoom - when the layout has been zoomed.

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, using 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.

Note: Growing edges with style tapered is not supported.

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) <>

If precision is a number, sets the precision used during path tweening to precision points. The precision is the length of each path segment during tweening. If instead precision is a string containing '%', sets the relative precision. When using a relative precision, the absolute precision is calculated as the length of the object being tweened multiplied with the relative precision, i.e. the number of line segments during tweening will be the inverse of the relative precision. For example, a relative precision of 5% will give 20 line segments. For a path which is 200 points long, this will give a precision of 10 points. Default is an absolute precision of 1 point.

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.zoomBehavior() <>

Returns the zoom behavior if zooming is enabled and a graph has been rendered, else returns null.

# graphviz.zoomSelection() <>

Returns the selection to which the zoom behavior has been applied if zooming is enabled and a graph has been rendered, else returns null.

# graphviz.zoomScaleExtent([extent]) <>

Sets the scale extent to the specified array of numbers [k0, k1] where k0 is the minimum allowed scale factor and k1 is the maximum allowed scale factor. The scale extent restricts zooming in and out. For details see zoom.scaleExtent.

# graphviz.zoomTranslateExtent([extent]) <>

Sets the translate extent to the specified array of points [[x0, y0], [x1, y1]], where [x0, y0] is the top-left corner of the world and [x1, y1] is the bottom-right corner of the world. The translate extent restricts panning, and may cause translation on zoom out. For details see zoom.translateExtent.

# 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 achieve 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 error 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.

Accessing the Extracted Data

# graphviz.data() <>

Returns the data extracted by graphviz.dot or null if no such data exists.

Modifying an Existing Graph and Animating the Changes

This API provides methods draw nodes and edges and inserting them into the graph data. The application can then animate the changes made by providing and updated DOT source and render a new layout. The API also supports removing nodes and edge from the graph and the graph data.

# graphviz.drawEdge(x1, y1, x2, y2[, attributes][, options]) <>

Draws a straight edge from (x1, y1) to (x2, y2) using coordinates relative to top level G container element of the graph. Typically these coordinates are obtained with d3.mouse. If attributes is specified, it is taken to be an object containing DOT attributes as properties to be used when drawing the node. If not specified, the default values of those attributes are used. If options is specified, it is taken to be an object containing properties which are used as options when drawing the edge. The currently supported options are:

  • shortening - The number of points by which to draw the edge shorter than given by the coordinates. This is useful to avoid that the currently drawn edge is prohibiting mouse events on elements beneath the current mouse position. A typical such value is 2. The default value is 0.

# graphviz.updateDrawnEdge(x1, y1, x2, y2[, attributes][, options]) <>

Updates properties and attributes of the edge currently drawn with graphviz.drawEdge, using the same arguments. This method cannot be used after the edge has been inserted into the graph data with graphviz.insertDrawnEdge.

# graphviz.moveDrawnEdgeEndPoint( x2, y2[, options]) <>

Updates the end point of the edge currently drawn with graphviz.drawEdge, accepting the same options argument. This method cannot be used after the edge has been inserted into the graph data with graphviz.insertDrawnEdge.

# graphviz.insertDrawnEdge(name) <>

Inserts the edge into the graph data, making it available for an animated transition into a subsequent new layout perfomed with graphviz.render or graphviz.renderDot. name is typically node_id edgeop node_id according to the DOT language, e.g. "a -> b".

# graphviz.removeDrawnEdge() <>

Removes the edge currently drawn with graphviz.drawEdge. This method cannot be used after the edge has been inserted into the graph data with graphviz.insertDrawnEdge.

# graphviz.drawNode(x, y, nodeId[, attributes]) <>

Draws a node with the upper left corner of its bounding box at (x, y), using coordinates relative to the top level G container element of the graph. Typically these coordinates are obtained with d3.mouse. nodeId is the node_id according to the DOT language. If attributes is specified, it is taken to be an object containing DOT attributes as properties to be used when drawing the node. If not specified, the default values of those attributes are used.

NOTE: User-defined shapes are not supported.

# graphviz.updateDrawnNode(x, y, nodeId[, attributes]) <>

Updates properties and attributes of the node currently drawn with graphviz.drawNode, using the same arguments. This method cannot be used after the node has been inserted into the graph data with graphviz.insertDrawnNode.

# graphviz.moveDrawnNode( x, y[, options]) <>

Updates the position of the upper left corner of the node currently drawn with graphviz.drawNode, accepting the same options argument. This method cannot be used after the node has been inserted into the graph data with graphviz.insertDrawnNode.

# graphviz.insertDrawnNode(nodeId) <>

Inserts the node into the graph data, making it available for an animated transition into a subsequent new layout performed with graphviz.render or graphviz.renderDot. nodeId is the node_id according to the DOT language.

# graphviz.removeDrawnNode() <>

Removes the node currently drawn with graphviz.drawNode. This method cannot be used after the node has been inserted into the graph data with graphviz.insertDrawnNode.

# graphviz.drawnNodeSelection() <>

Returns a selection containing the node currently being drawn. The selection is empty if no node has been drawn or the last drawn node has been inserted into the graph data with graphviz.insertDrawnNode.

Destroying the Graphviz Renderer

# graphviz.destroy() <>

Removes the Graphviz renderer from the element it was created on, terminates any active dedicated web worker and closes any port connected to a shared web worker.

Examples

There are plenty of examples in the examples folder. To see them in action you must build the library and start a web server. You cannot just open them through file: in your browser. Here is one way to do it:

git clone https://github.com/magjac/d3-graphviz.git
cd d3-graphviz
npm install
npm run build
npm install http-server
node_modules/.bin/http-server .

There are also a few examples directly available online:

Building Applications with d3-graphviz

SVG structure

The generated SVG graph has exactly the same structure as the SVG generated by @hpcc-js/wasm, 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 be used by 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.

Requirements

d3-graphviz transpiles the production build to ES5 before publishing it on npm, so it should be possible to use it with most build tools and browsers.

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 14.x or later. Version 18.8.0 was used during development.

Credits

d3-graphviz's People

Contributors

dependabot[bot] avatar gordonsmith avatar inokawa avatar magjac avatar mrdrogdrog avatar ngg avatar npetzall avatar rwilhelm avatar schneiderl avatar sjlevine avatar tucker-gordon-bah avatar whitten avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-graphviz's Issues

Pan/Scale to center graph?

Is there any way to center the graph on the canvas and scale it to fit (if its too large), I imagine this would be done with the attributer somehow

The graph jumps when panned in some situations after rendering a second graph without using a transition

One case is when the second graph height changes. Another when the first graph has been panned or zoomed.

https://jsfiddle.net/fc4os5m0/8/

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://unpkg.com/[email protected]/viz.js" type="application/javascript"></script>
<script src="https://unpkg.com/[email protected]/build/d3-graphviz.js"></script>
<div id="graph"></div>
<script>

d3.select("#graph").graphviz()
    .width(800)
    .height(400)
    .scale(0.5)
    .fit(true)
    .renderDot('digraph  {label="Click me"; a -> b}')
    .on("end", function() {
        d3.select(document).on("click", function() {
            var event = d3.event;
            event.preventDefault();
            event.stopPropagation();
            console.log('document click');
            render2();
        });
    })
    ;
        
function render2() {
    d3.select("#graph").graphviz()
        .renderDot('digraph  {label="Pan me"; c; d}')
    ;
}

</script>

A workaround is to use a transition.

Support setting svg size and scale the contained graph to fit or with a scaling factor

The default behaviour of Graphviz is to create an svg that just fits the contained graph.

These cases should be supported:

  • Set the svg size, but don't scale the contained graph
  • Set the svg size and scale the contained graph to fit
  • Set the svg size and scale the contained graph with a given scaling factor

Special consideration may be necessary to handle the svg aspect ratio.

See https://beta.observablehq.com/@magjac/d3-graphviz#adot for a possible implementation.

Growing edges to nodes with style "wedged" throws error

When growing edges are enabled (default), growing of edges to or from a node with a style attribute "wedged" causes the following error to be thrown:

"Unexpected tag: path. Please file an issue at https://github.com/magjac/d3-graphviz/issues"

Also, both the graph before and after the transition is shown,

The problem can be seen with this code:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="../../node_modules/viz.js/viz.js" type="javascript/worker"></script>
<script src="../../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .tweenPaths(false)
    .tweenShapes(false)
    .transition(function () {
        return d3.transition("main")
            .ease(d3.easeLinear)
            .delay(500)
            .duration(1500);
    })
    .logEvents(true)
    .on("initEnd", render);

function render() {
    var dot = dots[dotIndex];
    graphviz
        .renderDot(dot)
        .on("end", function () {
            dotIndex += 1;
            if (dotIndex < dots.length) {
                render();
            }
        });
}

var dots = [
    `
digraph G {
    node [
        shape="circle"
        style="wedged"
    ]
    a [label="A", fillcolor="#d62728;0.4:#1f77b4"]
    b [label="B", fillcolor="#d62728;0.6:#1f77b4", ]
}
    `,
    `
digraph G {
    node [
        shape="circle"
        style="wedged"
    ]
    a [label="A", fillcolor="#d62728;0.4:#1f77b4"]
    b [label="B", fillcolor="#d62728;0.6:#1f77b4", ]
    a -> b
}
    `,
];

render();

</script>

Shape tweening to and from a cylinder shape throws error

The error message is:

TypeError: guideData.attributes.points is undefined

Local example:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="../node_modules/d3/dist/d3.js"></script>
<script src="../node_modules/viz.js/viz.js" type="javascript/worker"></script>
<script src="../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .transition(function () {
        return d3.transition("main")
            .ease(d3.easeLinear)
            .delay(500)
            .duration(1500);
    })
    .on("initEnd", render);

function render() {
    var dot = dots[dotIndex];
    graphviz
        .renderDot(dot)
        .on("end", function () {
            if (dotIndex != 2) {
                dotIndex = (dotIndex + 1) % dots.length;
                render();
            }
        });
}

var dots = [`
digraph  {
    a
}
`,`
digraph  {
    a [shape="cylinder"]
}
`,`
digraph  {
    a
}
`];

</script>

The layout engine cannot be changed between two graph renderings

If an attempt to change the layout engine is made after rendering the first graph, d3-graphviz throws Error: Too late to change engine.

Although this implementation was intentional and aimed at informing the user that a change of the layout engine between a call to graphviz.dot and graphviz.render had no effect, it was a bad idea since it prohibits transitions between two graphs rendered with different layout engines.

Rather than throwing an error, such a change should be allowed and its effect properly documented.

Browserify

Version 0.1.3 works great with Browserify, but how to Browerify the latest version 1.1.0?
The problem is (I think) the way the webworker is used/created.

Changing SVG width affects height and vice versa

In the current implementation, if only one of the width and height attributes are specified, the unspecified attribute is automatically set to a value that preserves the current aspect ratio of the SVG.

Although this was a conscious design decision, it was a bad idea. If only one of the attributes are specified, the other should be left unchanged. The preservation of the aspect ratio is then taken care of by the preserveAspectRatio attribute. The default value of this attribute is xMidYMid meet which in the case when fit is true, has the effect that the graph is scaled so that it is fully visible and centered in the direction where the SVG is larger than the graph itself.

Since the implementation is correct with respect to the current documentation, this might not be considered a bug and changing the functionality might not be considered a backwards compatible change of the API. I'm however going to do so anyway, because I think that the current functionality is not what you would expect, and release this as a patch release.

Growing edges to nodes with URL attribute throws error

When growing edges are enabled (default), growing of edges to or from a node with an URL attribute causes the following error to be thrown:

"Unexpected tag: g. Please file an issue at https://github.com/magjac/d3-graphviz/issues"

Also, both the graph before and after the transition is shown,

The problem can be seen with this code:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/[email protected]/viz.js"></script>
<script src="https://unpkg.com/[email protected]/build/d3-graphviz.min.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var graphviz = d3.select("#graph").graphviz()
    .fade(false);

graphviz
    .renderDot('digraph {node [URL="DUMMY-URL"]; a; b}');

graphviz
    .transition(
        d3.transition()
            .delay(1000)
            .duration(1000)
    )
    .renderDot('digraph {node [URL="DUMMY-URL"]; a -> b}');

</script>

useWorker = false has a TypeError

d3.select("#graph").graphviz(false); yields the following error:

Uncaught TypeError: Viz is not a function
  at Graphviz.initViz (d3-graphviz.js:796)
  at new Graphviz (d3-graphviz.js:1931)
  at Selection.selection_graphviz [as graphviz] (d3-graphviz.js:1974)

This is with the following imports:

<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://unpkg.com/[email protected]/viz.js" type="javascript/worker"></script>
<script src="https://unpkg.com/[email protected]/build/d3-graphviz.js"></script>

It appears Viz is undefined.

Pan and zoom does not work after the graph ID has been changed

This bug is valid when key mode 'title' (default) is used.

The zoom works after this:

var graphviz = d3.select("#graph").graphviz()
    .renderDot('digraph graph1 {a -> b}', function () {
        this.renderDot('digraph graph1 {a -> b; a -> c}');
    });

The zoom does not work after this:

var graphviz = d3.select("#graph").graphviz()
    .renderDot('digraph graph1 {a -> b}', function () {
        this.renderDot('digraph graph2 {a -> b; a -> c}');
    });

The reason is that the zooming is performed by changing the transform attribute on the top level g element that exists when the zoom behavior is created and this element is replaced by a new element when the graph ID is changed.

Relative paths in local URL's

Hello,

First of all, please allow me to thank you for this wonderful package! I find it extremely useful for a project I am working on, and the graph transitions are beautiful. Thank you again for your hard work.

I ran into a small issue I wanted to bring to your attention, along with a possible fix. I'm using this library on a website I'm developing locally (not on a server) with web workers. As such, I believe the following line is used to to add the file:// protocol so the web worker can load viz.js:

vizURL = document.location.protocol + '//' + document.location.host + '/' + vizURL;

For my local website, this tends to produce paths like file:///libs/viz.js (note the three /, thereby making it an absolute URL pointing to /lib/viz.js relative to the root of my filesystem, instead of my desired relative directory, /path/to/local/website/libs/viz.js. Then, the script does not load because the file does not exist. So, I wonder if this line may not be correctly handling that my website's root is not the same as my file system's root, for these local URL's.

I have a possible fix, using the URL API:

vizURL = (new URL(vizURL, document.location.href)).href;

With this change, I get correct paths like file://path/to/local/website/libs/viz.js, and then the library works perfectly (I'm using Firefox, but haven't run any of your other tests).

What do you think about this change? I'm happy to answer any questions.

Thank you again!

resetZoom method: Uncaught TypeError: Cannot read property 'call' of undefined

Flushed with the success of removing the width and height attributes of the <svg> to set the initial size of the graph to the viewport, I thought I'd try the new-in-v1.1.0 resetZoom.

I get the following error message in the browser console (latest Chrome on Windows 10, using d3-graphviz v1.1.0):

Uncaught TypeError: Cannot read property 'call' of undefined
at Graphviz.resetZoom (d3-graphviz.js:69)
at Object.resetZoom (cpattl.html:95)
at HTMLButtonElement.onclick (VM1182 cpattl.html:80)

In d3-graphviz.js, the _zoomselection property of this is undefined:

function resetZoom() {
    this._zoomSelection.call(this._zoomBehavior.transform, this._originalTransform);

    return this;
}

My JavaScript function, triggered by a button click:

dot2viz.resetZoom = function resetZoom() {
  var t = d3.transition()
    .duration(750)
    .ease(d3.easeLinear)
  d3.select("#graph").graphviz()
    .transition(t)
    .resetZoom()
}

where #graph refers to the HTML <div id="graph"> that is the container for the rendered DOT:

d3.select("#graph").graphviz().renderDot(data, renderDotEnd)

I'm frankly not sure what I should be selecting to perform the resetZoom:

  • #graph
  • #graph > svg
  • #graph0 (the ID of the first child <g> of the <svg>

all result in the same error.

I suspect that this error is caused by me doing something wrong, but I can't work out what.

`renderDot`/`render` callbacks are ignored

d3.select("#graph").graphviz()
    .renderDot(dotSrc, function () {
        console.log("XXX-0");  // <-- not called
    });

There is no "XXX-0" in the console.

d3.select("#graph").graphviz()
    .dot(dotSrc, function () {
        console.log("XXX-1");
    })
    .render(function () {
        console.log("XXX-2");  // <-- not called
    });

Only "XXX-1" is printed.

Fail to minify d3-graphviz.js with react-app build

I create a project with create-react-app and with d3-graphviz as dependency. However, when build, it always popup such kind of error:

react-scripts build

Creating an optimized production build...
Failed to compile.

Failed to minify the code from this file:

./node_modules/d3-graphviz/src/drawEdge.js:14 

Read more here: http://bit.ly/2tRViJ9

It seems that some code in d3-graphviz fail to ES5?

Panning and zooming during transition are sometimes queued until after the transition

This has been a known issue since the inception of d3-graphviz and thought to be unavoidable. Recent discoveries (I finally took the time to understand transition.tween) have however shown that it isn't.

The problem occurs when the transform of the top level g element is transitioned from one value to another, which occurs when the height of the graph changes. The same transform is used by the zoom functionality and when the panning or zooming occurs, it is immediately overwritten by the transition. After the transition, the transform is recalculated based on the current zoom transform and the correct value is set. This looks to the user as if the zooming or panning was queued during the transition.

Interactive Visualizations

I like d3-graphviz very much for making the graphviz rendering available to d3. Would it be possible to give an example of how to implement actual user interaction with the graph? As simple as logging the name of a node to the console when I click on it (or a mouseover) would already be great... Thank you!

Support the new viz.js 2.0 API

viz.js version 2.0 will have a new API based on promises, see mdaines/viz-js#113. d3-graphviz should support this API by any of these options:

Option 1. Backwards compatible adaptation to the new viz.js API in a minor version
Option 2. Breaking changes by providing a promise-based API like the one viz.js has in new major version

Before investigating this, I don't know which one is to prefer, but my guess is option 2.

Error in Microsoft Edge: Unable to get property 'matrix' of undefined or null reference

d3-graphviz works for me in latest Chrome and Firefox on Windows 10, but Edge (version 40.15063.0.0) fails to render the DOT and displays the following error:

SCRIPT5007: Unable to get property 'matrix' of undefined or null reference
d3-graphviz.min.js (1,1398)

(It occurred to me to look for an existing related issue for Viz.js, but I couldn't find one, so I thought I'd begin by reporting it here.)

Software versions:

<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/[email protected]/viz.js" type="javascript/worker"></script>
<script src="https://unpkg.com/[email protected]/build/d3-graphviz.min.js"></script>

Labeltooltip draw issue

Hi everyone,

Try render this dot:

digraph X {
rankdir=LR;
"X" [label=Bob, shape=square, URL="Y", style=filled, fillcolor="#dd4477"];
"Y" [label=Bob2, shape=square, URL="Y", style=filled, fillcolor="#ff9900"];
"X" -> "Y" [label=Danser, labeltooltip=asdada];
}

When hovering the X -> Y edge, the tooltip is not displayed. Looking at the HTML it seems to be an order issue.

Move the g-tag for the label above all others in the rendered html and it works.

DOT-to-DOT transition: Uncaught TypeError: Cannot read property 'x' of undefined

(I couldn't resist writing "DOT-to-DOT" in the title.)

Transitioning from one DOT to another causes this error (latest Chrome on Windows 10, graphviz v1.1.0):

d3-graphviz.js:622 Uncaught TypeError: Cannot read property 'x' of undefined
at convertToPathData (d3-graphviz.js:622)
at extractData (d3-graphviz.js:730)
at SVGPathElement. (d3-graphviz.js:755)
at S.each (d3.v4.min.js:2)
at extractData (d3-graphviz.js:745)
at SVGAElement. (d3-graphviz.js:755)
at S.each (d3.v4.min.js:2)
at extractData (d3-graphviz.js:745)
at SVGGElement. (d3-graphviz.js:755)
at S.each (d3.v4.min.js:2)

The problem surfaces here:

var bbox = guideData.bbox;
bbox.cx = bbox.x + bbox.width / 2;

bbox is undefined.

This error only occurs for some DOT files. Any idea what the problem might be?

Animating a growing edge from a cylinder shape throws error

The error message is:
TypeError: prevStartShape is undefined

Local example:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="../node_modules/d3/dist/d3.js"></script>
<script src="../node_modules/viz.js/viz.js" type="javascript/worker"></script>
<script src="../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .transition(function () {
        return d3.transition("main");
    })
    .on("initEnd", render);

function render() {
    var dot = dots[dotIndex];
    graphviz
        .renderDot(dot)
        .on("end", function () {
            dotIndex = (dotIndex + 1) % dots.length;
            render();
        });
}

var dots = [`
digraph  {
    node [shape="cylinder"]
    a
}
`,`
digraph  {
    node [shape="cylinder"]
    a -> b
}
`];

</script>

renderDot does not trigger callback

This works (renders the graph in data):

d3.select("#graph").graphviz().renderDot(data);

So does this (as above, with an anonymous callback function), except that I don't get a console message; renderDot does not trigger the callback:

d3.select("#graph").graphviz().renderDot(data, function(){console.log("Ended")});

I'm interested in using a callback function because I want to zoom the graph to fit the viewport. That's what I want to do in the callback (although, I'm still working out the "zoom to fit" function).

I'm using latest Google Chrome on Windows 10.

In case it's related, the Chrome console shows the following error (which doesn't seem to affect the rendering):

Invalid asm.js: Function definition doesn't match use - viz.js:37

Software versions:

<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/[email protected]/viz.js" type="javascript/worker"></script>
<script src="https://unpkg.com/[email protected]/build/d3-graphviz.min.js"></script>

Null Reference Error on Safari on Mac OS.

I encountered a null reference error on Safari on Mac OS.
The error is as same as the one which occurs when I access demo page

I explored the code, and found
var matrix = transform.baseVal.consolidate().matrix; in getTranslation() seems to be the cause of this error.

render() does not work when graphviz instance is attached to a DOM element with sub-nodes

Even this simple case with a div containing a text node with a newline does not work:

<!DOCTYPE` html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="../../node_modules/viz.js/viz.js"></script>
<script src="../../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;">
</div>
<script>

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

</script>

The error message in the console is:

TypeError: d is undefined

Transpile the production build to ES5

The d3-graphviz source code uses a few ES6 language features which causes problems for and older browsers and certain build tools such as react-scripts.

In order to remedy this, the production build should be transpiled to ES5 before publishing it on npm.

This will fix #45.

How do I trap errors in the DOT?

How do I trap errors in the DOT; that is, the data that I pass to d3.select("#graph").graphviz().renderDot(data, myRenderDOTEndFunction)?

I've read the the following in the viz.js readme:

If Graphviz encounters an error, Viz will throw an Error object with the error message.

and I've looked at the d3-graphviz code that calls Viz, but I can't yet figure out how to trap errors in the input DOT that I pass to d3-graphviz.

I see that the Graphviz object returned by d3.select("#graph").graphviz().renderDot(data, myRenderDOTEndFunction) contains a _worker property, and _worker contains an onerror property that has the value null. I suspect I need to set that property.

I could burn time trying to figure out what to do next—I'm enjoying improving my JavaScript coding skills—but I need to crack on with other work.

P.S. I really like d3-graphviz. Thank you so much for this. I'm using d3-graphviz for what is, so far, a small internal-use-only project, but I hope to get management approval to publish my project as a GitHub repo, which will, effectively, be a showcase for d3-graphviz.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.