/** * Constructs a new, empty arc layout. Layouts are not typically constructed * directly; instead, they are added to an existing panel via * {@link pv.Mark#add}. * * @class Implements a layout for arc diagrams. An arc diagram is a network * visualization with a one-dimensional layout of nodes, using circular arcs to * render links between nodes. For undirected networks, arcs are rendering on a * single side; this makes arc diagrams useful as annotations to other * two-dimensional network layouts, such as rollup, matrix or table layouts. For * directed networks, links in opposite directions can be rendered on opposite * sides using directed(true). * *
Arc layouts are particularly sensitive to node ordering; for best results, * order the nodes such that related nodes are close to each other. A poor * (e.g., random) order may result in large arcs with crossovers that impede * visual processing. A future improvement to this layout may include automatic * reordering using, e.g., spectral graph layout or simulated annealing. * *
This visualization technique is related to that developed by * M. Wattenberg, "Arc * Diagrams: Visualizing Structure in Strings" in IEEE InfoVis, 2002. * However, this implementation is limited to simple node-link networks, as * opposed to structures with hierarchical self-similarity (such as strings). * *
As with other network layouts, three mark prototypes are provided:
Note that arc diagrams are particularly sensitive to order. This is * referred to as the seriation problem, and many different techniques exist to * find good node orders that emphasize clusters, such as spectral layout and * simulated annealing. * * @param {function} f comparator function for nodes. * @returns {pv.Layout.Arc} this. */ pv.Layout.Arc.prototype.sort = function(f) { this.$sort = f; return this; }; /** @private Populates the x, y and angle attributes on the nodes. */ pv.Layout.Arc.prototype.buildImplied = function(s) { if (pv.Layout.Network.prototype.buildImplied.call(this, s)) return; var nodes = s.nodes, orient = s.orient, sort = this.$sort, index = pv.range(nodes.length), w = s.width, h = s.height, r = Math.min(w, h) / 2; /* Sort the nodes. */ if (sort) index.sort(function(a, b) { return sort(nodes[a], nodes[b]); }); /** @private Returns the mid-angle, given the breadth. */ function midAngle(b) { switch (orient) { case "top": return -Math.PI / 2; case "bottom": return Math.PI / 2; case "left": return Math.PI; case "right": return 0; case "radial": return (b - .25) * 2 * Math.PI; } } /** @private Returns the x-position, given the breadth. */ function x(b) { switch (orient) { case "top": case "bottom": return b * w; case "left": return 0; case "right": return w; case "radial": return w / 2 + r * Math.cos(midAngle(b)); } } /** @private Returns the y-position, given the breadth. */ function y(b) { switch (orient) { case "top": return 0; case "bottom": return h; case "left": case "right": return b * h; case "radial": return h / 2 + r * Math.sin(midAngle(b)); } } /* Populate the x, y and mid-angle attributes. */ for (var i = 0; i < nodes.length; i++) { var n = nodes[index[i]], b = n.breadth = (i + .5) / nodes.length; n.x = x(b); n.y = y(b); n.midAngle = midAngle(b); } }; /** * The orientation. The default orientation is "left", which means that nodes * will be positioned from left-to-right in the order they are specified in the * nodes property. The following orientations are supported: