Skip to content

Instantly share code, notes, and snippets.

@nuvic
Last active June 1, 2022 09:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nuvic/8aa80d458d853a7abb9cac3f5016cb8f to your computer and use it in GitHub Desktop.
Save nuvic/8aa80d458d853a7abb9cac3f5016cb8f to your computer and use it in GitHub Desktop.
obsidian.md habit counter with streak and total count
* START Initialize constants
	The values from here to (END intialization of constants)
	need to be updated according to your system.
*/

const habit_names = ['habit_1', 'habit_2', 'habit_3'];

// How you designate habit completion in your daily frontmatter
const done = 1;
const missed = 0;

// How you want your habit records to show up in this table
const done_sym = '✅';
const missed_sym = '✖️';

// Dates that you want the table to show
const start = DateTime.fromISO("2022-01-01");
const end = DateTime.fromISO("2022-12-31");

// Location of your daily journals
const daily_journal_loc = '"Journal/2022"';

/* END initialization of constants */


/* START initialize functions */
function initHabits(habit_names) {
	let habits = {};
	habit_names.forEach((h) => {
		habits[h] = {
			name: h,
			history: [],
			streak: 0,
			total: 0
		}
	})
	return habits;
}

// Used to get the records for each habit per date and transform them using processBinaryHabit
function get_values_from_keys(obj, keys) {
	return keys.map(k => processBinaryHabit(obj?.[k]));
}

// Transforms the habits in the frontmatter from 1 -> done, and 0 -> missed
function processBinaryHabit(habit) {
	switch (habit) {
		case 1:
			return done_sym;
		case 0:
			return missed_sym;
		default:
			return '';
	}
}

// Determines if the journal entry is within the specified date range
function inRange(start, end, date) {
	return (start <= date && end >= date) ? true : false;
}


function countStreak(habit_arr) {
	let streak = 0;
	for (let i=0; i<=habit_arr.length; i++) {
		if (habit_arr[i] == missed) {
			break;
		} else if (habit_arr[i] == done) {
			streak++;
		}
	}
	return streak;
}

// Used for totalling the count
function add_count_reducer(prev, curr) {
	if (curr == done) {
		return prev + 1;
	}
	return prev;
}

/* END initialization of functions */


/* START Calculate other values */

const habits = initHabits(habit_names);

// Headings of the table
const heading = ['Date', ...habit_names];

const pages_for_daily_habits = dv.pages(daily_journal_loc)
		.sort(d => d.file.day, 'desc');

// All your habits where each row in `values` represents the records per date
// These are the dates shown in the table
const habit_records_per_date = pages_for_daily_habits
			.where(d => inRange(start, end, d.file.day))
			.map(d => [
				d.file.link,
				...get_values_from_keys(d.habits, habit_names)
			])

/* Create the individual habits' histories */
pages_for_daily_habits.values.forEach((page) => {
	// used to skip the first row if you haven't filled out anything
	// for the current date
	if (Object.values(page.habits).every(el => el == null)) {
		return;
	}

	habit_names.forEach((h) => {
		habits[h].history.push(page.habits[h])
	})
})

habit_names.forEach((h) => {
	/* Get the current streak count for each habit */
	habits[h].streak = countStreak(habits[h].history);

	/* Get the total count for each habit */
	habits[h].total = habits[h].history.reduce(add_count_reducer, 0);
})


// Row for streaks of each habit
const streak_row = [
	"**Streak**", ...habit_names.map(h => habits[h].streak)
]

// Row for total count for each habit
const total_row = [
	"**Total**", ...habit_names.map(h => habits[h].total)
]

/* END calculating other values */


/* Create the table that is shown */
dv.table(
	heading,
	[
		streak_row,
		total_row,
		// The record of done/missed per habit per date
		...habit_records_per_date.values
	]
);
@nuvic
Copy link
Author

nuvic commented Jan 11, 2022

I made a snippet to track my daily habits along with a streak and total count.

obsidian_habits

To make this work, each daily journal entry needs frontmatter that looks like this:

---
habits:
  habit_1:
  habit_2:
  habit_3:
---

To use this frontmatter everyday, you can enable the Templates core plugin in Settings > Core plugins > Templates.

Then go to the options in Settings > Plugin options > Templates, and choose your folder location. In that folder, create a file called Daily_note where you will add the above frontmatter.
Then in Settings > Plugin options > Daily notes, update your Template File Location to the above your_template_folder/Daily_note.md. Now whenever you create a daily note, it will contain that frontmatter.

To see the table view of all your habits like in the first image, you can copy this snippet 5. You need to get the raw snippet, so if you’re not familiar with how that works, just use this link 2

In that snippet, the first section tells you what you need to change in order to match your system. Most important, habit_names have to match the exact names of the habits in your frontmatter. And daily_journal_loc needs to point to the folder where your journals are located.

Hope this helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment