3 Simple Examples of Dataview for Obsidian
At the end of last year I finally downloaded Obsidian. One of my friends had been very eager about it, but admittedly I didn’t understand it at first. It seemed like a lot of work and digital bureaucracy that I didn’t want.
You have to manage a bunch of documents, but Dataview seemed like a bunch of complexity on top of that.
I gave it a second look and now I’ll admit that I’m a big fan of it. It takes some time to really get into it and begin to appreciate what it can do. One goal this year is Digital Zen. I want to take full advantage of Obsidian’s capabilities to achieve it.
Dataview is a powerful scripting tool that can let you pull data from your notes to generate tables, graphs, and simple observations.
Since I found Dataview intimidating to start with, this is a short blogpost with a few examples I adopted. Maybe it’ll help you.
Christmas Gifts
I have been maintaining a number of spreadsheets for various kinds of things. But it’s overkill to have to go to Google Sheets and trying to navigate that UI on my phone. I either need a shortcut to the file on my home screen as a widget or have to load the app and search for it after.
One such spreadsheet is for gifts. I like keeping track of things to buy friends and family throughout the year. That way, when it comes time for Christmas shopping I already have a good sense of what to get. In a spreadsheet, I can also keep track of gift prices so I can budget properly.
Moving this to an Obsidian page means creating something that is slightly like a spreadsheet, but a lot simpler. I can find it in my vault faster. I can link it to other pages and take advantage of offline sync. Better yet, it’ll make it a lot easier to quickly add to things on my phone throughout the year.
```dataviewjs
window.getGiftsFor = (person) => {
const lists = dv.current().file.lists
const gifts = lists.filter(l => l.section.subpath === person).map(l => l.text)
if (!gifts.length) return {values:['Nothing: 0']}
return gifts
}
window.getGiftTotal = (person) => {
console.log(person, getGiftsFor(person).values)
const prices = getGiftsFor(person).values.map(gift => parseFloat(gift.split(': ')[1]))
const total = prices.reduce((prev, curr) => prev + curr)
return total
}
const names = ['Dad', 'Mom', ...]
const giftTotal = names.map(n => getGiftTotal(n)).reduce((prev, curr) => prev + curr)
dv.header(6, `Total Gift Spending: $${giftTotal}`)
```
In the Dataview JS API, it is easy to get list items on the page. I can also filter for it based on the various names
of people I’m buying for.
The getGiftsFor
and getGiftTotal
are two short functions that grab all the list items on the page. Each person has a list of gifts under their heading. Those get filtered out. Then, I am able to calculate the total gift spending, but also the spending per-person:
## Nephew
- [MyTracks](https://techcrunch.com/2024/03/27/musical-toy-startup-playtime-engineering-wants-to-simplify-electronic-music-making-for-kids/): 200
- ...
```dataviewjs
dv.header(6, `$${getGiftTotal('Isaiah')}`)
```
This is a very simple example of how a small amount of dynamic code on the page can make things easier.
MTA Stations
There are over 400 different subway stops in the sprawling NYC Subway system. I want to visit all of them.
Again, I’ve been using a spreadsheet to keep track of every place, using a bit of spreadsheet logic to help track it over time. Again, it does make it hard to tick off as soon as I arrive at a station.
Each subway line is defined as its own heading with a list of check boxes. Overlapping stations (like how the A/C/E share stations) only get defined the first time.
```dataviewjs
window.getChecked = (train) => {
const data = dv.current().file.lists
const stations = data.values.filter(v => v.section.subpath.startsWith(train)).filter(v => v.text !== '...')
return stations
}
window.getLineTracker = (train) => {
const stations = getChecked(train)
const completed = stations.filter(s => s.completed)
return `${completed.length}/${stations.length}`
}
window.getTotalTracker = () => {
const values = dv.current().file.lists.values.filter(v => v.text !== '...')
const completed = values.filter(v => v.completed)
return `${completed.length}/${values.length}`
}
```
In these simple functions, attached to the window, I again filter by the heading name. I can also check whether a given list item has been completed
.
Then, I spent some time writing out each station. Using the inline operator $=
, I could even render the current tracker next to the heading.
For stations being skipped, like 168th St here, I use three dots. My functions above filter these out before continuing.
### A Train `$= window.getLineTracker('A Train')`
- [x] Inwood-207 St
- [ ] DyckMan St
- [ ] 190 St
- [x] 181 St
- [ ] 175 St-GW Bridge
- [ ] ...
- [x] 145 St
- [ ] 125 St
- [ ] ...
- [x] 42 St-PABT
-
At the top of the page I can render the total number of stations visited. With the header
method, I can render HTML onto the page in addition to just running computations:
```dataviewj.
dv.header(5, getTotalTracker())
```
Right now, I have visited 215 out 423 stations. So I’m more than halfway there.
Pokémon Cards
This is definitely the most complicated Dataview project I’ve built so far, and I can see some performance issues with my approach.
Yet in my quest to catch them all, I’ve been trying to build some small software projects. As the last two simple projects were migrated off spreadsheets and into Obsidian, so I hoped this would work too.
I wanted to keep track of several kinds of data: Set name, set code, generation number, number of cards I have in the set, total number of cards in the set. With all of that, I want to generate a table to show my progress.
I started by defining each set as a separate file property. The key is the set title. The value is a comma-separated string with the other values:
---
Base: BS,1,87,102
Jungle: JU,1,46,64
Fossil: FU,1,46,62
Base Set 2: B2,1,27,130
Team Rocket: TR,1,32,83
Gym Heroes: G1,1,44,132
Gym Challenge: G2,1,34,132
...
---
It took some time to get all these written down. But now that they’re all entered, it’s actually easy for me to go back on my phone and update the cards count.
This approach is one way to enter a file property with multiple values. It’s easier than hand-writing JSON but it does mean I need to track my commas.
Further down in the page, I can generate my table:
```dataviewjs
let sets = dv.current().file.frontmatter
dv.table(
['Set', 'Gen', 'Collected', 'Total', 'Pct'],
Object.entries(sets).map(([key, s]) => {
if (key === 'sets') return []
const [code, gen, cur, tot] = s.split(',')
return [`${key} (${code})`, gen, cur, tot, `${(cur/tot).toFixed(2) * 100}%`]
})
)
```
Sometimes when I try scrolling through this table there’s a weird scroll snap where it keeps sending me backwards. I don’t know if using the front matter in this way, for 85+ sets, is the optimal way. Maybe a list would be easier?
I can also calculate my total progress:
```dataviewjs
let sets = dv.current().file.frontmatter
let curr = 0
let total = 0
Object.entries(sets).map(([key, s]) => {
const [code, gen, cur, tot] = s.split(',')
curr += parseInt(cur)
total += parseInt(tot)
})
dv.header(6, `Cards: ${curr}/${total} (${(curr/total).toFixed(3) * 100}%)`)
```
Rather than being kinda restricted by the grid layout of a spreadsheet, I do like the more freeform approach here, where I can add sections and some text in a way that makes sense given the context of the specific page.]
It also allows me to generate graphs embedded in the page. Take a look at my card collection over time:
Conclusion
Sure, I could use a spreadsheet to do all of these things. In fact I did for a while. But Obsidian with Dataview definitely does a few things better for at least these three use-cases.
I still use spreadsheets for many other reasons, including monthly budget tracking. When it comes to regular spreadsheet applications, a spreadsheet is still a better tool!
So maybe this has inspired you to try out Dataview for yourself. If nothing else, at least I learned a bit about these technologies and how I can continue to use them to get closer to digital zen.