# typed: strict # frozen_string_literal: true require_relative "base" module Spoom module Coverage module D3 class Pie < Base extend T::Sig extend T::Helpers abstract! sig { params(id: String, title: String, data: T.untyped).void } def initialize(id, title, data) super(id, data) @title = title end sig { returns(String) } def self.header_style <<~CSS .pie .title { font: 18px Arial, sans-serif; font-weight: bold; fill: #212529; text-anchor: middle; pointer-events: none; } .pie .arc { stroke: #fff; stroke-width: 2px; } CSS end sig { returns(String) } def self.header_script <<~JS function tooltipPie(d, title, kind, sum) { moveTooltip(d) .html("" + title + "

" + "" + d.data.value + " " + kind + "
" + "" + toPercent(d.data.value, sum) + "%") } JS end sig { override.returns(String) } def script <<~JS #{tooltip} var json_#{id} = #{@data.to_json}; var pie_#{id} = d3.pie().value((d) => d.value); var data_#{id} = pie_#{id}(d3.entries(json_#{id})); var sum_#{id} = d3.sum(data_#{id}, (d) => d.data.value); var title_#{id} = #{@title.to_json}; function draw_#{id}() { var pieSize_#{id} = document.getElementById("#{id}").clientWidth - 10; var arcGenerator_#{id} = d3.arc() .innerRadius(pieSize_#{id} / 4) .outerRadius(pieSize_#{id} / 2); d3.select("##{id}").selectAll("*").remove() var svg_#{id} = d3.select("##{id}") .attr("width", pieSize_#{id}) .attr("height", pieSize_#{id}) .attr("class", "pie") .append("g") .attr("transform", "translate(" + pieSize_#{id} / 2 + "," + pieSize_#{id} / 2 + ")"); svg_#{id}.selectAll("arcs") .data(data_#{id}) .enter() .append('path') .attr("class", "arc") .attr('fill', (d) => strictnessColor(d.data.key)) .attr('d', arcGenerator_#{id}) .on("mouseover", (d) => tooltip.style("opacity", 1)) .on("mousemove", tooltip_#{id}) .on("mouseleave", (d) => tooltip.style("opacity", 0)); svg_#{id}.selectAll("labels") .data(data_#{id}) .enter() .append('text') .attr("class", "label") .attr("transform", (d) => "translate(" + arcGenerator_#{id}.centroid(d) + ")") .filter(d => (d.endAngle - d.startAngle) > 0.25) .append("tspan") .attr("x", 0) .attr("y", -3) .text((d) => d.data.value) .append("tspan") .attr("class", "small") .attr("x", 0) .attr("y", 13) .text((d) => toPercent(d.data.value, sum_#{id}) + "%"); svg_#{id} .append("text") .attr("class", "title") .append("tspan") .attr("y", 7) .text(title_#{id}); } draw_#{id}(); window.addEventListener("resize", draw_#{id}); JS end class Sigils < Pie extend T::Sig sig { params(id: String, title: String, snapshot: Snapshot).void } def initialize(id, title, snapshot) super(id, title, snapshot.sigils.select { |_k, v| v }) end sig { override.returns(String) } def tooltip <<~JS function tooltip_#{id}(d) { tooltipPie(d, "typed: " + d.data.key, "files", sum_#{id}); } JS end end class Calls < Pie extend T::Sig sig { params(id: String, title: String, snapshot: Snapshot).void } def initialize(id, title, snapshot) super(id, title, { true: snapshot.calls_typed, false: snapshot.calls_untyped }) end sig { override.returns(String) } def tooltip <<~JS function tooltip_#{id}(d) { tooltipPie(d, d.data.key == "true" ? " checked" : " unchecked", "calls", sum_#{id}) } JS end end class Sigs < Pie extend T::Sig sig { params(id: String, title: String, snapshot: Snapshot).void } def initialize(id, title, snapshot) super(id, title, { true: snapshot.methods_with_sig, false: snapshot.methods_without_sig }) end sig { override.returns(String) } def tooltip <<~JS function tooltip_#{id}(d) { tooltipPie(d, (d.data.key == "true" ? " with" : " without") + " a signature", "methods", sum_#{id}) } JS end end end end end end