Skip to content

Instantly share code, notes, and snippets.

@DudeThatsErin
Last active August 7, 2024 02:16
Show Gist options
  • Save DudeThatsErin/b088ad960a35d293343abb30bb0187f8 to your computer and use it in GitHub Desktop.
Save DudeThatsErin/b088ad960a35d293343abb30bb0187f8 to your computer and use it in GitHub Desktop.
Erin's Copy of HiroMike's Timeline for Obsidian Daily Note

If you found this, you are probably wanting to add a timeline to your daily note that looks similar to the image below. Screenshot 2024-08-06 at 8 55 44 PM

The one above, is the version that is included in my daily note. For some reason it doesn't render well when created on mobile. So, as long as you create your files on desktop first, it will render perfectly.

All credit goes to HiroMike for originally creating this. I just changed the dates and I'm using an older version because I prefer the way it looks and functions. https://gist.github.com/ms3056/ebb6a7e175dcfd47f414a70addbd18c7

async function generateTimelineSvg() {
// Edit these variables
// Gap between Months
const gap = 10;
// Height of the Quarter Bar
const quarterRowHeight = 85;
// Height of the Row Bar
const monthRowHeight = 65;
// Gap between Quarter and Row Bars
const gapBetweenRows = 10;
// Height of the text
const textHeight = 60;
// Vertical position of the userDates
const quarterRowYPosition = 60;
// Bar radii
const barRadaii = 12;
// Follow this pattern - as few or as many as you like. Text can be what you want - like the day number or name
// The variable verticalPosition is an adjustment on where you want the icon and text to show
// The size variable is the font size for that event
// Dates are in MONTH-DAY format, so 03-10 is March 10
const userDates = [
{ date: "02-02", symbol: "🎂", text: "", verticalPosition: -106, size: 80 },
{ date: "03-29", symbol: "💍", text: "", verticalPosition: -106, size: 80 },
{ date: "06-08", symbol: "🎂", text: "", verticalPosition: -106, size: 80 },
{ date: "12-25", symbol: "🎄", text: "", verticalPosition: -106, size: 100 },
];
// Font size of the today date text
const todayTextSize = 60;
// Size of the today symbol marker
const todaySymbolSize = 30;
// Padding between today marker and text
const todayPadding = 6;
// Size of the background of the
const todayBackgroundWidth = 240;
// Don't change anything from here unless you know what you are doing
const year = new Date().getFullYear();
const isLeapYear = (year) =>
year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
const daysInMonth = [
31,
isLeapYear(year) ? 29 : 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
const monthColors = [
"#6AA9FF",
"#3F85FF",
"#6AB04A",
"#B4E051",
"#F8E352",
"#FFC30F",
"#FF7F27",
"#FF4C3B",
"#DAA520",
"#6A5ACD",
"#483D8B",
"#5F9EA0",
];
const monthNames = moment.monthsShort();
const monthRowYPosition =
quarterRowYPosition + quarterRowHeight + gapBetweenRows;
const calculateDayOfYear = (dateStr) => {
const [month, day] = dateStr.split("-").map(Number);
const isLeap = year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
// Adjust for non-leap year if date is Feb 29
if (!isLeap && month === 2 && day === 29) {
dateStr = "2-28";
}
// Continue with day of year calculation
const newDate = new Date(`${year}-${dateStr}`);
const start = new Date(newDate.getFullYear(), 0, 0);
const diff = newDate - start;
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
return dayOfYear;
};
// Unified method to calculate xPosition for any date
const calculateXPosition = (dateStr) => {
const dayOfYear = calculateDayOfYear(dateStr);
const monthIndex = parseInt(dateStr.split("-")[0], 10) - 1; // Convert 'MM' to index
const xPosition = (dayOfYear - 1) * 10 + monthIndex * gap; // Calculate xPosition with day width and gap
return xPosition;
};
let currentX = 0;
let monthPositions = [],
quarterPositions = [],
quarterWidths = [];
daysInMonth.forEach((days, index) => {
monthPositions.push({ x: currentX, width: days * 10 });
currentX += days * 10 + gap;
if ((index + 1) % 3 === 0) {
let quarterWidth =
daysInMonth
.slice(index - 2, index + 1)
.reduce((acc, curr) => acc + curr, 0) *
10 +
2 * gap;
quarterPositions.push({
x: monthPositions[index - 2].x,
width: quarterWidth,
});
}
});
const userDatesPositions = userDates.map((date) => {
const xPosition = calculateXPosition(date.date);
const yPosition =
monthRowYPosition +
monthRowHeight +
gapBetweenRows +
date.verticalPosition;
return { ...date, x: xPosition, y: yPosition };
});
// Define gapCenterYPosition correctly
const gapCenterYPosition =
quarterRowYPosition + quarterRowHeight + gapBetweenRows / 2;
const today = new Date();
// Use for display
const todayFormatted = moment(today).format("D-MMM");
// Convert 'today' to 'MM-DD' format for calculation purposes
const todayCalculationFormat = `${today.getMonth() + 1}-${today.getDate()}`;
// Calculate xPosition using a function compatible with 'MM-DD' format
const todayXPosition = calculateXPosition(todayCalculationFormat);
const todayTextXPosition = todayXPosition - todaySymbolSize - 36;
const todayTextYPosition =
quarterRowYPosition +
quarterRowHeight +
gapBetweenRows / 2 +
todaySymbolSize -
10;
const todayBackgroundHeight = todayTextSize + todayPadding * 2;
const todayBackgroundY = todayTextYPosition - todayTextSize + todayPadding;
let quarterGradients = quarterPositions
.map((q, i) => {
const startMonthIndex = i * 3;
return `
<linearGradient id="Q${i + 1}Gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:${monthColors[startMonthIndex]}" />
<stop offset="50%" style="stop-color:${monthColors[startMonthIndex + 1]}" />
<stop offset="100%" style="stop-color:${monthColors[startMonthIndex + 2]}" />
</linearGradient>
`;
})
.join("");
let filterDefinition = `
<filter id="brightness" x="0" y="0" width="100%" height="100%">
<feColorMatrix type="matrix" values="0.4 0 0 0 0
0 0.4 0 0 0
0 0 0.4 0 0
0 0 0 1 0" />
</filter>
<filter id="dropShadow" height="130%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="2" dy="2" result="offsetblur"/>
<feMerge>
<feMergeNode in="offsetblur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
`;
// Generate the SVG content with dynamic sizing and positioning
let svgContent = `
<svg viewBox="0 -100 ${currentX} 400" xmlns="http://www.w3.org/2000/svg">
<title>Dynamic Timeline ${year}</title>
<defs>
${filterDefinition}
${quarterGradients}
</defs>
<g filter="url(#brightness)">
${quarterPositions
.map(
(q, i) => `
<rect x="${q.x}" y="${quarterRowYPosition}" width="${q.width}" height="${quarterRowHeight}" fill="url(#Q${i + 1}Gradient)" rx="${barRadaii}" ry="${barRadaii}" />
<text x="${q.x + q.width / 2}" y="${quarterRowYPosition - 40}" fill="white" font-size="${textHeight}" text-anchor="middle">Q${i + 1}</text>
`
)
.join("")}
${monthPositions
.map(
(m, i) => `
<rect x="${m.x}" y="${monthRowYPosition}" width="${m.width}" height="${monthRowHeight}" fill="${monthColors[i]}" rx="${barRadaii}" ry="${barRadaii}" />
<text x="${m.x + m.width / 2}" y="${monthRowYPosition + monthRowHeight + 70}" fill="white" font-size="${textHeight}" text-anchor="middle">${monthNames[i]}</text>
`
)
.join("")}
</g>
<g>
${userDatesPositions
.map(
(date) => `
<text filter="url(#dropShadow)" x="${date.x}" y="${date.y}" fill="white" font-size="${date.size}" text-anchor="middle">${date.symbol}${date.text}</text>
`
)
.join("")}
<circle filter="url(#dropShadow)" cx="${todayXPosition}" cy="${gapCenterYPosition}" r="${todaySymbolSize}" stroke="white" fill="#FF00FF" stroke-width="5" />
<rect x="${todayTextXPosition - todayBackgroundWidth + todayPadding * 2 + 6}" y="${todayBackgroundY}" width="${todayBackgroundWidth}" height="${todayBackgroundHeight}" stroke-width="5" stroke="#FF00FF" fill="var(--background-primary)" rx="10" ry="10" />
<text filter="url(#dropShadow)" x="${todayTextXPosition}" y="${todayTextYPosition}" fill="white" font-size="${todayTextSize}" text-anchor="end">${todayFormatted}</text>
</g>
</svg>
`;
return svgContent;
}
module.exports = generateTimelineSvg;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment