DataviewJS & Line Graph - show habits across multiple months

What I’m trying to do

I’m trying to create a line chart where:

  • The x-axis are the days of the month,
  • The y-axis is how long I spend meditating,
  • There is a different line for each month in the year.

I’m struggling with:

  • Developing an array for the month (where missing or empty daily notes = 0)
  • A better way of grouping/filtering by month

YAML and File Structure

YAML

In my Daily Notes, I track this within the YAML:

---
fitness:
    meditation: ##
category: daily
date: yyyy-mm-dd
---

File Structure

My Daily Notes are gathered in a quarterly file structure, so I have to filter for notes in 2023.

  • 202 BuJo
    – 202.12 Daily
    — 21 Notes
    — 22 01-03 Notes (etc)
    — 22 10-12 Notes
    — 23 01-03 Notes
    — 23 04-06 Notes

Things I’ve tried

Line graph for all of 2023 - success

The first thing was building a standard line graph across all of 2023.

const pages = dv
	.pages('"202 BuJo/202.12 Daily"')
	.where((page) => dv.func.contains(page.file.name, "2023-"))
	.where(p => p.fitness.meditation)
	.sort(p => p.date, 'asc')

const testNames = pages.map(p => p.file.name).values
const testMarks = pages.map(p => p.fitness.meditation).values

const chartData = {
    type: 'line',
    data: {
        labels: testNames,
        datasets: [{
            label: 'Mark',
            data: testMarks,
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)'
            ],
            borderWidth: 1,
        }]
    }
}

window.renderChart(chartData, this.container)

This renders out just fine!

image

Multiple months, and an array - but skips the missing days

I developed my initial query to have preassigned labels, and am able to have each month as their own line!

However, the value wasn’t ‘0’ on missing days - they were just skipped.

Also, I am wondering if there’s a simpler way of doing this - rather than having each month as its own element, but something closer to DataviewJS and Charts: split by frontmatter variable ("Medium") ?

var labels = ["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31",];

const cardSix = dv
	.pages('"202 BuJo/202.12 Daily"')
	.where((page) => dv.func.contains(page.file.name, "2023-06"))
	.where(p => p.fitness.meditation)
	.sort(p => p.date, 'asc')

const monthsix = cardSix.map(p => p.fitness.meditation).values

const cardFive = dv
	.pages('"202 BuJo/202.12 Daily"')
	.where((page) => dv.func.contains(page.file.name, "2023-05"))
	.where(p => p.fitness.meditation)
	.sort(p => p.date, 'asc')

const monthfive = cardFive.map(p => p.fitness.meditation).values
		
const chartData = {
		type: 'line',
		data: {
			labels: labels,
			datasets: [{
				label: 'June',
				data: monthsix,
				backgroundColor: ['#ffa244'],
				borderColor: ['#ffa244'],
				borderWidth: 1
			},
				{label: 'May',
				data: monthfive,
				backgroundColor: ['#69EDFF'],
				borderColor: ['#69EDFF'],
				borderWidth: 1
			}]
		},
	}

window.renderChart(chartData, this.container);

image

Next Steps?

Both of my struggles feel like they should be solved by:

  • Identifying the day and the month of each daily note,
  • Grouping / Filtering each month to become its own line,
  • Then sorting the values by day, correlated to the provided array.
  • Each month will then be its own element in ‘data’.

I haven’t looked to closely on your queries, but given a date like p.date, you’ve also got access to p.date.year and p.date.month and p.date.day. So it seems one way to do it would be to do a group by month, and possibly limit the entire query to current year?

Remember when grouping all the other values are moved into the rows object.

So, I’ve managed to make a heap of progress with this - solving both of the key problems I had.

It does the array for each month as a separate line, and it provides the value ‘0’ for any missing files!
(Insane kudos to TheSoCalled and Part-Permissions, whose Q&As saved me)

I managed to render the graph

image

with this
var labels = ["00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31",];
var colors = [['#ff6384'],['#36a2eb'],['#ffce56'],['#4bc0c0'],['#9966ff'],['#ff9f40'],]
var datasets = [];

for(
	let group of dv
		.pages('"202 BuJo/202.12 Daily"')
		.where((page) => dv.func.contains(page.file.name, "2023-"))
		.where(p => p.fitness.meditation && p.date)
		.groupBy(p => (dv.date(p.date).toFormat("dd MMMM")))
		.sort(dm => dm.rows.date.first(), 'asc')
		.groupBy(dm => (dv.date(dm.rows.date.first()).month))
		.sort(d => d.key, 'asc')
	) {
		let lbl = group.key;
		let backCol = colors[datasets.length%colors.length];
		let bordCol = colors[datasets.length%colors.length];
		let bWidth = 1;

	let innerArray = [,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
		group.rows.forEach(d => {
			let medTime = dv.array(d.rows).fitness.meditation.values;
			innerArray[d.rows.date.first().day] = medTime;
		})
		let da = {
			label: lbl,
			data: innerArray,
			backgroundColor: backCol,
			borderColor: bordCol,
			borderWidth: bWidth
		};					
	datasets.push(da)
		}

	const chartData = {
		type: 'line',
		data: {labels: labels, datasets: datasets},
		options: {scales: {yAxis: {suggestedMin: 0, ticks: {stepSize: 1}}}
	}}

window.renderChart(chartData, this.container);

New Problems

However, I’ve now run into a couple of interesting (minor) issues:

1 - It relies on the metadata 'date', when I want to use file.name

Rather than being able to parse ‘file-name’ in innerArray[d.rows.date.first().day].

Ideally, I’d like it to work off file-name, because otherwise I’ll have to go back through All my daily notes to add the metadata.
(My files are titled in yyyy-mm-dd format, if not already mentioned)

2 - I want to relabel the months

They’re currently generated as the group.key, but I’d like to rename them ‘Jan’, ‘Feb’, etc.
I feel like there should just be a way to change the group.key rendering as a number and into plain-text?

3 - The x-axis currently needs to start at 00 instead of 01

If I used innerArray[d.rows.date.first().day-1] = medTime; (note the -1), I wouldn’t need the ‘00’ label on the x-axis.
However, it also wouldn’t show anything from the first two months?

4 - Values recorded at zero are treated the same as those missing

When I meditate more than 20 minutes, I currently mark it as ‘0’.

However, the current array displays all missing values as ‘0’, confusing the two sets.
If I leave the innerArray as [, , , …], it removes all fitness.meditation time that equal 0.
If I lower innerArray to [-1,-1,-1,…], all values entered at zero get reduced to that as well.

I don’t particularly want to go back through all my notes and replace them with something like 21, but is there anything that will actually deal with this?

Why did I need the two groupings?

This is less a problem and more something I noticed with the structure.

I managed to build a table that correctly grouped and ordered the information

(Based off DataviewJS Snippet Showcase - #241 by justdoitcc)

// GATHER PAGES
let pages = dv
	.pages('"202 BuJo/202.12 Daily"')
	.where((page) => dv.func.contains(page.file.name, "2023-"))
	.where(p => p.reading.card)
	.sort(p => p.file.name)

// GROUP
let groups = pages
	.groupBy(p => (dv.date(p.file.name).month))

// OUTPUT
for (let G1 of groups) {
    dv.header(2, G1.key);
    dv.table(
        ["Date", "Card"],
        G1.rows
            .map(p => [p.file.link, p.reading.card])
    );
}

But if I tried to use this in a line-graph

using this query
const pages = dv
	.pages('"202 BuJo/202.12 Daily"')
	.where((page) => dv.func.contains(page.file.name, "2023-"))
	.where(p => p.rfitness.meditation)
	.sort(p => p.file.name)
	.groupBy(p => (dv.date(p.file.name).month))

for(let G1 of pages) {
		let innerArray = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
		pages.forEach(d => {
			let medTime = dv.array(d.rows).fitness.meditation.values;
			innerArray[d.rows.file.name.first().day] = medTime;
		})
it rendered this graph of a straight line

image

I’m mostly curious as to why it didn’t work? What am I missing about the structure?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.