Showing cell evaluation progress

What is the current state-of-the-art way of giving the user a clearer indication (over and above the grey vertical line that we are used to, but which many may not interpret fully) that a cell is being evaluated, before it renders? I’ve seen some answers but wondered what the latest, robust and most elegant (because I’m lazy) solution was. I don’t need a progress value: a busy spinner or equivalent would do.

Have a look at this notebook:

Thanks @mootari. I’m struggling a little bit to apply this to a generic cell that is evaluating on page load. Can I just wrap the code in a cell with something? For context (not asking to have my homework done for me), I have a notebook World Government Revenue per Capita / GRADE | Observable where the choro cell takes a while to load.

Can you identify the cells that cause the delay? You will have to modify those, as any downstream cell (that is, dependent cell) will only evaluate once its dependencies have resolved.

If you’re pressed for time you could also unlink the choro cell and have it depend on a mutable cell that only gets set once the data has been loaded. However, I’d advise against this approach as it creates problems of its own.

I think

import {data} from "@stuwilmur/grade-determinants-of-health-data"

is the likely candidate. In this particular case, I could actually trim the data needed significantly and just attach that as an attachment, but in other situations I will probably need the whole dataset.

Thank you for the suggestions. Yes it’s probably more development than I can spare for this small project, and I don’t want to introduce additional problems, complexity or overhead really.

My other thought was, I use this notebook as an iframe in another webpage, where it’s probably more important that the user know something is happening: is there a neat way that the Observable runtime can tell my page it’s ready and then restyle the page on this trigger? E.g. style a loading message which is replaced by the iframe when it’s ready to go?

I don’t think the size of the data is the problem, but how you retrieve it. When you fetch it via esm.run (which is jsdelivr), jsdelivr first has to resolve the module, fetch its source, then transpile and finally serve it. You’ll notice that subsequent requests resolve much faster, even if you disable your browser cache.

It also seems that you’re loading a lot of dependencies that are probably meant to be dev dependencies.

Thanks that is some useful learning for me. I hadn’t considered the CDN overhead. Unfortunately, this part is unlikely to change in general. Unfortunately it’s still the first request that is going to make users think something is broken.

Which dependencies did you notice?

You can retrieve your data directly:

d3.csv("https://raw.githubusercontent.com/stuwilmur/GRADE-DOH-data/main/assets/data/BASE%20data%202022%20interpolated.csv")

If you need some placeholder, have that cell yield twice:

data = {
  yield null;
  yield d3.csv("https://raw.githubusercontent.com/stuwilmur/GRADE-DOH-data/main/assets/data/BASE%20data%202022%20interpolated.csv")
}

Then in your choro cell you can display a placeholder if data is null. It will automatically reevaluate on the second yield.

It’s d3 which loads all its libraries individually because it gets imported as a module. If you want to prepackage your data you’re likely better off publishing it as JSON or Parquet.

Thank you this is immensely helpful, addressing both my immediate and slightly bigger problems.

I’m now retrieving the data directly; this is a great suggestion thank you. I’ve added a second yield as suggested.

The final silly question is how to get a placeholder to appear in my choro cell: I tried something like:

choro = {
  if (data == null){return md`Loading...`} else
return bertin.draw({
\\ drawing stuff
})}

but realise I’m going wrong somewhere.

There are other cells involved that also need to handle the null value. Have a look at your minimap to surface the dependencies:

You may also want to look out for errors in your dev tools console.

1 Like

Phew this stuff is hard for me :hot_face:

Am I on the right track if I do

yearData = {
  yield null;
  yield data.filter(x=>x.year == year)
}

and then choro as above?

Actually the more I think about it, chasing dependencies is kind of the thing I was hoping to avoid, so maybe I won’t go down this route, or any other route. Perhaps all I can really justify right now is a “please be patient while this page loads” message at the top. It might be really neat if Observable could have a slightly more intuitive indication of evaluation/execution though.

Aha finally I think I got something working.

Thank you as every for your patience, of which I am undeserving!

1 Like

This guide might help:

So, if loading the data is slow and you want to show a loading indicator, you could say:

viewof data = {
  const div = htl.html`<div>Loading…</div>`;
  const promise = d3.csv("https://raw.githubusercontent.com/stuwilmur/GRADE-DOH-data/main/assets/data/BASE%20data%202022%20interpolated.csv");
  div.value = promise;
  promise.then(
    () => div.textContent = "Ready!",
    () => div.textContent = "Error!"
  );
  return div;
}

To answer your other question, a cell like this will wait until data is loaded before it runs, so it’ll be just as slow as data:

data2 = {
  yield null;
  yield data;
}

If you want to put your data loading code in another cell but not wait for that data to load, then you can wrap it in a function:

async function loadData() {
  return d3.csv(…); // or whatever
}

Then you can say:

data = {
  yield;
  yield loadData();
}
1 Like