Dmytro-Shulha / obsidian-plotly

Obsidian plugin to embed Plotly charts into markdown notes.
MIT License
70 stars 4 forks source link

How to create time-series charts? #7

Closed kalmir closed 11 months ago

kalmir commented 2 years ago

I would like to use dataviewjs and plot various values on a timeline, be it in a line chart or bar chart or such. But I couldn't figure out how to do that from examples.

If, say, I have a CharacterCount key with some value in several notes, I can use pg.CharacterCount.values to plot the numbers. I don't know how to use inherent metadata, e.g. file.cday, to assign the values to their dates. I found a workaround in providing another key in those notes, e.g. Cdate, with the date value (which works only if surrounded with quotes, as far as i can tell). Not ideal but doable. I also dont know how to show dates with no values with corresponding 0 values (I mean for days when no note contains CharacterCount, meaning nothing was written).

I would be glad for any help and i am already grateful for the plugin, really cool, thanks.

DLBPointon commented 2 years ago

Hi Kalmir

It took me far longer than I am willing admit (JS is not my language of choice), but I came up with a script that would see nulls and turn them into a predefined what ever and plot them on a time series.

So in my notes I have a templater template which gives auto-filled Sunrise, Sunset, today's date in a standard format (which I would suggest you use too, although thinking about it you could make another function to take the cdate data and use DateTime.fromISO(YOUR_VALUE).toJSDate() in the mapping function to change your .file.cdate to a human readable and useable format. I just prefer having date in your yaml and using the cday as a truth), as well as a bunch of work based metrics.

Using this I can use the below:

// Dataviewjs Query
let pg = dv.pages('"90 - PLANNER/2022"')
.filter(b=>!b.file.name.contains('W'))
.filter(b=> b.today)
.sort(b => b.file.cday)
.map(b=> [nullCheck(b.Sunrise), b.today, b.file.cday])
// nullCheck must happen here
// Sorting must happen before mapping

// Path declarations for vault and plotly
let path = app.vault.adapter.basePath;
var d3 = require(path+"//d3.v7.min.js");

// Function to change nulls to a default
function nullCheck(arr) {
    console.log(arr)
    let value = DateTime.fromISO("01:01:01").toJSDate()
    if (arr !== null) {
      let value = DateTime.fromISO(arr).toJSDate()
      return value
    } else {
      console.log("null found: replace with: " + value)
      return value
    }
 }

let x = []
let y = []

// These are ordered thanks to the mapping via cday
pg.forEach(e => x.push(e[1]))
pg.forEach(e => y.push(e[0]))

// Set plotly data and render
var data = [
 {x: x,
  y: y}
];

var layout = {title:"Timeseries of Sunrise times per day"};
var config = {displaylogo:false};

window.renderPlotly(this.container, data, layout, config)

Which makes this:

image

The big dip being the default value ("01:01:01" in my case).

The key parts for you will be the nullCheck function as well as the data assignment. Mine is set up for dateTime values but if you were to adapt that to something more like:

function nullCheck(arr) {
    console.log(arr)
        // value is the default
    let value = 0 
    if (arr !== null) {
      let value = arr
      return value
    } else {
      console.log("null found: replace with: " + value)
      return value
    }
 }

Because of how .map works this will create a new array with the new data.

and

pg.forEach(e => x.push(e[1]))
pg.forEach(e => y.push(e[0]))

As in my case, I have mapped to data as (converted(values), date, file.cdate) having only used the cdate as a true value for sorting earlier. Hopefully, it works for you, and hopefully, it's self-explanatory enough that you can adapt it for yourself. if not then feel free to message me.

kalmir commented 2 years ago

@DLBPointon , so much work, much appreciated! I am in the middle of devising own solution, much less elegant way based on looping through date ranges, still not working though. So thanks for your code! I will use it as well as try to understand it to learn something from this.