UNPKG

d3-graphviz

Version:

Graphviz DOT rendering and animated transitions for D3

365 lines (322 loc) 10.8 kB
<!DOCTYPE html> <meta charset="utf-8"> <body> <script src="../node_modules/d3/dist/d3.js"></script> <script src="../node_modules/@hpcc-js/wasm/dist/graphviz.umd.js" type="text/javascript"></script> <script src="../build/d3-graphviz.js"></script> <div id="graph" style="text-align: center;"></div> <script> 'use strict'; var margin = 20; // to avoid scrollbars var isDrawingEdge = false; var isDrawingNode = false; var startNode; var selectedEdge = d3.select(null); var selectedEdgeFill; var selectedEdgeStroke; var selectedNode = d3.select(null); var selectedNodeStroke; var selectedNodeFill; var nodeIndex; var edgeIndex; var prevNodeName = 'b'; var shapes = [ "box", "polygon", "ellipse", "oval", "circle", "point", "egg", "triangle", "plaintext", "plain", "diamond", "trapezium", "parallelogram", "house", "pentagon", "hexagon", "septagon", "octagon", "doublecircle", "doubleoctagon", "tripleoctagon", "invtriangle", "invtrapezium", "invhouse", "Mdiamond", "Msquare", "Mcircle", "rect", "rectangle", "square", "star", "none", "underline", "cylinder", "note", "tab", "folder", "box3d", "component", "promoter", "cds", "terminator", "utr", "primersite", "restrictionsite", "fivepoverhang", "threepoverhang", "noverhang", "assembly", "signature", "insulator", "ribosite", "rnastab", "proteasesite", "proteinstab", "rpromoter", "rarrow", "larrow", "lpromoter", ]; var dotSrc = `strict digraph { node [style="filled"] a [shape="box" fillcolor="` + d3.schemeCategory10[0] + `"] b [shape="polygon" fillcolor="` + d3.schemeCategory10[1] + `"] a -> b [fillcolor="` + d3.schemePaired[0] + `" color="` + d3.schemePaired[1] + `"] }`; var graphviz = d3.select("#graph").graphviz() .attributer(attributer) .transition(function() { return d3.transition().duration(1000); }); render(); function attributer(datum, index, nodes) { var selection = d3.select(this); if (datum.tag == "svg") { var width = window.innerWidth - margin; var height = window.innerHeight - margin; var x = "10"; var y = "10"; var unit = 'px'; selection .attr("width", width + unit) .attr("height", height + unit); datum.attributes.width = width + unit; datum.attributes.height = height + unit; } } function render() { graphviz .renderDot(dotSrc, startApp); } function startApp() { var nodes = d3.selectAll(".node"); var edges = d3.selectAll(".edge"); // click outside of nodes d3.select(document).on("click mousedown", function(event) { event.preventDefault(); event.stopPropagation(); console.log('document click or mousedown'); unSelectEdge(); if (event.which == 2) { var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g"); var width = 54; var height = 36; var [x0, y0] = d3.pointer(event, graph0.node()); var x1 = x0 - width / 2; var x2 = x0 + width / 2; var y1 = y0 - height / 2; var y2 = y0 + height / 2; if (nodeIndex == null) { nodeIndex = d3.selectAll('.node').size(); } else { nodeIndex += 1; } var shape = shapes[nodeIndex % shapes.length]; var numLetters = 'z'.charCodeAt(0) - 'a'.charCodeAt(0) + 1; var letter = String.fromCharCode('a'.charCodeAt(0) + (nodeIndex % numLetters)); var digit = Math.floor(nodeIndex / numLetters) || ''; var nodeName = letter + digit; var fillcolor = d3.schemeCategory10[nodeIndex % 10]; var color = 'black'; graphviz .drawNode(x1, y1, nodeName, {shape: shape, text: nodeName, fillcolor: fillcolor, color: color}) .insertDrawnNode(nodeName); var dotSrcLines = dotSrc.split('\n'); var newNodeString = nodeName + ' [shape="' + shape + '" color="' + color + '"; fillcolor="' + fillcolor + '"; penwidth="1"]'; dotSrcLines.splice(-1, 0, newNodeString); dotSrc = dotSrcLines.join('\n'); render(); } }); // keyup outside of nodes d3.select(document).on("keyup", function(event) { event.preventDefault(); console.log('document keyup', event); if (event.keyCode == 27) { graphviz.removeDrawnEdge(); unSelectEdge(); } if (event.keyCode == 46) { deleteSelectedEdge(); deleteSelectedNode(); graphviz.removeDrawnEdge(); render(); } isDrawingEdge = false; }); // move d3.select(document).on("mousemove", function(event) { event.preventDefault(); event.stopPropagation(); console.log('document mousemove'); var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g"); var [x0, y0] = d3.pointer(event, graph0.node()); var shortening = 2; // avoid mouse pointing on edge if (isDrawingEdge) { graphviz .moveDrawnEdgeEndPoint(x0, y0, {shortening: shortening}) } }); // click and mousedown on nodes nodes.on("click mousedown", function(event) { event.preventDefault(); console.log('node click or mousedown'); if (!isDrawingEdge && event.which == 1) { unSelectEdge(); selectNode(d3.select(this)); } }); // double-click on nodes nodes.on("dblclick", function(event) { event.preventDefault(); event.stopPropagation(); console.log('node dblclick'); unSelectEdge(); if (isDrawingEdge) { var endNode = d3.select(this); var startNodeName = startNode.selectWithoutDataPropagation("title").text(); var endNodeName = endNode.selectWithoutDataPropagation("title").text(); graphviz .insertDrawnEdge(startNodeName + '->' + endNodeName); var fillcolor = d3.schemePaired[(edgeIndex * 2) % 12]; var color = d3.schemePaired[(edgeIndex * 2 + 1) % 12]; var dotSrcLines = dotSrc.split('\n'); var newEdgeString = startNodeName + ' -> ' + endNodeName + ' [color="' + color + '"; fillcolor="' + fillcolor + '"; penwidth="1"]'; dotSrcLines.splice(-1, 0, newEdgeString); dotSrc = dotSrcLines.join('\n'); render(); } isDrawingEdge = false; }); // right-click on nodes nodes.on("contextmenu", function(event) { event.preventDefault(); event.stopPropagation(); console.log('node contextmenu'); unSelectEdge(); unSelectNode(); graphviz.removeDrawnEdge(); startNode = d3.select(this); var graph0 = d3.select("#graph").selectWithoutDataPropagation("svg").selectWithoutDataPropagation("g"); var [x0, y0] = d3.pointer(event, graph0.node()); if (edgeIndex == null) { edgeIndex = d3.selectAll('.edge').size(); } else { edgeIndex += 1; } var fillcolor = d3.schemePaired[(edgeIndex * 2) % 12]; var color = d3.schemePaired[(edgeIndex * 2 + 1) % 12]; graphviz .drawEdge(x0, y0, x0, y0, {fillcolor: fillcolor, color: color}); isDrawingEdge = true; }); // click and mousedown on edges edges.on("click mousedown", function(event) { event.preventDefault(); event.stopPropagation(); console.log('node click or mousedown'); unSelectNode(); selectEdge(d3.select(this)); }); // right-click outside of nodes d3.select(document).on("contextmenu", function(event) { event.preventDefault(); event.stopPropagation(); console.log('document contextmenu'); unSelectEdge(); }); } function selectEdge(edge) { unSelectEdge(); selectedEdge = edge; selectedEdgeFill = selectedEdge.selectAll('polygon').attr("fill"); selectedEdgeStroke = selectedEdge.selectAll('polygon').attr("stroke"); selectedEdge.selectAll('path, polygon').attr("stroke", "red"); selectedEdge.selectAll('polygon').attr("fill", "red"); } function unSelectEdge() { selectedEdge.selectAll('path, polygon').attr("stroke", selectedEdgeStroke); selectedEdge.selectAll('polygon').attr("fill", selectedEdgeFill); selectedEdge = d3.select(null); } function deleteSelectedEdge() { selectedEdge.style("display", "none"); if (selectedEdge.size() != 0) { var edgeName = selectedEdge.selectWithoutDataPropagation("title").text(); edgeName = edgeName.replace('->', ' -> '); var dotSrcLines = dotSrc.split('\n'); while (true) { var i = dotSrcLines.findIndex(function (element, index) { return element.indexOf(edgeName) >= 0; }); if (i < 0) break; dotSrcLines.splice(i, 1); } dotSrc = dotSrcLines.join('\n'); } } function selectNode(node) { unSelectNode(); selectedNode = node; selectedNodeFill = selectedNode.selectAll('polygon, ellipse').attr("fill"); selectedNodeStroke = selectedNode.selectAll('polygon, ellipse').attr("stroke"); selectedNode.selectAll('polygon, ellipse').attr("stroke", "red"); selectedNode.selectAll('polygon, ellipse').attr("fill", "red"); } function unSelectNode() { selectedNode.selectAll('polygon, ellipse').attr("stroke", selectedNodeStroke); selectedNode.selectAll('polygon, ellipse').attr("fill", selectedNodeFill); selectedNode = d3.select(null); } function deleteSelectedNode() { selectedNode.style("display", "none"); if (selectedNode.size() != 0) { var nodeName = selectedNode.selectWithoutDataPropagation("title").text(); var dotSrcLines = dotSrc.split('\n'); while (true) { var i = dotSrcLines.findIndex(function (element, index) { var trimmedElement = element.trim(); if (trimmedElement == nodeName) { return true; } if (trimmedElement.indexOf(nodeName + ' ') == 0) { return true; } if (trimmedElement.indexOf(' ' + nodeName + ' ') >= 0) { return true; } return false; }); if (i < 0) break; dotSrcLines.splice(i, 1); } dotSrc = dotSrcLines.join('\n'); } } </script>