🏠 back to Observable

Steps from Vanilla JS to observable


#1

Hello,

On my journey to learn d3, I often have difficulties passing from vanilla JS (standard js right?) to observable code and vice versa. Problems rise when I have external data or when I need to update the chart (zoom, pan ect…)

So my question is:
Is there a well defined methodology to port code from let’s say https://bl.ocks.org/ to observable and vice versa ?

(edit: seen this, https://beta.observablehq.com/@jashkenas/downloading-and-embedding-notebooks I guess that it’s easy to get observable code working in standard javascript, but I need more time see how… well looking at runtime lib and examples it looks straight forward, Thank you Mike)

I did write down some of the steps I used in my last porting attempt (https://beta.observablehq.com/@maliky/d3js-enter-update-and-exit) but since I did not succeed entirely I must be missing a lot.

Here’s another attempt (https://beta.observablehq.com/@maliky/conversation-visu) to port something working on its own elsewhere (https://idev.kone.ci/visu/uvci/conv5) to observable.


#2

I wrote some notes on porting a different block a while ago that you can find here: https://beta.observablehq.com/@bryangingechen/force-update-pattern-in-observable

Feel free to ask if anything is unclear!

I don’t know of a “well-defined” methodology, but I did put some tips at the end of the above notebook. For me the most important thing was to understand how the code that I’m porting works inside and out before trying to adapt it to Observable’s runtime.


#3

Here’s a quick fork of your first notebook that gets it running; hope it helps:


#4

Thank you very much Bryan. You are of great help. Seen a correction after trying several hours is useful. I continued with your solution. Rephrasing and clarifying the code. I now understand that the cell should return a DOM element that will be inserted in the div attached to the cell. But I’m left with what looks like subtleties to the beginner that I am.

For example, why the following 2 selects are not returning the exact same DOM element ?
foo = html<button id="fooId">Change data</button>
d3.select(foo)
d3.select("#fooid")

Same for :
DOM.svg(width, height) and html<svg width=width, height=height></svg>

Also in your correction you wrote
const div = html'<div>;
With just with one div as in the html doc example where it’s written :
html<h1>Hello, world!
with just one h1.
Is that just a short cut to html<div></div> ?

Thank you very much


#5

Here’s my (possibly inaccurate) understanding. When select is given a string, it searches the current DOM tree using the string as a CSS selector. foo hasn’t yet been appended to the DOM tree so you get the null selection. Hopefully someone will jump in and correct me if this is way off.

In the second one you probably mean:

html`<svg width="width" height="height"></svg>`

These will create slightly different svg elements, check the Observable stdlib docs. DOM.svg also specifies a viewBox in the svg element it creates.

Yep, the html function conveniently closes all HTML tags in the string.


#6

Great, your explanation for the foo makes a lot of sens if the three lignes are in the same cell but if each is in a different cell then foo may be rendered when select search the DOM tree.
But in the latter case there is still a difference with the _parents

Thank you for the precision about the viewBox I had the stdlib docs in front of me but did not notice that difference.

and thank you html function :slight_smile:


#7

Observable doesn’t run cells in top-down order; it uses references to compute topological order. See this notebook:

If you use document.querySelector, you’re reaching through the DOM rather than using an explicit reference, and the behavior of your notebook will be nondeterministic.


#8

Ok, I got that. I even made a draft translation of your notebook how Observable Runs(fr) to make sur I had read it.

From your answer I understand that
d3.select(bouton2) is by construction an orphan DOM element, so my mistake was to use a document.querySelector and not remove its parents. That would make sense…

Merci.

(edit: also this notebook is of great help https://beta.observablehq.com/@tmcw/observable-anti-patterns-and-code-smells
Observable needs explicite reference to other cells, but in d3.select('#monBouton2') observable does not know what cell it should execute before the current)


#9

If a cell references a variable bouton2, the referencing cell will wait until the cell that defines bouton2 runs (and adds the button to the DOM) before running. Thus the referencing cell will see the bouton2 as an HTMLButtonElement that has been added to the DOM. In addition, the referencing cell will re-run automatically whenever the defining cell runs, for example if you change the definition or if the definition depends on some other changing value.

On the other hand, if a cell says

document.querySelector("#monBouton2")

it might run before the bouton2 cell runs, or it might not, meaning it’s uncertain whether it will find a matching element. Additionally, such a cell would not re-run automatically when the bouton2 definition changes, because Observable can’t see the dependency between the two cells.


#10

Fun fact: For cells without async dependencies the execution order appears to be bottom to top:


#11

This is likely just a coincidence. The order is not guaranteed. And equally important, cells are re-evaluated only if a cell they reference changes (other than running them explicitly). Dependencies should be explicit.


#12

Didn’t mean to imply it was something to be relied on. But I’ve added, removed and reordered cells, and the order always remained the same. Just something I though was curious. :slight_smile: