Public
Edited
Mar 14, 2024
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
calendar = {
const context = DOM.context2d(canvasWidth, canvasHeight);
context.font = "300 10px 'Roboto Condensed'";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "white";
context.fillRect(0, 0, canvasWidth, canvasHeight);

// Outer rectangle
context.lineWidth = 2;
context.strokeStyle = "black";
context.strokeRect(margin.left, margin.top, calendarWidth, calendarHeight);

// Vertical month dividers and labels
context.fillStyle = "black";
context.lineWidth = 2;
months.forEach((month) => {
const x = xPosition(month.start) - cellWidth;
const y = margin.top;
context.beginPath();
context.moveTo(x, y);
context.lineTo(x, y + calendarHeight);
context.stroke();
const x2 = xPosition(month.end) + cellWidth;
context.fillText(month.name, (x + x2) / 2, margin.top - cellHeight / 2);
});

// Extra divider before the year label
const yearLabelPosition = xPosition(middleOfYear) + cellWidth;
context.lineWidth = 2;
context.beginPath();
context.moveTo(yearLabelPosition, margin.top);
context.lineTo(yearLabelPosition, margin.top + calendarHeight);
context.stroke();

// One row for each year
years.forEach((year) => {
const yearLabel = year[0].getFullYear();
const y = yPosition(yearLabel);
context.lineWidth = yearLabel % 10 === 1 ? 2 : 1;
context.beginPath();
context.moveTo(margin.left, y - cellHeight / 2);
context.lineTo(margin.left + calendarWidth, y - cellHeight / 2);
context.stroke();
context.fillText(
yearLabel,
yearLabelPosition + yearCellWidth / 2 - 0.5,
y + 1.5
);

year.forEach((day) => {
const x = xPosition(day);
if (useDots && day.getDay() === 0) {
// On Sundays, draw dot instead of number
context.beginPath();
context.arc(x, y, 2, 0, 2 * Math.PI, false);
context.fillStyle = "black";
context.fill();
} else {
// Day of month (with slight offsets to keep it in the middle)
context.fillStyle = "black";
context.fillText(day.getDate(), x - 0.5, y + 1.5);
}
});
});

return html`<div style="max-width: ${width}px; max-height: ${screen.height}px; overflow: scroll">${context.canvas}</div>`;
}
Insert cell
minYear = 1801
Insert cell
numYears = 100
Insert cell
endYear = startYear + numYears
Insert cell
years = d3.range(startYear, endYear).map((year) => {
const startDay = new Date(year, 0, 1);
const endDay = new Date(year + 1, 0, 1);
return d3.timeDay.range(startDay, endDay);
})
Insert cell
today = new Date()
Insert cell
daysInLeapYear = 366
Insert cell
cellWidth = 14
Insert cell
cellHeight = 28
Insert cell
yearCellWidth = 42
Insert cell
calendarWidth = daysInLeapYear * cellWidth +
months.length * cellWidth +
yearCellWidth
Insert cell
calendarHeight = numYears * cellHeight
Insert cell
canvasWidth = margin.left + margin.right + calendarWidth
Insert cell
canvasHeight = margin.top + margin.bottom + calendarHeight
Insert cell
leapYear = 2000 // used to get consistent day-of-month positions
Insert cell
middleOfYear = new Date(leapYear, 5, 30)
Insert cell
months = d3.timeMonth
.range(new Date(leapYear, 0, 1), new Date(leapYear + 1, 0, 1))
.map((d) => ({
start: d,
end: d3.timeDay.offset(d3.timeMonth.offset(d, 1), -1),
name: d3.timeFormat("%B")(d)
}))
Insert cell
margin = ({ top: 50, right: 30, bottom: 30, left: 30 })
Insert cell
// Horizontal position of this date if it was in a leap year, with extra padding at start and end of each month
xPosition = function (date) {
const day = new Date(date.getTime());
day.setFullYear(leapYear);
const dayOfYear = d3.timeDay.count(d3.timeYear(day), day);
const position =
margin.left + dayOfYear * cellWidth + (day.getMonth() + 1) * cellWidth;
return day <= middleOfYear ? position : position + yearCellWidth;
}
Insert cell
yPosition = function (year) {
return margin.top + (year - startYear) * cellHeight + cellHeight / 2;
}
Insert cell
html`
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300&display=swap');
</style>
`
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