Public
Edited
Jan 14
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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();
}
Insert cell
html`<style>
.annotation-group text {
font-size: 24px;
dx: -100px;
}
</style>`
Insert cell
d3.schemeCategory10[0]
Insert cell
Insert cell
data = d3.csvParse(
await FileAttachment('hits_homerun_dataset.csv').text(),
d3.autoType
)
Insert cell
hankStats = data
.filter(d => d.name == 'Hank Aaron')
.map(d => {
return { hits: d.hits, homers: d.HRs };
})
Insert cell
Insert cell
hankStats[0].homers
Insert cell
Insert cell
height = 600
Insert cell
width = 800
Insert cell
margin = ({ top: 20, right: 60, bottom: 20, left: 60 })
Insert cell
radius = 3
Insert cell
Insert cell
xAccessor = d => d.HRs
Insert cell
yAccessor = d => d.hits
Insert cell
colorAccessor = d => d.HRs
Insert cell
Insert cell
xScale = d3
.scaleLinear()
.domain(d3.extent(data, xAccessor))
.nice()
.range([margin.left, width - margin.right])
Insert cell
yScale = d3
.scaleLinear()
.domain(d3.extent(data, yAccessor))
.nice()
.range([height - margin.bottom, margin.top])
Insert cell
colorScale = d3
.scaleLinear()
.domain(d3.extent(data, colorAccessor))
.range(d3.schemeBlues)
Insert cell
Insert cell
xAxis = g =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale).ticks(width / 80))
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(data.x)
)
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale))
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(data.y)
)
Insert cell
Insert cell
grid = g =>
g
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1)
.call(g =>
g
.append("g")
.selectAll("line")
.data(xScale.ticks())
.join("line")
.attr("x1", d => 0.5 + xScale(d))
.attr("x2", d => 0.5 + xScale(d))
.attr("y1", margin.top)
.attr("y2", height - margin.bottom)
)
.call(g =>
g
.append("g")
.selectAll("line")
.data(yScale.ticks())
.join("line")
.attr("y1", d => 0.5 + yScale(d))
.attr("y2", d => 0.5 + yScale(d))
.attr("x1", margin.left)
.attr("x2", width - margin.right)
)
Insert cell
Insert cell
delaunay = d3.Delaunay.from(data)
Insert cell
Insert cell
annotations = [
{
type: d3_annotation.annotationCalloutCircle,
note: {
// label: "A true legend",
// title: "Hank Aaron",
wrap: 300
},
//settings for the subject, in this case the circle radius
subject: {
radius: 20
},
x: xScale(755),
y: yScale(3771),
dy: -50,
dx: -80
}
].map(function(d) {
d.color = "black";
return d;
})
Insert cell
makeAnnotations = d3_annotation
.annotation()
.type(d3_annotation.annotationLabel)
.annotations(annotations)
Insert cell
Insert cell
d3_annotation = require("https://cdnjs.cloudflare.com/ajax/libs/d3-annotation/2.3.0/d3-annotation.min.js")
Insert cell
d3_base = require("d3@6")
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more