Idiomatic way to update multiple attributes in d3

If I have a function which computes multiple attributes to set on each SVG element in a selection, based on both bound data and additional outside variables, what is the most idiomatic way to call the function once per datum and then update all of the attributes at once?

I notice d3-selection-multi. Is that generally the best approach? How would one go about importing both d3 and that in an observable notebook? I tried d3 = require("d3", "d3-selection-multi") but that doesn’t seem to cut it. I get an error that attrs isn’t a function. (I admit I still don’t really understand the ins and outs of require, etc.)

My current code in one notebook

ratio_circles
  .attr("cx", d => circlewithratio(p1, p2, d)[0])
  .attr("cy", d => circlewithratio(p1, p2, d)[1])
  .attr("r", d => circlewithratio(p1, p2, d)[2])
  .attr("stroke", (d,i) => (i % 4 ? "#eee" : "#ccc"));      

…is not very elegant.

Unfortunately, you can’t mix-and-match D3 modules with the d3 default bundle using require. If you want to use d3-selection-multi, you must import the D3 modules separately:

d3 = require("d3-selection", "d3-selection-multi")

Another approach is to use a closure:

ratio_circles
  .each(update_ratio_circle)
  .attr("stroke", (d, i) => i % 4 ? "#eee" : "#ccc");

function update_ratio_circle(d) {
  const [cx, cy, r] = circlewithratio(p1, p2, d);
  d3.select(this).attr("cx", cx).attr("cy", cy).attr("r", r);
}

Probably more natural would be to store the circle in the data:

const ratio_circles = svg.append("g")
  .selectAll("circle")
  .data(ratio_list.map(ratio => ({ratio})))
  …

Then use selection.datum to update it:

ratio_circles
  .datum(({ratio}) => ({ratio, ...circlewithratio(p1, p2, ratio)}))
  .attr("cx", d => d[0])
  .attr("cy", d => d[1])
  .attr("r", d => d[2])
  …

I’m having trouble getting either this one – d3 = require("d3-selection", "d3-selection-multi") – or the datum version to work. I’ll keep fiddling with it.