<!DOCTYPE html>
<html lang="en">
<head>
  <title>DyNeuSR</title>
  <link rel="icon" type="image/svg+xml" href="http://web.stanford.edu/group/bdl/jekyll/images/bdl-logo.svg">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> 
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
  <script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
  <script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<style>   
  body {
    font-family: "Roboto Mono", "Helvetica", sans-serif;

    font-size:14px;
    font-weight:300;
    line-height:1.2;
  }

  #dark {
    background-color: #242731; /*#0c1d2a;*/ /*#fdf9f0;*/ /*#384953;*/ /*#fdf9f0;*/ /*#384953;*/
    color: #8797a0;
   
  }
  #dark svg.graph {
      background-color: #242731; /*#0a1d2b;*/ /*#fdf9f0; *//*#ece4d2;*/ 
      margin: 0px;
      /*box-shadow: 2px 2px 20px 2px #384953;*/
      /*box-shadow: inset 0px 0px 10px 2px #384953;*/
      box-shadow: 0px 0px 5px 1px #384953;
  }

  #light {
    color: #242731; /*#0c1d2a;*/
    background: #ffffff; /*#e4e7e8;*/
  }
  #light svg.graph {
      background-color: #ffffff; /*#e4e7e8;*/
      margin: 0px;
      /*box-shadow: 2px 2px 20px 2px #384953;*/
      /*box-shadow: inset 0px 0px 10px 2px #384953;*/
      box-shadow: inset 0px 0px 5px 1px #384953;

  }


  .well {
    padding: 20px 20px 20px 20px;
  }

  .well .well {
    padding: 10px 10px 10px 10px;
  }
  #dark .well {
    color:#e4e7e8;
    background-color: #242731;
    border-color:#e4e7e8;

  }
  #light .well {
    color:#242731;
    background: #ffffff; /*#e4e7e8;*/
    border-color:#242731;
  }

  .navbar { 
      padding: 1rem 0rem;
   }
  .navbar-inverse2 {
    background-color:#ffffff00;
    border-color:#2220;
  }

  #dark .navbar-inverse {
    background-color: #ffffff00;/*#242731;*/
    border-color:#2220;
  }

  #light .navbar-inverse {
    background-color: #ffffff00; /*#ffffff;*/ /*#e4e7e8;*/
    border-color:#2220;
  }

  #dark .navbar-inverse .navbar-nav>li>a {
      color:#e4e7e8;
  }
  #light .navbar-inverse .navbar-nav>li>a {
      color:#242731;
  }
  #dark .navbar-brand {
    color: #00bcdb;
  }
  #light .navbar-brand {
    color: #00bcdb;
  }
   a.navbar-brand:hover {
    /*color: #00dbff !important;*/
   }

  .navbar-dyneusr {
   background: #0c1d2a; /*#384953;*/
   border-color: #0c1d2a; /*#2b3c4e;*/
   line-height: 20px;
   color: #8797a0; /*#fdf9f0;*/ 
   box-shadow: 0px 0px 10px 3px #384953; /*#161d21;*/
  }
  h1, h2 {
   font-size:24px;
   font-weight: 300;
   color: #00bcdb; /* #ece4d2;*/
  }
  h2 {
    font-size: 18px;
    padding-bottom: 10px;
    font-weight: 700;
  }
  h3 {
    font-size:16px;
    font-weight: 700;
    /*float: left;*/
    padding-right: 8px;
    display: inline-block;
  }


  .node { 
    opacity: 1.0;
  }
  .node#defocus { 
    opacity: 0.3;
  }
  #dark .node .circle {
    stroke: #fff;
    stroke-width: 0.0px;
  }
  #dark .node#hover .circle,
  #dark .node#highlight .circle,
  #dark .node#focus .circle {
    opacity: 1.0;
    stroke: #fff; /*#00bcdb;*/
    stroke-width: 1.5px;
  }

  #light .node .circle {
    stroke: #000;
    stroke-width: 0.0px;
  }
  #light .node#hover .circle,
  #light .node#highlight .circle,
  #light .node#focus .circle {
    opacity: 1.0;
    stroke: #000; /*#00bcdb;*/
    stroke-width: 1.5px;
  }

  #dark .link {
    opacity: 1.0;
    stroke: #777;
    stroke-opacity: 0.5;
    stroke-width: 1.5px;
  }
  #light .link {
    opacity: 1.0;
    stroke: #777;
    stroke-opacity: 0.5;
    stroke-width: 1.5px;
  }
  .link#defocus {
    opacity: 0.3;
  }

  #dark .link#hover,
  #dark .link#highlight,
  #dark .link#focus {
    stroke: #fff;
    stroke-opacity: 1.0;
    stroke-width: 3.0px;
  }
  #light .link#hover,
  #light .link#highlight,
  #light .link#focus {
    stroke: #000;
    stroke-opacity: 1.0;
    stroke-width: 3.0px;
  }


  .node text {
    display: none;
    font: 10px sans-serif;
  }

  .node:hover text {
    display: inline;
  }

  .cell {
    fill: none;
    pointer-events: all;
  }


  #tooltip {
    position: absolute;
    bottom: 0;
    left: 0;
    bottom: 0;
    width: 400px;
    padding: 20px;
    overflow: auto;
    display: none;
  }

  .tooltip-image {
    width: 100%; 

  }
  #main {
    position: relative;
      overflow: auto;
      padding-top:15px;
      /*width:70%;*/
      top:65px;
  }
  #meta {
    opacity: 0.85;
    position: fixed;
      top: 65px;
      right: 0px;
      height:100%;
      padding: 15px;
      overflow: auto;
      display: block;
  }
  #dark #meta {
    background-color: #242732 ;
  }

  #light #meta {
    background-color:#ffffff; /*#e4e7e8;*/
  }
  #main-container {
    height:100%;
  }

  svg.legend {
      margin-right:20px;
  }
  #dark text.legend-text {
    fill:#e4e7e8;
  } 
  #light text.legend-text {
    fill:#242731;
  }

  .menu-choice {
    font-size: 14pt;
    margin-bottom:0px;
    border: 0px solid #fff;
    background-color: transparent;
    width:100%;
  }
  .menu-button {
    font-size: 14pt;
    margin-bottom:0px;
    border: 0px solid #fff;
    background-color: transparent;
    width:100%;
  }
  
  #node-meta table,
  #node-meta tr,
  #node-meta td {
    text-align: center;
    padding: 5px ;
  }
     
  </style>


</head>


<body id="dark">

<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container-fluid">
    <div class="navbar-header">
      <a href="#title" data-toggle="collapse" class="navbar-brand" href="#">DyNeuSR</a>
      <div id="title" class="collapse" style="line-height:50px; width:500px;">
        <b>Dy</b>namical <b>Neu</b>roimaging <b>S</b>patiotemporal <b>R</b>epresentations
      </div>
    </div>
    <ul class="nav navbar-nav navbar-right">
        <li><a href="#" id="toggle_save_as">SAVE</a></li>
        <li><a href="#" id="toggle_display_mode" onclick="toggle_display_mode();">MODE</a></li>
        <li><a href="#" id="toggle_meta" onclick="toggle_meta();">META</span></a></li>
    </ul>
  </div>

</nav>

    <div id="main" class="col-sm-8"></div>
    <div id="meta" class="col-sm-4">
          <div class="well">
            <div id="graph-meta"></div>
          </div>
          <div class="well">
            <div id="node-meta"></div>
          </div>
    </div>
 
</body>






<script>

///////////////////////////////////////////////
// Main
///////////////////////////////////////////////
var color_palettes = {
    'castillo': ['#0500ff', '#65ff00',  '#fae23d', '#ff8c00', '#ff0000'],

    'kepler3': ['#0500ff', '#65ff00', '#ff0000'],
    'kepler5': ['#0500ff', '#00a4ff', '#65ff00', '#ff8c00', '#ff0000'],
    'kepler15': ['#0500ff', '#0100ff', '#0022ff', '#0064ff', '#00a4ff', 
               '#00e4ff', '#00ff83', '#65ff00', '#fdff00', '#ffdc00', 
               '#ffb400', '#ff7800', '#ff5000', '#ff2800', '#ff0000'],
    
    'spectral3': ['#9e0142', '#ffffbe', '#5e4fa2'],
    'spectral5':['#9e0142', '#f98e52', '#ffffbe', '#86cfa5', '#5e4fa2'],
    'spectral15':['#9e0142', '#c52c4b', '#e2514a', '#f57547', '#fca55d', 
                  '#feca79', '#fee999', '#ffffbe', '#edf8a3', '#cdeb9d', 
                  '#a2d9a4', '#6ec5a5', '#47a0b3', '#3f77b5', '#5e4fa2'],

    'viridis3':['#440154', '#21918c', '#fde725'],
    'viridis5': ['#440154', '#3b528b', '#21918c', '#5ec962', '#fde725'],
    'viridis15':['#440154', '#481b6d', '#46327e', '#3f4788', '#365c8d', 
                 '#2e6e8e', '#277f8e', '#21918c', '#1fa187', '#2db27d', 
                 '#4ac16d', '#73d056', '#a0da39', '#d0e11c', '#fde725'],

    'category10': d3.scale.category10().range(),
    'category20': d3.scale.category20().range(),

    };
