Plotting plug

Plug to plot things

page1
---
date: 2025-01-01
squirrels: 2
cats: 4
---

page1
---
date: 2025-01-03
squirrels: 4
cats: 4
---

Replicate a subset of the functionality from some plotting library. Perhaps the api could look something like

```plotplug
type: scatter
data: query(from tags.page select {x: date, y: [squirrels, cats]})

This could be achieved using Chartist and some SpaceLua.

I have tried Chart.js with below space-lua:

local chartjs = js.import("https://cdn.jsdelivr.net/npm/[email protected]/auto/+esm")

function chart(config)
  local canvas = dom.canvas {
    id = "chart-" .. os.time() .. "-" .. math.random()
  }
  local rendered = false

  local function timeout()
    local target = js.window.document.getElementById(canvas.id)
    if not target then
      js.window.setTimeout(timeout, 100)
      return
    end

    if rendered then
      return
    end

    rendered = true
    js.new(chartjs.Chart, target, config)
  end

  js.window.setTimeout(timeout, 100)
  return widget.html(canvas)
end

And it should be used as inline expression, with similar config of its JS version:

${chart {
    type = "bar",
    data = {
      labels = {"A", "B", "C"},
      datasets = {{data = {10, 20, 30}}}
    }
}}

It then turns into something like:

But it has some limits:

  1. current js interop doesn’t import static member of an exported class. one of the static member is Chart.register, which is used to enable plugins. so plugins can’t be used
  2. Chart.js uses canvas to render chart, and it loses all drawn content once it’s passed to main frame. so I have to use a timeout to monitor when it’s mounted. it’s better to have a native “mount” event for widget

Nice!
This can be adapted into a simple inline expression and a YAML block with configuration and datapoints.

#plot
type: bar
labels: ['eat', 'sleep', 'code']
name: hrs
data: [2,8,1]
#plot
type: scatter
name: func1
data:
- [0,0]
- [2,2]
- [3,9]

or simply:

#plot
data: [3,2,4]

${plt.plot()} for first local #plot data block or
${plt.plot('objname', i)} for i-th local #objname data block

space-lua:


plt = plt or {}
plt.queryLocalObjs = function (object, idx)
  local pg = editor.getCurrentPage()
  local ret = query[[from index.tag(object) where page == pg order by pos]]
  if idx then
    ret = ret[idx]
  end
  return ret
end

local chartjs = js.import("https://cdn.jsdelivr.net/npm/[email protected]/auto/+esm")

plt.plot = function (obj, idx)
  local obj = obj or 'plot'
  local idx = idx or 1
  local plotcfg = plt.queryLocalObjs(obj, idx)
  local config = {
    type = plotcfg.type or 'bar',
    data = {
      labels = plotcfg.labels or plotcfg.data,
      datasets = {
        {
          label = plotcfg.name or {'trace1'},
          showLine = 'true',
          data = plotcfg.data
        }
      }
    }
  }
  
  local canvas = dom.canvas {
    id = "chart-" .. os.time() .. "-" .. math.random()
  }
  local rendered = false

  local function timeout()
    local target = js.window.document.getElementById(canvas.id)
    if not target then
      js.window.setTimeout(timeout, 100)
      return
    end

    if rendered then
      return
    end

    rendered = true
    js.new(chartjs.Chart, target, config)
  end

  js.window.setTimeout(timeout, 100)
  return widget.html(canvas)
end

Is there any way right now to get Silverbullet to distribute JS libraries locally? @zef

It’ll be useful here, since this won’t be available offline.

I think we can do that with the new Libraries in the edge version. But it still requires online when you first install that library.

Let me try it later.

I have created a plug to provide offline chart plot here: GitHub - LelouchHe/silverbullet-chart

You can install it from Library Manager with URI ghr:LelouchHe/silverbullet-chart/PLUG.md

For now, it bundles Chart.js (4.5.1) and chartjs-plugin-annotation (3.1.0)

Feel free to try and create a pull request to add new plugins.

Fantastic. The next step is to publish It as SB library

Work in progress.

Hi,

I'm using Lelouch's Plug and it is working fine so far (Thanks for that, @LelouchHe !)
However, the graphs appear pretty small on my screen:

Is there any way to alter the displayed size?
Especially, if I have lots of datapoints it's getting hard to read... So it would be nice to have something like "width=70%" or similar.

I can not say, if there is some setting / styling within my Space, which affects that, but I could not find anything...

Thanks a lot for any hint!
Nico

Additional information:
Looks like the size is browser specific. When I open the page in Chrome instead of Firefox, the graphs are using much more width, in fact, it is way too big there...

OK, after some tinkering, I found a workaround.

I added two lines to the canvas definition in the chart LUA-function:

  local canvas = dom.canvas {
    id = "chart-" .. os.time() .. "-" .. math.random(),
    width = config.options.width or nil,
    height = config.options.height or nil,
  }

This way I could add some new options to the config-Object of the graph:

    options = {
      responsive = false,
      width= 1000,
      height = 300,
      plugins = { ....

@LelouchHe: Maybe something like that could be added in your original version, too?

Another working approach seems to be adding a class for the canvas, like this:

  local canvas = dom.canvas {
  (...)
    class = config.class or nil
  (...)
  }

and a style:

.chart {
  width: 50%;
  height: 100wv;
}

And adding that to the function call (one must add "responsive = false", too!):

${chart {
(...)
    class = "chart",
(...)
    options = {
            responsive = false,
      plugins = {
(...)