function renderArrowHead(vw: Dagre.Edge): VNode { return svg.defs([ svg.marker({ attrs: { id: `arrowhead${vw.v}-${vw.w}`, viewBox: '0 0 10 10', refX: '9', refY: '5', markerUnits: 'strokeWidth', markerWidth: '8', markerHeight: '6', orient: 'auto', }, }, [ svg.path({ class: { [styles.edgeArrowHeadStyle]: true, }, attrs: { d: 'M 0 0 L 10 5 L 0 10 z', }, }), ]), ]); }
function renderSourceOrSinkNode(node: StreamGraphNode, zaps: Array<Zap>) { const index = zaps.map(zap => zap.id).indexOf(node.id); const zap = index === -1 ? null : zaps[index]; const P = 5; // text padding const hook = { insert(vnode: VNode) { const gElem = vnode.elm as Element; const rectElem = gElem.childNodes[0] as Element; const textElem = gElem.childNodes[1] as Element; const tspanElem = textElem.childNodes[0] as Element; tspanElem.setAttribute('x', String(DIAGRAM_PADDING_H + node.x - textElem.clientWidth * 0.5 - P * 0.4), ); tspanElem.setAttribute('y', String(DIAGRAM_PADDING_V + node.y + textElem.clientHeight * 0.5 - P * 0.5), ); rectElem.setAttribute('x', String(DIAGRAM_PADDING_H + node.x - textElem.clientWidth * 0.5 - P), ); rectElem.setAttribute('y', String(DIAGRAM_PADDING_V + node.y - textElem.clientHeight * 0.5 - P), ); rectElem.setAttribute('width', String(textElem.clientWidth + 2 * P)); rectElem.setAttribute('height', String(textElem.clientHeight + 2 * P)); }, }; return svg.g({ hook }, [ svg.rect({ class: { [styles.sourceOrSinkNodeStyle]: !zap, [styles.nodeZapNextStyle]: zap && zap.type === 'next', [styles.nodeZapErrorStyle]: zap && zap.type === 'error', [styles.nodeZapCompleteStyle]: zap && zap.type === 'complete', }, attrs: { x: node.x - node.width * 0.5 + DIAGRAM_PADDING_H, y: node.y - node.height * 0.5 + DIAGRAM_PADDING_V, rx: 9, ry: 9, width: node.width, height: node.height, }, }), svg.text({ class: { [styles.sourceOrSinkNodeNameStyle]: true } }, [ svg.tspan(String(node.label)), ]), renderNodeLabel(node, zap, styles.sourceOrSinkNodeLabelStyle), ]); }
function renderEdgeType1(vw: Dagre.Edge, graph: Dagre.Graph): VNode | null { const edgeData: StreamGraphEdge = (graph as any).edge(vw.v, vw.w); if (!edgeData.points) { return null; } const points = edgeData.points.map(({x, y}) => ({ x: x + DIAGRAM_PADDING_H, y: y + DIAGRAM_PADDING_V }), ); // Make arrow tail not touch origin stream points[0].x = points[0].x * 0.4 + points[1].x * 0.6; points[0].y = points[0].y * 0.4 + points[1].y * 0.6; const x0 = points[0].x, x1 = points[1].x, y0 = points[0].y, y1 = points[1].y; if (Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)) < 6) { points.shift(); } return svg.path({ class: { [styles.edgeType1Style]: true, }, attrs: { d: `M ${points.map(({ x, y }) => `${x} ${y}`).join(' ')}`, }, }); }
function renderNodeLabel(node: StreamGraphNode, zap: Zap | null, style: string): VNode { let label = ''; if (zap) { // MUTATION! if (Array.isArray(zap.value)) { const cappedArr = (zap.value as Array<any>).slice(0, 4).map(() => '\u25A1'); if (typeof cappedArr[3] !== 'undefined') { cappedArr[3] = '\u22EF'; } label = `[${cappedArr.join(',')}]`; } else if (typeof zap.value === 'object' && zap.value !== null) { label = '{...}'; } else if (zap.value === null) { label = 'null'; } else if (typeof zap.value === 'string') { label = `"${zap.value}"`; } else { label = String(zap.value); } } function setTSpanContent(vnode: VNode) { const textElem = vnode.elm as Element; const tspanElem = textElem.childNodes[0] as Element; if (label && !(zap && zap.type === 'complete')) { tspanElem.innerHTML = label; } } return svg.text({ class: { [style]: !zap || (zap && zap.type === 'complete'), [styles.nodeLabelZapNextStyle]: zap && zap.type === 'next', [styles.nodeLabelZapErrorStyle]: zap && zap.type === 'error', }, attrs: { x: DIAGRAM_PADDING_H + node.x + node.width * 0.5 + 10, y: DIAGRAM_PADDING_V + node.y + 5, }, hook: { insert(vnode: VNode) { setTSpanContent(vnode); }, update(oldVNode: VNode, newVNode: VNode) { setTSpanContent(newVNode); }, }, }, [ svg.tspan(''), ]); }
function renderOperatorNode(node: StreamGraphNode) { const hook = { insert(vnode: VNode) { const textElem = vnode.elm as Element; const tspanElem = textElem.childNodes[0] as Element; tspanElem.setAttribute('x', String(DIAGRAM_PADDING_H + node.x - textElem.clientWidth * 0.5), ); tspanElem.setAttribute('y', String(DIAGRAM_PADDING_V + node.y + textElem.clientHeight * 0.5 - 4), ); }, }; return svg.text({ hook, class: {[styles.operatorNodeStyle]: true} }, [ svg.tspan(String(node.label)), ]); }
function renderCommonNode(node: StreamGraphNode, zaps: Array<Zap>): VNode { const index = zaps.map(zap => zap.id).indexOf(node.id); const zap = index === -1 ? null : zaps[index]; return svg.g([ svg.rect({ class: { [styles.activeNodeStyle]: !zap, [styles.nodeZapNextStyle]: zap && zap.type === 'next', [styles.nodeZapErrorStyle]: zap && zap.type === 'error', [styles.nodeZapCompleteStyle]: zap && zap.type === 'complete', }, attrs: { x: node.x - node.width * 0.5 + DIAGRAM_PADDING_H, y: node.y - node.height * 0.5 + DIAGRAM_PADDING_V, rx: 9, ry: 9, width: node.width, height: node.height, }, hook: { update(oldVNode: VNode, newVNode: VNode) { const rectElem = newVNode.elm as Element; const inactiveAttr = 'data-inactive-state'; const inactiveAttrValue = rectElem.getAttribute(inactiveAttr); if (zap && zap.type === 'complete') { rectElem.setAttribute(inactiveAttr, 'complete'); } else if (zap && zap.type === 'error') { rectElem.setAttribute(inactiveAttr, 'error'); } else if (zap && zap.type === 'next' && inactiveAttrValue) { rectElem.removeAttribute(inactiveAttr); rectElem.setAttribute('class', styles.nodeZapNextStyle); } else if (inactiveAttrValue === 'complete') { rectElem.setAttribute('class', styles.nodeInactiveCompleteStyle); } else if (inactiveAttrValue === 'error') { rectElem.setAttribute('class', styles.nodeInactiveErrorStyle); } }, }, }), renderNodeLabel(node, zap, styles.commonNodeLabelStyle), ]); }
function renderEdgeType2(vw: Dagre.Edge, graph: Dagre.Graph): VNode | null { const edgeData: StreamGraphEdge = (graph as any).edge(vw.v, vw.w); if (!edgeData.points) { return null; } const points = edgeData.points .map(({x, y}) => ({ x: x + DIAGRAM_PADDING_H, y: y + DIAGRAM_PADDING_V })); return svg.g([ svg.path({ class: { [styles.edgeType2Style]: true, }, attrs: { d: `M ${points.map(({ x, y }) => `${x} ${y}`).join(' ')}`, 'marker-end': `url("#arrowhead${vw.v}-${vw.w}")`, }, }), renderArrowHead(vw), ]); }