Creating a chart of completed task per day with dataview and obsidian-charts

I have been unable to make this work as I am not proficient in javascript.
I am trying to make this chart display the completed tasks for each day. The completed tasks are stored in a folder of daily notes.

Chart

const {Charts} = customJS

const pages = dv.pages('"Daily Notes"').file.tasks.where(p=>p.checked).sort(p=>p.path)
const labels = pages.map(b=>Charts.fileDate(b.path))
const completed = pages.map(p=>p)

const chartData = {
    type: 'line',
    data: {
        labels: labels.values,
        datasets: [{
            backgroundColor: Charts.fillColor('green'),
            borderColor: Charts.borderColor('green'),
            borderWidth: 1,
            label: 'Tasks',
            data: completed.values,
            spanGaps: true,
            tension: 0.25,
            fill: '1'
        }]
    },
    options:{
		scales: {
			y: {
				grid: {
					borderColor: Charts.textColor(),
					tickColor: Charts.textColor(),
					color: Charts.gridColor()
				},
				ticks: {
					display: false
				},
				min: 1,
				max: 5
			},
			x: {
				grid: {
					borderColor: Charts.textColor(),
					tickColor: Charts.textColor(),
					color: Charts.gridColor(),
				},
				ticks: {
					color: Charts.textColor(),
					font: {
					    family: Charts.fontFamily()
				    }
				},
				type: 'time',
				time: {
					unit: 'day',
					tooltipFormat: 'MMM D, YYYY'
				}
			}
		},
		interaction: {
            mode: 'index'
        },
        plugins: {
	        legend: {
		        display: false
	        }
        }
	}
}

window.renderChart(chartData, this.container)

up, still unsolved.

Given that you’re referring to customJS code which we can’t see, and you’re not stating what goes wrong or right. Or what’s missing in your setup, it’s very hard to actually provide any help for you.

Sorry, I should have been more specific. The chart works fine the only problem is gathering the tasks from the folder. The problematic code should be this one:

const pages = dv.pages('"Daily Notes"').file.tasks.where(p=>p.checked).sort(p=>p.path)
const labels = pages.map(b=>Charts.fileDate(b.path))
const completed = pages.map(p=>p

This is the the customJS code:

class Charts {
  fontFamily() {
    const theme = window.getComputedStyle(document.body)
    return theme.getPropertyValue("--font-preview")
  }
  textColor() {
    const theme = window.getComputedStyle(document.body)
    return theme.getPropertyValue("--text-muted")
  }
  gridColor() {
    const theme = window.getComputedStyle(document.body)
    return theme.getPropertyValue("--background-modifier-border")
  }
  fillColor(color) {
    const theme = window.getComputedStyle(document.body)
    var themecolor = {
      blue : theme.getPropertyValue("--chart-blue-fill"),
      red : theme.getPropertyValue("--chart-red-fill"),
      green : theme.getPropertyValue("--chart-green-fill"),
      yellow : theme.getPropertyValue("--chart-yellow-fill")
    };
    return themecolor[color] || theme.getPropertyValue("--chart-blue-fill");
  }
  borderColor(color) {
    const theme = window.getComputedStyle(document.body)
    var themecolor = {
      blue : theme.getPropertyValue("--chart-blue"),
      red : theme.getPropertyValue("--chart-red"),
      green : theme.getPropertyValue("--chart-green"),
      yellow : theme.getPropertyValue("--chart-yellow")
    };
    return themecolor[color] || theme.getPropertyValue("--chart-blue");
  }
  fileDate(file) {
    const date = file.split("/").pop().replace(".md", "");
    return date
  }
}

If I’m understanding you correctly, you want the count of completed tasks out of this query. To get that you need to change a few things.

  • Using p.checked will check for any character other than space as the status of the task. This means that tasks like - [/] and - [!] and various other variations would be included. I think you want to use p.completed to only get those actually completed where you’ve entered - [x] (or clicked to complete it)
  • Instead of just sorting by the path, which I’m assuming is your unique daily notes, I would rather do groupBy the path to gather up the various completed tasks into a rows object list
  • Given an rows list for each page, we can now simply get the length of this list as the number of completed tasks.

The first part of your query should now look something like:

const completedTasks = dv.pages('"Daily Notes"')
  .file.tasks
  .where(t => t.completed)
  .groupBy(t => t.path)

const labels = completedTasks.map( t => Charts.fileDate(t.path) )
const completed = completedTasks.values.map( t => t.rows.length )

And I think you then could use data: completed directly within the charts data settings.

I’ve not got a correct setup to test the entirety of your script, but please try this out, and see if it gets you closer to a solution.

( I changed pages to completedTasks just to reflect that you’ve picked out the completed tasks, and are not working with pages anymore. Related to this I also changed the p => to t => to help visualise that we’re working with tasks, and not pages anymore. This is purely a visual thing I do to help me keep track of what the objects I’m working with in the various contexts. )

1 Like

Using the following code:

const {Charts} = customJS
const completedTasks = dv.pages('"Daily Notes"')
  .file.tasks
  .where(t => t.completed)
  .groupBy(t => t.path)

const labels = completedTasks.map( t => Charts.fileDate(t.path) )
const completed = completedTasks.values.map( t => t.rows.length )
const chartData = {
    type: 'line',
    data: {
        labels: labels.values,
        datasets: [{
            backgroundColor: Charts.fillColor('green'),
            borderColor: Charts.borderColor('green'),
            borderWidth: 1,
            label: 'Tasks',
            data: completed,
            spanGaps: true,
            tension: 0.25,
            fill: '1'
        }]
    },
    options:{
		scales: {
			y: {
				grid: {
					borderColor: Charts.textColor(),
					tickColor: Charts.textColor(),
					color: Charts.gridColor()
				},
				ticks: {
					display: false
				},
				min: 1,
				max: 5
			},
			x: {
				grid: {
					borderColor: Charts.textColor(),
					tickColor: Charts.textColor(),
					color: Charts.gridColor(),
				},
				ticks: {
					color: Charts.textColor(),
					font: {
					    family: Charts.fontFamily()
				    }
				},
				type: 'time',
				time: {
					unit: 'day',
					tooltipFormat: 'MMM D, YYYY'
				}
			}
		},
		interaction: {
            mode: 'index'
        },
        plugins: {
	        legend: {
		        display: false
	        }
        }
	}
}

window.renderChart(chartData, this.container)

I get this error:

Evaluation Error: TypeError: Cannot read properties of undefined (reading 'split')
    at Charts.fileDate (eval at <anonymous> (plugin:customjs), <anonymous>:35:23)
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:7:48)
    at Array.map (<anonymous>)
    at Proxy.map (plugin:dataview:8038:39)
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:7:31)
    at DataviewInlineApi.eval (plugin:dataview:18370:16)
    at evalInContext (plugin:dataview:18371:7)
    at asyncEvalInContext (plugin:dataview:18381:32)
    at DataviewJSRenderer.render (plugin:dataview:18402:19)
    at DataviewJSRenderer.onload (plugin:dataview:17986:14)

My daily notes are formatted as dates: YYYY-MM-DD

Try changing to t.key in this line. The group by changed the access to the path.

1 Like

Thanks for your help, it works!

1 Like

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