Published
Edited
Mar 19, 2019
4 stars
Insert cell
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height));
svg.append("g")
.call(xAxis);
svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.selectAll("rect")
.data(d3.range(7).filter((_, ix) => ix % 2 === 0))
.enter()
.append('rect')
.attr('y', 0)
.attr("x", n => n * x_.bandwidth())
.attr('width', x_.bandwidth())
.attr('height', height - margin.bottom)
.attr("fill", "#cdf2");

svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.selectAll("rect")
.data(d3.range(8))
.enter()
.append('line')
.attr("y1", 0)
.attr("y2", height - margin.bottom)
.attr("x1", n => n * x_.bandwidth())
.attr("x2", n => n * x_.bandwidth())
.attr("stroke", "#0003")
.attr("stroke-dasharray", "4 4")

svg.append("g")
.selectAll("circle")
.data(dodge(data, radius * 2 + padding))
.enter().append("circle")
.attr("cx", d => d.x)
.attr("cy", d => height / 2 + d.y)
.attr("r", radius)
.append("title")
.text(d => d.data.name);

return svg.node();
}
Insert cell
Insert cell
Insert cell
dodge = (data, radius) => {
const radius2 = radius ** 2;
const circles = data.map(d => ({x: x(d.value), data: d})).sort((a, b) => a.x - b.x);
const epsilon = 1e-4;
let head = null, tail = null;

// Returns true if circle ⟨x,y⟩ intersects with any circle in the queue.
function intersects(x, y) {
let a = head;
while (a) {
if (radius2 - epsilon > (a.x - x) ** 2 + (a.y - y) ** 2) {
return true;
}
a = a.next;
}
return false;
}

// Place each circle sequentially.
for (const b of circles) {

// Remove circles from the queue that can’t intersect the new circle b.
while (head && head.x < b.x - radius2) head = head.next;

// Choose the minimum non-intersecting tangent.
if (intersects(b.x, b.y = 0)) {
let a = head;
b.y = Infinity;
do {
let y1 = a.y + Math.sqrt(radius2 - (a.x - b.x) ** 2);
let y2 = a.y - Math.sqrt(radius2 - (a.x - b.x) ** 2);
if (Math.abs(y1) < Math.abs(b.y) && !intersects(b.x, y1)) b.y = y1;
if (Math.abs(y2) < Math.abs(b.y) && !intersects(b.x, y2)) b.y = y2;
a = a.next;
} while (a);
}

// Add b to the queue.
b.next = null;
if (head === null) head = tail = b;
else tail = tail.next = b;
}

return circles;
}
Insert cell
data = (await d3.json("https://gist.githubusercontent.com/ptrfrncsmrph/367501cad9820252890d9bd88de5dc77/raw/1defdc40f845f912fea81105543802f300b0b318/data.json")).data.reduce((acc, { day, hour, commits }) =>
[...acc, ...Array(commits).fill({ value: (daysOfWeek.findIndex(d => d == day) * 24 + parseHour(hour)) })], []
)
Insert cell
parseHour = hour =>
(([h, ap]) => (+h === 12 ? ap.includes("A") ? 0 : 12 : ap.includes("P") ? +h + 12 : +h))(hour.split(" "))
Insert cell
daysOfWeek = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
]
Insert cell
x = d3.scaleLinear()
.domain(d3.extent(data, d => d.value))
.range([margin.left, width - margin.right])
Insert cell
x_ = d3.scaleBand()
.domain(daysOfWeek)
.range([margin.left, width - margin.right])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x_).tickSizeOuter(0))
Insert cell
height = 480
Insert cell
radius = 2.5
Insert cell
padding = 1
Insert cell
margin = ({top: 20, right: 20, bottom: 30, left: 20})
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more