diff --git a/components/graph/base.js b/components/graph/base.js index 7902a3f..bfbabd8 100644 --- a/components/graph/base.js +++ b/components/graph/base.js @@ -1,8 +1,14 @@ +const r = require('r-dom'); + const { GraphView: GraphViewBase, + Edge: EdgeBase, + GraphUtils, } = require('react-digraph'); +const math = require('mathjs'); + class GraphView extends GraphViewBase { constructor(props) { super(props); @@ -10,6 +16,9 @@ class GraphView extends GraphViewBase { Object.assign(this, { _super_handleNodeMove: this.handleNodeMove, handleNodeMove: this.constructor.prototype.handleNodeMove.bind(this), + + _super_getEdgeComponent: this.handleNodeMove, + getEdgeComponent: this.constructor.prototype.getEdgeComponent.bind(this), }); } @@ -19,6 +28,89 @@ class GraphView extends GraphViewBase { this.props.onNodeMove(position, nodeId, shiftKey); } } + + getEdgeComponent(edge) { + if (!this.props.renderEdge) { + return this._super_getEdgeComponent(edge); + } + + const sourceNodeMapNode = this.getNodeById(edge.source); + const sourceNode = sourceNodeMapNode ? sourceNodeMapNode.node : null; + const targetNodeMapNode = this.getNodeById(edge.target); + const targetNode = targetNodeMapNode ? targetNodeMapNode.node : null; + const { targetPosition } = edge; + const { edgeTypes, edgeHandleSize, nodeSize, nodeKey, renderEdgeText } = this.props; + const selected = this.isEdgeSelected(edge); + + return r(this.props.renderEdge || Edge, { + data: edge, + edgeTypes, + edgeHandleSize, + nodeSize, + sourceNode, + targetNode: targetNode || targetPosition, + nodeKey, + isSelected: selected, + renderEdgeText, + }); + } } -module.exports = { GraphView }; +const size = 120; + +EdgeBase.calculateOffset = function (nodeSize, source, target) { + const arrowVector = math.matrix([ target.x - source.x, target.y - source.y ]); + const offsetLength = Math.max(0, Math.min((0.75 * size), (math.norm(arrowVector) / 2) - 40)); + const offsetVector = math.dotMultiply(arrowVector, (offsetLength / math.norm(arrowVector)) || 0); + + return { + xOff: offsetVector.get([ 0 ]), + yOff: offsetVector.get([ 1 ]), + }; +}; + +class Edge extends EdgeBase { + render() { + const { data } = this.props; + const id = `${data.source || ''}_${data.target}`; + const className = GraphUtils.classNames('edge', { + selected: this.props.isSelected, + }); + + return r.g({ + className: 'edge-container', + 'data-source': data.source, + 'data-target': data.target, + }, [ + r.g({ + className, + }, [ + r.path({ + className: 'edge-path', + d: this.getPathDescription(data) || undefined, + }), + this.props.renderEdgeText && r(this.props.renderEdgeText, { + data, + transform: this.getEdgeHandleTranslation(), + }), + ]), + r.g({ + className: 'edge-mouse-handler', + }, [ + r.path({ + className: 'edge-overlay-path', + ref: this.edgeOverlayRef, + id, + 'data-source': data.source, + 'data-target': data.target, + d: this.getPathDescription(data) || undefined, + }), + ]), + ]); + } +} + +module.exports = { + GraphView, + Edge, +}; diff --git a/components/graph/index.js b/components/graph/index.js index e7c8501..05da97e 100644 --- a/components/graph/index.js +++ b/components/graph/index.js @@ -19,8 +19,6 @@ const { bindActionCreators } = require('redux'); const math = require('mathjs'); -const { Edge } = require('react-digraph'); - const d = require('../../utils/d'); const { @@ -34,16 +32,9 @@ const { GraphView, } = require('./satellites-graph'); -Edge.calculateOffset = function (nodeSize, source, target) { - const arrowVector = math.matrix([ target.x - source.x, target.y - source.y ]); - const offsetLength = Math.max(0, Math.min((0.75 * size), (math.norm(arrowVector) / 2) - 40)); - const offsetVector = math.dotMultiply(arrowVector, (offsetLength / math.norm(arrowVector)) || 0); - - return { - xOff: offsetVector.get([ 0 ]), - yOff: offsetVector.get([ 1 ]), - }; -}; +const { + Edge, +} = require('./base'); const weakmapId_ = new WeakMap(); const weakmapId = o => { @@ -279,6 +270,31 @@ const afterRenderEdge = (id, element, edge, edgeContainer) => { } }; +const renderEdge = edgeProps => r(Edge, edgeProps); + +const renderEdgeText = state => ({ data, transform }) => r('foreignObject', { + transform, +}, r.div({ + style: { + width: size, + height: size, + + padding: 2, + + whiteSpace: 'pre', + + backgroundRepeat: 'no-repeat', + backgroundSize: '60%', + backgroundPosition: 'center', + }, +}, [ + r(DebugText, { + dgo: data, + pai: data.type && getPaiByTypeAndIndex(data.type, data.index)({ pulse: state }), + state, + }), +])); + class Graph extends React.Component { constructor(props) { super(props); @@ -442,6 +458,11 @@ class Graph extends React.Component { dgoToPai.set(node, pai); }); + edges.forEach(edge => { + const pai = getPaiByTypeAndIndex(edge.type, edge.index)({ pulse: this.props }); + dgoToPai.set(edge, pai); + }); + return r.div({ id: 'graph', style: {}, @@ -472,8 +493,13 @@ class Graph extends React.Component { backgroundFillId: '#background-pattern', renderDefs, + renderNode, renderNodeText: renderNodeText(this.props), + + renderEdge, + renderEdgeText: renderEdgeText(this.props), + afterRenderEdge, })); } diff --git a/components/graph/satellites-graph.js b/components/graph/satellites-graph.js index 7d02a1a..d845f78 100644 --- a/components/graph/satellites-graph.js +++ b/components/graph/satellites-graph.js @@ -26,6 +26,8 @@ const Satellite = () => r(React.Fragment); const satelliteSpread = 36; +const satelliteEdgeToOriginalEdge = new WeakMap(); + class GraphView extends React.Component { constructor(props) { super(props); @@ -43,6 +45,8 @@ class GraphView extends React.Component { renderNode: this.renderNode.bind(this), renderNodeText: this.renderNodeText.bind(this), + + afterRenderEdge: this.afterRenderEdge.bind(this), }); } @@ -108,6 +112,11 @@ class GraphView extends React.Component { return r(React.Fragment); } + afterRenderEdge(id, element, edge, edgeContainer) { + const originalEdge = satelliteEdgeToOriginalEdge.get(edge); + this.props.afterRenderEdge(id, element, originalEdge || edge, edgeContainer); + } + render() { const { nodeKey } = this.props; const { edgesByTargetNodeKey, satelliteNodesByTargetNodeKey } = this.state; @@ -118,14 +127,18 @@ class GraphView extends React.Component { return satelliteNodes.concat(node); }, this.props.nodes)); - const edges = flatten(values(mapObjIndexed((edges, target) => mapIndexed((edge, i) => ({ - id: edge.id, - source: edge.source, - target: satelliteNodesByTargetNodeKey[target][i][nodeKey], - originalTarget: edge.target, - index: edge.index, - type: edge.type, - }), edges), edgesByTargetNodeKey))); + const edges = flatten(values(mapObjIndexed((edges, target) => mapIndexed((edge, i) => { + const satelliteEdge = { + id: edge.id, + source: edge.source, + target: satelliteNodesByTargetNodeKey[target][i][nodeKey], + originalTarget: edge.target, + index: edge.index, + type: edge.type, + }; + satelliteEdgeToOriginalEdge.set(satelliteEdge, edge); + return satelliteEdge; + }, edges), edgesByTargetNodeKey))); return r(GraphViewBase, { ...this.props, @@ -140,6 +153,8 @@ class GraphView extends React.Component { renderNode: this.renderNode, renderNodeText: this.renderNodeText, + + afterRenderEdge: this.afterRenderEdge, }); } }