/ src / sirocco / svg-interactive-script.js
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  }