UNPKG

dc.graph

Version:

Graph visualizations integrated with crossfilter and dc.js

397 lines (376 loc) 17.4 kB
dc_graph.draw_graphs = function(options) { var select_nodes_group = dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes'), select_edges_group = dc_graph.select_things_group(options.select_edges_group || 'select-edges-group', 'select-edges'), label_nodes_group = dc_graph.label_things_group('label-nodes-group', 'label-nodes'), label_edges_group = dc_graph.label_things_group('label-edges-group', 'label-edges'), fix_nodes_group = dc_graph.fix_nodes_group('fix-nodes-group'); var _nodeIdTag = options.idTag || 'id', _edgeIdTag = options.edgeIdTag || _nodeIdTag, _sourceTag = options.sourceTag || 'source', _targetTag = options.targetTag || 'target', _nodeLabelTag = options.labelTag || 'label', _edgeLabelTag = options.edgeLabelTag || _nodeLabelTag; var _sourceDown = null, _targetMove = null, _targetValid = false, _edgeLayer = null, _hintData = [], _crossout; function update_hint() { var data = _hintData.filter(function(h) { return h.source && h.target; }); var line = _edgeLayer.selectAll('line.hint-edge').data(data); line.exit().remove(); line.enter().append('line') .attr('class', 'hint-edge') .style({ fill: 'none', stroke: 'black', 'pointer-events': 'none' }); line.attr({ x1: function(n) { return n.source.x; }, y1: function(n) { return n.source.y; }, x2: function(n) { return n.target.x; }, y2: function(n) { return n.target.y; } }); } function port_pos(p) { var style = _mode.parent().portStyle(_mode.parent().portStyleName.eval(p)); var pos = style.portPosition(p); pos.x += p.node.cola.x; pos.y += p.node.cola.y; return pos; } function update_crossout() { var data; if(_crossout) { if(_mode.usePorts()) data = [port_pos(_crossout)]; else data = [{x: _crossout.node.cola.x, y: _crossout.node.cola.y}]; } else data = []; var size = _mode.crossSize(), wid = _mode.crossWidth(); var cross = _edgeLayer.selectAll('polygon.graph-draw-crossout').data(data); cross.exit().remove(); cross.enter().append('polygon') .attr('class', 'graph-draw-crossout'); cross .attr('points', function(d) { var x = d.x, y = d.y; return [ [x-size/2, y+size/2], [x-size/2+wid, y+size/2], [x, y+wid/2], [x+size/2-wid, y+size/2], [x+size/2, y+size/2], [x+wid/2, y], [x+size/2, y-size/2], [x+size/2-wid, y-size/2], [x, y-wid/2], [x-size/2+wid, y-size/2], [x-size/2, y-size/2], [x-wid/2, y] ] .map(function(p) { return p.join(','); }) .join(' '); }); } function erase_hint() { _hintData = []; _targetValid = false; _sourceDown = _targetMove = null; update_hint(); } function create_node(diagram, pos, data) { if(!_mode.nodeCrossfilter()) throw new Error('need nodeCrossfilter'); var node, callback = _mode.addNode() || promise_identity; if(data) node = data; else { node = {}; node[_nodeIdTag] = uuid(); node[_nodeLabelTag] = ''; } if(pos) fix_nodes_group.new_node(node[_nodeIdTag], node, {x: pos[0], y: pos[1]}); callback(node).then(function(node2) { if(!node2) return; _mode.nodeCrossfilter().add([node2]); diagram.redrawGroup(); select_nodes_group.set_changed([node2[_nodeIdTag]]); }); } function create_edge(diagram, source, target) { if(!_mode.edgeCrossfilter()) throw new Error('need edgeCrossfilter'); var edge = {}, callback = _mode.addEdge() || promise_identity; edge[_edgeIdTag] = uuid(); edge[_edgeLabelTag] = ''; if(_mode.conduct().detectReversedEdge && _mode.conduct().detectReversedEdge(edge, source.port, target.port)) { edge[_sourceTag] = target.node.orig.key; edge[_targetTag] = source.node.orig.key; var t; t = source; source = target; target = t; } else { edge[_sourceTag] = source.node.orig.key; edge[_targetTag] = target.node.orig.key; } callback(edge, source.port, target.port).then(function(edge2) { if(!edge2) return; fix_nodes_group.new_edge(edge[_edgeIdTag], edge2[_sourceTag], edge2[_targetTag]); _mode.edgeCrossfilter().add([edge2]); select_nodes_group.set_changed([], false); select_edges_group.set_changed([edge2[_edgeIdTag]], false); diagram.redrawGroup(); }); } function check_invalid_drag(coords) { var msg; if(!(d3.event.buttons & 1)) { // mouse button was released but we missed it _crossout = null; if(_mode.conduct().cancelDragEdge) _mode.conduct().cancelDragEdge(_sourceDown); erase_hint(); update_crossout(); return true; } if(!_sourceDown.started && Math.hypot(coords[0] - _hintData[0].source.x, coords[1] - _hintData[0].source.y) > _mode.dragSize()) { if(_mode.conduct().startDragEdge) { if(_mode.conduct().startDragEdge(_sourceDown)) { _sourceDown.started = true; } else { if(_mode.conduct().invalidSourceMessage) { msg = _mode.conduct().invalidSourceMessage(_sourceDown); console.log(msg); if(options.negativeTip) { options.negativeTip .content(function(_, k) { k(msg); }) .displayTip(_mode.usePorts() ? _sourceDown.port : _sourceDown.node); } } erase_hint(); return true; } } } return false; } function draw(diagram, node, edge, ehover) { var select_nodes = diagram.child('select-nodes'); if(select_nodes) { if(_mode.clickCreatesNodes()) select_nodes.clickBackgroundClears(false); } node .on('mousedown.draw-graphs', function(n) { d3.event.stopPropagation(); if(!_mode.dragCreatesEdges()) return; if(options.tipsDisable) options.tipsDisable.forEach(function(tip) { tip .hideTip() .disabled(true); }); if(_mode.usePorts()) { var activePort; if(typeof _mode.usePorts() === 'object' && _mode.usePorts().eventPort) activePort = _mode.usePorts().eventPort(); else activePort = diagram.getPort(diagram.nodeKey.eval(n), null, 'out') || diagram.getPort(diagram.nodeKey.eval(n), null, 'in'); if(!activePort) return; _sourceDown = {node: n, port: activePort}; _hintData = [{source: port_pos(activePort)}]; } else { _sourceDown = {node: n}; _hintData = [{source: {x: _sourceDown.node.cola.x, y: _sourceDown.node.cola.y}}]; } }) .on('mousemove.draw-graphs', function(n) { var msg; d3.event.stopPropagation(); if(_sourceDown) { var coords = dc_graph.event_coords(diagram); if(check_invalid_drag(coords)) return; var oldTarget = _targetMove; if(n === _sourceDown.node) { _mode.conduct().invalidTargetMessage && console.log(_mode.conduct().invalidTargetMessage(_sourceDown, _sourceDown)); _targetMove = null; _hintData[0].target = null; } else if(_mode.usePorts()) { var activePort; if(typeof _mode.usePorts() === 'object' && _mode.usePorts().eventPort) activePort = _mode.usePorts().eventPort(); else activePort = diagram.getPort(diagram.nodeKey.eval(n), null, 'in') || diagram.getPort(diagram.nodeKey.eval(n), null, 'out'); if(activePort) _targetMove = {node: n, port: activePort}; else _targetMove = null; } else if(!_targetMove || n !== _targetMove.node) { _targetMove = {node: n}; } if(_mode.conduct().changeDragTarget) { var change; if(_mode.usePorts()) { var oldPort = oldTarget && oldTarget.port, newPort = _targetMove && _targetMove.port; change = oldPort !== newPort; } else { var oldNode = oldTarget && oldTarget.node, newNode = _targetMove && _targetMove.node; change = oldNode !== newNode; } if(change) if(_mode.conduct().changeDragTarget(_sourceDown, _targetMove)) { _crossout = null; if(options.negativeTip) options.negativeTip.hideTip(); msg = _mode.conduct().validTargetMessage && _mode.conduct().validTargetMessage() || 'matches'; if(options.positiveTip) { options.positiveTip .content(function(_, k) { k(msg); }) .displayTip(_mode.usePorts() ? _targetMove.port : _targetMove.node); } _targetValid = true; } else { _crossout = _mode.usePorts() ? _targetMove && _targetMove.port : _targetMove && _targetMove.node; if(_targetMove && _mode.conduct().invalidTargetMessage) { if(options.positiveTip) options.positiveTip.hideTip(); msg = _mode.conduct().invalidTargetMessage(_sourceDown, _targetMove); console.log(msg); if(options.negativeTip) { options.negativeTip .content(function(_, k) { k(msg); }) .displayTip(_mode.usePorts() ? _targetMove.port : _targetMove.node); } } _targetValid = false; } } else _targetValid = true; if(_targetMove) { if(_targetMove.port) _hintData[0].target = port_pos(activePort); else _hintData[0].target = {x: n.cola.x, y: n.cola.y}; } else { _hintData[0].target = {x: coords[0], y: coords[1]}; } update_hint(); update_crossout(); } }) .on('mouseup.draw-graphs', function(n) { _crossout = null; if(options.negativeTip) options.negativeTip.hideTip(true); if(options.positiveTip) options.positiveTip.hideTip(true); if(options.tipsDisable) options.tipsDisable.forEach(function(tip) { tip.disabled(false); }); // allow keyboard mode to hear this one (again, we need better cooperation) // d3.event.stopPropagation(); if(_sourceDown && _targetValid) { var finishPromise; if(_mode.conduct().finishDragEdge) finishPromise = _mode.conduct().finishDragEdge(_sourceDown, _targetMove); else finishPromise = Promise.resolve(true); var source = _sourceDown, target = _targetMove; finishPromise.then(function(ok) { if(ok) create_edge(diagram, source, target); }); } else if(_sourceDown) { if(_mode.conduct().cancelDragEdge) _mode.conduct().cancelDragEdge(_sourceDown); } erase_hint(); update_crossout(); }); diagram.svg() .on('mousedown.draw-graphs', function() { _sourceDown = null; }) .on('mousemove.draw-graphs', function() { var data = []; if(_sourceDown) { // drawing edge var coords = dc_graph.event_coords(diagram); _crossout = null; if(check_invalid_drag(coords)) return; if(_mode.conduct().dragCanvas) _mode.conduct().dragCanvas(_sourceDown, coords); if(_mode.conduct().changeDragTarget && _targetMove) _mode.conduct().changeDragTarget(_sourceDown, null); _targetMove = null; _hintData[0].target = {x: coords[0], y: coords[1]}; update_hint(); update_crossout(); } }) .on('mouseup.draw-graphs', function() { _crossout = null; if(options.negativeTip) options.negativeTip.hideTip(true); if(options.positiveTip) options.positiveTip.hideTip(true); if(options.tipsDisable) options.tipsDisable.forEach(function(tip) { tip.disabled(false); }); if(_sourceDown) { // drag-edge if(_mode.conduct().cancelDragEdge) _mode.conduct().cancelDragEdge(_sourceDown); erase_hint(); } else { // click-node if(d3.event.target === this && _mode.clickCreatesNodes()) create_node(diagram, dc_graph.event_coords(diagram)); } update_crossout(); }); if(!_edgeLayer) _edgeLayer = diagram.g().append('g').attr('class', 'draw-graphs'); } function remove(diagram, node, edge, ehover) { node .on('mousedown.draw-graphs', null) .on('mousemove.draw-graphs', null) .on('mouseup.draw-graphs', null); diagram.svg() .on('mousedown.draw-graphs', null) .on('mousemove.draw-graphs', null) .on('mouseup.draw-graphs', null); } var _mode = dc_graph.mode('highlight-paths', { draw: draw, remove: remove }); // update the data source/destination _mode.nodeCrossfilter = property(options.nodeCrossfilter); _mode.edgeCrossfilter = property(options.edgeCrossfilter); // modeal options _mode.usePorts = property(null); _mode.clickCreatesNodes = property(true); _mode.dragCreatesEdges = property(true); _mode.dragSize = property(5); // draw attributes of indicator for failed edge _mode.crossSize = property(15); _mode.crossWidth = property(5); // really this is a behavior or strategy _mode.conduct = property({}); // callbacks to modify data as it's being added // as of 0.6, function returns a promise of the new data _mode.addNode = property(null); // node -> promise(node2) _mode.addEdge = property(null); // edge, sourceport, targetport -> promise(edge2) // or, if you want to drive.. _mode.createNode = function(pos, data) { create_node(_mode.parent(), pos, data); }; return _mode; };