Digital Bullet Journal Setup (DataviewJS, ChartJS & To-dos!)

Hi! Recently, I’ve really enjoyed the process of making my own little system inside Obsidian. Tested out a bunch of plugins and themes, I’ve stayed with Gruvbox from the Minimal Theme. I tried some setups and found none that really appealed to me, so I decided to make my own.
DataviewJS and ChartJS have been the pillars of my setup, as the sheer amount of possible customization is endless. It’s still a work in progress, but here’s my daily page:


A nice banner, displaying the goals of the week from my weekly page.



My todo list for the day. I display first the tasks that are late.
Then I display todos by urgency level, each having a custom little emoji face.
In terms of architecture : 1 to-do = 1 note in my to-do folder



My habits tracker. (Taking data from the YAML of dailies)
Also tracking health, sleep and productivity.
I had to figure out how to transpose (inverse the rows and columns in the table) to get this view, as the files normally display by row, but the effort was worth it. I really like having the habits tracker perfectly aligned with my mood/sleep/productivity tracker to better see patterns.

15 Likes

I would LOVE to know how you set up your habit tracker, particularly inverting the rows and columns. This has been the main thing I’ve had trouble replicating in moving from a physical do a digital bullet journaling system.

6 Likes

Please publish the code! The overview from daily notes looks amazing!

6 Likes

Yes, I would also like to see the code as that looks like it will solve my daily and habit tracker needs! Please share!

Got inspired by the OP, and just put together a hacky version of the dashboard. This needs Minimal theme, Dataview, and Obsidian Charts.

Here’s the main page:

---
cssclasses:
  - fullwidth
---
'''dataviewjs 

// Get all the DateTimes for days in the current calendar month.
function daysInThisMonth() {
	var num = DateTime.now().daysInMonth
	return Array.from({length: num}, 
	(_, i) => 
		DateTime.fromObject({ 
			year: DateTime.now().year, 
			month: DateTime.now().month, 
			day: i + 1}))
}

function header() {
	return [""].concat(daysInThisMonth().map( d => (d.day).toString() ))
}

// Get an array of Page objects corresponding to the dates, 
// with undefined if the page is missing.
function dataPages(dates) {
	var pages = dv.pages('#daily and -"Templates"')
	return dates.map( d => pages.find(p => p.file.day.equals(d)))
}

// You'd want to do something different/smarter for each row.
function formatEntry(e) {
	switch(e) {  
	  case true:  
	   return "🟩";  
	  case false:  
	   return "🟥";  
	  default:  
	   return ""; 
	}
}

var allData = dataPages(daysInThisMonth())
dv.table(header(), [
	["🍔"].concat(allData.map(d => d ? formatEntry(d["ate-well"]) : "")),
	["🛌"].concat(allData.map(d => d ? formatEntry(d["slept-well"]) : "")),
	["🫀"].concat(allData.map(d => d ? formatEntry(d["exercise-cardio"]) : "")),
])

// Just using fake data for now.
const testData = [8, 8, 8, 8, 8, 7, 7, 6, 6.5, 7, 8, 9]
const testData2 = [5, 7, 3, 6, 9, 8, 8, 5.5, 6.5, 2, 10, 9]
// In reality, you'd add a field (like my-mood) to the daily note and use something like 
// testData = allData.map(d => d ? d["my-mood"] : undefined);

const chartData = { 
	data: { 
		labels:  header().slice(1), 
		datasets: [{ 
			type: 'bar', 
			label: 'Data', 
			data: testData, 
			backgroundColor: [ 'rgba(255, 99, 132, 0.2)' ], 
			borderColor: [ 'rgba(255, 99, 132, 1)' ], 
			borderWidth: 1, 
		},
		{ 
			type: 'bar', 
			label: 'Data 2', 
			data: testData2, 
			backgroundColor: [ 'rgba(99, 255, 132, 0.2)' ], 
			borderColor: [ 'rgba(99, 255, 132, 1)' ], 
			borderWidth: 1, 
		},
		{
            type: 'line',
            label: 'Line Dataset',
            data: [2, 10, 7, 8], // yes, more fake data
            borderWidth: 3, 
            pointBorderWidth: 2,
            pointRadius: 5,
            pointBackgroundColor: [ 'rgba(99, 132, 255, 0.2)' ], 
			pointBorderColor: [ 'rgba(99, 132, 255, 1)' ], 
			borderColor: [ 'rgba(99, 132, 255, 1)' ], 
        }],
	},
	options: {
		scales: {
		    yAxis: {
				afterSetDimensions: function(axes) {
				axes.labelAutoFit = false;
		                 axes.maxWidth = 30;
		                 axes.minWidth = 30;
		        }
		    }
		},
		plugins: {
            legend: { // Could display legend if you want
                display: false,
                labels: {
                    color: 'rgb(255, 99, 132)'
                }
            }
        }, 
        layout: {
            padding: {
                left: 5
            }
        }
    }
} 

window.renderChart(chartData, this.container)
'''

The CSS was a pain:

/* selectively enable wide pages even with Minimal theme */
.fullwidth.markdown-source-view.mod-cm6.is-readable-line-width .cm-content,
.fullwidth.markdown-source-view.mod-cm6.is-readable-line-width .cm-line,
.fullwidth.markdown-source-view.mod-cm6.is-readable-line-width .cm-embed-block {
  --line-width: 100rem;
  --line-width-wide: 100rem;
}

.fullwidth.markdown-source-view.mod-cm6.is-readable-line-width .cm-preview-code-block {
  --table-edge-cell-padding-first: 0px;
  --table-cell-padding: 0px 0px;
  --container-table-width: 100rem;
  --max-width: 100%;
  --container-chart-width: 88%;
  --container-chart-max-width: 100%;
}

/* suppress # of rows next to first column */
.fullwidth .dataview.small-text {
  display: none;
}

.fullwidth table td {
  font-size: 14px;
  color: #000000;
  word-wrap: break-word;
  border-left: 1px solid #222222 !important;
  border-bottom: 1px solid #222222 !important;
}

.fullwidth .table-view-table tr td {
  text-align: center;
  padding: 8px 0px;
}

.fullwidth .table-view-table tr th {
  min-width: 20px;
  text-align: center;
}

.fullwidth .table-view-table tr th:nth-child(1) {
  width: 35px; /* must equal chart yaxis label width + left padding */
  text-align: center;
}

And a minimal version of my daily note (uses YYYY-MM-DD as the name format):

---
tags:
  - "#daily"
---

Habit Tracking:
[slept-well:: true] 
[ate-well:: false]
[exercise-cardio:: ]

1 Like