viewof boxplotChart = {
const margin = { top: 80, right: 40, bottom: 80, left: 100 };
const width = 700;
const height = 300;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background", "#fff")
.style("font-family", "'Helvetica Neue', Georgia, serif")
.style("font-size", "13px");
const chart = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const spiValues = data
.map(d => +d.SPI)
.filter(d => !isNaN(d))
.sort(d3.ascending);
const q1 = d3.quantile(spiValues, 0.25);
const median = d3.quantile(spiValues, 0.5);
const q3 = d3.quantile(spiValues, 0.75);
const min = d3.min(spiValues);
const max = d3.max(spiValues);
const x = d3.scaleLinear()
.domain([min - 5, max + 5])
.range([0, innerWidth]);
chart.append("g")
.attr("transform", `translate(0,${innerHeight / 2 + 20})`)
.call(d3.axisBottom(x).ticks(6));
chart.append("rect")
.attr("x", x(q1))
.attr("y", innerHeight / 2 - 20)
.attr("width", x(q3) - x(q1))
.attr("height", 40)
.attr("fill", "#007BC7")
.attr("opacity", 0.2)
.attr("stroke", "#007BC7");
chart.append("line")
.attr("x1", x(median))
.attr("x2", x(median))
.attr("y1", innerHeight / 2 - 20)
.attr("y2", innerHeight / 2 + 20)
.attr("stroke", "#007BC7")
.attr("stroke-width", 2);
chart.append("line")
.attr("x1", x(min))
.attr("x2", x(q1))
.attr("y1", innerHeight / 2)
.attr("y2", innerHeight / 2)
.attr("stroke", "#aaa");
chart.append("line")
.attr("x1", x(q3))
.attr("x2", x(max))
.attr("y1", innerHeight / 2)
.attr("y2", innerHeight / 2)
.attr("stroke", "#aaa");
chart.selectAll("line.whisker")
.data([min, max])
.join("line")
.attr("x1", d => x(d))
.attr("x2", d => x(d))
.attr("y1", innerHeight / 2 - 10)
.attr("y2", innerHeight / 2 + 10)
.attr("stroke", "#aaa");
chart.selectAll("text.stats")
.data([
{ label: "Min", value: min },
{ label: "Q1", value: q1 },
{ label: "Median", value: median },
{ label: "Q3", value: q3 },
{ label: "Max", value: max }
])
.join("text")
.attr("x", d => x(d.value))
.attr("y", innerHeight / 2 - 30)
.attr("text-anchor", "middle")
.attr("fill", "#333")
.text(d => `${d.label}: ${d.value != null ? d.value.toFixed(1) : "N/A"}`);
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.attr("font-weight", "bold")
.text("SPI Distribution for Premier League Teams");
svg.append("text")
.attr("x", width / 2)
.attr("y", 50)
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.attr("fill", "#666")
.text("Box plot of Soccer Power Index (SPI) from FiveThirtyEight predictions");
svg.append("text")
.attr("x", width - 10)
.attr("y", height - 10)
.attr("text-anchor", "end")
.attr("font-size", "11px")
.attr("fill", "#666")
.text("Source: FiveThirtyEight | Chart: You");
return svg.node();
}