text: var dotSourceList = {{ dot_source_list|tojson }};
  
  var state = {
    transitionDelay: 0,
    index: 0,
    playing: false,
  }
  var controls = {
    index: null,
    buttons: {
      next: null,
      prev: null,
      toggle: null,
    },
    slider: null,
  }
  var graph = null;
  
  /////////////
  // Helpers //
  /////////////
  // From https://stackoverflow.com/a/37623959/1931274
  function onRangeChange(r, f) {
    var n, c, m;
    r.addEventListener("input", function (e) {
      n = 1; c = e.target.value; if (c != m) { f(e); }; m = c;
    });
    r.addEventListener("change", function (e) { if (!n) { f(e); }});
  }
  
  //////////////
  // UI Logic //
  //////////////
  function setupControls() {
    controls.index = document.getElementById("index");
  
    controls.buttons.next = document.getElementById("next");
    controls.buttons.next.onclick = showNext;
  
    controls.buttons.prev = document.getElementById("prev");
    controls.buttons.prev.onclick = showPrevious;
  
    controls.buttons.toggle = document.getElementById("toggle");
    controls.buttons.toggle.onclick = toggle;
  
    controls.slider = document.getElementById("slider");
    controls.slider.setAttribute("max", dotSourceList.length - 1);
    onRangeChange(controls.slider, showSliderState);
  
  }
  
  function updateControls() {
    controls.slider.value = state.index;
    controls.index.innerHTML = state.index;
  
    if (state.playing) {
      controls.buttons.toggle.innerHTML = "Pause";
    } else {
      controls.buttons.toggle.innerHTML = "Play";
    }
  }
  
  //////////////////
  // UI callbacks //
  //////////////////
  function toggle() {
    console.log("toggle");
    state.playing = !state.playing;
    showThisGraph();
  }
  
  function showNext() {
    console.log("next");
    state.playing = false;
    if (state.index < dotSourceList.length - 1) {
      state.index = state.index + 1;
    }
    showThisGraph();
  }
  
  function showPrevious() {
    console.log("previous");
    state.playing = false;
    if (state.index != 0) {
      state.index = state.index - 1;
    }
    showThisGraph();
  }
  
  function showSliderState() {
    console.log("slider");
    var value = Number(controls.slider.value);
    state.index = value;
    state.playing = false;
  
    showThisGraph();
  }
  
  ///////////////
  // Rendering //
  ///////////////
  function prepareGraph() {
    graph = d3.select("#graph").graphviz();
    graph
      .zoom(false)
      .transition(function () {
        return d3.transition("main")
          .ease(d3.easeExpOut)
          .duration(500)
          // .delay(state.transitionDelay);
      })
      .on("initEnd", showThisGraph);
  
  }
  
  function showThisGraph() {
    updateControls();
  
    var dotSource = dotSourceList[state.index];
    return graph
      .renderDot(dotSource)
      // The following sets up the "play" mode.
      .on("end", continueIfPlaying);
  }
  
  
  function continueIfPlaying() {
    if (!state.playing) {
      return
    }
    state.index = (state.index + 1) % dotSourceList.length;
    showThisGraph();
  }
  
  /////////////////
  // Entry Point //
  /////////////////
  function main() {
    setupControls();
    prepareGraph();
  }
  
  document.addEventListener("DOMContentLoaded", main);
  