Books per year: a reading log chart in Obsidian
bucket: yeardate_fieldYou keep a note per book and want to see how many you read each year, and how this year is going against the last. A book note is named after the book, not a date, so the date has to come from a property. Two Dashy blocks read it: a row of numbers for this year and a bar per year.
dashy-chartObsidian Charts also uses the chart code block. When Charts is on, Dashy leaves chart to it. Name the chart below dashy-chart instead: same keys, always Dashy.
1Give each book the day you finished it
One date property for when you finished the book. Here it is date_read, next to the page count and your rating.
---
date_read: 2026-03-14
pages: 476
rating: 5
---
Read it in four evenings.
date_read: 2026-03-14Year, month, day, or a Date property from Obsidian's Properties panel. A book you are still reading leaves it out and is not counted yet.pages,ratingOptional. Plain numbers, for the cards in step 2.
If your book notes are named with the date first, like 2026-03-14 Project Hail Mary.md, that works too: leave date_field out of the blocks below and the date is read from the name.
2This year in numbers
Make a note for the log, say Reading log.md, and paste this block. It is the row of cards in the picture above.
```stats
source: Reading
date_field: date_read
period: year
items:
- { label: Books this year, agg: count, compare: true, better: up, icon: 📚 }
- { label: Pages this year, field: pages, agg: sum }
- { label: Average rating, field: rating, agg: avg, precision: 1 }
```
source,date_field,periodWritten once, aboveitems, they apply to every card. A card can still set its own, and its own wins.period: year1 January to today. Books without adate_readare left out first.compare: trueThe change against last year, to the same day: on 5 October it compares with 1 January to 5 October of last year, not with all of it.better: upmakes a rise green.
3One bar per year
Paste this under the cards.
```chart
source: Reading
date_field: date_read
agg: count
bucket: year
type: bar
range: 1825d
label: Books
goal: 24
```
agg: countCounts the books dated in each year. Nofieldis needed.bucket: yearOne bar per calendar year, labelled with the year alone.range: 1825dFive years back from today. The window starts on 1 January of the year it reaches, so in 2026 the bars run from 2021 to 2026. Leave it out for the last ten years and this one.goal: 24A dashed line at 24 books, its value named in the legend.
This year's bar is drawn lighter and its tooltip says "so far": the year is not over. A year with no books is a 0, since counting nothing is still a count. Hover a bar for the number.
The whole note
Both blocks in one piece, ready to paste into an empty note.
```stats
source: Reading
date_field: date_read
period: year
items:
- { label: Books this year, agg: count, compare: true, better: up, icon: 📚 }
- { label: Pages this year, field: pages, agg: sum }
- { label: Average rating, field: rating, agg: avg, precision: 1 }
```
```chart
source: Reading
date_field: date_read
agg: count
bucket: year
type: bar
range: 1825d
label: Books
goal: 24
```
Ask your agent
With Dashy's skill installed, an AI agent can write this from a request in plain words: it looks at your property names and folders first, then writes the blocks. Open the note in Reading view and paste any error a block shows back to the agent.
In a new note called Reading log, show how many books I read this year against last year, how many pages that was and my average rating. Under it, a bar for each of the last five years with a line at my goal of 24 books. Each book note has the date I finished it.
What comes back is something like the note above. The agent's YAML may differ from the guide's, with your own property names and labels, but it should do the same, and this guide is the reference to compare it against. More on working with an agent.
The same chart with dataviewjs
The usual way: a Dataview script groups the books by year and hands the numbers to Obsidian Charts.
```dataviewjs
const years = {};
for (const p of dv.pages('"Reading"').where(p => p.date_read)) {
const y = String(p.date_read.year);
years[y] = (years[y] ?? 0) + 1;
}
const labels = Object.keys(years).sort();
window.renderChart({
type: "bar",
data: { labels, datasets: [{ label: "Books", data: labels.map(y => years[y]) }] },
}, this.container);
```
- Two plugins: Dataview with Enable JavaScript queries on, which is off by default, and Obsidian Charts for
renderChart. - A year with no books has no key in
years, so it drops off the axis instead of showing 0. - No goal line, and nothing marks the current year as unfinished. Both are more code.
- This year's count, pages and the change against last year are a second script.
| Dashy | dataviewjs | |
|---|---|---|
| Group by year | bucket: year | The loop above |
| A year with no books | 0 | Missing from the axis |
| Goal line | goal: 24 | Write it |
| This year against last | compare: true | Write it |
| What you install | Dashy | Dataview, JavaScript queries on, and Obsidian Charts |
Variations
A yearly goal as a bar
How far along this year's goal you are, as a percentage.
```progress
items:
- { label: Reading goal, source: Reading, date_field: date_read, period: year, agg: count, goal: 24, icon: 📚 }
```
Pages, not books
A long book counts for more. sum adds the pages up; a year with none is a gap.
```chart
source: Reading
date_field: date_read
field: pages
bucket: year
type: bar
label: Pages
```
This year by month
The same books, one bar per month since 1 January.
```chart
source: Reading
date_field: date_read
agg: count
bucket: month
range: year
type: bar
label: Books
```
Only the five-star ones
where narrows the books first. It works on the chart and on the cards alike.
```stats
source: Reading
items:
- { label: Five-star books, where: "rating = 5", agg: count }
```
If a block shows an error
A block that cannot do what you asked says so, inside the note. The ones you are likely to see here:
- None of the selected notes has a date in "date_read". The property is spelled differently in the book notes, or
sourcenames the wrong folder. - None of the selected notes has a name starting with a date like YYYY-MM-DD. The block has no
date_fieldand the book notes are named after the book. Adddate_field: date_read. bucketexpects day, week, month or year. Something likeyearlywas written. The chart falls back to days until you fix it.- An error from Obsidian Charts, not from Dashy. With Charts on, a
chartblock is Charts'. Writedashy-chart.
Related guides

A number summed by week, with a goal line.

A clock, a line of counts, folder tiles and what is coming up.

Days left and the age they turn, from the date in a person's note.
Dashy is in Obsidian's community plugins. Search for Dashy, install, enable, then paste the note above.