exports._drawPhylo = drawPhylo;
exports._drawWordCloud = drawWordCloud;

// set javascript date from a string year
function yearToDate(year) {
  var d = new Date()
  d.setYear(parseInt(year));
  d.setMonth(0);
  d.setDate(1);
  return d;
}

function stringToDate(str) {
var arr = (str.replace('"','')).split('-');
var d = new Date();
d.setYear(parseInt(arr[0]));
d.setMonth(parseInt(arr[1]));
d.setMonth(d.getMonth() - 1);
d.setDate(parseInt(arr[2]));
return d;
}

function utcStringToDate(str) {
var arr = ((str.replace('"','')).replace(' UTC','')).split(/[\s-:]+/);
var d = new Date();
d.setYear(parseInt(arr[0]));
d.setMonth(parseInt(arr[1]));
d.setDate(parseInt(arr[2]));
d.setHours(parseInt(arr[3]),parseInt(arr[4]),parseInt(arr[5]))
return d;
}

function stringArrToArr(str) {
var arr = ((str.replace('["','')).replace('"]','')).split('","');
return arr;
}

function intArrToArr(int) {
var arr = ((int.replace('[','')).replace(']','')).split(',');
return arr;
}

function yearToDateHacked(w) {
var d = new Date(2020,0,0);
d.setDate(d.getDate() + (w * 7));
return d
}

function toCoord(id) {
  var x = parseFloat(d3.select("#group" + id).attr("cx")),
      y = parseFloat(d3.select("#group" + id).attr("cy"));
  return [x,y];
}

function toXLabels(branches, groups, xMax) {

  var xLabels = branches.map(function(b) {
      var bId = b.bId,
           xs = groups.filter(g => g.bId == bId).map(g => g.x),
          inf = Math.min(...xs),
          sup = Math.max(...xs);

      return { x : b.x2,
           label : b.label.replace(/\"/g, '').replace(/\|/g, ''),
             inf : inf,
             sup : sup,
             bId : bId};
  })

  return xLabels.map(function(b,i){
      var prec = 0,
          succ = xMax;

      if (i != 0)
          prec = xLabels[i -1].sup

      if (i != (xLabels.length - 1))
          succ = xLabels[i + 1].inf

      var w = Math.min(...[(b.x - prec) / 2,(succ - b.x) / 2]),
        inf = b.x - w,
        sup = b.x + w;

      inf = (b.inf < inf) ? b.inf : inf + (w / 10);
      sup = (b.sup > sup) ? b.sup : sup - (w / 10);

      return { x : (sup + inf) / 2,
           label : b.label,
             inf : inf,
             sup : sup,
             bId : b.bId};
  })
}

function xOverFlow(ticks,arr) {
  ticks.each(function(t,i){
      var text = d3.select(this),
         chars = d3.select(this).text().split('').reverse(),
            nb = chars.length,
             y = text.attr("y"),
            dy = parseFloat(text.attr("dy")),
          line = [],
         tspan = text.attr("bId",arr[i][1]).text(null).append("tspan").attr("x", 0).attr("y", -14).attr("dy", dy + "em").attr("bId","");

      while ((char = chars.pop()) && (tspan.node().getComputedTextLength() < (arr[i][0] - 2))) {
          line.push(char);
          tspan.text(line.join(''));
      }

      if (line.length != nb) {
          line.slice(-3)
          tspan.text(line.join('') + '...')
      }

  })
}

var branchFocus = [];

function addMarkX(ticks,ws,ids) {
  ticks.each(function(t,i){
      d3.select(this)
        .append("rect")
        .attr("x","-" + (ws[i]/2 + 1))
        .attr("y","-4")
        .attr("height","8")
        .attr("width",ws[i] + 1)
        .attr("class","x-mark")
        .attr("id", "xmark-" + ids[i])

      if (branchFocus.includes("" + ids[i])) {
        d3.select("#xmark-" + ids[i]).style("fill","#F0684D");
      }
  })
}

function setMarkYLabel(labels) {
  labels.each(function(l,i){
      d3.select(this).attr("dx","-5").attr("class","y-label").attr("id","y-label-" +  d3.timeYear(l).getFullYear());
  })
}

function addMarkY(ticks) {
  ticks.each(function(d,i){
      if (d3.timeYear(d) < d) {
          // month
          d3.select(this)
            .append("circle").attr("cx",0).attr("cy",0).attr("r",3).attr("class","y-mark-month");
      } else {
          var from = d3.timeYear(d).getFullYear();
          // year
          d3.select(this)
            .append("circle").attr("cx",0).attr("cy",0).attr("r",6).attr("class","y-mark-year-outer").attr("id","y-mark-year-outer-" + from);
          d3.select(this)
            .append("circle").attr("cx",0).attr("cy",0).attr("r",3).attr("class","y-mark-year-inner").attr("id","y-mark-year-inner-" + from);
      }
  })
}

function addDays(date, days) {
  var result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

function removeDays(date, days) {
  var result = new Date(date);
  result.setDate(result.getDate() - days);
  return result;
}

function setYDomain(labels) {

  var ts = ["week","month","day","year","epoch"];

  //console.log(labels)

  if (ts.includes(window.timeScale)) {
    labels = labels.sort(function(d1,d2){return d1.from - d2.from;})
  }

  var inf = (labels[0]).from,
      sup = (labels[labels.length - 1]).to;

  if (window.timeScale == "week") {
    inf =  addDays(inf,7)
    sup = addDays(sup,7)
  } else if (window.timeScale == "month") {
    inf = removeDays(inf,31)
    sup = addDays(sup,31)
  } else if (window.timeScale == "day") {
    inf = removeDays(inf,1)
    sup = addDays(sup,1)
  } else if (window.timeScale == "year") {
    inf = removeDays(inf,365)
    sup = addDays(sup,365)
  } else if (window.timeScale == "epoch") {
    inf = inf
    sup = sup
  } else {
    inf = new Date((inf.getFullYear() - 1),0,0);
    sup = new Date((sup.getFullYear() + 1),0,0);
  }

  // inf = new Date((inf - 1),6,0);
  // inf = new Date((1950 - 1),6,0);
  // sup = new Date((sup + 1),0,0);
  return [inf,sup];
}

function groupTermsBy(elements, attr) {
let grouped = {},
    curr = "";
for (var i = 0; i < elements.length; i++) {
  let from = elements[i].getAttribute(attr)
  if (curr != from) {
    grouped[from] = [[(elements[i]).getAttribute("gx"),(elements[i]).getAttribute("gy"),(elements[i]).getAttribute("bid")]];
    curr = from
  } else {
    grouped[from].push([(elements[i]).getAttribute("gx"),(elements[i]).getAttribute("gy"),(elements[i]).getAttribute("bid")]);
  }
}
return Object.values(grouped);
};


function findValueByPrefix(prefix) {
  for(var i = 0; i < window.terms.length; i++) {
    var object = (window.terms)[i];
      if(object.label.toLowerCase().startsWith(prefix.toLowerCase()))
      {
          return object;
      }

  }
  return null;
}

 function drawWordCloud (groups) {

    let labels = {},
        count  = 0;

    d3.selectAll(".word-cloud").remove();

    groups.forEach(function(g){
      let gid = (g.getAttribute("id")).replace("group","");
      let terms = d3.selectAll(".term").filter(".g-" + gid).nodes();
      terms.forEach(function(t){
        count ++;
        if (labels[t.getAttribute("fdt")] == undefined) {
          labels[t.getAttribute("fdt")] = {"freq" : 1, "label" : t.getAttribute("label")}
        } else {
          labels[t.getAttribute("fdt")].freq = labels[t.getAttribute("fdt")].freq + 1
        }
      })
    });

    labels = (Object.values(labels)).map(function(l){
      return {"freq":(l.freq / count),"label":l.label};
    }).sort(function(l1,l2){
      return l2.freq - l1.freq;
    })

    let y = 20
    let opacity = d3.scaleLinear().domain([Math.log((labels[labels.length - 1]).freq),Math.log((labels[0]).freq)]).range([0.5,1]);

    labels.forEach(function(l){
      y = y + 12;
      window.svg3.append("text")
          .attr("class","word-cloud")
          .attr("x", 10)
          .attr("y", y)
          .style("opacity", opacity(Math.log(l.freq)))
          .text(l.label);
    })
  }


function drawPhylo(branches, periods, groups, links, aLinks, bLinks, frame) {

  /* ** draw the sources box ** */

  document.querySelector("#checkSource").style.display  = "inline-block";

  /* ** draw the search box ** */

  var inputSearch = document.getElementById("search-box");
  inputSearch.style.visibility = "visible";
  inputSearch.addEventListener("keyup", autocomplete);
  document.getElementById("search-autocomplete").style.visibility = "visible";
  document.getElementById("search-label").style.visibility = "visible";

  /* ** draw the isoline ** */

  d3.select('#phyloIsoLine').style("background","#EBE4DD");

  var div0 = getIsolineDOMElement(),
        m0 = {t:5,r:5,b:5,l:5},
        w0 = div0.width,
        h0 = div0.height;

  var svg0 = d3
        .select('#phyloIsoLine')
        .append("svg")
          .attr("width", w0)
          .attr("height",h0)
        .append("g");

  var xScale0 = d3.scaleLinear().domain([0,Math.max(...branches.map(b => b.x1))]).range([2 * m0.l, w0 - 2 * m0.l]),
      yScale0 = d3.scaleLinear().domain(d3.extent(branches, b => b.y)).nice().range([2 * m0.t, h0 - 2 * m0.t]);

  var density =
      d3.contourDensity()
          .x(function(b) { return xScale0(b.x1); })
          .y(function(b) { return yScale0(b.y); })
          .size([w0, h0])
          .thresholds(Math.round(branches.length / 2))
          (branches)

  function getIsolineDOMElement() {
    return d3.select('#phyloIsoLine').node().getBoundingClientRect();
  }

  /* shadows and lights */

  svg0.append("g")
     .selectAll("circle")
     .data(branches)
     .enter()
     .append("circle")
       .attr("cx", b => xScale0(b.x1))
       .attr("cy", b => yScale0(b.y))
       .attr("r","55")
       .attr("id",b => "peak-shadow" + b.bId)
       .attr("visibility","visible")
       .style("fill","#f5eee6");

  svg0.selectAll("path")
     .data(density)
     .enter()
     .append("path")
       .attr("d", d3.geoPath())
       .attr("fill", "none")
       .attr("stroke", "#74B5FF")
       .attr("stroke-width", (d, i) => i % 2 ? 0.25 : 1)
       .attr("stroke-linejoin", "round");


  var label =
      d3.select("#phyloIsoLine")
        .append("div")
        .attr("class","peak-label");

  svg0.append("g")
     .selectAll("text")
     .data(branches)
     .enter()
     .append("text")
       .attr("x", b => xScale0(b.x1))
       .attr("y", b => yScale0(b.y) + 4)
       .attr("class","peak")
       .attr("id",b => "peak-" + b.bId)
       .style("fill","#0d1824")
       .attr("visibility","visible")
       .text("▲")
       .on("mouseover", function(e, b) {
        peakOver(b, b.bId);
       })
       .on("mouseout", function(e, b) {
         peakOut(b, b.bId);
       })
       .on("click", function(e, b) {
         peakClick(b, b.bId);
       });

  /* *** draw the phylo *** */

  var div1 = d3.select('#phyloScape')
               .node().getBoundingClientRect(),
      div2 = d3.select('#phyloTimeline')
               .node().getBoundingClientRect(),
         m = {t:10, b:10, l:10, r:10},
         w = div1.width,
         h = div1.height
      ydiv = div1.y;

  const svg = d3
      .select('#phyloScape')
      .append("svg")
      .attr("viewBox",[0,0,w,h]);

  var xo = div2.width + m.l,
      yo = 2.5 * m.t,
      wo = w - xo - m.r,
      ho = h - yo - m.b;

  /* *** draw the graph *** */

  var div3 = d3.select('#phyloGraph')
               .node().getBoundingClientRect();

  window.svg3 = d3
        .select('#phyloGraph')
        .append("svg")
          .attr("width", div3.width)
          .attr("height",div3.height)
        .append("g");


  /* labels */

  var firstDate = Math.min(...groups.map(g => (g.from).getFullYear()))

  var yLabels = (periods.map(p => ({y:p.y,from:p.from,to:p.to,label:(p.from).getFullYear()}))).filter(p => p.label >= firstDate);
  var xLabels = toXLabels(branches,groups,frame[2]);

  /* weight */

  if (window.weighted == true) {
    var wInf = Math.min(...groups.map(g => g.weight))
    var wSup = Math.max(...groups.map(g => g.weight))
    var wScale = d3.scaleLog().domain([1,wSup]).range([3,10])

  }


  /* scales */

  var xScale = d3.scaleLinear().domain([0,frame[2]]).range([xo + m.t,wo + xo]),
      yScale = d3.scaleTime().domain(setYDomain(yLabels)).range([m.t + yo, ho + yo]);

  /* panel and& mask */

  var mask = svg
      .append("defs")
      .append("svg:clipPath")
          .attr("id","mask")
      .append("svg:rect")
          .attr("width", wo)
          .attr("height",ho)
          .attr("x",xo)
          .attr("y",yo);

  const panel = svg.append("g").attr("clip-path", "url(#mask)").attr("id","panel")

  /* highlight */

  xLabels.forEach(b =>
      panel.append("rect")
              .attr("class","branch-hover")
              .attr("x", xScale(b.inf))
              .attr("y", -10000)
              .attr("width", xScale(b.sup) - xScale(b.inf))
              .attr("height", 20000)
              .attr("id","hover-" + b.bId)
              .style("visibility","hidden"))

  yLabels.forEach(l =>
      panel.append("line")
           .attr("class","y-highlight")
           .attr("id","y-highlight-" + l.label)
           .attr("x1", -10000)
           .attr("y1", yScale(l.from))
           .attr("x2", 10000)
           .attr("y2", yScale(l.from))
           .style("visibility","hidden"))

  /* links */

  function findGroup (id, xsc, ysc) {
      var group = groups.find(g => g.gId == id),
          x = xsc(group.x);
          y = ysc(group.to);
      return [x,y]
  }

  var linkGen = d3.linkVertical();
  var groupLinks = links.map(l => ({source: findGroup(l.from, xScale, yScale), target: findGroup(l.to, xScale, yScale),from: l.from, to: l.to, label: l.label}));

  var groupAncestors = aLinks.map(l => ({source: findGroup(l.from, xScale, yScale), target: findGroup(l.to, xScale, yScale),from: l.from, to: l.to, label: l.label}));

  panel
    .selectAll("path")
    .data(groupLinks.concat(groupAncestors))
    .join("path")
    .attr("d", linkGen)
    .attr("fill", "none")
    .attr("stroke","#0d1824")
    .attr("class", "group-path")
    .attr("source",d => d.from)
    .attr("target",d => d.to)
    .attr("label", d => d.label)
    // .on("click", function(){
    //   // console.log(this)
    // })

  var colors = ["#F0684D","#aa8c58","#74b5ff","#0d1824"];

  /* groups */

  groups.forEach(g => setGroup(g));

  /* axis */

  var xAxis = svg.append("g").attr("class","x-axis").attr("transform", "translate(0," + yo + ")"),
      yAxis = svg.append("g").attr("class","y-axis").attr("transform", "translate(" + xo + ",0)");

  setAxisX(xScale,xLabels);
  setAxisY(yScale,yLabels);

  /* zoom */

  var zoom = d3.zoom()
      .scaleExtent([1,50])
      .extent([[xo,yo],[wo,ho]])
      .on("zoom", function(e) {
        debouncedOnZoom(e);
      });

  svg.call(zoom).on("dblclick.zoom",null).on("dblclick",doubleClick);

  d3.select("#reset").on("click",reset);

  function reset() {
    svg.transition()
        .duration(750)
        .call(zoom.transform, d3.zoomIdentity);
  }

  function debounce(fn, wait, immediate) {
    var timeout;
    return function() {
      var context = this
      , args = arguments
      , later = function() {
        timeout = null;
        if (immediate !== true) {
          fn.apply(context, args);
        }
      }
      , now = immediate === true && timeout === null;

      clearTimeout(timeout);
      timeout = setTimeout(later, wait);

      if (now === true) {
        fn.apply(context, args);
      }
    };
  };

  var debouncedOnZoom = debounce(
    onZoom
    , 50
  );

  function onZoom(event) {
      var zoomX = event.transform.rescaleX(xScale),
          zoomY = event.transform.rescaleY(yScale),
          zoomXLabels = xLabels
              .filter(b => (zoomX(b.x) >= xo) && (zoomX(b.x) <= (wo + xo))),
          zoomYLabels = yLabels
              .filter(p => (zoomY(p.y) >= yo) && (zoomY(p.y) <= (ho + yo)));

      setAxisX(zoomX,zoomXLabels);

      setAxisY(zoomY,zoomYLabels);

      panel.selectAll("circle").attr("transform", event.transform);
      panel.selectAll("text").attr("transform", event.transform);
      panel.selectAll("path").attr("transform", event.transform);
      panel.selectAll(".branch-hover").attr("transform", event.transform);
      panel.selectAll(".y-highlight").attr("transform", event.transform);
      panel.selectAll(".ngrams").attr("transform", event.transform);
      panel.selectAll(".term-path").attr("transform", event.transform);
      panel.selectAll(".emergence").attr("transform", event.transform);
      panel.selectAll(".header").attr("transform", event.transform);

      showPeak()
  }

  /* label */

  // https://observablehq.com/@d3/parallel-coordinates

  d3.select("#label").on("click", function(e, l) {
    showLabel(l);
  });

  function autocomplete(e) {
      var txt = e.target.value;

      if (txt.length < 1) {
        document.getElementById("search-autocomplete").value = '';
        return;
      }

      var placeholder = findValueByPrefix(txt);

      if (placeholder !== null) {
        document.getElementById("search-autocomplete").value = placeholder.label;
      } else {
        document.getElementById("search-autocomplete").value = '';
      }

      // press enter
      if (e.keyCode === 13) {
        e.preventDefault();
        if (placeholder !== null) {
          showLabel("search")
          termClick(placeholder.label,placeholder.fdt,0,"search")
        }
      }
  }

  function showLabel(type) {
    if ((document.getElementsByClassName("header"))[0].style.visibility != "hidden") {
      showHeading()
    }
    doubleClick()
    let ngrams = document.getElementsByClassName("ngrams")
    let groups = document.getElementsByClassName("group-inner")
    if (ngrams[0].style.visibility == "hidden") {
      document.getElementById("label").classList.add("labeled")
      for (var i = 0; i < groups.length; i++) {
        groups[i].style.fill = "#fff";
      }
      for (var i = 0; i < ngrams.length; i++){
        ngrams[i].style.visibility = "visible";
      }
    } else {
      if (type != "search") {
        document.getElementById("label").classList.remove("labeled")
        for (var i = 0; i < groups.length; i++) {
          groups[i].style.fill = "#61a3a9";
        }
        for (var i = 0; i < ngrams.length; i++){
          ngrams[i].style.visibility = "hidden";
        }
      }
    }
  }

  /* role & dynamic */

  d3.select("#heading").on("click",showHeading);

  var emergences = {};
  var branchByGroup = {};
  groups.forEach(function(g){
    // is a term in many branches ?
    for (var i = 0; i < (g.foundation).length; i++) {
      var fdt = (g.foundation)[i];
      if (fdt in branchByGroup) {
        (branchByGroup[fdt]).push(g.bId);
      } else {
        branchByGroup[fdt] = [g.bId];
      }
    }
    // is emerging ?
    if ((g.role).includes(0)) {
      for (var i = 0; i < (g.role).length; i++) {
        if ((g.role)[i] == 0) {
          var gf = (g.foundation)[i];
          if (gf in emergences) {
            (emergences[gf].x).push(xScale(g.x));
            (emergences[gf].y).push(yScale(g.to));
          } else {
             emergences[gf] = {"label":g.label[i],"x":[xScale(g.x)],"y":[yScale(g.to)],"bid":g.bId}
          }
        }
      }
    }
  });

  var keys = Object.keys(emergences);
  var freqs = (keys.map(k => window.freq[k])).filter(f => f != null);

  const arraySum = (acc, curr) => acc + curr;

  function rdm() {
    if (Math.random() > 0.5) {
      return 1;
    } else {
      return -1;
    }
  }

  // var fontScale = d3.scaleLinear().domain([0,Math.max(...freqs)]).range([2,10]);
  var fontScale = d3.scaleLinear().domain([0,Math.sqrt(Math.max(...freqs))]).range([2,20]);
  var opacityScale = d3.scaleLinear().domain([0,1/Math.sqrt(Math.max(...freqs))]).range([0.1,1]);

  keys.forEach(function(k){
    let x = ((emergences[k]).x).reduce(arraySum) / ((emergences[k]).x).length;
    let y = ((emergences[k]).y).reduce(arraySum) / ((emergences[k]).y).length;
    let bid = Array.from(new Set(branchByGroup[k]));
    var freq = 0;
    // console.log(k)

    if (k in window.freq) {
      freq = window.freq[k];
    }
    panel.append("text")
         .attr("x",x + (rdm() * Math.random() * 10))
         .attr("y",y + (rdm() * Math.random() * 10))
         .attr("fdt",k)
         .attr("id","head" + k)
         .attr("mem-size", fontScale(Math.sqrt(freq)))
         .attr("mem-opac", opacityScale(Math.sqrt(freq)))
         .attr("bid",(emergences[k]).bid)
         .style("font-size", fontScale(Math.sqrt(freq)) + "px")
         .style("opacity", opacityScale(1/Math.sqrt(freq)))
         .attr("class","header")
         .style("visibility","hidden")
         .style("text-anchor", "middle")
         // .style("fill",(bid.length > 1) ? "#012840" : "#CC382F")
         .style("fill",(bid.length > 1) ? "#012840" : "#012840")
         .text((emergences[k]).label)
         .on("click",function(){
            showLabel("header")
            termClick((emergences[k]).label,k,k,"head");
         });
  });

  function landingView() {
    window.ldView = true;
    doubleClick()
    let headers = document.getElementsByClassName("header")
    let groups = document.getElementsByClassName("group-inner")
    if (headers[0].style.visibility == "hidden") {
      document.getElementById("heading").classList.add("headed")
      for (var i = 0; i < groups.length; i++) {
        groups[i].style.fill = "#f5eee6";
        groups[i].classList.add("group-heading")
      }
      d3.selectAll(".group-path").classed("path-heading",true);
      for (var i = 0; i < headers.length; i++){
        headers[i].style.visibility = "visible";
      }
    } else {
      document.getElementById("heading").classList.remove("headed")
      for (var i = 0; i < groups.length; i++) {
        groups[i].style.fill = "#61a3a9";
        groups[i].classList.remove("group-heading")
      }
      d3.selectAll(".group-path").classed("path-heading",false);
      for (var i = 0; i < headers.length; i++){
        headers[i].style.visibility = "hidden";
      }
    }
  }

  landingView()

  function showHeading() {
    if ((document.getElementsByClassName("ngrams"))[0].style.visibility != "hidden") {
      showLabel("header")
    }
    landingView()
  }

  /* groups */

  function textWidth(text) {
      const context = document.createElement("canvas").getContext("2d");
      return context.measureText(text).width;
  }

  function toLines(words,fdt,role,targetWidth) {
    let line;
    let lineWidth0 = Infinity;
    const lines = [];
    for (let i = 0, n = words.length; i < n; ++i) {
      let lineText1 = (line ? line.text + " " : "") + words[i];
      let lineFdt1 = (line ? line.fdt + " " : "") + fdt[i];
      let lineRole1 = (line ? line.role + " " : "") + role[i];
      let lineWidth1 = textWidth(lineText1);
      if ((lineWidth0 + lineWidth1) / 2 < targetWidth + 10) {
        line.width = lineWidth0 = lineWidth1;
        // line.text = lineText1;
        line.text.push(words[i])
        line.fdt.push(fdt[i])
        line.role.push(role[i])
      } else {
        lineWidth0 = textWidth(words[i]);
        line = {width: lineWidth0, text: [words[i]], fdt: [fdt[i]], role: [role[i]]};
        lines.push(line);
      }
    }
    return lines;
  }

  function toTextRadius(lines,lineHeight) {
    let radius = 0;
    for (let i = 0, n = lines.length; i < n; ++i) {
      const dy = (Math.abs(i - n / 2 + 0.5) + 2) * lineHeight;
      const dx = lines[i].width / 2;
      const sdy = Math.pow(dy, 2);
      const sdx = Math.pow(dx, 2);
      radius = Math.max(radius, Math.sqrt(sdx + sdy));
    }
    return radius;
  }

  function findFreq(fdt) {
    let freq = 0;
    if (window.freq[fdt] != null) {
      freq = window.freq[fdt]
    }
    return freq;
  }

  function setGroupClass(g) {
    var str = "group-inner" + " " + "branch-" + g.bId;
    for (var i = 0; i < g.source.length; i++) {
      str += " source-" + g.source[i];
    }
    return str;
  }

  function setGroup(g) {

      // console.log(window.weighted)

      if(window.weighted == true) {
        var radius = wScale(g.weight)
      } else {
        var radius = 5;
      }

      // var radius = 5;

      // var col = Math.round(Math.random() * 3) - 1

      panel
        .append("circle")
        .attr("class","group-outer")
        .attr("cx", xScale(g.x))
        .attr("cy", yScale(g.to))
        .attr("r" , radius + 0.5);

      panel
        .append("circle")
        .attr("class", setGroupClass(g))
        .attr("cx", xScale(g.x))
        .attr("cy", yScale(g.to))
        .attr("bId", g.bId)
        .attr("id"  ,  "group" + g.gId)
        .attr("gid"  , g.gId)
        .attr("r" ,radius)
        // .attr("stroke",colors[col])
        .attr("stroke","#0d1824")
        .style("fill", "#61a3a9")
        .attr("from",(g.to).getFullYear())
        // .on("mouseover",groupOver)
        // .on("mouseout" ,groupOut)

      /* group label */

      var lineHeight = 12,
          targetWidth = Math.sqrt(textWidth(g.label.join('').trim()) * radius),
          lines = toLines(g.label,g.foundation,g.role,targetWidth),
          textRadius = toTextRadius(lines,lineHeight)
          textRatio = (radius - 0.5) / textRadius;

      for (let i = 0; i < lines.length; i++) {

        let words  = lines[i].text,
            fdt    = lines[i].fdt,
            roles  = lines[i].role,
            terms  = mergeLists(words,fdt,roles)
            toSpan = (acc, w) => acc + "<tspan fdt=" + w[1]
                                     + " class='term fdt-" + w[1] + " " + "g-" + g.gId + findRole(w[2]) + "'"
                                     + " gy=" + yScale(g.to)
                                     + " gx=" + xScale(g.x)
                                     + " freq=" + findFreq(w[1])
                                     + " label='" + w[0] + "'"
                                     + " gid=" + g.gId
                                     + " bid=" + g.bId
                                     + " from="   + (g.to).getFullYear()
                                     + ">" + w[0] + "</tspan>";

        panel
            .append("text")
            .attr("class","ngrams")
            .attr("text-anchor", "middle")
            .style("font-size", 12 * textRatio + "px")
            .style("visibility", "hidden")
              .append("tspan")
                .attr("x", xScale(g.x))
                .attr("y", yScale(g.to) + (i - lines.length / 2.8) * (lineHeight * textRatio))
                .html(terms.reduce(toSpan,""));


        d3.selectAll(".term")
          .on("click",function(){
            termClick(this.textContent,this.getAttribute("fdt"),this.getAttribute("gid"),"group");
          })
          // .on("mouseover",function(){
          //   d3.selectAll(".term").classed("term-unfocus",true);
          //   d3.selectAll(".term").filter(".g-" + this.getAttribute("gid")).classed("term-focus",true);
          // })
          // .on("mouseout",function(){
          //   d3.selectAll(".term").classed("term-unfocus",false);
          //   d3.selectAll(".term").classed("term-focus",false);
          // });

      }
  }

  d3.selectAll(".header").raise();


  function findRole(r) {
    if (r == 0) {
      return " emerging";
    } else if (r == 2) {
      return " decreasing";
    } else {
      return "";
    }
  }

  function mergeLists(l1,l2,l3) {
    let merged = [];
    for (let i = 0; i < l1.length; i++) {
          merged.push([l1[i],l2[i],l3[i]])
    }
    return merged;
  }

  function setAxisY(scale,labels) {
      yAxis.call(d3.axisLeft(scale)
                      .tickFormat(function(d){
                          if (d3.timeYear(d) < d) {
                              // '%B'
                              return d3.timeFormat('%d %B')(d);
                          } else {
                              return d3.timeFormat('%Y')(d);
                          }
                      })
                      .tickSizeOuter(0));
      yAxis.selectAll(".tick line").remove();
      yAxis.selectAll(".tick circle").remove();
      yAxis.selectAll(".tick")
           .call(addMarkY)
      yAxis.selectAll(".tick text")
           .call(setMarkYLabel)
  }

  function setAxisX(scale,labels) {
      xAxis.call(d3.axisTop(scale)
                      .tickValues(labels.map(l => l.x))
                      .tickFormat((l, i) => labels[i].label)
                      .tickSizeOuter(0));
      xAxis.selectAll(".tick text")
           .call(xOverFlow, labels.map(l => [scale(l.sup) - scale(l.inf),l.bId]))
           .on("mouseover", tickOver)
           .on("click", tickClick)
           .on("mouseout" , tickOut);
      xAxis.selectAll(".tick line").remove();
      xAxis.selectAll(".tick rect").remove();
      xAxis.selectAll(".tick")
           .call(addMarkX, labels.map(l => scale(l.sup) - scale(l.inf)),labels.map(l => l.bId));
  }

  function showPeak() {
      d3.selectAll(".peak").style("fill",function(peak,i){
          var isVisible = d3
              .selectAll(".branch-" + i)
              .nodes()
              .map(function(g){
                  var x = g.getBoundingClientRect().x,
                      y = g.getBoundingClientRect().y;
                  if ((x >= xo) && (x  <= (wo + xo)) && (y >= (div1.y + yo - m.t)) && (y <= (div1.y + ho + yo))) {
                      return true;
                  } else {
                      return false;
                  }})
              .reduce((mem,cur) => {return mem || cur;})
          if (isVisible) {
              d3.select("#peak-shadow" + i).attr("visibility","visible");
              return "#0d1824";
          } else {
              d3.select("#peak-shadow" + i).attr("visibility","hidden");
              return "#A9A9A9";
          }
      })
  }

  function countTerms(groups) {
    var terms = [];
    for (var i = 0; i < groups.length; i++) {
      let gid = ((groups[i].getAttribute("id")).split("group"))[1]
      d3.selectAll(".g-" + gid).nodes().forEach(e => terms.push(e.getAttribute("fdt")))
    }
    return (Array.from(new Set(terms))).length;
  }

  function countBranches(groups) {
    var branches = [];
    for (var i = 0; i < groups.length; i++) {
      branches.push(groups[i].getAttribute("bId"));
    }
    return (Array.from(new Set(branches))).length;
  }

  function highlightGroups (groups) {

    window.ldView = false;

    // console.log(groups)

    let paths = document.getElementsByClassName("group-path"),
        gids  = [];

    for (var i = 0; i < groups.length; i++) {

      // highlight the groups

      groups[i]
        .classList.add("group-focus");
      groups[i]
        .classList.remove("group-unfocus");
        // .classed("group-unfocus", false)
        // .classed("group-focus", true);

      gids.push(groups[i].getAttribute("gid"))

      // highlight the branches peak

      let bid = groups[i].getAttribute("bId")

      d3.select("#peak-" + bid)
        .classed("peak-focus", true);
      d3.select("#xmark-" + bid)
        .style("fill", "#F0684D");

    }

    // facets

    document.querySelector("#phyloGroups").innerHTML = groups.length;
    document.querySelector("#phyloTerms").innerHTML = countTerms(groups);
    document.querySelector("#phyloBranches").innerHTML = countBranches(groups);
    document.querySelector("#phyloGroups").classList.add("phylo-focus");
    document.querySelector("#phyloTerms").classList.add("phylo-focus");
    document.querySelector("#phyloBranches").classList.add("phylo-focus");

    // highlight the links

    for (var i = 0; i < paths.length; i++) {
      if (gids.includes((paths[i]).getAttribute("source")) && (paths[i]).getAttribute("target")) {
        paths[i].classList.add("path-focus");
        paths[i].classList.remove("path-unfocus");
      }
    }

  }

  function termClick (txt,idx,nodeId,typeNode) {

    // remove old focus
    initPath()

    // catch the last transformations
    if (typeNode == "group") {
      var transform = d3.select("#group" + nodeId).node().getAttribute("transform");
    } else if (typeNode == "head") {
      var transform = d3.select("#head" + nodeId).node().getAttribute("transform");
    } else {
      var transform = (d3.selectAll(".header").nodes())[0].getAttribute("transform");
    }

    // focus

    document.querySelector("#phyloPhylo").innerHTML = txt;
    document.querySelector("#phyloPhylo").classList.add("phylo-focus");
    document.querySelector("#phyloSearch").setAttribute("href",'https://en.wikipedia.org/w/index.php?search="' + txt + '"')

    // highlight the groups

    var terms = document.getElementsByClassName("fdt-" + idx),
        periods = groupTermsBy(terms,"from");

    var groups  = [];

    for (var i = 0; i < terms.length; i++) {
      groups.push(d3.select("#group" + (terms[i]).getAttribute("gid")));
      branchFocus.push((terms[i]).getAttribute("bid"));
    }

    highlightGroups(groups.map(g => g.node()));
    drawWordCloud(groups.map(g => g.node()));

    // highlight the cross branches links

    var bids  = [];

    for (var i = 0; i < periods.length; i++) {
      if (i != periods.length - 1) {
        for (var j = 0; j < periods[i].length; j++) {
          bids.push(periods[i][j][2])
          var x1 = periods[i][j][0],
              y1 = periods[i][j][1];
          for (var k = 0; k < periods[i + 1].length; k++) {
            var x2 = periods[i + 1][k][0],
                y2 = periods[i + 1][k][1];
            if ((periods[i][j][2] != periods[i + 1][k][2]) && (!bids.includes(periods[i + 1][k][2]))) {
              // draw the links between branches
              panel
                .append("path")
                .attr("class","term-path")
                .attr("d", function(d) {
                  return "M" + x1 + "," + y1
                    + "C" + x2 + "," + y1
                    + " " + x2 + "," + y2
                    + " " + x2 + "," + y2;
                })
                .attr("transform",transform)
                .style("stroke-opacity", 0.4)
                .lower();
            }
            bids.push(periods[i + 1][k][2])
          }
        }
      }
    }

    d3.selectAll(".path-unfocus").lower();
  }

  function peakOver (b,i) {
      var el = getIsolineDOMElement();
      d3.select("#peak-" + i).classed("peak-focus",false);
      d3.select("#peak-" + i).classed("peak-over",true);
      label.text(b.label.replace(/"/g,''))
           .style("visibility", "visible")
           .style("top", (yScale0(b.y) + el.top  - 18) + "px")
           .style("left",(xScale0(b.x1) + el.left + 12) + "px");
      branchOver(b.bId);
  }

  function peakOut (b,i) {
      d3.select("#peak-" + i).classed("peak-over",false);
      if (branchFocus.includes("" + b.bId)) {
        d3.select("#peak-" + i).classed("peak-focus",true);
      }
      branchOut();
  }

  function peakClick (b,i) {
    initPath()
    let groups = d3.selectAll(".group-inner").filter(".branch-" + b.bId).nodes()
    branchFocus.push(b.bId);
    /* word cloud */

    drawWordCloud(groups);
    highlightGroups(groups);
    /* rescale */
    let tx = (groups[0]).getAttribute("cx")
    svg.transition()
        .duration(750)
        .call(zoom.transform, d3.zoomIdentity.translate(((wo + xo) / 2) - tx, 0).scale(1));

    d3.selectAll(".path-unfocus").lower();
  }



  function branchOver(bId) {
      // headers
      if (d3.select("#heading").classed("headed")) {
        d3.selectAll(".header").nodes().forEach(function(header){
          if (header.getAttribute("bid") == bId) {
            header.style["font-size"] = "10px";
            header.style["opacity"] = 1;
          } else {
            header.style["opacity"] = 0.3;
          }
        })
      }
      // branches
      d3.select("#xmark-"  + bId).style("fill","#f3be54");
      d3.select("#hover-" + bId).style("visibility","visible");
  }

  function headerOut() {
    d3.selectAll(".header").nodes().forEach(function(header){
      header.style["font-size"] = header.getAttribute("mem-size") + "px";
      header.style["opacity"] = header.getAttribute("mem-opac");
    })
  }

  function branchOut(bId) {
      d3.selectAll(".peak-label").style("visibility","hidden");
      d3.selectAll(".branch-hover").style("visibility","hidden");
      d3.selectAll(".x-mark").style("fill","#4A5C70");
      for (var i = 0; i < branchFocus.length; i++) {
        d3.select("#xmark-" + branchFocus[i]).style("fill","#F24C3D");
      }
      headerOut();
  }

  function tickClick() {
    initPath()
    let bid = this.getAttribute("bId"),
        groups = d3.selectAll(".group-inner").filter(".branch-" + bid).nodes();

    // draw the word cloud

    branchFocus.push(bid);
    drawWordCloud(groups);

    // highlight the groups

    highlightGroups(groups);
    d3.selectAll(".path-unfocus").lower();
  }

  function tickOver() {
      var ego = this.getAttribute("bId"),
          branch = branches.find(b => b.bId == ego);
      if (d3.select("#peak-" + ego).node().style.visibility != "hidden") {
          branchOver(ego);
          peakOver(branch,ego);
      }
  }

  function tickOut() {
      var ego = this.getAttribute("bId"),
          branch = branches.find(b => b.bId == ego);
      branchOut();
      peakOut(branch,ego)
  }

  // function groupOver() {
  //     var from = this.getAttribute("from");
  //     d3.select("#y-highlight-" + from).style("visibility","visible");
  //     // d3.select("#y-mark-year-inner-" + from).node().setAttribute("class","y-mark-year-inner-highlight");
  //     // d3.select("#y-mark-year-outer-" + from).node().setAttribute("class","y-mark-year-outer-highlight");
  //     // d3.select("#y-label-" + from).node().setAttribute("class","y-label-bold");
  // }

  // function groupOut() {
  //     var from = this.getAttribute("from");
  //     d3.select("#y-highlight-" + from).style("visibility","hidden");
  //     // d3.select("#y-mark-year-inner-" + from).node().setAttribute("class","y-mark-year-inner");
  //     // d3.select("#y-mark-year-outer-" + from).node().setAttribute("class","y-mark-year-outer");
  //     // d3.select("#y-label-" + from).node().setAttribute("class","y-label");
  // }

  function initPath () {
    window.highlighted = true;
    window.ldView = false;
    let groups = d3.selectAll(".group-inner");
    (groups.nodes()).map(function(g){
      if (!g.classList.contains("source-focus")) {
        g.classList.add("group-unfocus");
        g.classList.remove("group-focus");
      }
    })
    d3.selectAll(".group-path")
      .classed("path-unfocus",true)
      .classed("path-focus",false);
    d3.selectAll(".term-path").remove();
    d3.selectAll(".peak").classed("peak-focus",false);
    d3.selectAll(".peak").classed("peak-focus-source",false);
    d3.selectAll(".x-mark").style("fill","#4A5C70");
    branchFocus = [];
  }

  function doubleClick() {
    window.highlighted = false;
    headerOut();
    d3.selectAll(".group-inner")
      .classed("group-unfocus",false)
      .classed("group-focus",false);
    d3.selectAll(".group-path")
      .classed("path-unfocus",false)
      .classed("path-focus",false);
    d3.selectAll(".term-path").remove();
    document.querySelector("#phyloPhylo").innerHTML = "phylomemy";
    document.querySelector("#phyloPhylo").classList.remove("phylo-focus");
    document.querySelector("#phyloGroups").innerHTML = window.nbGroups;
    document.querySelector("#phyloTerms").innerHTML = window.nbTerms;
    document.querySelector("#phyloBranches").innerHTML = window.nbBranches;
    document.querySelector("#phyloGroups").classList.remove("phylo-focus");
    document.querySelector("#phyloTerms").classList.remove("phylo-focus");
    document.querySelector("#phyloBranches").classList.remove("phylo-focus");
    d3.selectAll(".peak").classed("peak-focus",false);
    d3.selectAll(".peak").classed("peak-focus-source",false);
    d3.selectAll(".x-mark").style("fill","#4A5C70");
    branchFocus = [];
  }

  /* export */

  d3.select("#export").on("click",exportViz);

  function exportViz() {


const xmlns = "http://www.w3.org/2000/xmlns/";
const xlinkns = "http://www.w3.org/1999/xlink";
const svgns = "http://www.w3.org/2000/svg";

var time = new Date();

serialize(svg.node(),"phylomemy-" + Date.parse(time.toString())  + ".svg")
function serialize(graph,name) {
  graph = graph.cloneNode(true);
  const fragment = window.location.href + "#";
  const walker = document.createTreeWalker(graph, NodeFilter.SHOW_ELEMENT, null, false);
  while (walker.nextNode()) {
    for (const attr of walker.currentNode.attributes) {
      if (attr.value.includes(fragment)) {
        attr.value = attr.value.replace(fragment, "#");
      }
    }
  }
  graph.setAttributeNS(xmlns, "xmlns", svgns);
  graph.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);

  var cssStyleText = getCSSStyles( graph );
  appendCSS( cssStyleText, graph );

  const serializer = new window.XMLSerializer;
  const string = serializer.serializeToString(graph);
  var svgBlob = new Blob([string], {type: "image/svg+xml"});
  var svgUrl = URL.createObjectURL(svgBlob);
  var downloadLink = document.createElement("a");
  downloadLink.href = svgUrl;
  downloadLink.download = name;
  document.body.appendChild(downloadLink);
  downloadLink.click();
  document.body.removeChild(downloadLink);
};


function getCSSStyles( parentElement ) {
  var selectorTextArr = [];

  // Add Parent element Id and Classes to the list
  selectorTextArr.push( '#'+parentElement.id );
  for (var c = 0; c < parentElement.classList.length; c++)
      if ( !contains('.'+parentElement.classList[c], selectorTextArr) )
        selectorTextArr.push( '.'+parentElement.classList[c] );

  // Add Children element Ids and Classes to the list
  var nodes = parentElement.getElementsByTagName("*");
  for (var i = 0; i < nodes.length; i++) {
    var id = nodes[i].id;
    if ( !contains('#'+id, selectorTextArr) )
      selectorTextArr.push( '#'+id );

    var classes = nodes[i].classList;
    for (var c = 0; c < classes.length; c++)
      if ( !contains('.'+classes[c], selectorTextArr) )
        selectorTextArr.push( '.'+classes[c] );
  }

  // Extract CSS Rules
  var extractedCSSText = "";
  for (var i = 0; i < document.styleSheets.length; i++) {
    var s = document.styleSheets[i];

    try {
        if(!s.cssRules) continue;
    } catch( e ) {
          if(e.name !== 'SecurityError') throw e; // for Firefox
          continue;
        }

    var cssRules = s.cssRules;
    for (var r = 0; r < cssRules.length; r++) {
      if ( contains( cssRules[r].selectorText, selectorTextArr ) )
        extractedCSSText += cssRules[r].cssText;
    }
  }


  return extractedCSSText;

  function contains(str,arr) {
    return arr.indexOf( str ) === -1 ? false : true;
  }

}

function appendCSS( cssText, element ) {
  var styleElement = document.createElement("style");
  styleElement.setAttribute("type","text/css");
  styleElement.innerHTML = cssText;
  var refNode = element.hasChildNodes() ? element.children[0] : null;
  element.insertBefore( styleElement, refNode );
}
}

}