Creating a new row in a dataview table

Things I have tried

So far this is what i have. I am pulling from my YAML from “spent” and “purchase”
image
with this:

	table WITHOUT ID purchase as "Purchased", spent as "Item Cost"
	WHERE file.name = this.file.name

and using this to sum it up for me

| Total Spent | <nobr>                                 |
| -----------:| -------------------------------------- |
|       Today | $`=round(sum(number(this.spent)), 2)` |
|  This week: | ![[DVB#^7fqk1y]]                       |
| This month: | ![[DVB#^0dezgl]]                       |
|  This year: | ![[DVB#^jngwod]]                       |

those link to blocks containing:

dv.paragraph("$" + dv.pages("#W51")
	.spent.array().reduce((acc, val) => acc + val, 0).toFixed(2))

all together this gives me :
image

What I’m trying to do

Ideally i would like to have this all be part of one table in the dataview query but im stumped as to how i can get this done/if i can get this done. any help would be great! and i apologize if this has been asked before but i feel like ive been searching for an answer for days! O_O

Also just noticed I’m using tags to pull the right week but I’m trying to change it to the day, week, month, year sections I just put in shown in first pic

Hi.

Previous declaration

To start I need to clarify: I’m not a coder or similar, I’m just an obsidian user exploring a new world.

This to say: my attempt below is only that, an attempt with a bad code. It’s just an idea with a mix of sources, trying to put together different references.

I acquired some skills in DQL (regular dataview) but I’m not versed in JS. So, if any good expert in the Forum want to “rebuild” or correct the code below, please do it.


Example notes

You don’t need to add multiple fields for date attributes (week, month, year…). You just need to have a valid date. You can create a specific field - as date: 2022-12-16 - or, as suggested in the examples below, daily notes with a date in title in the format YYYY-MM-DD (with this type of name you can use the implicit field file.day, a date type of metadata).

Some examples:

2022-11-28

---
spent:
  - {name: eggs, value: 2.90}
  - {name: cooffe, value: 2.10}
---

2022-12-01

---
spent:
  - {name: cigarretes, value: 9.11}
  - {name: cat food, value: 11.36}
---

2022-12-12

---
spent:
  - {name: eggs, value: 2.5}
  - {name: restaurant, value: 27.88}
---

2022-12-16

---
spent:
  - {name: cigarretes, value: 4.09}
  - {name: newspaper, value: 2.40}
---

2022-12-18

---
spent:
  - {name: flowers, value: 6.75}
  - {name: journal, value: 2.75}
---

The query

To start, it’s important to say that there is no way to do totals calculations in normal way in a new row at the bottom of the column. This implies some tricky ways with js (at least for js dummies like me), “injecting” multiple calculations.

Well, now, the ugly part (because I’m more comfortable in DQL, I use a mix of DQL and DVJS).

(a query placed in the note 2022-12-18)

// defining the query for current daily note date (let's say, today)
const DQL = await dv.tryQuery(`
TABLE WITHOUT ID S.name, S.value
WHERE file.path = this.file.path
FLATTEN spent AS S
`)

// Total title 1
const totalTitle = ["<span style='display:flex;justify-content:right'><b>Total Spent</b></span>"]

// calculate today total
const spentToday = DQL.values.map(g => g[1]).reduce((acc, val) => acc + val, 0).toFixed(2)

// Total today
const totalToday = ["<span style='display:flex;justify-content:right'>Today</span>", "$ " + spentToday]

// calculate costs in the current week until today
const DQLWeek = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE dateformat(file.day, "yyyy-WW") = dateformat(this.file.day, "yyyy-WW") AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total week
const totalWeek = ["<span style='display:flex;justify-content:right'>This week</span>", "$ " + DQLWeek.values[0][0]]

// calculate costs in the current month
const DQLMonth = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE dateformat(file.day, "yyyy-MM") = dateformat(this.file.day, "yyyy-MM") AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total month
const totalMonth = ["<span style='display:flex;justify-content:right'>This month</span>", "$ " + DQLMonth.values[0][0]]

// calculate costs in the current year
const DQLYear = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE file.day.year = this.file.day.year AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total year
const totalYear = ["<span style='display:flex;justify-content:right'>This year</span>", "$ " + DQLYear.values[0][0]]


// define a horizontal line in table
const hrArray = Array(2).fill('<hr style="padding:0; margin:0 -10px; border-top: 1px solid var(--background-modifier-border)">');

// push all values
DQL.values.push(hrArray)
DQL.values.push(totalTitle)
DQL.values.push(hrArray)
DQL.values.push(totalToday)
DQL.values.push(hrArray)
DQL.values.push(totalWeek)
DQL.values.push(hrArray)
DQL.values.push(totalMonth)
DQL.values.push(hrArray)
DQL.values.push(totalYear)
DQL.values.push(hrArray)

// create table
dv.table(["Purchased", "Cost"], DQL.values);

// remove the number of rows in first column title
this.container.querySelectorAll(".table-view-table tr:first-of-type th:first-of-type > span.small-text")[0].style.visibility = "hidden";

The result

Oh my god , you are awesome! >_< this has been driving me mad! not lying when i say i ave about 8 different “versions” i went through making some still saved in my vault for later use if the need comes up of code based on various topics you have helped in!
image lol

anyway enough fanboying lol
nothing i came up with was ever as detailed as that so I’m very grateful because i never would have gotten that without taking some classes! (which i had just recently started online XD)

So i appear to be running into a problem. It is working perfectly it seems in some notes but not all, so i started fresh and created just four days.

Yesterday and todays notes seem to not work

cssclass: linewidth2
tags: daily
spent:
	- {name: cigarrets, value: 9.05}

cssclass: linewidth2
tags: daily
spent:
	- {name: test cheese, value: 42.69}

BUT
the 9th and the 10th do?

cssclass: linewidth2
tags: daily
spent:
 - {name: coffee, value: 1.49}
 - {name: 7/11 snack pie, value: 1.99}
 - {name: winston Red 100s, value: 6.74}
 - {name: tax, value: 0.63}

cssclass: linewidth2
tags: daily
spent:
 - {name: coffee, value: 1.49}
 - {name: 7/11 snack pie, value: 1.99}
 - {name: winston Reds, value: 6.74}
 - {name: tax, value: 0.63}

Not sure what its trying to point me towards with that error. Thank you again!

Hmm. This is one more reason to next time not trying any dvjs query :slight_smile:
Because debug things inside js is another level…

But I guess we’re facing multiple cases where there’s no values to calculate in the current day, week, etc.

But to start, be sure you use two spaces and not indent tab in yaml list. For example:

  • this:
spent:
  - {name: cigarrets, value: 9.05}
  • not this
spent:
	- {name: cigarrets, value: 9.05}

Now another rude query as an attempt to solve undefined cases (not sure if it solves anything or if it adds another issues :confused: ):

// defining the query for current day
const DQL = await dv.tryQuery(`
TABLE WITHOUT ID S.name, S.value
WHERE file.path = this.file.path
FLATTEN spent AS S
`)

// Total title 1
const totalTitle = ["<span style='display:flex;justify-content:right'><b>Total Spent</b></span>"]

// calculate today total
const spentToday = DQL.values.map(g => g[1]).reduce((acc, val) => acc + val, 0).toFixed(2);


// Total today
const totalToday = ["<span style='display:flex;justify-content:right'>Today</span>", "$ " + spentToday]

// calculate costs in the current week until today
const DQLWeek = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE dateformat(file.day, "yyyy-WW") = dateformat(this.file.day, "yyyy-WW") AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total week
const totalWeek = ["<span style='display:flex;justify-content:right'>This week</span>", "$ " + (DQLWeek.values[0] || "0.00")];

// calculate costs in the current month
const DQLMonth = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE dateformat(file.day, "yyyy-MM") = dateformat(this.file.day, "yyyy-MM") AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total month
const totalMonth = ["<span style='display:flex;justify-content:right'>This month</span>", "$ " + (DQLMonth.values[0] || "0.00")]

// calculate costs in the current year
const DQLYear = await dv.tryQuery(`
TABLE WITHOUT ID round(sum(rows.val),2)
WHERE spent
WHERE file.day.year = this.file.day.year AND file.day <= this.file.day
FLATTEN spent.value AS val
GROUP BY true
`)

// Total year
const totalYear = ["<span style='display:flex;justify-content:right'>This year</span>", "$ " + (DQLYear.values[0] || "0.00")]


// define a horizontal line in table
const hrArray = Array(2).fill('<hr style="padding:0; margin:0 -10px; border-top: 1px solid var(--background-modifier-border)">');

// push all values
DQL.values.push(hrArray)
DQL.values.push(totalTitle)
DQL.values.push(hrArray)
DQL.values.push(totalToday)
DQL.values.push(hrArray)
DQL.values.push(totalWeek)
DQL.values.push(hrArray)
DQL.values.push(totalMonth)
DQL.values.push(hrArray)
DQL.values.push(totalYear)
DQL.values.push(hrArray)

// create table
dv.table(["Purchased", "Cost"], DQL.values);

// remove the number of rows in first column title
this.container.querySelectorAll(".table-view-table tr:first-of-type th:first-of-type > span.small-text")[0].style.visibility = "hidden";

I suggest you use the last part of the code to remove the total number of the rows in first column, because the result isn’t reliable (it counts all the pushed rows)

If you use by default rows horizontal lines, then if you want you can remove all DQL.values.push(hrArray) lines in the code.

It looks like it was the indent actually but i used this new code provided and looks to be working a bit quicker now as well. could just be in my head but it did seem it lol.

I was so confused there, i saw "reading ‘0’ " and went looking through my book database because i use usually 1 and 0 as done or not done in the YAML for status on my databases lol! thanks again!

image

image

TYTY!

I’m not sure about the expert part, but I wanted to give it a go, but I’m not sure whether I like yours or mine solution the best. It’ll either way get a little messy due to doing multiple totals, and handling that week total.

Long dataviewjs to build the totals
```dataviewjs
function spanRight(text) {
  return `<span style='display:flex;justify-content:right'>${text}</span>`
}

function dollarValue(cost, decimals=2) {
  return `$ ${cost.toFixed(decimals)}`
}

// Pick the day of the query
// either from field or file.name
let day
if (dv.current().day) {
  day = dv.current().day
} else {
  day = dv.date(dv.current().file.name)
}

// Generate the various filter dates
const startOfMonth = dv.date(`${day.year}-${day.month}-01`)
const startOfYear = dv.date(`${day.year}-01-01`)
const thisWeek = `${day.year}-${day.weekNumber}`
// console.log(day.c, startOfMonth.c, startOfYear.c, thisWeek)

// Get each of the spendings in a separate row with date and week
const spendings = await dv.tryQuery(`
   table without id 
     file.name as date, 
     dateformat(date(file.name), "yyyy-WW") as week,
     spent.name, spent.value
   where spent
   flatten spent`)

let totalToday = 0
let totalWeek = 0
let totalMonth = 0
let totalYear = 0
let sumSpendings = {}

for (let [date, week, name, cost] of spendings.values) {
  console.log(date, week, name, cost, parseFloat(cost))
  
  // Only accept spendings before or at this day
  if (date > day) 
    continue

 // Convert to a number
 cost = parseFloat(cost)
 
 if (name in sumSpendings) {
    sumSpendings[name] += cost
  } else {
    sumSpendings[name] = cost
  }
  console.log("this ->", day, date)
  if (day.ts == dv.date(date).ts)
    totalToday += cost

  if (week == thisWeek)
    totalWeek += cost
    
  if (day > startOfMonth)
    totalMonth += cost

  if (day > startOfYear)
    totalYear += cost
}

let expenditures = []

console.log(sumSpendings)

for (let key in sumSpendings) {
  expenditures.push([key, 
  dollarValue(sumSpendings[key])])
}

// Sort them according to expenses, expecting a "$" at front
expenditures = expenditures.sort((a, b) => a[1].slice(1) - b[1].slice(1))

// Add a line into the table
expenditures.push(Array(2).fill('<hr style="padding:0; margin:0 -10px; border-top: 1px solid var(--background-modifier-border)">'))

// Add the totals to the table
expenditures.push([spanRight("Total today"), dollarValue(totalToday)])
expenditures.push([spanRight("Total this week"), dollarValue(totalWeek)])
expenditures.push([spanRight("Total month"), dollarValue(totalMonth)])
expenditures.push([spanRight("Total year"), dollarValue(totalYear)])

// Display the end table
dv.table(["Name", "Cost"], expenditures)
```
The pseudocode of this query

The thought process for this query was to gather all the single spendings, and then loop over them to gather the totals. In the end I also needed a few utility functions to help build the table. This resulted in this pseudocode:

  • Two utility functions to do the right-aligned columns for totals, and to present a cost with two decimals
  • Choose the day for which the query is run, either from a field in the current document, or from the file.name, as in a file like 2022-12-17
  • Since we later on gather the day (and week) of the single spending, we now generate the various dates matching the totals we seek
  • Get all the spendings, with the additional details of the day and the week. I got the week here for two reasons: 1) To get the same week number independent of date packages (dataviews/luxon vs javascript), 2) To avoid too much date calculations
  • Loop through all spendings and do the following:
    • Check if the spending was after the day of the query
    • Convert the cost into an actual number (and not just a string)
    • Sum up all the spending for every item into sumSpendings
    • Depending on the filter dates, add the cost to the various totals
  • Sort the spendings, and format with proper display, into `expenditures``
  • Add the line to separate the totals from the actual spendings
  • Add each total using the utility functions
  • Display the end result in all its glory

For my test data this script generated this result:
image

Working with this query I also discovered what you’re discussing in the recent post, and that is that doing the frontmatter correctly is vital to actually get the correct output. It’s finicky to say the least.

1 Like

ohh, man i want to get to the point i can just sit down and write some code like that lol “yeha ill try”. I am not a brand new to code individual but ill just say my coding days were back when tables is what made a webpage. My first website was written in notepad and i thought i was cool as hell for having a marque of “affiliate” banners of other websites that had MY “affiliate” banner on theirs XD ahh fun times. now its DIVs and i’m a newb when i style inside my html instead of using CSS lmao. thing is i never did well learning by reading. i need to see and do. copy paste and make it my own and mess around with the code. taking some courses on Udemy ATM :smiley:

Actually this code is almost a copy-paste of various scripts I written last days, so it could be used as a boiler-plate for other queries.

The gist of this approach is:

  • Do some query to get the base data
  • Loop through those data to do extra processing
  • Finally sort/map present the data

Good luck in your endeavours in future queries!

1 Like

An expert, for sure. :slight_smile:
I’ll read later with more attention (do not have much time now).
Thank you.

1 Like

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