class Spoom::Coverage::D3::CircleMap
Public Class Methods
header_script()
click to toggle source
# File lib/spoom/coverage/d3/circle_map.rb, line 39 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
header_style()
click to toggle source
# File lib/spoom/coverage/d3/circle_map.rb, line 13 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
Public Instance Methods
script()
click to toggle source
# File lib/spoom/coverage/d3/circle_map.rb, line 57 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