
Obsidian 12WY Review Template

It's the 13th week of the year… I'm supposed to use this time to consider my goals, review my intentions, and plan the next twelve weeks.
Instead, I've been staying up till midnight automating the Quarterly template. 🤷
I've been using this template for quite some time… but each quarter required some setup. I had to choose a color, set the start week, and (if required) update the year everywhere it appeared in the code. No more!
Here's the whole thing in a gist, but let's walk through it!
Let's make sure you have all the dependency plugins:
- Periodic Notes to handle quarterly and weekly notes.
- Templater to insert the template.
- Dataview to do data magic with YAML frontmatter.
- Obsidian Charts for visualizations.
Periodic Notes is configured to enable quarterly notes and weekly notes.
The paths for those notes are /journal/quarterly
and journal/weekly
.
You could put them anywhere, but you'd have to rewrite some of the
following dataviewjs.
Now let's look at the "bones" of our template in markdown, named {year}-Q{number}
.
---
# rate your intention from 1 to 5 in the following roles…
roles:
- Follower:
- Husband:
- Father:
- Friends:
- Work:
- Hobbies:
- Emotions:
- Mental:
- Health:
- Rest:
- Finances:
---
# {{date:YYYY}}-Q{{date:Q}}
## Plan
### Goals
#### Goal 1: _Goal_
#### Goal 2: _Goal_
## Review
### How do you rate your intentions in each area of life?
( dataview visualization of intentions over quarters )
### Scoreboard
( dataview visualization of accomplishing 12WY goals in Weekly notes )
You can remove and add roles
from the YAML frontmatter if you want, they are automatically pulled into the visualizations.
Let's look at that first visualization, in a dataviewjs
block:
All s/1
does is convert the string representing a quarter into a number: s("2023-Q1") = 20231
which can be easily sorted1… by the next function. Unfortunately when I first wrote the compare function, I used <
instead of -
and got very unpredictable results. The compare for a sort is supposed to return an integer, not a boolean.
// we have to sort somehow, so we convert the strings to numbers and
// sort the numbers
const s = function(i) {
return Number(i.replace('-Q', ''));
}
const allQuarters =
// get all files in "journal/quarterly"
dv.pages('"journal/quarterly"')
.array()
// ensure they are in sequential order, not last-modified
.sort(function(a, b) {
return s(a.file.name) - s(b.file.name);
})
// find the current file in the list
let currentIndex = allQuarters.findIndex((q) => q.file.name == dv.current().file.name);
// slice the list of all quarters into the current and previous three.
const quarters = allQuarters.slice(currentIndex - 3, currentIndex+1);
Now that we have our data sources, it's time to build the actual visualization!
let datasets = [];
// make an rgba color transparent
let trans = function(color) {
return color.replace(', 1)', ', 0.5)');
}
// declaring some monochrome grays
let colors = [
'rgba(105,105,105, 1)',
'rgba(169,169,169, 1)',
'rgba(211,211,211, 1)',
]
// this purple is the highlight color from the default Obsidian theme.
const purple = 'rgba(139,108,239, 1)';
let highlight = '#fff';
// if we are in light mode, invert the color scheme.
if (document.body.classList.contains('theme-light')) {
colors.reverse();
highlight = '#000';
}
colors.push(purple);
for (let i in quarters) {
let quarter = quarters[i];
if (quarter.hasOwnProperty('roles')) {
// this line gets an array of all the roles you specified in this quarter's YAML.
let values = quarter.roles.map(o => Object.values(o)).flat()
datasets.unshift({
label: quarter.file.name,
data: values,
fill: true,
backgroundColor: trans(colors[i]),
borderColor: colors[i],
pointBackgroundColor: colors[i],
pointBorderColor: highlight,
pointHoverBackgroundColor: highlight,
pointHoverBorderColor: colors[i]
})
}
}
let data = dv.current().roles
let keys = data.map(o => Object.keys(o)).flat()
const chartData = {
type: 'radar',
data: {
labels: keys,
datasets: datasets
},
options: {
scales: {
r: {
suggestedMin: 0,
suggestedMax: 5
}
}
}
}
window.renderChart(chartData, this.container);
Cool!
The second visualization is a simpler table with more gross JS that shouldn't be stared at too long.
I believe the math computing the quarters holds up… but it hurts my head nonetheless.
// quick function to display the results as emoji
let check = function(bool) {
if (bool) { return "✅" };
return "⛔";
}
let quarter = parseInt(dv.current().file.name.match(/Q\d/)[0].replace('Q', ''));
let year = dv.current().file.name.match(/\d{4}/)[0];
// I'm _pretty_ sure this math works out correctly.
// I work on my goals for 12 weeks, plan/rest for one, repeat.
let start = (quarter-1)*13+1
dv.table(
["", "Week", "Goal 1", "Goal 2"], dv.pages('"journal/weekly"')
.filter(function(f) {
let num = f.file.name.match(new RegExp(`${year}-W(\\d+)`));
if (!num) { return false; }
num = parseInt(num[1]);
return num >= start && num < start + 12;
})
.sort(w => w.file.name, 'asc')
.map(w => [(parseInt(w.file.name.match(new RegExp(`${year}-W(\\d+)`))[1]) - start)+1, w.file.link, check(w["Goal1"]), check(w["Goal2"])])
)
The resulting visualization looks like this!
This is nearly identical to the old version. The main change is using new RegExp
instead of the Regex literals to build the regex. By switching to the constructor I can use a literal string to interpolate in the current year so that I don't have to update my template every year.
…
I hope this inspires you to consider some visual accountability for your next quarter's journaling and planning. As for me, now that I've written this post I can get back to planning my next 12WY.
-
To explain this, let me show you a wat. ↩
❯ node Welcome to Node.js v19.6.0. Type ".help" for more information. > quarters = ['2022-Q4', '2023-Q2', '2022-Q3', '2023-Q1'] [ '2022-Q4', '2023-Q2', '2022-Q3', '2023-Q1' ] > quarters.sort((a, b) => a > b) [ '2022-Q4', '2023-Q2', '2022-Q3', '2023-Q1' ] > # wat
Changelog
-
2023-03-31 13:24:26 -0500Fix sort
I've been having all kinds of bugs, turns out the compare function
passed to `sort()` expects an integer as a return, not a boolean. -
2023-03-29 19:01:36 -0500Include details about periodic notes configuration
-
2023-03-28 22:12:28 -0500remove typo
-
2023-03-28 19:34:54 -0500Fix typo
Thank you Mykel.
-
2023-03-28 18:01:15 -0500Publish
-
2023-03-28 18:00:51 -0500Add images and proof
-
2023-03-28 16:21:59 -0500Check in draft of post