chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
svg
.append("g")
.call(xAxis)
.call(g => g.selectAll('.domain').remove());
svg
.append("g")
.call(yAxis)
.call(g => g.selectAll('.domain').remove());
svg.append("g").call(grid);
let voronoi = d3.Delaunay.from(
data,
d => xScale(xAccessor(d)),
d => yScale(yAccessor(d))
).voronoi([
margin.left,
margin.top,
width - margin.right,
height - margin.bottom
]);
const mesh = svg
.selectAll(".voronoi")
.data(data)
.join("path")
.attr("fill", "transparent")
.attr("stroke", "transparent")
.attr("class", "voronoi")
.attr("d", (d, i) => voronoi.renderCell(i));
svg
.append("rect")
.attr("x", margin.left)
.attr("y", yScale(4500))
.attr("height", yScale(3500) - margin.bottom)
.attr("width", width - margin.left - margin.right)
.attr("fill", d3.schemeCategory10[2])
.attr("opacity", 0.3)
.style("pointer-events", "none");
svg
.append("rect")
.attr("x", xScale(500))
.attr("y", margin.top)
.attr("height", height - margin.bottom - margin.top)
.attr("width", xScale(300) - margin.left)
.attr("fill", d3.schemeCategory10[0])
.attr("opacity", 0.3)
.style("pointer-events", "none");
svg
.append("rect")
.attr("x", xScale(0))
.attr("y", margin.top + yScale(3500) - margin.bottom)
.attr("height", height - margin.bottom - yScale(3500))
.attr("width", xScale(500) - margin.left)
.attr("fill", d3.schemeCategory10[1])
.attr("opacity", 0.3)
.style("pointer-events", "none");
const xLabel = svg
.append('text')
.attr('x', width - margin.right - 34)
.attr('y', height - margin.bottom - 2)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.style("font-size", "10px")
.html('Home Runs →');
const yLabel = svg
.append('text')
.attr('x', margin.left + 20)
.attr('y', margin.top + 10)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.style("font-size", "10px")
.html('Hits ↑');
const dots = svg
.selectAll("circle")
.append("g")
.data(data)
.join("circle")
.attr("cx", d => xScale(xAccessor(d)))
.attr("cy", d => yScale(yAccessor(d)))
.attr("r", radius)
.attr("fill", function(d) {
if (d.hits < 3500 && d.HRs < 500) {
return 'rgba(0.2, 0.2, 0.2, 0.4)';
} else {
return 'black';
}
})
.style("pointer-events", "none");
mesh.on('mouseenter', mouseEnter).on('mouseleave', mouseLeave);
const tooltip = svg
.append('g')
.attr('visibility', 'hidden')
.attr("pointer-events", "none");
const tooltipHeight = 20;
const tooltipRect = tooltip
.append('rect')
.attr('fill', '#f5f5f5')
.attr("stroke", "black")
.attr("x", 8)
.attr('height', tooltipHeight);
const tooltipText = tooltip
.append('text')
.attr('color', 'black')
.attr('font-family', 'sans-serif')
.attr('font-size', 10)
.attr('dy', 5)
.attr('dx', 10)
.attr('dominant-baseline', 'hanging')
.attr("text-anchor", "right");
// handle hovering over a circle
function mouseEnter(event, d) {
// make the circle larger
d3.select(this).attr('r', radius * 2);
// update the text and get its width
tooltipText.text(d.name);
// tooltipText.append("xhtml:body").html(`<p>Test</p>`);
const labelWidth = tooltipText.node().getComputedTextLength();
// set the width of the tooltip's background rectangle
// to match the width of the label
tooltipRect.attr('width', labelWidth + 6);
// move the tooltip and make it visible
const xPos = xScale(xAccessor(d)) - radius * 3;
const yPos = yScale(yAccessor(d)) - tooltipHeight;
tooltip
.attr('transform', `translate(${xPos},${yPos})`)
.attr('visibility', 'visible');
}
// handle leaving a circle
function mouseLeave(event, d) {
// reset its size
d3.select(this).attr('r', radius);
// make the tooltip invisible
tooltip.attr('visibility', 'hidden');
}
const annotationLabel = svg
.append("g")
.attr("class", "annotation-group")
.attr("pointer-events", "none")
.call(makeAnnotations);
svg
.append("text")
.text("Hank Aaron")
.attr("x", xScale(755) - 120)
.attr("y", yScale(3771) - 55)
.attr("fill", "black")
.attr("pointer-events", "none");
return svg.node();
}