class Spoom::Coverage::D3::CircleMap

def self.header_script

def self.header_script
  <<~JS
    function treeHeight(root, height = 0) {
      height += 1;
      if (root.children && root.children.length > 0)
        return Math.max(...root.children.map(child => treeHeight(child, height)));
      else
        return height;
    }
    function tooltipMap(d) {
      moveTooltip(d)
        .html("<b>" + d.data.name + "</b>")
    }
  JS
end

def self.header_style

def self.header_style
  <<~CSS
    .node {
      cursor: pointer;
    }
    .node:hover {
      stroke: #333;
      stroke-width: 1px;
    }
    .label.dir {
      fill: #333;
    }
    .label.file {
      font: 12px Arial, sans-serif;
    }
    .node.root, .node.file {
      pointer-events: none;
    }
  CSS
end

def script

def script
  <<~JS
    var root = {children: #{@data.to_json}}
    var dataHeight = treeHeight(root)
    var opacity = d3.scaleLinear()
        .domain([0, dataHeight])
        .range([0, 0.2])
    root = d3.hierarchy(root)
        .sum((d) => d.children ? d.children.length : 1)
        .sort((a, b) => b.value - a.value);
    var dirColor = d3.scaleLinear()
      .domain([1, 0])
      .range([strictnessColor("true"), strictnessColor("false")])
      .interpolate(d3.interpolateRgb);
    function redraw() {
      var diameter = document.getElementById("#{id}").clientWidth - 20;
      d3.select("##{id}").selectAll("*").remove()
      var svg_#{id} = d3.select("##{id}")
        .attr("width", diameter)
        .attr("height", diameter)
        .append("g")
          .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
      var pack = d3.pack()
          .size([diameter, diameter])
          .padding(2);
      var focus = root,
          nodes = pack(root).descendants(),
          view;
      var circle = svg_#{id}.selectAll("circle")
        .data(nodes)
        .enter().append("circle")
          .attr("class", (d) => d.parent ? d.children ? "node" : "node file" : "node root")
          .attr("fill", (d) => d.children ? dirColor(d.data.score) : strictnessColor(d.data.strictness))
          .attr("fill-opacity", (d) => d.children ? opacity(d.depth) : 1)
          .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); })
          .on("mouseover", (d) => tooltip.style("opacity", 1))
          .on("mousemove", tooltipMap)
          .on("mouseleave", (d) => tooltip.style("opacity", 0));
      var text = svg_#{id}.selectAll("text")
        .data(nodes)
        .enter().append("text")
          .attr("class", (d) => d.children ? "label dir" : "label file")
          .attr("fill-opacity", (d) => d.depth <= 1 ? 1 : 0)
          .attr("display", (d) => d.depth <= 1 ? "inline" : "none")
          .text((d) => d.data.name);
      var node = svg_#{id}.selectAll("circle,text");
      function zoom(d) {
        var focus0 = focus; focus = d;
        var transition = d3.transition()
            .duration(d3.event.altKey ? 7500 : 750)
            .tween("zoom", function(d) {
              var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
              return (t) => zoomTo(i(t));
            });
        transition.selectAll("text")
          .filter(function(d) { return d && d.parent === focus || this.style.display === "inline"; })
            .attr("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
            .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
            .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
      }
      function zoomTo(v) {
        var k = diameter / v[2]; view = v;
        node.attr("transform", (d) => "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")");
        circle.attr("r", (d) => d.r * k);
      }
      zoomTo([root.x, root.y, root.r * 2]);
      d3.select("##{id}").on("click", () => zoom(root));
    }
    redraw();
    window.addEventListener("resize", redraw);
  JS
end