///////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////
if (!d3.select("#json_graph").empty()) {
    load_force_graphs();
} else {
    load_force_graphs_csv("index.csv");
}

// Load a list of JSON files
function load_force_graphs() {
  d3.select("#json_graph").forEach(function(element) {
    console.log(element[0]);
    var data = JSON.parse(element[0].dataset.json);
    var inputs = {data:data, source:"default", total:1};
    console.log(inputs);
    draw_force(inputs);
  });
}

// Load a list of JSON files
function load_force_graphs_csv(csv) {
    d3.csv(csv, function(error, data) {
      data.forEach(function(d, i) {
        console.log(i, data.length, d);
        load_force_graph(d.json, data.length);
    }); 
  });
}

function load_force_graph(json, n) {
    d3.json(json, function(error, data) {
      var inputs = {data:data, source:json, total:n};
      console.log(inputs);
      draw_force(inputs);
  });
}



//allow 10 times zoom in or out
function zoomer () {
  return d3.behavior.zoom()
    .scaleExtent([0.1,7])
    .on("zoom", zoom);
}

//define the event handler function
function zoom() {
    //console.log("zoom", d3.event.translate, d3.event.scale);
    d3.selectAll("g.zoomable").attr("transform", 
             "translate(" + d3.event.translate + ")" 
         + " scale(" + d3.event.scale + ")" );
};


// Draw json
function draw_force(data) {
    ////////////////////////////////////////////////////
    //// inits
    ////////////////////////////////////////////////////
  var json = data.data,
      source = data.source,
      n = data.total;

  var ncols = d3.min([n, 3]);
  var nrows = d3.max([1, Math.round(n / 3)]);
  var colsize = 12 / ncols;

  console.log("source: " + source);
  console.log(ncols, nrows);

  var margin = {top: 0, right: 0, bottom: 100, left: 0},
      width = (window.innerWidth / ncols) - margin.left - margin.right,
      height = (window.innerHeight / ncols) - margin.top - margin.bottom;
  console.log(width, height);

  var div = d3.select("#main")
      .append("div")
      .attr("class", "col-lg-" + colsize)
      .attr("width", width)
      .attr("height", height)
      .attr("display", "inline-block");
  width = document.getElementById("main").offsetWidth;

  
  var svg = div.append("svg")
    .attr("id", "svg")
    .attr("class", "graph")
    .attr("width", "100%")
    .attr("height", height)
        .call(zoomer())
      .append("g")
        .attr("class", "zoomable");


  ////////////////////////////////////////////////////
  //// Reindex nodes, links
  ////////////////////////////////////////////////////
  // get mapping of node id => index
  var node_index_from = {name:{}, id:{}};
  json.nodes.forEach(function(d,i) {
    // add to mapping
    node_index_from.name[d.name] = i;
    node_index_from.id[d.id] = i;
    // update name, id
    json.nodes[i].id = i;
  });

  // reindex links
  json.links.forEach(function(d,i) {
    if (typeof node_index_from.id[d.source] !== "undefined") {
      // save source, target
    //d.source_id = d.source;
    //d.target_id = d.target;
    // update link source, target
    d.source = node_index_from.id[d.source];
    d.target = node_index_from.id[d.target];
    } else if (typeof node_index_from.name[d.source] !== "undefined") {
      // save source, target
    //d.source_name = d.source;
    //d.target_name = d.target;
    // update link source, target
    d.source = node_index_from.name[d.source];
    d.target = node_index_from.name[d.target];
    }
    // update link data
    json.links[i] = d;
  });

  ////////////////////////////////////////////////////
  //// Force setup
  ////////////////////////////////////////////////////
  var w = Math.round(width * 1);
  var h = Math.round(height * 1);

  var force = d3.layout.force()
      .gravity(0.2)
      .charge(-300)
      .linkDistance(1)
      .size([w, h])
      .nodes(json.nodes)
      .links(json.links)
        .linkDistance(function(link) {return Math.sqrt(link.distance);})
      .start();


  ////////////////////////////////////////////////////
  //// Colors + labels setup
  ////////////////////////////////////////////////////
  // color by
  var color_by_options = {};
  json.nodes.map(function(d,i) {
      d3.keys(d.proportions).map(function(k) {
        if (color_by_options[k]!==true) {
          color_by_options[k] = true;
        }
      });
    });
  color_by_options = d3.keys(color_by_options);
  console.log("color_by_options: "+color_by_options);

  var color_by = json.graph.color_by ? json.graph.color_by : color_by_options[0];  
  console.log("color_by: "+color_by);


  // colors
  var groups = {}
  if (typeof json.graph.groups !== "undefined") {
    d3.keys(json.graph.groups).map(function(k, i) {
      groups[k] = json.graph.groups[k];
    });
  }
  color_by_options.map(function(k) {
    console.log(k)
    groups[k] = {};
    json.nodes.map(function(d,i) {
      d.proportions[k].map(function(p,j) {
        if (groups[k][p.group]!==true) {
          groups[k][p.group] = true;
        }
      })
    })
    groups[k] = d3.keys(groups[k]);
  });
  console.log("groups: "+groups);
  console.log("groups: "+groups[color_by]);


  // set custom color palette if defined
  //var color_palette_key = 'kepler15';
  // hack for castillo cme data
  color_palette_key = "castillo";
  if (groups[color_by].length <= 3) {
    color_palette_key = 'kepler3';
  } else if (groups[color_by].length <= 5) {
    color_palette_key = 'kepler5';
  } 
  color_palette_key = "castillo";
  console.log("color_palette_key: "+color_palette_key);

  // check for custom color palettes
  var custom_colors = json.graph.colors ? json.graph.colors : json.graph.color;
  if (typeof custom_colors !== "undefined") {
    d3.keys(custom_colors).map(function(k, i) {
      color_palettes[k] = custom_colors[k];      
    })
  }
  // use color_by as color_palette_key, if it is valid
  //if (typeof color_palettes[color_by] !== "undefined") {
  //  color_palette_key = color_by;
  //}  
  var color_palette = color_palettes[color_palette_key];

  // define color scale
  var color = d3.scale.linear()
    .domain(groups[color_by])
    .range(color_palette);
  console.log("color.domain: "+color.domain());
  console.log("color.range: "+color.range());

  // color labels
  var color_labels = {};
  d3.keys(groups).map(function(k, i) {
    color_labels[k] = groups[k];
  });
  if (typeof json.graph.labels !== "undefined") {
    d3.keys(json.graph.labels).map(function(k, i) {
      color_labels[k] = json.graph.labels[k]
    })
  } else if (typeof json.graph.label !== "undefined") {
    d3.keys(json.graph.label).map(function(k, i) {
      color_labels[k] = json.graph.label[k]
    })
  }

  var color_label = d3.scale.ordinal()
            .domain(color_labels[color_by])
            .range(color.range());
  console.log("color_label.domain: "+color_label.domain());
  console.log("color_label.range: "+color_label.range());

  
  
    
  // node-pie 
  var pie = d3.layout.pie()
            .sort(null)
            .value(function(d) { return d.value; });
    
  // scale pie by max row count
  var data_size = d3.max(json.nodes.map(d => { 
        return d3.max(d.members) + 1;
    }));
  console.log("data size: "+data_size);

  var pie_size = json.nodes.map(d => { 
    return d3.max(d.proportions[color_by].map(p => 
        { return p.row_count; }
            ));
      });
  console.log("pie_size: "+pie_size);

  // size node proportional to data
  var pie_norm = d3.scale.log()
      .domain([1, data_size])
      .rangeRound([3, 30]);
  //.rangeRound([2, 15]);
  console.log("pie_norm([1,10,100,1000]): "+[1,10,100,1000].map(pie_norm));

  var link = svg.selectAll(".link")
      .data(json.links)
      .enter().append("line")
      .attr("class", "link");

  var node = svg.selectAll(".node")
      .data(json.nodes)
      .enter().append("g")
      .attr("class", "node")
      .call(force.drag);

  var circle = node.selectAll("path")
      .data(function(d, i) {return pie(d.proportions[color_by]); })
      .enter()
      .append("svg:path")
        .attr("d", d3.svg.arc().innerRadius(0).outerRadius(function(d, i) {
          return pie_norm(d.data.row_count)*0.8;
        }))
        .attr("fill", function(d, i) { return color(d.data.group); })
        .attr("class", "circle");
      


  //var label = node.append("text")
  //  .attr("dy", ".25em")
  //  .attr("dx", "1em")
  //  .text(function(d) { return d.name + " (n="+d.members.length+")";});
  var label = node.append("a")
      .attr("href", "#")
      .attr("class", "pop")
      .attr("data-toggle", "popover")
      .attr("data-placement", "right")
      .attr("title", function(d) { return d.name; })
      .attr("data-content", function(d) { return "size : "+d.members.length; })
      .html("text");



  ////////////////////////////////////////////////////
  //// Node Position functions / settings
  ////////////////////////////////////////////////////
  // position by
  var w = 750;
  var h = 750;
  var pos_functions = {
    "force": function(d) {return {x:d.x, y:d.y};},
    "center": function(d) {return {x:(w/2), y:h-(h/2)};},
    "node (index)": function(d) {
      var xy = d.index / d3.values(json.nodes).length;
      return {x:(xy * w/2), y:h-(xy * h/2)};
    },
    "node (degree)": function(d) {var xy = d.degree*50; return {x:xy, y:h-xy};},
    "node (size)": function(d) {var xy = d.size*50; return {x:xy, y:h-xy};},
    "members (min)": function(d) {
      var xy = d3.min(d.members) / data_size; 
      return {x:(xy * w/2), y:h-(xy * h/2)};
    },
    "members (max)": function(d) {
      var xy = d3.max(d.members) / data_size; 
      return {x:(xy * w/2), y:h-(xy * h/2)};
    },
    "members (mean)": function(d) {
      var xy = d3.mean(d.members) / data_size; 
      return {x:(xy * w/2), y:h-(xy * h/2)};
    },
    "members (median)": function(d) {
      var xy = d3.median(d.members) / data_size; 
      return {x:(xy * w/2), y:h-(xy * h/2)};
    },
  };
  // quick encoding for groups to ints
  var groups_to_int = {};
  d3.keys(groups).map(function(k) {
    groups_to_int[k] = {};
    groups[k].map(function(i, g) {
      groups_to_int[k][parseInt(i)] = parseInt(g);
    });
  });
  // some more func options based on colorings
  color_by_options.map(function(k) {
    if (k==="uniform" || k==="degree" || k==="multiclass") {
      return;
    }
    pos_functions[k] = function(d) {
      var g = parseInt(d.group[k]);
      var xy = groups_to_int[k][g] / d3.max(d3.values(groups_to_int[k]));
      return {x:(xy * w/2), y:h-(xy * h/2)};
    };
    pos_functions[k + ' (weighted)'] = function(d) {
      var xy = 0;
      d.proportions["multiclass"].map(function(p) {
        if (p.label === k) { 
          xy = p.value / p.row_count;
        } 
      });
      return {x:(xy * w/2), y:h-(xy * h/2)};
    };
    pos_functions[k + " (diversity)"] = function(d) {
      var p = d.proportions[k];
      var xy = d3.keys(p).length / d3.keys(groups[k]).length;
      return {x:(xy * w/2), y:h-(xy * h/2)};
    };
  });
  // custom pos
  var custom_layouts = json.graph.custom_layouts ? json.graph.custom_layouts : {};
  d3.keys(custom_layouts).map(function(k) {
    //
    // assume position is defined for each node
    // i.e. pos = {'custom': {1: [x, y], ...}}
    //
    pos_functions[k] = function(d) {
      var xy = custom_layouts[k][d.name];
      return {x:(xy * w/2), y:h-(xy * h/2)};
    };
  });
  // define initial settings
  var pos_by_x = "force";
  var pos_by_y = "force";





  ////////////////////////////////////////////////////
  //// Menu + Menu Options
  ////////////////////////////////////////////////////
  // images ?
  var div = d3.select("#meta");
  
  // init Graph Meta section
  d3.select('#graph-meta')
    .append("div")
    .attr("id", "source")
    .html("<h4>Source</h4><div class='well'>"+source+"</div>");
  d3.select('#node-meta')
    .html("<h4>Node</h4><div class='well'></div>");


  // Create a new menu with options
  // TODO: move to something like dat.gui at some point
  const Menu = function(container) {
    this.container = d3.select(container);
    this.options = {};
    this.buttons = {};
    var self = this;

    // add an option to the menu
    this.add_option = function(name, values, selected, callback) {  
      // check inputs
      if (d3.keys(values)[0] !== "0") {
        values = d3.keys(values);
      }
      // initialize 
    var option = self.container
      .append("div").html("<h4>"+name+"</h4>")
      .append("div").attr("class", "well").attr("id", "menu-" + name.split(" ").join("-").toLowerCase())
      .append("select").attr("class", "menu-choice");
    // add data
    option.selectAll("option")
      .data(values).enter().append("option")
        .attr('value', function (d) {console.log(d); return d;})
        .text(function (d) {console.log(d); return d;})
        .property("selected", function(d){ return d === selected; });
    // add callback
    option.on("change", callback);
    // store
    self.options[name] = option;
    return option;
  };
  // add a button
    this.add_button = function(name, text, callback) {          
      // initialize 
    var button = self.container
      .append("div").html("<h4>"+name+"</h4>")
      .append("div").attr("class", "well").attr("id", "button-" + name.split(" ").join("-").toLowerCase())
      .append("button").attr("class", "menu-button");
    // add data
    button.text(text);
    // add callback
    button.on("click", callback);
    // store
    self.buttons[name] = button;
    return button;
  };
  return this;
  };
  // create new menu, add options
  // TODO: add graph selection option
  // TODO: add movie settings as options
  var menu = new Menu('#graph-meta');
  var option_color_by = menu.add_option("Color", color_by_options, color_by, update_colors);
  var option_color_palette = menu.add_option("Color Palette", color_palettes, color_palette_key, update_colors);
  var option_pos_by_x = menu.add_option("x coordinate", pos_functions, pos_by_x, update_pos);
  var option_pos_by_y = menu.add_option("y coordinate", pos_functions, pos_by_y, update_pos);
  var button_movie = menu.add_button("Movie", "Play", update_movie);



  ////////////////////////////////////////////////////
  //// Movie + node highlighting
  ////////////////////////////////////////////////////
  // Mouseover effects
  var focus_node = null, highlight_node = null;
  node.on("mouseover", function(d) {
    set_highlight(d);
    //d3.select("#meta").transition()       
    //    .duration(0)      
    //    .style("opacity", .85); 
    //d3.select('#graph-meta#source')
    //  .html("<h4>Source</h4><div class='well'>"+source+"</div>");
    //d3.select('#node-meta')
    //  .html("<h4>Node</h4><div class='well'>"+d.tooltip+"</div>");
  }).on("mousedown", function(d) { 
    d3.event.stopPropagation();
    if (focus_node === d) {
      exit_focus();
    } else {
      set_focus(d);
    }
  }).on("mouseout", function(d) {
    if (focus_node === null) {
        exit_highlight();
    }
  });

  //d3.select(window)
  d3.select("#main").on("mouseup", function() {
    if (focus_node!==null) {
      //exit_focus();
    } else if (highlight_node === null) {
      exit_highlight();
    }
  }).on("mousedown", function() {
    //exit_focus();
    //exit_highlight();
  });

  // Node highlighting logic
  function exit_highlight(){
      highlight_node = null;
      if (focus_node===null) {
        svg.style("cursor","pointer");
        node.attr("id", null);
        link.attr("id", null);
      } 
  }
  function exit_focus(){
    node.attr("id", null);
    link.attr("id", null);
    set_highlight(highlight_node);
    focus_node = null;
  }

  var linkedByIndex = {};
  json.links.forEach(function(d,i) {
    linkedByIndex[d.source.index+","+d.target.index] = true;
    linkedByIndex[d.target.index+","+d.source.index] = true;
  });
  function isConnected(a, b) {
      return linkedByIndex[a+","+b] || a == b;
  }
  function set_focus(d) {
    if (focus_node!==d) {
      focus_node = d;
      focus_path = {};
      node.attr("id", function(o,i) {
        return isConnected(d.index, i) ? "focus" : "defocus";
      }); 
      link.attr("id", function(o,i) {
        return o.source.index == d.index || o.target.index == d.index ? "focus" : "defocus";
      }); 
      show_tooltip(d);
    }
  }
  function set_highlight(d) {
    svg.style("cursor","pointer");
    if (focus_node!==null) {
      if (highlight_node!==null) {
        return;
      } 
      d = focus_node;
    }
    highlight_node = d;
    if (d===null) {
      return;
    }
    // set id to highlight
    node.attr("id", function(o,i) {
          return isConnected(d.index, i) ? "highlight" : null;
      }); 
    link.attr("id", function(o,i) {
      return o.source.index == d.index || o.target.index == d.index ? "highlight" : null;
    }); 
    // show tooltip
    show_tooltip(d);
  }
  function show_tooltip(d) {
    // display tooltip
    d3.select('#node-meta')
      .html("<h4>Node</h4><div class='well'>"+d.tooltip+"</div>");
  }

  ////////////////////////////////////////////////////
  // LEGEND 
  ////////////////////////////////////////////////////

  //var svg_legned = d3.select(".legend")
  //    .append("svg")
  //    .attr("width", width)
  //    .attr("height", height)
  var n_labels = color_label.domain().length;
  var max_n_labels = d3.max(d3.values(groups).map(v => {return v.length;}));
  var legend_height = 20 * max_n_labels + 10;
  var legend = d3.select('#graph-meta')
      .append("div")
        .attr("id", "legend")
        .html("<h4>Legend</h4>")
      .append("div")
        .attr("class", "well")
      .append("svg")
        .attr("class", "legend")
        .attr("width", "100%")
        .attr("height", legend_height+"px");
      
  // D3 Vertical Legend //////////////////////////
  var label_style = {w: 100, h: legend_height / n_labels};
  label_style.font_size = label_style.w / 5 - 4;
  label_style.x = label_style.w + (label_style.w / 5);
  label_style.y = label_style.h - (label_style.h - label_style.font_size) / 2;

  // add legend labels 
  glegend = legend.selectAll('.legend')
      .data(color_label.domain().reverse())
      .enter().append('g')
      .attr("transform", function (d, i) {
        return "translate(0," + i * label_style.h + ")";
      });
  var gcolors = glegend.append('rect')
      .attr("x", 5)
      .attr("y", 5)
      .attr("width", label_style.w)
      .attr("height", label_style.h)
      .style("fill", function (d, i) {return color_label(d);})
  var glabels = glegend.append('text')
      .attr("x", label_style.x) 
      .attr("y", label_style.y)
      .text(function (d, i) {return d;})
      .attr("class", "legend-text")
      .style("text-anchor", "start")
      .style("font-size", label_style.font_size)

  
  ////////////////////////////////////////////////////
  //// Color palette selection
  ////////////////////////////////////////////////////         
  // force data to update when menu is changed    
  function update_colors() {
    // get selected color by 
    color_by = option_color_by.property("value");
    console.log('color_by: '+color_by);
    // get selected color palette
    color_palette_key = option_color_palette.property("value");
    console.log('color_palette: '+color_palette_key);
    // set palette
    color_palette = color_palettes[color_palette_key];
    color = color
      .domain(groups[color_by])
      .range(color_palette);
    console.log("color.domain: "+color.domain());
    //console.log("color.range: "+color.range());
    color_label = color_label
      .domain(color_labels[color_by])
      .range(color.range());
    console.log("color_label.domain: "+color_label.domain());
    //console.log("color_label.range: "+color_label.range());

    
    //////////////////////////////////////////
    // Reset Legend //////////////////////////
    //////////////////////////////////////////
    n_labels = color_label.domain().length;
    label_style = {w: 100, h: legend_height / n_labels};
    label_style.font_size = label_style.w / 5 - 4;
    label_style.x = label_style.w + (label_style.w / 5);
    label_style.y = label_style.h - (label_style.h - label_style.font_size) / 2;

    glegend.remove('*');
    glegend = legend.selectAll('.legend')
        .data(color_label.domain().reverse())
        .enter().append('g')
        .attr("transform", function (d, i) {
          return "translate(0," + i * label_style.h + ")";
        });
    var gcolors = glegend.append('rect')
        .attr("x", 5)
        .attr("y", 5)
        .attr("width", label_style.w)
        .attr("height", label_style.h)
        .style("fill", function (d, i) {return color_label(d);})
    var glabels = glegend.append('text')
        .attr("x", label_style.x)
        .attr("y", label_style.y)
        .text(function (d, i) {return d;})
        .attr("class", "legend-text")
        .style("text-anchor", "start")
        .style("font-size", label_style.font_size)

    //////////////////////////////////////////
    // Reset Nodes  //////////////////////////
    //////////////////////////////////////////
    circle.remove('*');
    circle = node.selectAll("path")
          .data(function(d, i) {return pie(d.proportions[color_by]);})
          .enter()
          .append("svg:path")
            .attr("d", d3.svg.arc().innerRadius(0).outerRadius(function(d, i) {
              return pie_norm(d.data.row_count)*0.8;
            }))
            .attr("fill", function(d, i) { return color(d.data.group); })
            .attr("class", "circle");
    
    // re-color circles, legend
    //circle.attr("fill", function(d, i) { return color(d.data.group); });
    //gcolors.style("fill", function (d, i) {return color_label(d);});
  }


  ////////////////////////////////////////////////////
  //// Node position selection
  ////////////////////////////////////////////////////  
  function update_pos() {
    // get selected 
    pos_by_x = option_pos_by_x.property("value");
    pos_by_y = option_pos_by_y.property("value");  
    console.log("\npos_by_x: "+pos_by_x+
                "\npos_by_y: "+pos_by_y);
    update_pos_force();
  }
  function update_pos_force() {
    // set positions
    link.attr("x1", function(d) { return pos_functions[pos_by_x](d.source).x; })
      .attr("y1", function(d) { return pos_functions[pos_by_y](d.source).y; })
      .attr("x2", function(d) { return pos_functions[pos_by_x](d.target).x; })
      .attr("y2", function(d) { return pos_functions[pos_by_y](d.target).y; })
    node.attr("x", function(d) { return pos_functions[pos_by_x](d).x; })
      .attr("y", function(d) { return pos_functions[pos_by_y](d).y; })
      .attr("transform", function(d) { 
        var dx = pos_functions[pos_by_x](d).x;
        var dy = pos_functions[pos_by_y](d).y;
        return "translate(" + dx + "," + dy + ")"
      });
  }

  // initialize position
  force.on("tick", update_pos_force);



  ////////////////////////////////////////////////////
  //// Movie
  ////////////////////////////////////////////////////   
  var movie_playing = null;
  var focus_i = null;
  var movie_start_pos = 0;   
  function update_movie(e) {
    if (movie_playing===true) {
      movie_playing = false;
      button_movie.text('Replay');
      return;
    }
  
    // movie length
    var n_frames = data_size; //d3.max(node.each(function(d,i) {return i+1}));
    var frame_duration = 0.2 * 1000; // 1 fps = 20 / 2
    var duration = frame_duration * n_frames;

    // play movie
    movie_playing = true;
    button_movie.text('Pause');


    // loop over nodes until elapsed >= duration
    d3.timer(function(elapsed) {
      // offset by start pos
      elapsed = elapsed + movie_start_pos;

      // figure out which node to focus
      focus_i = Math.floor(elapsed / frame_duration);
      frame_number = focus_i + 1;
      button_movie.text('Pause ('+frame_number+' of '+n_frames+')');

      
      // loop over nodes and focus the focus node
      node.each(function(d, i) {
        d.members.map(function(dd, ii) {
          if (focus_i == dd) {
            console.log("[movie] TR="+dd+", NodeID="+d.index);
            set_highlight(d);
            set_focus(d);
          }
        })
      })
    
      if (movie_playing === false) {
        movie_start_pos = elapsed;
        button_movie.text('Play ('+frame_number+' of '+n_frames+')');
        return true; 
      }
      if (elapsed >= duration) {
        focus_i = 0
        movie_start_pos = 0;
        movie_playing = true;
        button_movie.text('Replay');
        exit_focus();
        exit_highlight();
        return false;
      };
      return elapsed >= duration;
    })

  }
  
  

  
  ////////////////////////////////////////////////////
  //// SVG width/heigh
  ////////////////////////////////////////////////////     
  svg.width = function(value) {
      if (!arguments.length) return width;
      width = value;
      return svg;
  };

  svg.height = function(value) {
      if (!arguments.length) return height;
      height = value;
      return svg;
  };


  ////////////////////////////////////////////////////
  //// SVG saving
  ////////////////////////////////////////////////////     
  d3.select("a#toggle_save_as").on('click', function(){
      toggle_save_as(svg, source, 'svg');
  });
  ////////////////////////////////////////////////////     
  ////////////////////////////////////////////////////

  return svg;
};



////////////////////////////////////////////////////
//// SVG saving
//////////////////////////////////////////////////// 
function toggle_save_as(svg, source, format) {  
  // check inputs
  var format = format ? format : 'svg';
  var save_as = source.replace(/.*\//, '')       // basename
                      .replace(/json/g, format); // ext
  // save as svg
  console.log("Saving...");
  console.log("format: "+format);
  save_as_svg(svg, save_as);
  console.log("saved: "+save_as);
}

function save_as_svg(svg, save_as){
  var svg_head = '<svg'
     + ' title="graph" version="1.1" xmlns="http://www.w3.org/2000/svg"'
     + ' width="' + svg.width() + '" height="' + svg.height() + '"'
     + ' style="margin:20%; overflow:visible;"'
     + '>';
  var svg_style = '<style>'
     + ' .node, circle {cursor: pointer;stroke-width: 1.5px;}'
     + ' .link, path {stroke: #000;stroke-width: 1.5px;}'
     + '</style>';
  var svg_save = '<svg>' 
    + svg_head + svg_style + svg.node().innerHTML + '</svg>'
    +'</svg>';
  var blob = new Blob([svg_save], {type: "image/svg+xml"});  
  saveAs(blob, save_as);
};
////////////////////////////////////////////////////
////////////////////////////////////////////////////


function toggle_display_mode() {
  if (d3.select("body").attr('id')=="dark") {
    d3.select("body").attr('id', null).attr('id', "light");
    //d3.select("a#toggle_display_mode").html("NIGHT")
  } else {
    d3.select("body").attr('id', null).attr('id', "dark");
    //d3.select("a#toggle_display_mode").html("LIGHT"); 
  }
}

function toggle_meta() {
  if (d3.select("#meta").style('display')=="block") {
    d3.select("#meta").style('display', "none");
    //d3.select("#meta").attr("class", null);
    d3.select("#main").attr("class", "col-sm-12");
  } else {
    d3.select("#meta").style('display', "block");
    d3.select("#meta").attr("class", "col-sm-4");
    d3.select("#main").attr("class", "col-sm-8");
  }
}

// Hide meta by default
toggle_meta();


// Key press events
window.addEventListener("keydown", function (event) {
  if (event.defaultPrevented) {
      console.log('event.defaultPrevented');
      return; // Do nothing if the event was already processed
  }
  switch (event.key) {
  case "d":
      toggle_display_mode();
      break;     
  default:
      return; // Quit when this doesn't handle the key event.
  }
  // Cancel the default action to avoid it being handled twice
  event.preventDefault();
    }, true);



//d3.selectAll("a.pop")
//    .call(popover);

</script>

</html>
