UNPKG

dc.graph

Version:

Graph visualizations integrated with crossfilter and dc.js

1,283 lines (1,189 loc) 103 kB
/** * `dc_graph.diagram` is a dc.js-compatible network visualization component. It registers in * the dc.js chart registry and its nodes and edges are generated from crossfilter groups. It * logically derives from the dc.js * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}, * but it does not physically derive from it since so much is different about network * visualization versus conventional charts. * @class diagram * @memberof dc_graph * @param {String|node} parent - Any valid * {@link https://github.com/mbostock/d3/wiki/Selections#selecting-elements d3 single selector} * specifying a dom block element such as a div; or a dom element. * @param {String} [chartGroup] - The name of the dc.js chart group this diagram instance * should be placed in. Filter interaction with a diagram will only trigger events and redraws * within the diagram's group. * @return {dc_graph.diagram} **/ dc_graph.diagram = function (parent, chartGroup) { // different enough from regular dc charts that we don't use dc.baseMixin // but attempt to implement most of that interface, copying some of the most basic stuff var _diagram = dc.marginMixin({}); _diagram.__dcFlag__ = dc.utils.uniqueId(); _diagram.margins({left: 10, top: 10, right: 10, bottom: 10}); var _dispatch = d3.dispatch('preDraw', 'data', 'end', 'start', 'render', 'drawn', 'receivedLayout', 'transitionsStarted', 'zoomed', 'reset'); var _nodes = {}, _edges = {}; // hold state between runs var _ports = {}; // id = node|edge/id/name var _clusters = {}; var _nodePorts; // ports sorted by node id var _stats = {}; var _nodes_snapshot, _edges_snapshot; var _arrows = {}; var _running = false; // for detecting concurrency issues var _anchor, _chartGroup; var _animateZoom; var _minWidth = 200; var _defaultWidthCalc = function (element) { var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width; return (width && width > _minWidth) ? width : _minWidth; }; var _widthCalc = _defaultWidthCalc; var _minHeight = 200; var _defaultHeightCalc = function (element) { var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height; return (height && height > _minHeight) ? height : _minHeight; }; var _heightCalc = _defaultHeightCalc; var _width, _height, _lastWidth, _lastHeight; function deprecate_layout_algo_parameter(name) { return function(value) { if(!_diagram.layoutEngine()) _diagram.layoutAlgorithm('cola', true); var engine = _diagram.layoutEngine(); if(engine.getEngine) engine = engine.getEngine(); if(engine[name]) { console.warn('property is deprecated, call on layout engine instead: dc_graph.diagram.%c' + name, 'font-weight: bold'); if(!arguments.length) return engine[name](); engine[name](value); } else { console.warn('property is deprecated, and is not supported for Warning: dc_graph.diagram.<b>' + name + '</b> is deprecated, and it is not supported for the "' + engine.layoutAlgorithm() + '" layout algorithm: ignored.'); if(!arguments.length) return null; } return this; }; } /** * Set or get the height attribute of the diagram. If a value is given, then the diagram is * returned for method chaining. If no value is given, then the current value of the height * attribute will be returned. * * The width and height are applied to the SVG element generated by the diagram on render, or * when `resizeSvg` is called. * * If the value is falsy or a function, the height will be calculated the first time it is * needed, using the provided function or default height calculator, and then cached. The * default calculator uses the client rect of the element specified when constructing the chart, * with a minimum of `minHeight`. A custom calculator will be passed the element. * * If the value is `'auto'`, the height will be calculated every time the diagram is drawn, and * it will not be set on the `<svg>` element. Instead, the element will be pinned to the same * rectangle as its containing div using CSS. * * @method height * @memberof dc_graph.diagram * @instance * @param {Number} [height=200] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.height = function (height) { if (!arguments.length) { if (!dc.utils.isNumber(_height)) { _lastHeight = _heightCalc(_diagram.root().node()); if(_height === 'auto') // 'auto' => calculate every time return _lastHeight; // null/undefined => calculate once only _height = _lastHeight; } return _height; } if(dc.utils.isNumber(height) || !height || height === 'auto') _height = height; else if(typeof height === 'function') { _heightCalc = height; _height = undefined; } else throw new Error("don't know what to do with height type " + typeof height + " value " + height); return _diagram; }; _diagram.minHeight = function(height) { if(!arguments.length) return _minHeight; _minHeight = height; return _diagram; }; /** * Set or get the width attribute of the diagram. If a value is given, then the diagram is * returned for method chaining. If no value is given, then the current value of the width * attribute will be returned. * * The width and height are applied to the SVG element generated by the diagram on render, or * when `resizeSvg` is called. * * If the value is falsy or a function, the width will be calculated the first time it is * needed, using the provided function or default width calculator, and then cached. The default * calculator uses the client rect of the element specified when constructing the chart, with a * minimum of `minWidth`. A custom calculator will be passed the element. * * If the value is `'auto'`, the width will be calculated every time the diagram is drawn, and * it will not be set on the `<svg>` element. Instead, the element will be pinned to the same * rectangle as its containing div using CSS. * * @method width * @memberof dc_graph.diagram * @instance * @param {Number} [width=200] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.width = function (width) { if (!arguments.length) { if (!dc.utils.isNumber(_width)) { _lastWidth = _widthCalc(_diagram.root().node()); if(_width === 'auto') // 'auto' => calculate every time return _lastWidth; // null/undefined => calculate once only _width = _lastWidth; } return _width; } if(dc.utils.isNumber(width) || !width || width === 'auto') _width = width; else if(typeof width === 'function') { _widthCalc = width; _width = undefined; } else throw new Error("don't know what to do with width type " + typeof width + " value " + width); return _diagram; }; _diagram.minWidth = function(width) { if(!arguments.length) return _minWidth; _minWidth = width; return _diagram; }; /** * Get or set the root element, which is usually the parent div. Normally the root is set * when the diagram is constructed; setting it later may have unexpected consequences. * @method root * @memberof dc_graph.diagram * @instance * @param {node} [root=null] * @return {node} * @return {dc_graph.diagram} **/ _diagram.root = property(null).react(function(e) { if(e.empty()) console.log('Warning: parent selector ' + parent + " doesn't seem to exist"); }); /** * Get or set whether mouse wheel rotation or touchpad gestures will zoom the diagram, and * whether dragging on the background pans the diagram. * @method mouseZoomable * @memberof dc_graph.diagram * @instance * @param {Boolean} [mouseZoomable=true] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.mouseZoomable = property(true); _diagram.zoomExtent = property([.1, 2]); /** * Whether zooming should only be enabled when the alt key is pressed. * @method altKeyZoom * @memberof dc_graph.diagram * @instance * @param {Boolean} [altKeyZoom=true] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.modKeyZoom = _diagram.altKeyZoom = property(false); /** * Set or get the fitting strategy for the canvas, which affects how the translate * and scale get calculated when `autoZoom` is triggered. * * * `'default'` - simulates the preserveAspectRatio behavior of `xMidYMid meet`, but * with margins - the content is stretched or squished in the more constrained * direction, and centered in the other direction * * `'vertical'` - fits the canvas vertically (with vertical margins) and centers * it horizontally. If the canvas is taller than the viewport, it will meet * vertically and there will be blank areas to the left and right. If the canvas * is wider than the viewport, it will be sliced. * * `'horizontal'` - fits the canvas horizontally (with horizontal margins) and * centers it vertically. If the canvas is wider than the viewport, it will meet * horizontally and there will be blank areas above and below. If the canvas is * taller than the viewport, it will be sliced. * * Other options * * `null` - no attempt is made to fit the content in the viewport * * `'zoom'` - does not scale the content, but attempts to bring as much content * into view as possible, using using the same algorithm as `restrictPan` * * `'align_{tlbrc}[2]'` - does not scale; aligns up to two sides or centers them * @method fitStrategy * @memberof dc_graph.diagram * @instance * @param {String} [fitStrategy='default'] * @return {String} * @return {dc_graph.diagram} **/ _diagram.fitStrategy = property('default'); /** * Do not allow panning (scrolling) to push the diagram out of the viewable area, if there * is space for it to be shown. */ _diagram.restrictPan = property(false); /** * Auto-zoom behavior. * * `'always'` - zoom every time layout happens * * `'once'` - zoom the next time layout happens * * `null` - manual, call `zoomToFit` to fit * @method autoZoom * @memberof dc_graph.diagram * @instance * @param {String} [autoZoom=null] * @return {String} * @return {dc_graph.diagram} **/ _diagram.autoZoom = property(null); _diagram.zoomToFit = function(animate) { // if(!(_nodeLayer && _edgeLayer)) // return; auto_zoom(animate); }; _diagram.zoomDuration = property(500); /** * Set or get the crossfilter dimension which represents the nodes (vertices) in the * diagram. Typically there will be a crossfilter instance for the nodes, and another for * the edges. * * *Dimensions are included on the diagram for similarity to dc.js, however the diagram * itself does not use them - but {@link dc_graph.filter_selection filter_selection} will.* * @method nodeDimension * @memberof dc_graph.diagram * @instance * @param {crossfilter.dimension} [nodeDimension] * @return {crossfilter.dimension} * @return {dc_graph.diagram} **/ _diagram.nodeDimension = property(); /** * Set or get the crossfilter group which is the data source for the nodes in the * diagram. The diagram will use the group's `.all()` method to get an array of `{key, * value}` pairs, where the key is a unique identifier, and the value is usually an object * containing the node's attributes. All accessors work with these key/value pairs. * * If the group is changed or returns different values, the next call to `.redraw()` will * reflect the changes incrementally. * * It is possible to pass another object with the same `.all()` interface instead of a * crossfilter group. * @method nodeGroup * @memberof dc_graph.diagram * @instance * @param {crossfilter.group} [nodeGroup] * @return {crossfilter.group} * @return {dc_graph.diagram} **/ _diagram.nodeGroup = property(); /** * Set or get the crossfilter dimension which represents the edges in the * diagram. Typically there will be a crossfilter instance for the nodes, and another for * the edges. * * *Dimensions are included on the diagram for similarity to dc.js, however the diagram * itself does not use them - but {@link dc_graph.filter_selection filter_selection} will.* * @method edgeDimension * @memberof dc_graph.diagram * @instance * @param {crossfilter.dimension} [edgeDimension] * @return {crossfilter.dimension} * @return {dc_graph.diagram} **/ _diagram.edgeDimension = property(); /** * Set or get the crossfilter group which is the data source for the edges in the * diagram. See `.nodeGroup` above for the way data is loaded from a crossfilter group. * * The values in the key/value pairs returned by `diagram.edgeGroup().all()` need to * support, at a minimum, the {@link dc_graph.diagram#nodeSource nodeSource} and * {@link dc_graph.diagram#nodeTarget nodeTarget}, which should return the same * keys as the {@link dc_graph.diagram#nodeKey nodeKey} * * @method edgeGroup * @memberof dc_graph.diagram * @instance * @param {crossfilter.group} [edgeGroup] * @return {crossfilter.group} * @return {dc_graph.diagram} **/ _diagram.edgeGroup = property(); _diagram.edgesInFront = property(false); /** * Set or get the function which will be used to retrieve the unique key for each node. By * default, this accesses the `key` field of the object passed to it. The keys should match * the keys returned by the {@link dc_graph.diagram#edgeSource edgeSource} and * {@link dc_graph.diagram#edgeTarget edgeTarget}. * * @method nodeKey * @memberof dc_graph.diagram * @instance * @param {Function} [nodeKey=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.nodeKey = _diagram.nodeKeyAccessor = property(function(kv) { return kv.key; }); /** * Set or get the function which will be used to retrieve the unique key for each edge. By * default, this accesses the `key` field of the object passed to it. * * @method edgeKey * @memberof dc_graph.diagram * @instance * @param {Function} [edgeKey=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.edgeKey = _diagram.edgeKeyAccessor = property(function(kv) { return kv.key; }); /** * Set or get the function which will be used to retrieve the source (origin/tail) key of * the edge objects. The key must equal the key returned by the `.nodeKey` for one of the * nodes; if it does not, or if the node is currently filtered out, the edge will not be * displayed. By default, looks for `.value.sourcename`. * * @method edgeSource * @memberof dc_graph.diagram * @instance * @param {Function} [edgeSource=function(kv) { return kv.value.sourcename; }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.edgeSource = _diagram.sourceAccessor = property(function(kv) { return kv.value.sourcename; }); /** * Set or get the function which will be used to retrieve the target (destination/head) key * of the edge objects. The key must equal the key returned by the * {@link dc_graph.diagram#nodeKey nodeKey} for one of the nodes; if it does not, or if the node * is currently filtered out, the edge will not be displayed. By default, looks for * `.value.targetname`. * @method edgeTarget * @memberof dc_graph.diagram * @instance * @param {Function} [edgeTarget=function(kv) { return kv.value.targetname; }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.edgeTarget = _diagram.targetAccessor = property(function(kv) { return kv.value.targetname; }); _diagram.portDimension = property(null); _diagram.portGroup = property(null); _diagram.portNodeKey = property(null); _diagram.portEdgeKey = property(null); _diagram.portName = property(null); _diagram.portStyleName = property(null); _diagram.portElastic = property(true); _diagram.portStyle = named_children(); _diagram.portBounds = property(null); // position limits, in radians _diagram.edgeSourcePortName = property(null); _diagram.edgeTargetPortName = property(null); /** * Set or get the crossfilter dimension which represents the edges in the * diagram. Typically there will be a crossfilter instance for the nodes, and another for * the edges. * * *As with node and edge dimensions, the diagram will itself not filter on cluster dimensions; * this is included for symmetry, and for modes which may want to filter clusters.* * @method clusterDimension * @memberof dc_graph.diagram * @instance * @param {crossfilter.dimension} [clusterDimension] * @return {crossfilter.dimension} * @return {dc_graph.diagram} **/ _diagram.clusterDimension = property(null); /** * Set or get the crossfilter group which is the data source for clusters in the * diagram. * * The key/value pairs returned by `diagram.clusterGroup().all()` need to support, at a minimum, * the {@link dc_graph.diagram#clusterKey clusterKey} and {@link dc_graph.diagram#clusterParent clusterParent} * accessors, which should return keys in this group. * * @method clusterGroup * @memberof dc_graph.diagram * @instance * @param {crossfilter.group} [clusterGroup] * @return {crossfilter.group} * @return {dc_graph.diagram} **/ _diagram.clusterGroup = property(null); // cluster accessors /** * Set or get the function which will be used to retrieve the unique key for each cluster. By * default, this accesses the `key` field of the object passed to it. * * @method clusterKey * @memberof dc_graph.diagram * @instance * @param {Function} [clusterKey=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.clusterKey = property(dc.pluck('key')); /** * Set or get the function which will be used to retrieve the key of the parent of a cluster, * which is another cluster. * * @method clusterParent * @memberof dc_graph.diagram * @instance * @param {Function} [clusterParent=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.clusterParent = property(null); /** * Set or get the function which will be used to retrieve the padding, in pixels, around a cluster. * * **To be implemented.** If a single value is returned, it will be used on all sides; if two * values are returned they will be interpreted as the vertical and horizontal padding. * * @method clusterPadding * @memberof dc_graph.diagram * @instance * @param {Function} [clusterPadding=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.clusterPadding = property(8); // node accessor /** * Set or get the function which will be used to retrieve the parent cluster of a node, or * `null` if the node is not in a cluster. * * @method nodeParentCluster * @memberof dc_graph.diagram * @instance * @param {Function} [nodeParentCluster=function(kv) { return kv.key }] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.nodeParentCluster = property(null); /** * Set or get the function which will be used to retrieve the radius, in pixels, for each * node. This determines the height of nodes,and if `nodeFitLabel` is false, the width too. * @method nodeRadius * @memberof dc_graph.diagram * @instance * @param {Function|Number} [nodeRadius=25] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodeRadius = _diagram.nodeRadiusAccessor = property(25); /** * Set or get the function which will be used to retrieve the stroke width, in pixels, for * drawing the outline of each node. According to the SVG specification, the outline will * be drawn half on top of the fill, and half outside. Default: 1 * @method nodeStrokeWidth * @memberof dc_graph.diagram * @instance * @param {Function|Number} [nodeStrokeWidth=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodeStrokeWidth = _diagram.nodeStrokeWidthAccessor = property(1); /** * Set or get the function which will be used to retrieve the stroke color for the outline * of each node. * @method nodeStroke * @memberof dc_graph.diagram * @instance * @param {Function|String} [nodeStroke='black'] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.nodeStroke = _diagram.nodeStrokeAccessor = property('black'); _diagram.nodeStrokeDashArray = property(null); /** * If set, the value returned from `nodeFill` will be processed through this * {@link https://github.com/mbostock/d3/wiki/Scales d3.scale} * to return the fill color. If falsy, uses the identity function (no scale). * @method nodeFillScale * @memberof dc_graph.diagram * @instance * @param {Function|d3.scale} [nodeFillScale] * @return {Function|d3.scale} * @return {dc_graph.diagram} **/ _diagram.nodeFillScale = property(null); /** * Set or get the function which will be used to retrieve the fill color for the body of each * node. * @method nodeFill * @memberof dc_graph.diagram * @instance * @param {Function|String} [nodeFill='white'] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.nodeFill = _diagram.nodeFillAccessor = property('white'); /** * Set or get the function which will be used to retrieve the opacity of each node. * @method nodeOpacity * @memberof dc_graph.diagram * @instance * @param {Function|Number} [nodeOpacity=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodeOpacity = property(1); /** * Set or get the padding or minimum distance, in pixels, for a node. (Will be distributed * to both sides of the node.) * @method nodePadding * @memberof dc_graph.diagram * @instance * @param {Function|Number} [nodePadding=6] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodePadding = property(6); /** * Set or get the padding, in pixels, for a node's label. If an object, should contain fields * `x` and `y`. If a number, will be applied to both x and y. * @method nodeLabelPadding * @memberof dc_graph.diagram * @instance * @param {Function|Number|Object} [nodeLabelPadding=0] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodeLabelPadding = property(0); /** * Set or get the line height for nodes with multiple lines of text, in ems. * @method nodeLineHeight * @memberof dc_graph.diagram * @instance * @param {Function|Number} [nodeLineHeight=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.nodeLineHeight = property(1); /** * Set or get the function which will be used to retrieve the label text to display in each * node. By default, looks for a field `label` or `name` inside the `value` field. * @method nodeLabel * @memberof dc_graph.diagram * @instance * @param {Function|String} [nodeLabel] * @return {Function|String} * @example * // Default behavior * diagram.nodeLabel(function(kv) { * return kv.value.label || kv.value.name; * }); * @return {dc_graph.diagram} **/ _diagram.nodeLabel = _diagram.nodeLabelAccessor = property(function(kv) { return kv.value.label || kv.value.name; }); _diagram.nodeLabelAlignment = property('center'); _diagram.nodeLabelDecoration = property(null); /** * Set or get the function which will be used to retrieve the label fill color. Default: null * @method nodeLabelFill * @memberof dc_graph.diagram * @instance * @param {Function|String} [nodeLabelFill=null] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.nodeLabelFill = _diagram.nodeLabelFillAccessor = property(null); /** * Whether to fit the node shape around the label * @method nodeFitLabel * @memberof dc_graph.diagram * @instance * @param {Function|Boolean} [nodeFitLabel=true] * @return {Function|Boolean} * @return {dc_graph.diagram} **/ _diagram.nodeFitLabel = _diagram.nodeFitLabelAccessor = property(true); /** * The shape to use for drawing each node, specified as an object with at least the field * `shape`. The names of shapes are mostly taken * [from graphviz](http://www.graphviz.org/doc/info/shapes.html); currently ellipse, egg, * triangle, rectangle, diamond, trapezium, parallelogram, pentagon, hexagon, septagon, octagon, * invtriangle, invtrapezium, square, polygon are supported. * * If `shape = polygon`: * * `sides`: number of sides for a polygon * @method nodeShape * @memberof dc_graph.diagram * @instance * @param {Function|Object} [nodeShape={shape: 'ellipse'}] * @return {Function|Object} * @return {dc_graph.diagram} * @example * // set shape to diamond or parallelogram based on flag * diagram.nodeShape(function(kv) { * return {shape: kv.value.flag ? 'diamond' : 'parallelogram'}; * }); **/ _diagram.nodeShape = property(default_shape); // for defining custom (and standard) shapes _diagram.shape = named_children(); _diagram.shape('nothing', dc_graph.no_shape()); _diagram.shape('ellipse', dc_graph.ellipse_shape()); _diagram.shape('polygon', dc_graph.polygon_shape()); _diagram.shape('rounded-rect', dc_graph.rounded_rectangle_shape()); _diagram.shape('elaborated-rect', dc_graph.elaborated_rectangle_shape()); _diagram.nodeContent = property('text'); _diagram.content = named_children(); _diagram.content('text', dc_graph.text_contents()); // really looks like these should reside in an open namespace - this used only by an extension // but it's no less real than any other computed property _diagram.nodeIcon = property(null); /** * Set or get the function which will be used to retrieve the node title, usually rendered * as a tooltip. By default, uses the key of the node. * @method nodeTitle * @memberof dc_graph.diagram * @instance * @param {Function|String} [nodeTitle] * @return {Function|String} * @example * // Default behavior * diagram.nodeTitle(function(kv) { * return _diagram.nodeKey()(kv); * }); * @return {dc_graph.diagram} **/ _diagram.nodeTitle = _diagram.nodeTitleAccessor = property(function(kv) { return _diagram.nodeKey()(kv); }); /** * By default, nodes are added to the layout in the order that `.nodeGroup().all()` returns * them. If specified, `.nodeOrdering` provides an accessor that returns a key to sort the * nodes on. *It would be better not to rely on ordering to affect layout, but it may * affect the layout in some cases.* * @method nodeOrdering * @memberof dc_graph.diagram * @instance * @param {Function} [nodeOrdering=null] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.nodeOrdering = property(null); /** * Specify an accessor that returns an {x,y} coordinate for a node that should be * {@link https://github.com/tgdwyer/WebCola/wiki/Fixed-Node-Positions fixed in place}, * and returns falsy for other nodes. * @method nodeFixed * @memberof dc_graph.diagram * @instance * @param {Function|Object} [nodeFixed=null] * @return {Function|Object} * @return {dc_graph.diagram} **/ _diagram.nodeFixed = _diagram.nodeFixedAccessor = property(null); /** * Set or get the function which will be used to retrieve the stroke color for the edges. * @method edgeStroke * @memberof dc_graph.diagram * @instance * @param {Function|String} [edgeStroke='black'] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.edgeStroke = _diagram.edgeStrokeAccessor = property('black'); /** * Set or get the function which will be used to retrieve the stroke width for the edges. * @method edgeStrokeWidth * @memberof dc_graph.diagram * @instance * @param {Function|Number} [edgeStrokeWidth=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.edgeStrokeWidth = _diagram.edgeStrokeWidthAccessor = property(1); _diagram.edgeStrokeDashArray = property(null); /** * Set or get the function which will be used to retrieve the edge opacity, a number from 0 * to 1. * @method edgeOpacity * @memberof dc_graph.diagram * @instance * @param {Function|Number} [edgeOpacity=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.edgeOpacity = _diagram.edgeOpacityAccessor = property(1); /** * Set or get the function which will be used to retrieve the edge label text. The label is * displayed when an edge is hovered over. By default, uses the `edgeKey`. * @method edgeLabel * @memberof dc_graph.diagram * @instance * @param {Function|String} [edgeLabel] * @example * // Default behavior * diagram.edgeLabel(function(e) { * return _diagram.edgeKey()(e); * }); * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.edgeLabel = _diagram.edgeLabelAccessor = property(function(e) { return _diagram.edgeKey()(e); }); // vertical spacing when there are multiple lines of edge label _diagram.edgeLabelSpacing = property(12); /** * Set or get the function which will be used to retrieve the name of the arrowhead to use * for the target/ head/destination of the edge. Arrow symbols can be specified with * `.defineArrow()`. Return null to display no arrowhead. * @method edgeArrowhead * @memberof dc_graph.diagram * @instance * @param {Function|String} [edgeArrowhead='vee'] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.edgeArrowhead = _diagram.edgeArrowheadAccessor = property('vee'); /** * Set or get the function which will be used to retrieve the name of the arrow tail to use * for the tail/source of the edge. Arrow symbols can be specified with * `.defineArrow()`. Return null to display no arrowtail. * @method edgeArrowtail * @memberof dc_graph.diagram * @instance * @param {Function|String} [edgeArrowtail=null] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.edgeArrowtail = _diagram.edgeArrowtailAccessor = property(null); /** * Multiplier for arrow size. * @method edgeArrowSize * @memberof dc_graph.diagram * @instance * @param {Function|Number} [edgeArrowSize=1] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.edgeArrowSize = property(1); /** * To draw an edge but not have it affect the layout, specify a function which returns * false for that edge. By default, will return false if the `notLayout` field of the edge * value is truthy, true otherwise. * @method edgeIsLayout * @memberof dc_graph.diagram * @instance * @param {Function|Boolean} [edgeIsLayout] * @example * // Default behavior * diagram.edgeIsLayout(function(kv) { * return !kv.value.notLayout; * }); * @return {Function|Boolean} * @return {dc_graph.diagram} **/ _diagram.edgeIsLayout = _diagram.edgeIsLayoutAccessor = property(function(kv) { return !kv.value.notLayout; }); // if false, don't draw or layout the edge. this is not documented because it seems like // the interface could be better and this combined with edgeIsLayout. (currently there is // no way to layout but not draw an edge.) _diagram.edgeIsShown = property(true); /** * Currently, three strategies are supported for specifying the lengths of edges: * * 'individual' - uses the `edgeLength` for each edge. If it returns falsy, uses the * `baseLength` * * 'symmetric', 'jaccard' - compute the edge length based on the graph structure around * the edge. See * {@link https://github.com/tgdwyer/WebCola/wiki/link-lengths the cola.js wiki} * for more details. * 'none' - no edge lengths will be specified * * **Deprecated**: Use {@link dc_graph.cola_layout#lengthStrategy cola_layout.lengthStrategy} instead. * @method lengthStrategy * @memberof dc_graph.diagram * @instance * @param {Function|String} [lengthStrategy='symmetric'] * @return {Function|String} * @return {dc_graph.diagram} **/ _diagram.lengthStrategy = deprecate_layout_algo_parameter('lengthStrategy'); /** * When the `.lengthStrategy` is 'individual', this accessor will be used to read the * length of each edge. By default, reads the `distance` field of the edge. If the * distance is falsy, uses the `baseLength`. * @method edgeLength * @memberof dc_graph.diagram * @instance * @param {Function|Number} [edgeLength] * @example * // Default behavior * diagram.edgeLength(function(kv) { * return kv.value.distance; * }); * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.edgeLength = _diagram.edgeDistanceAccessor = property(function(kv) { return kv.value.distance; }); /** * This should be equivalent to rankdir and ranksep in the dagre/graphviz nomenclature, but for * now it is separate. * * **Deprecated**: use {@link dc_graph.cola_layout#flowLayout cola_layout.flowLayout} instead. * @method flowLayout * @memberof dc_graph.diagram * @instance * @param {Object} [flowLayout] * @example * // No flow (default) * diagram.flowLayout(null) * // flow in x with min separation 200 * diagram.flowLayout({axis: 'x', minSeparation: 200}) **/ _diagram.flowLayout = deprecate_layout_algo_parameter('flowLayout'); /** * Direction to draw ranks. Currently for dagre and expand_collapse, but I think cola could be * generated from graphviz-style since it is more general. * * **Deprecated**: use {@link dc_graph.dagre_layout#rankdir dagre_layout.rankdir} instead. * @method rankdir * @memberof dc_graph.diagram * @instance * @param {String} [rankdir] **/ _diagram.rankdir = deprecate_layout_algo_parameter('rankdir'); /** * Gets or sets the default edge length (in pixels) when the `.lengthStrategy` is * 'individual', and the base value to be multiplied for 'symmetric' and 'jaccard' edge * lengths. * * **Deprecated**: use {@link dc_graph.cola_layout#baseLength cola_layout.baseLength} instead. * @method baseLength * @memberof dc_graph.diagram * @instance * @param {Number} [baseLength] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.baseLength = deprecate_layout_algo_parameter('baseLength'); /** * Gets or sets the transition duration, the length of time each change to the diagram will * be animated. * @method transitionDuration * @memberof dc_graph.diagram * @instance * @param {Number} [transitionDuration=500] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.transitionDuration = property(500); /** * How transitions should be split into separate animations to emphasize * the delete, modify, and insert operations: * * `none`: modify and insert operations animate at the same time * * `modins`: modify operations happen before inserts * * `insmod`: insert operations happen before modifies * * Deletions always happen before/during layout computation. * @method stageTransitions * @memberof dc_graph.diagram * @instance * @param {String} [stageTransitions='none'] * @return {String} * @return {dc_graph.diagram} **/ _diagram.stageTransitions = property('none'); /** * The delete transition happens simultaneously with layout, which can take longer * than the transition duration. Delaying it can bring it closer to the other * staged transitions. * @method deleteDelay * @memberof dc_graph.diagram * @instance * @param {Number} [deleteDelay=0] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.deleteDelay = property(0); /** * Whether to put connected components each in their own group, to stabilize layout. * @method groupConnected * @memberof dc_graph.diagram * @instance * @param {String} [groupConnected=false] * @return {String} * @return {dc_graph.diagram} **/ _diagram.groupConnected = deprecate_layout_algo_parameter('groupConnected'); /** * Gets or sets the maximum time spent doing layout for a render or redraw. Set to 0 for no * limit. * @method timeLimit * @memberof dc_graph.diagram * @instance * @param {Function|Number} [timeLimit=0] * @return {Function|Number} * @return {dc_graph.diagram} **/ _diagram.timeLimit = property(0); /** * Gets or sets a function which will be called with the current nodes and edges on each * redraw in order to derive new layout constraints. The constraints are built from scratch * on each redraw. * * This can be used to generate alignment (rank) or axis constraints. By default, no * constraints will be added, although cola.js uses constraints internally to implement * flow and overlap prevention. See * {@link https://github.com/tgdwyer/WebCola/wiki/Constraints the cola.js wiki} * for more details. * * For convenience, dc.graph.js implements a other constraints on top of those implemented * by cola.js: * * 'ordering' - the nodes will be ordered on the specified `axis` according to the keys * returned by the `ordering` function, by creating separation constraints using the * specified `gap`. * * 'circle' - (experimental) the nodes will be placed in a circle using "wheel" * edge lengths similar to those described in * {@link http://www.csse.monash.edu.au/~tdwyer/Dwyer2009FastConstraints.pdf Scalable, Versatile, and Simple Constrained Graph Layout} * *Although this is not as performant or stable as might be desired, it may work for * simple cases. In particular, it should use edge length *constraints*, which don't yet * exist in cola.js.* * * Because it is tedious to write code to generate constraints for a graph, **dc.graph.js** * also includes a {@link #dc_graph+constraint_pattern constraint generator} to produce * this constrain function, specifying the constraints themselves in a graph. * @method constrain * @memberof dc_graph.diagram * @instance * @param {Function} [constrain] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.constrain = property(function(nodes, edges) { return []; }); /** * If there are multiple edges between the same two nodes, start them this many pixels away * from the original so they don't overlap. * @method parallelEdgeOffset * @memberof dc_graph.diagram * @instance * @param {Number} [parallelEdgeOffset=10] * @return {Number} * @return {dc_graph.diagram} **/ _diagram.parallelEdgeOffset = property(10); /** * By default, edges are added to the layout in the order that `.edgeGroup().all()` returns * them. If specified, `.edgeOrdering` provides an accessor that returns a key to sort the * edges on. * * *It would be better not to rely on ordering to affect layout, but it may affect the * layout in some cases. (Probably less than node ordering, but it does affect which * parallel edge is which.)* * @method edgeOrdering * @memberof dc_graph.diagram * @instance * @param {Function} [edgeOrdering=null] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.edgeOrdering = property(null); _diagram.edgeSort = property(null); _diagram.cascade = cascade(_diagram); /** * Currently there are some bugs when the same instance of cola.js is used multiple * times. (In particular, overlaps between nodes may not be eliminated * {@link https://github.com/tgdwyer/WebCola/issues/118 if cola is not reinitialized} * This flag can be set true to construct a new cola layout object on each redraw. However, * layout seems to be more stable if this is set false, so hopefully this will be fixed * soon. * @method initLayoutOnRedraw * @memberof dc_graph.diagram * @instance * @param {Boolean} [initLayoutOnRedraw=false] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.initLayoutOnRedraw = property(false); /** * Whether to perform layout when the data is unchanged from the last redraw. * @method layoutUnchanged * @memberof dc_graph.diagram * @instance * @param {Boolean} [layoutUnchanged=false] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.layoutUnchanged = property(false); _diagram.nodeChangeSelect = property(function() { if(_diagram.layoutEngine().supportsMoving && _diagram.layoutEngine().supportsMoving()) return topology_node; else return basic_node; }); _diagram.edgeChangeSelect = property(function() { if(_diagram.layoutEngine().supportsMoving && _diagram.layoutEngine().supportsMoving()) return topology_edge; else return basic_edge; }); /** * When `layoutUnchanged` is false, this will force layout to happen again. This may be needed * when changing a parameter but not changing the topology of the graph. (Yes, probably should * not be necessary.) * @method relayout * @memberof dc_graph.diagram * @instance * @return {dc_graph.diagram} **/ _diagram.relayout = function() { _nodes_snapshot = _edges_snapshot = null; return this; }; /** * Function to call to generate an initial layout. Takes (diagram, nodes, edges) * * **Deprecated**: The only layout that was using this was `tree_positions` and it never * worked as an initialization step for cola, as was originally intended. Now that * `tree_layout` is a layout algorithm, this should go away. * * In the future, there will be support for chaining layout algorithms. But that will be a * matter of composing them into a super-algorithm, not a special step like this was. * @method initialLayout * @memberof dc_graph.diagram * @instance * @param {Function} [initialLayout=null] * @return {Function} * @return {dc_graph.diagram} **/ _diagram.initialLayout = deprecated_property('initialLayout is deprecated - use layout algorithms instead', null); _diagram.initialOnly = deprecated_property('initialOnly is deprecated - see the initialLayout deprecation notice in the documentation', false); /** * By default, all nodes are included, and edges are only included if both end-nodes are * visible. If `.induceNodes` is set, then only nodes which have at least one edge will be * shown. * @method induceNodes * @memberof dc_graph.diagram * @instance * @param {Boolean} [induceNodes=false] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.induceNodes = property(false); /** * If this flag is true, the positions of nodes and will be updated while layout is * iterating. If false, the positions will only be updated once layout has * stabilized. Note: this may not be compatible with transitionDuration. * @method showLayoutSteps * @memberof dc_graph.diagram * @instance * @param {Boolean} [showLayoutSteps=false] * @return {Boolean} * @return {dc_graph.diagram} **/ _diagram.showLayoutSteps = property(false); /** * Assigns a legend object which will be displayed within the same SVG element and * according to the visual encoding of this diagram. * @method legend * @memberof dc_graph.diagram * @instance * @param {Object} [legend=null] * @return {Object} * @return {dc_graph.diagram} **/ // (pre-deprecated; see below) /** * Specifies another kind of child layer or interface. For example, this can * be used to display tooltips on nodes using `dc_graph.tip`. * The child needs to support a `parent` method, the diagram to modify. * @method child * @memberof dc_graph.diagram * @instance * @param {String} [id] - the name of the child to modify or add * @param {Object} [object] - the child object to add, or null to remove * @example * // Display tooltips on node hover, via the d3-tip library * var tip = dc_graph.tip() * tip.content(function(n, k) { * // you can do an asynchronous call here, e.g. d3.json, if you need * // to fetch data to show the tooltip - just call k() with the content * k("This is <em>" + n.orig.value.name + "</em>"); * }); * diagram.child('tip', tip); * @return {dc_graph.diagram} **/ _diagram.mode = _diagram.child = named_children(); _diagram.mode.reject = function(id, object) { var rtype = _diagram.renderer().rendererType(); if(!object) return false; // null is always a valid mode for any renderer if(!object.supportsRenderer) console.log('could not check if "' + id + '" is compatible with ' + rtype); else if(!object.supportsRenderer(rtype)) return 'not installing "' + id + '" because it is not compatible with renderer ' + rtype; return false; }; _diagram.legend = deprecate_function(".legend() is deprecated; use .child() for more control & multiple legends", function(_) { if(!arguments.length) return _diagram.child('node-legend'); _diagram.child('node-legend', _); return _diagram; }); /** * Specify 'cola' (the default) or 'dagre' as the Layout Algorithm and it will replace the * back-end. * * **Deprecated**: use {@link dc_graph.diagram#layoutEngine diagram.layoutEngine} with the engine * object instead * @method layoutAlgorithm * @memberof dc_graph.diagram * @instance * @param {String} [algo='cola'] - the name of the layout algorithm to use * @example * // use dagre for layout * diagram.layoutAlgorithm('dagre'); * @return {dc_graph.diagram} **/ _diagram.layoutAlgorithm = function(value, skipWarning) { if(!arguments.length) return _diagram.layoutEngine() ? _diagram.layoutEngine().layoutAlgorithm() : 'cola'; if(!skipWarning) console.warn('dc.graph.diagram.layoutAlgorithm is deprecated - pass the layout engine object to dc_graph.diagram.layoutEngine instead'); var engine; switch(value) { case 'cola': engine = dc_graph.cola_layout(); break; case 'dagre': engine = dc_graph.dagre_layout(); } engine = dc_graph.webworker_layout(engine); _diagram.layoutEngine(engine); return this; }; /** * The layout engine determines positions of nodes and edges. * @method layoutEngine * @memberof dc_graph.diagram * @instance * @param {Object} [engine=null] - the layout engine to use * @exa