svg-interactive-script.js
1 // https://github.com/BartBrood/Interactive-Graphviz-Diagrams 2 3 // MIT License 4 5 // Copyright (c) 2024 BartBrood 6 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 14 // The above copyright notice and this permission notice shall be included in all 15 // copies or substantial portions of the Software. 16 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 // SOFTWARE. 24 25 function addInteractivity(evt) { 26 27 var svg = evt.target; 28 var edges = document.getElementsByClassName('edge'); 29 var nodes = document.getElementsByClassName('node'); 30 var clusters = document.getElementsByClassName('cluster'); 31 var selectedElement, offset, transform, nodrag, origmousepos; 32 33 svg.addEventListener('mousedown', startDrag); 34 svg.addEventListener('mousemove', drag); 35 svg.addEventListener('mouseup', endDrag); 36 svg.addEventListener('mouseleave', endDrag); 37 svg.addEventListener('touchstart', startDrag); 38 svg.addEventListener('touchmove', drag); 39 svg.addEventListener('touchend', endDrag); 40 svg.addEventListener('touchleave', endDrag); 41 svg.addEventListener('touchcancel', endDrag); 42 43 for (var i = 0; i < edges.length; i++) { 44 edges[i].addEventListener('click', clickEdge); 45 } 46 47 for (var i = 0; i < nodes.length; i++) { 48 nodes[i].addEventListener('click', clickNode); 49 } 50 51 var svg = document.querySelector('svg'); 52 var viewBox = svg.viewBox.baseVal; 53 adjustViewBox(svg); 54 55 function getMousePosition(evt) { 56 var CTM = svg.getScreenCTM(); 57 if (evt.touches) { evt = evt.touches[0]; } 58 return { 59 x: (evt.clientX - CTM.e) / CTM.a, 60 y: (evt.clientY - CTM.f) / CTM.d 61 }; 62 } 63 64 function startDrag(evt) { 65 origmousepos = getMousePosition(evt); 66 nodrag=true; 67 selectedElement = evt.target.parentElement; 68 if (selectedElement){ 69 offset = getMousePosition(evt); 70 71 // Make sure the first transform on the element is a translate transform 72 var transforms = selectedElement.transform.baseVal; 73 74 if (transforms.length === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) { 75 // Create an transform that translates by (0, 0) 76 var translate = svg.createSVGTransform(); 77 translate.setTranslate(0, 0); 78 selectedElement.transform.baseVal.insertItemBefore(translate, 0); 79 } 80 81 // Get initial translation 82 transform = transforms.getItem(0); 83 offset.x -= transform.matrix.e; 84 offset.y -= transform.matrix.f; 85 } 86 } 87 88 function drag(evt) { 89 if (selectedElement) { 90 evt.preventDefault(); 91 var coord = getMousePosition(evt); 92 transform.setTranslate(coord.x - offset.x, coord.y - offset.y); 93 } 94 } 95 96 function endDrag(evt) { 97 <!-- comment out the following line if you wnat drags to stay in place, with this line they snap back to their original position after drag end --> 98 //if statement to avoid the header section being affected by the translate (0,0) 99 if (selectedElement){ 100 if (selectedElement.classList.contains('header')){ 101 selectedElement = false; 102 } else { 103 selectedElement = false; 104 transform.setTranslate(0,0); 105 } 106 } 107 var currentmousepos=getMousePosition(evt); 108 if (currentmousepos.x===origmousepos.x|currentmousepos.y===origmousepos.y){ 109 nodrag=true; 110 } else { 111 nodrag=false; 112 } 113 114 } 115 116 function clickEdge() { 117 if (nodrag) { 118 if (this.classList.contains("edge-highlight")){ 119 this.classList.remove("edge-highlight"); 120 this.classList.remove("text-highlight-edges"); 121 } 122 else { 123 this.classList.add("edge-highlight"); 124 this.classList.add("text-highlight-edges"); 125 animateEdge(this); 126 } 127 } 128 } 129 130 function clickNode() { 131 if (nodrag) { 132 var nodeName = this.childNodes[1].textContent; 133 // Escape special characters in the node name 134 var nodeNameEscaped = nodeName.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&'); 135 136 var patroon = new RegExp('^' + nodeNameEscaped + '->|->' + nodeNameEscaped + '$|' + nodeNameEscaped + '--|--' + nodeNameEscaped + '$') 137 138 if (this.classList.contains("node-highlight")) { 139 this.classList.remove("node-highlight"); 140 this.classList.remove("text-highlight-nodes"); 141 var edges = document.getElementsByClassName('edge'); 142 for (var i = 0; i < edges.length; i++) { 143 if (patroon.test(edges[i].childNodes[1].textContent)) { 144 edges[i].classList.remove("edge-highlight"); 145 edges[i].classList.remove("text-highlight-edges"); 146 } 147 } 148 } else { 149 this.classList.add("node-highlight"); 150 this.classList.add("text-highlight-nodes"); 151 var edges = document.getElementsByClassName('edge'); 152 for (var i = 0; i < edges.length; i++) { 153 if (patroon.test(edges[i].childNodes[1].textContent)) { 154 edges[i].classList.add("edge-highlight"); 155 edges[i].classList.add("text-highlight-edges"); 156 animateEdge(edges[i]); 157 } 158 } 159 } 160 } 161 } 162 163 function animateEdge(edge){ 164 var path = edge.querySelector('path'); 165 var polygon = edge.querySelector('polygon'); 166 var length = path.getTotalLength(); 167 // Clear any previous transition 168 path.style.transition = path.style.WebkitTransition = 'none'; 169 if (polygon){polygon.style.transition = polygon.style.WebkitTransition = 'none';}; 170 // Set up the starting positions 171 path.style.strokeDasharray = length + ' ' + length; 172 path.style.strokeDashoffset = length; 173 if(polygon){polygon.style.opacity='0';}; 174 // Trigger a layout so styles are calculated & the browser 175 // picks up the starting position before animating 176 path.getBoundingClientRect(); 177 // Define our transition 178 path.style.transition = path.style.WebkitTransition = 179 'stroke-dashoffset 2s ease-in-out'; 180 if (polygon){polygon.style.transition = polygon.style.WebkitTransition = 181 'fill-opacity 1s ease-in-out 2s';}; 182 // Go! 183 path.style.strokeDashoffset = '0'; 184 if (polygon){setTimeout(function(){polygon.style.opacity='1';},2000)}; 185 } 186 } 187 188 function adjustViewBox(svg) { 189 var viewBoxParts = svg.getAttribute("viewBox").split(" "); 190 var newYMin = parseFloat(viewBoxParts[1]) - 30; // Adjust this value as needed 191 var newYMax = parseFloat(viewBoxParts[3]) + 30; // Adjust this value as needed 192 var newXMax = Math.max(parseFloat(viewBoxParts[2]),240); 193 var newViewBox = viewBoxParts[0] + " " + newYMin + " " + newXMax + " " + newYMax; 194 svg.setAttribute("viewBox", newViewBox); 195 }