Published
Edited
Sep 5, 2021
14 forks
Importers
53 stars
Insert cell
Insert cell
clock = {
const clockRadius = 200,
margin = 50,
w = (clockRadius + margin) * 2,
h = (clockRadius + margin) * 2,
hourHandLength = (2 * clockRadius) / 3,
minuteHandLength = clockRadius,
secondHandLength = clockRadius - 12,
secondHandBalance = 30,
secondTickStart = clockRadius,
secondTickLength = -10,
hourTickStart = clockRadius,
hourTickLength = -18,
secondLabelRadius = clockRadius + 16,
secondLabelYOffset = 5,
hourLabelRadius = clockRadius - 40,
hourLabelYOffset = 7,
radians = Math.PI / 180;

const twelve = d3
.scaleLinear()
.range([0, 360])
.domain([0, 12]);

const sixty = d3
.scaleLinear()
.range([0, 360])
.domain([0, 60]);

const handData = [
{
type: "hour",
value: 0,
length: -hourHandLength,
scale: twelve
},
{
type: "minute",
value: 0,
length: -minuteHandLength,
scale: sixty
},
{
type: "second",
value: 0,
length: -secondHandLength,
scale: sixty,
balance: secondHandBalance
}
];

function drawClock() {
// create all the clock elements
updateData(); //draw them in the correct starting position
const face = svg
.append("g")
.attr("id", "clock-face")
.attr("transform", `translate(${[w / 2, h / 2]})`);

// add marks for seconds
face
.selectAll(".second-tick")
.data(d3.range(0, 60))
.enter()
.append("line")
.attr("class", "second-tick")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", secondTickStart)
.attr("y2", secondTickStart + secondTickLength)
.attr("transform", d => `rotate(${sixty(d)})`);

// and labels...
face
.selectAll(".second-label")
.data(d3.range(5, 61, 5))
.enter()
.append("text")
.attr("class", "second-label")
.attr("text-anchor", "middle")
.attr("x", d => secondLabelRadius * Math.sin(sixty(d) * radians))
.attr(
"y",
d =>
-secondLabelRadius * Math.cos(sixty(d) * radians) + secondLabelYOffset
)
.text(d => d);

// ... and hours
face
.selectAll(".hour-tick")
.data(d3.range(0, 12))
.enter()
.append("line")
.attr("class", "hour-tick")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", hourTickStart)
.attr("y2", hourTickStart + hourTickLength)
.attr("transform", d => `rotate(${twelve(d)})`);

face
.selectAll(".hour-label")
.data(d3.range(3, 13, 3))
.enter()
.append("text")
.attr("class", "hour-label")
.attr("text-anchor", "middle")
.attr("x", d => hourLabelRadius * Math.sin(twelve(d) * radians))
.attr(
"y",
d => -hourLabelRadius * Math.cos(twelve(d) * radians) + hourLabelYOffset
)
.text(d => d);

const hands = face.append("g").attr("id", "clock-hands");

hands
.selectAll("line")
.data(handData)
.enter()
.append("line")
.attr("class", d => d.type + "-hand")
.attr("x1", 0)
.attr("y1", d => d.balance || 0)
.attr("x2", 0)
.attr("y2", d => d.length)
.attr("transform", d => `rotate(${d.scale(d.value)})`);

face
.append("g")
.attr("id", "face-overlay")
.append("circle")
.attr("class", "hands-cover")
.attr("x", 0)
.attr("y", 0)
.attr("r", clockRadius / 20);
}

function moveHands() {
d3.select("#clock-hands")
.selectAll("line")
.data(handData)
.transition()
.ease(d3.easeElastic.period(0.5))
.attr("transform", d => `rotate(${d.scale(d.value)})`);
}

function updateData() {
const t = new Date();
handData[0].value = (t.getHours() % 12) + t.getMinutes() / 60;
handData[1].value = t.getMinutes();
handData[2].value = t.getSeconds();
}

const svg = d3
.create("svg")
.attr("viewBox", [0, 0, w, h])
.style("max-width", "500px")
.attr("id", "clock");

drawClock();

// Animation
const interval = setInterval(() => {
updateData();
moveHands();
}, 1000);
invalidation.then(() => clearInterval(interval));

return svg.node();
}
Insert cell
Insert cell
style = html`<style>

#clock {
stroke: #000;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}

#clock #rim {
fill: none;
stroke: #999;
stroke-width: 3px;
}

#clock .second-hand {
stroke-width:3;
}

#clock .minute-hand {
stroke-width:8;
stroke-linecap:round;
}

#clock .hour-hand {
stroke-width:12;
stroke-linecap:round;
}

#clock .hands-cover {
stroke-width:3;
fill:#fff;
}

#clock .second-tick {
stroke-width:3;
fill:#000;
}

#clock .hour-tick {
stroke-width:8; // same as the minute hand
}

#clock .second-label {
font-size: 12px;
}

#clock .hour-label {
font-size: 24px;
}

</style>`
Insert cell
d3 = require("d3@7")
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