dc_graph.render_svg = function() { var _svg = null, _defs = null, _g = null, _nodeLayer = null, _edgeLayer = null; var _animating = false; // do not refresh during animations var _zoom; var _renderer = {}; _renderer.rendererType = function() { return 'svg'; }; _renderer.parent = property(null); _renderer.renderNode = _renderer._enterNode = function(nodeEnter) { if(_renderer.parent().nodeTitle()) nodeEnter.append('title'); nodeEnter.each(infer_shape(_renderer.parent())); _renderer.parent().forEachShape(nodeEnter, function(shape, node) { node.call(shape.create); }); return _renderer; }; _renderer.redrawNode = _renderer._updateNode = function(node) { var changedShape = node.filter(shape_changed(_renderer.parent())); changedShape.selectAll('.node-outline,.node-fill').remove(); changedShape.each(infer_shape(_renderer.parent())); _renderer.parent().forEachShape(changedShape, function(shape, node) { node.call(shape.create); }); node.select('title') .text(_renderer.parent().nodeTitle.eval); _renderer.parent().forEachContent(node, function(contentType, node) { node.call(contentType.update); _renderer.parent().forEachShape(contentType.selectContent(node), function(shape, content) { content .call(fit_shape(shape, _renderer.parent())); }); }); _renderer.parent().forEachShape(node, function(shape, node) { node.call(shape.update); }); node.select('.node-fill') .attr({ fill: compose(_renderer.parent().nodeFillScale() || identity, _renderer.parent().nodeFill.eval) }); node.select('.node-outline') .attr({ stroke: _renderer.parent().nodeStroke.eval, 'stroke-width': _renderer.parent().nodeStrokeWidth.eval, 'stroke-dasharray': _renderer.parent().nodeStrokeDashArray.eval }); return _renderer; }; _renderer.redrawEdge = _renderer._updateEdge = function(edge, edgeArrows) { edge .attr('stroke', _renderer.parent().edgeStroke.eval) .attr('stroke-width', _renderer.parent().edgeStrokeWidth.eval) .attr('stroke-dasharray', _renderer.parent().edgeStrokeDashArray.eval); edgeArrows .attr('marker-end', function(e) { var name = _renderer.parent().edgeArrowhead.eval(e), id = edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'head', name); return id ? 'url(#' + id + ')' : null; }) .attr('marker-start', function(e) { var name = _renderer.parent().edgeArrowtail.eval(e), arrow_id = edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'tail', name); return name ? 'url(#' + arrow_id + ')' : null; }) .each(function(e) { var fillEdgeStroke = _renderer.parent().edgeStroke.eval(e); _renderer.selectAll('#' + _renderer.parent().arrowId(e, 'head')) .attr('fill', _renderer.parent().edgeStroke.eval(e)); _renderer.selectAll('#' + _renderer.parent().arrowId(e, 'tail')) .attr('fill', _renderer.parent().edgeStroke.eval(e)); }); }; _renderer.selectAllNodes = function(selector) { selector = selector || '.node'; return _nodeLayer && _nodeLayer.selectAll(selector).filter(function(n) { return !n.deleted; }) || d3.selectAll('.foo-this-does-not-exist'); }; _renderer.selectAllEdges = function(selector) { selector = selector || '.edge'; return _edgeLayer && _edgeLayer.selectAll(selector).filter(function(e) { return !e.deleted; }) || d3.selectAll('.foo-this-does-not-exist'); }; _renderer.selectAllDefs = function(selector) { return _defs && _defs.selectAll(selector).filter(function(def) { return !def.deleted; }) || d3.selectAll('.foo-this-does-not-exist'); }; _renderer.resize = function(w, h) { if(_svg) { _svg.attr('width', w || (_renderer.parent().width_is_automatic() ? '100%' : _renderer.parent().width())) .attr('height', h || (_renderer.parent().height_is_automatic() ? '100%' : _renderer.parent().height())); } return _renderer; }; _renderer.rezoom = function(oldWidth, oldHeight, newWidth, newHeight) { var scale = _zoom.scale(), translate = _zoom.translate(); _zoom.scale(1).translate([0,0]); var xDomain = _renderer.parent().x().domain(), yDomain = _renderer.parent().y().domain(); _renderer.parent().x() .domain([xDomain[0], xDomain[0] + (xDomain[1] - xDomain[0])*newWidth/oldWidth]) .range([0, newWidth]); _renderer.parent().y() .domain([yDomain[0], yDomain[0] + (yDomain[1] - yDomain[0])*newHeight/oldHeight]) .range([0, newHeight]); _zoom .x(_renderer.parent().x()).y(_renderer.parent().y()) .translate(translate).scale(scale); }; _renderer.globalTransform = function(pos, scale, animate) { // _translate = pos; // _scale = scale; var obj = _g; if(animate) obj = _g.transition().duration(_renderer.parent().zoomDuration()); obj.attr('transform', 'translate(' + pos + ')' + ' scale(' + scale + ')'); }; _renderer.translate = function(_) { if(!arguments.length) return _zoom.translate(); _zoom.translate(_); return this; }; _renderer.scale = function(_) { if(!arguments.length) return _zoom ? _zoom.scale() : 1; _zoom.scale(_); return this; }; // argh _renderer.commitTranslateScale = function() { _zoom.event(_svg); }; _renderer.zoom = function(_) { if(!arguments.length) return _zoom; _zoom = _; // is this a good idea? return _renderer; }; _renderer.startRedraw = function(dispatch, wnodes, wedges) { // create edge SVG elements var edge = _edgeLayer.selectAll('.edge') .data(wedges, _renderer.parent().edgeKey.eval); var edgeEnter = edge.enter().append('svg:path') .attr({ class: 'edge', id: _renderer.parent().edgeId, opacity: 0 }) .each(function(e) { e.deleted = false; }); edge.exit().each(function(e) { e.deleted = true; }).transition() .duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().deleteDelay()) .attr('opacity', 0) .remove(); var edgeArrows = _edgeLayer.selectAll('.edge-arrows') .data(wedges, _renderer.parent().edgeKey.eval); var edgeArrowsEnter = edgeArrows.enter().append('svg:path') .attr({ class: 'edge-arrows', id: function(d) { return _renderer.parent().edgeId(d) + '-arrows'; }, fill: 'none', opacity: 0 }); edgeArrows.exit().transition() .duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().deleteDelay()) .attr('opacity', 0) .remove() .each('end.delarrow', function(e) { edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'head', null); edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'tail', null); }); if(_renderer.parent().edgeSort()) { edge.sort(function(a, b) { var as = _renderer.parent().edgeSort.eval(a), bs = _renderer.parent().edgeSort.eval(b); return as < bs ? -1 : bs < as ? 1 : 0; }); } // another wider copy of the edge just for hover events var edgeHover = _edgeLayer.selectAll('.edge-hover') .data(wedges, _renderer.parent().edgeKey.eval); var edgeHoverEnter = edgeHover.enter().append('svg:path') .attr('class', 'edge-hover') .attr('opacity', 0) .attr('fill', 'none') .attr('stroke', 'green') .attr('stroke-width', 10) .on('mouseover.diagram', function(e) { _renderer.select('#' + _renderer.parent().edgeId(e) + '-label') .attr('visibility', 'visible'); }) .on('mouseout.diagram', function(e) { _renderer.select('#' + _renderer.parent().edgeId(e) + '-label') .attr('visibility', 'hidden'); }); edgeHover.exit().remove(); var edgeLabels = _edgeLayer.selectAll('g.edge-label-wrapper') .data(wedges, _renderer.parent().edgeKey.eval); var edgeLabelsEnter = edgeLabels.enter() .append('g') .attr('class', 'edge-label-wrapper') .attr('visibility', 'hidden') .attr('id', function(e) { return _renderer.parent().edgeId(e) + '-label'; }); var textPaths = _defs.selectAll('path.edge-label-path') .data(wedges, _renderer.parent().textpathId); var textPathsEnter = textPaths.enter() .append('svg:path').attr({ class: 'edge-label-path', id: _renderer.parent().textpathId }); edgeLabels.exit().transition() .duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().deleteDelay()) .attr('opacity', 0).remove(); // create node SVG elements var node = _nodeLayer.selectAll('.node') .data(wnodes, _renderer.parent().nodeKey.eval); var nodeEnter = node.enter().append('g') .attr('class', 'node') .attr('opacity', '0') // don't show until has layout .each(function(n) { n.deleted = false; }); // .call(_d3cola.drag); _renderer.renderNode(nodeEnter); node.exit().each(function(n) { n.deleted = true; }).transition() .duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().deleteDelay()) .attr('opacity', 0) .remove(); dispatch.drawn(node, edge, edgeHover); var drawState = { node: node, nodeEnter: nodeEnter, edge: edge, edgeEnter: edgeEnter, edgeHover: edgeHover, edgeHoverEnter: edgeHoverEnter, edgeLabels: edgeLabels, edgeLabelsEnter: edgeLabelsEnter, edgeArrows: edgeArrows, edgeArrowsEnter: edgeArrowsEnter, textPaths: textPaths, textPathsEnter: textPathsEnter }; _refresh(drawState); return drawState; }; function _refresh(drawState) { _renderer.redrawEdge(drawState.edge, drawState.edgeArrows); _renderer.redrawNode(drawState.node); _renderer.drawPorts(drawState); } _renderer.refresh = function(node, edge, edgeHover, edgeLabels, textPaths) { if(_animating) return this; // but what about changed attributes? node = node || _renderer.selectAllNodes(); edge = edge || _renderer.selectAllEdges(); var edgeArrows = _renderer.selectAllEdges('.edge-arrows'); _refresh({node: node, edge: edge, edgeArrows: edgeArrows}); edgeHover = edgeHover || _renderer.selectAllEdges('.edge-hover'); edgeLabels = edgeLabels || _renderer.selectAllEdges('.edge-label-wrapper'); textPaths = textPaths || _renderer.selectAllDefs('path.edge-label-path'); var nullSel = d3.select(null); // no enters draw(node, nullSel, edge, nullSel, edgeHover, nullSel, edgeLabels, nullSel, edgeArrows, nullSel, textPaths, nullSel, false); return this; }; _renderer.reposition = function(node, edge) { node .attr('transform', function (n) { return 'translate(' + n.cola.x + ',' + n.cola.y + ')'; }); // reset edge ports edge.each(function(e) { e.pos.new = null; e.pos.old = null; e.cola.points = null; _renderer.parent().calcEdgePath(e, 'new', e.source.cola.x, e.source.cola.y, e.target.cola.x, e.target.cola.y); if(_renderer.parent().edgeArrowhead.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'head')) .attr('orient', function() { return e.pos.new.orienthead; }); if(_renderer.parent().edgeArrowtail.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'tail')) .attr('orient', function() { return e.pos.new.orienttail; }); _renderer.select('#' + _renderer.parent().edgeId(e) + '-arrows') .attr('d', generate_edge_path('new', true)); }) .attr('d', generate_edge_path('new')); return this; }; function generate_edge_path(age, full) { var field = full ? 'full' : 'path'; return function(e) { var path = e.pos[age][field]; return generate_path(path.points, path.bezDegree); }; }; function generate_edge_label_path(age) { return function(e) { var path = e.pos[age].path; var points = path.points[path.points.length-1].x < path.points[0].x ? path.points.slice(0).reverse() : path.points; return generate_path(points, path.bezDegree); }; }; function with_rad(f) { return function() { return f.apply(this, arguments) + 'rad'; }; } function unsurprising_orient_rad(oldorient, neworient) { return with_rad(unsurprising_orient)(oldorient, neworient); } function has_source_and_target(e) { return !!e.source && !!e.target; } _renderer.draw = function(drawState, animatePositions) { draw(drawState.node, drawState.nodeEnter, drawState.edge, drawState.edgeEnter, drawState.edgeHover, drawState.edgeHoverEnter, drawState.edgeLabels, drawState.edgeLabelsEnter, drawState.edgeArrows, drawState.edgeArrowsEnter, drawState.textPaths, drawState.textPathsEnter, animatePositions); }; function draw(node, nodeEnter, edge, edgeEnter, edgeHover, edgeHoverEnter, edgeLabels, edgeLabelsEnter, edgeArrows, edgeArrowsEnter, textPaths, textPathsEnter, animatePositions) { console.assert(edge.data().every(has_source_and_target)); var nodeEntered = {}; nodeEnter .each(function(n) { nodeEntered[_renderer.parent().nodeKey.eval(n)] = true; }) .attr('transform', function (n) { // start new nodes at their final position return 'translate(' + n.cola.x + ',' + n.cola.y + ')'; }); var ntrans = node .transition() .duration(_renderer.parent().stagedDuration()) .delay(function(n) { return _renderer.parent().stagedDelay(nodeEntered[_renderer.parent().nodeKey.eval(n)]); }) .attr('opacity', _renderer.parent().nodeOpacity.eval); if(animatePositions) ntrans .attr('transform', function (n) { return 'translate(' + n.cola.x + ',' + n.cola.y + ')'; }) .each('end.record', function(n) { n.prevX = n.cola.x; n.prevY = n.cola.y; }); // recalculate edge positions edge.each(function(e) { e.pos.new = null; }); edge.each(function(e) { if(e.cola.points) { e.pos.new = place_arrows_on_spline(_renderer.parent(), e, e.cola.points); } else { if(!e.pos.old) _renderer.parent().calcEdgePath(e, 'old', e.source.prevX || e.source.cola.x, e.source.prevY || e.source.cola.y, e.target.prevX || e.target.cola.x, e.target.prevY || e.target.cola.y); if(!e.pos.new) _renderer.parent().calcEdgePath(e, 'new', e.source.cola.x, e.source.cola.y, e.target.cola.x, e.target.cola.y); } if(e.pos.old) { if(e.pos.old.path.bezDegree !== e.pos.new.path.bezDegree || e.pos.old.path.points.length !== e.pos.new.path.points.length) { //console.log('old', e.pos.old.path.points.length, 'new', e.pos.new.path.points.length); if(is_one_segment(e.pos.old.path)) { e.pos.new.path.points = as_bezier3(e.pos.new.path); e.pos.old.path.points = split_bezier_n(as_bezier3(e.pos.old.path), (e.pos.new.path.points.length-1)/3); e.pos.old.path.bezDegree = e.pos.new.bezDegree = 3; } else if(is_one_segment(e.pos.new.path)) { e.pos.old.path.points = as_bezier3(e.pos.old.path); e.pos.new.path.points = split_bezier_n(as_bezier3(e.pos.new.path), (e.pos.old.path.points.length-1)/3); e.pos.old.path.bezDegree = e.pos.new.bezDegree = 3; } else console.warn("don't know how to interpolate two multi-segments"); } } else e.pos.old = e.pos.new; }); var edgeEntered = {}; edgeEnter .each(function(e) { edgeEntered[_renderer.parent().edgeKey.eval(e)] = true; }) .attr('d', generate_edge_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old')); edgeArrowsEnter .each(function(e) { // if staging transitions, just fade new edges in at new position // else start new edges at old positions of nodes, if any, else new positions var age = _renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old'; if(_renderer.parent().edgeArrowhead.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'head')) .attr('orient', function() { return e.pos[age].orienthead; }); if(_renderer.parent().edgeArrowtail.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'tail')) .attr('orient', function() { return e.pos[age].orienttail; }); }) .attr('d', generate_edge_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old', true)); edgeArrows .each(function(e) { if(_renderer.parent().edgeArrowhead.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'head')) .attr('orient', unsurprising_orient_rad(e.pos.old.orienthead, e.pos.new.orienthead)) .transition().duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().stagedDelay(false)) .attr('orient', function() { return e.pos.new.orienthead; }); if(_renderer.parent().edgeArrowtail.eval(e)) _renderer.select('#' + _renderer.parent().arrowId(e, 'tail')) .attr('orient', unsurprising_orient_rad(e.pos.old.orienttail, e.pos.new.orienttail)) .transition().duration(_renderer.parent().stagedDuration()) .delay(_renderer.parent().stagedDelay(false)) .attr('orient', function() { return e.pos.new.orienttail; }); }); var etrans = edge .transition() .duration(_renderer.parent().stagedDuration()) .delay(function(e) { return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]); }) .attr('opacity', _renderer.parent().edgeOpacity.eval); var arrowtrans = edgeArrows .transition() .duration(_renderer.parent().stagedDuration()) .delay(function(e) { return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]); }) .attr('opacity', _renderer.parent().edgeOpacity.eval); (animatePositions ? etrans : edge) .attr('d', function(e) { var when = _renderer.parent().stageTransitions() === 'insmod' && edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new'; return generate_edge_path(when)(e); }); (animatePositions ? arrowtrans : edgeArrows) .attr('d', function(e) { var when = _renderer.parent().stageTransitions() === 'insmod' && edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new'; return generate_edge_path(when, true)(e); }); var elabels = edgeLabels .selectAll('text').data(function(e) { var labels = _renderer.parent().edgeLabel.eval(e); if(!labels) return []; else if(typeof labels === 'string') return [labels]; else return labels; }); elabels.enter() .append('text') .attr({ 'class': 'edge-label', 'text-anchor': 'middle', dy: function(_, i) { return i * _renderer.parent().edgeLabelSpacing.eval(this.parentNode) -2; } }) .append('textPath') .attr('startOffset', '50%'); elabels .select('textPath') .html(function(t) { return t; }) .attr('opacity', function() { return _renderer.parent().edgeOpacity.eval(d3.select(this.parentNode.parentNode).datum()); }) .attr('xlink:href', function(e) { var id = _renderer.parent().textpathId(d3.select(this.parentNode.parentNode).datum()); // angular on firefox needs absolute paths for fragments return window.location.href.split('#')[0] + '#' + id; }); textPathsEnter .attr('d', generate_edge_label_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old')); var textTrans = textPaths.transition() .duration(_renderer.parent().stagedDuration()) .delay(function(e) { return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]); }); if(animatePositions) textTrans .attr('d', function(e) { var when = _renderer.parent().stageTransitions() === 'insmod' && edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new'; return generate_edge_label_path(when)(e); }); if(_renderer.parent().stageTransitions() === 'insmod' && animatePositions) { // inserted edges transition twice in insmod mode if(_renderer.parent().stagedDuration() >= 50) { etrans = etrans.transition() .duration(_renderer.parent().stagedDuration()) .attr('d', generate_edge_path('new')); textTrans = textTrans.transition() .duration(_renderer.parent().stagedDuration()) .attr('d', generate_edge_label_path('new')); arrowtrans.transition() .duration(_renderer.parent().stagedDuration()) .attr('d', generate_edge_path('new', true)); } else { // if transitions are too short, we run into various problems, // from transitions not completing to objects not found // so don't try to chain in that case // this also helped once: d3.timer.flush(); etrans .attr('d', generate_edge_path('new')); textTrans .attr('d', generate_edge_path('new')); arrowtrans .attr('d', generate_edge_path('new', true)); } } // signal layout done when all transitions complete // because otherwise client might start another layout and lock the processor _animating = true; if(!_renderer.parent().showLayoutSteps()) endall([ntrans, etrans, textTrans], function() { _animating = false; _renderer.parent().layoutDone(true); }); if(animatePositions) edgeHover.attr('d', generate_edge_path('new')); edge.each(function(e) { e.pos.old = e.pos.new; }); } // wait on multiple transitions, adapted from // http://stackoverflow.com/questions/10692100/invoke-a-callback-at-the-end-of-a-transition function endall(transitions, callback) { if (transitions.every(function(transition) { return transition.size() === 0; })) callback(); var n = 0; transitions.forEach(function(transition) { transition .each(function() { ++n; }) .each('end.all', function() { if (!--n) callback(); }); }); } _renderer.isRendered = function() { return !!_svg; }; _renderer.initializeDrawing = function () { _renderer.resetSvg(); _g = _svg.append('g') .attr('class', 'draw'); var layers = ['edge-layer', 'node-layer']; if(_renderer.parent().edgesInFront()) layers.reverse(); _g.selectAll('g').data(layers) .enter().append('g') .attr('class', function(l) { return l; }); _edgeLayer = _g.selectAll('g.edge-layer'); _nodeLayer = _g.selectAll('g.node-layer'); return this; }; /** * Standard dc.js * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin} * method. Execute a d3 single selection in the diagram's scope using the given selector * and return the d3 selection. Roughly the same as * ```js * d3.select('#diagram-id').select(selector) * ``` * Since this function returns a d3 selection, it is not chainable. (However, d3 selection * calls can be chained after it.) * @method select * @memberof dc_graph.diagram * @instance * @param {String} [selector] * @return {d3.selection} * @return {dc_graph.diagram} **/ _renderer.select = function (s) { return _renderer.parent().root().select(s); }; /** * Standard dc.js * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin} * method. Selects all elements that match the d3 single selector in the diagram's scope, * and return the d3 selection. Roughly the same as * * ```js * d3.select('#diagram-id').selectAll(selector) * ``` * * Since this function returns a d3 selection, it is not chainable. (However, d3 selection * calls can be chained after it.) * @method selectAll * @memberof dc_graph.diagram * @instance * @param {String} [selector] * @return {d3.selection} * @return {dc_graph.diagram} **/ _renderer.selectAll = function (s) { return _renderer.parent().root() ? _renderer.parent().root().selectAll(s) : null; }; _renderer.selectNodePortsOfStyle = function(node, style) { return node.selectAll('g.port').filter(function(p) { return _renderer.parent().portStyleName.eval(p) === style; }); }; _renderer.drawPorts = function(drawState) { var nodePorts = _renderer.parent().nodePorts(); if(!nodePorts) return; _renderer.parent().portStyle.enum().forEach(function(style) { var nodePorts2 = {}; for(var nid in nodePorts) nodePorts2[nid] = nodePorts[nid].filter(function(p) { return _renderer.parent().portStyleName.eval(p) === style; }); var port = _renderer.selectNodePortsOfStyle(drawState.node, style); _renderer.parent().portStyle(style).drawPorts(port, nodePorts2, drawState.node); }); }; _renderer.fireTSEvent = function(dispatch, drawState) { dispatch.transitionsStarted(drawState.node, drawState.edge, drawState.edgeHover); }; _renderer.calculateBounds = function(drawState) { if(!drawState.node.size()) return null; return _renderer.parent().calculateBounds(drawState.node.data(), drawState.edge.data()); }; /** * Standard dc.js * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin} * method. Returns the top `svg` element for this specific diagram. You can also pass in a new * svg element, but setting the svg element on a diagram may have unexpected consequences. * @method svg * @memberof dc_graph.diagram * @instance * @param {d3.selection} [selection] * @return {d3.selection} * @return {dc_graph.diagram} **/ _renderer.svg = function (_) { if (!arguments.length) { return _svg; } _svg = _; return _renderer; }; /** * Returns the top `g` element for this specific diagram. This method is usually used to * retrieve the g element in order to overlay custom svg drawing * programatically. **Caution**: The root g element is usually generated internally, and * resetting it might produce unpredictable results. * @method g * @memberof dc_graph.diagram * @instance * @param {d3.selection} [selection] * @return {d3.selection} * @return {dc_graph.diagram} **/ _renderer.g = function (_) { if (!arguments.length) { return _g; } _g = _; return _renderer; }; /** * Standard dc.js * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin} * method. Remove the diagram's SVG elements from the dom and recreate the container SVG * element. * @method resetSvg * @memberof dc_graph.diagram * @instance * @return {dc_graph.diagram} **/ _renderer.resetSvg = function () { // we might be re-initialized in a div, in which case // we already have an <svg> element to delete var svg = _svg || _renderer.select('svg'); svg.remove(); _svg = null; //_renderer.parent().x(null).y(null); return generateSvg(); }; _renderer.addOrRemoveDef = function(id, whether, tag, onEnter) { var data = whether ? [0] : []; var sel = _defs.selectAll('#' + id).data(data); var selEnter = sel .enter().append(tag) .attr('id', id); if(selEnter.size() && onEnter) selEnter.call(onEnter); sel.exit().remove(); return sel; }; function enableZoom() { _svg.call(_zoom); _svg.on('dblclick.zoom', null); } function disableZoom() { _svg.on('.zoom', null); } function generateSvg() { _svg = _renderer.parent().root().append('svg'); _renderer.resize(); _defs = _svg.append('svg:defs'); // for lack of a better place _renderer.addOrRemoveDef('node-clip-top', true, 'clipPath', function(clipPath) { clipPath.selectAll('rect').data([0]) .enter().append('rect').attr({ x: -1000, y: -1000, width: 2000, height: 1000 }); }); _renderer.addOrRemoveDef('node-clip-bottom', true, 'clipPath', function(clipPath) { clipPath.selectAll('rect').data([0]) .enter().append('rect').attr({ x: -1000, y: 0, width: 2000, height: 1000 }); }); _renderer.addOrRemoveDef('node-clip-left', true, 'clipPath', function(clipPath) { clipPath.selectAll('rect').data([0]) .enter().append('rect').attr({ x: -1000, y: -1000, width: 1000, height: 2000 }); }); _renderer.addOrRemoveDef('node-clip-right', true, 'clipPath', function(clipPath) { clipPath.selectAll('rect').data([0]) .enter().append('rect').attr({ x: 0, y: -1000, width: 1000, height: 2000 }); }); _renderer.addOrRemoveDef('node-clip-none', true, 'clipPath', function(clipPath) { clipPath.selectAll('rect').data([0]) .enter().append('rect').attr({ x: 0, y: 0, width: 0, height: 0 }); }); _zoom = d3.behavior.zoom() .on('zoom.diagram', _renderer.parent().doZoom) .x(_renderer.parent().x()).y(_renderer.parent().y()) .scaleExtent(_renderer.parent().zoomExtent()); if(_renderer.parent().mouseZoomable()) { var mod, mods; var brush = _renderer.parent().child('brush'); var keyboard = _renderer.parent().child('keyboard'); if(!keyboard) _renderer.parent().child('keyboard', keyboard = dc_graph.keyboard()); var modkeyschanged = function() { if(keyboard.modKeysMatch(_renderer.parent().modKeyZoom())) enableZoom(); else disableZoom(); }; keyboard.on('modkeyschanged.zoom', modkeyschanged); modkeyschanged(); } return _svg; } _renderer.animating = function() { return _animating; }; return _renderer; };