I have a buffer() function based on npm:geotoolbox. The 1st parameter is the size of the returned svg. The 2nd parameter is the buffer distance.
In work in a div element :
This works.
${buffer(200, k)}
But this returns [object Promise]
${resize((width) => buffer(width, k))}
How do I resolve this Promise?
cinxmo
March 8, 2024, 11:02pm
3
Hi,
Is the buffer()
function you mentioned a wrapper around https://github.com/riatelab/geotoolbox ? Could you post the full code snippet including the div?
Thanks!
The component (js file)
// Buffer
export async function buffer(width, k) {
const aus_simpl = await geo.buffer(aus, { dist: k });
let svg = viz.create({
projection: d3.geoEquirectangular(),
domain: aus_simpl,
margin: 1,
width,
});
svg.path({
data: aus,
fill: "#D75C48",
fillOpacity: 0.3,
stroke: "none",
});
svg.path({
data: aus_simpl,
fill: "none",
stroke: "#D75C48",
strokeWidth: 2,
strokeDasharray: [4, 2],
});
return Promise.resolve(svg.render());
}
and the page (md file)
<div class="card">
<h2><b>Buffer</b></h2>
${buffer_k}
<br/>
${buffer(200, bufferk)}
</div>
cinxmo
March 9, 2024, 4:59pm
5
Hi,
The function passed into resize needs to return an element. ${buffer(200, k)}
works because it is automatically resolved. You can modify this file locally: framework/src/client/stdlib/resize.ts at 2945151b5a6abacfadf338bac7d10544ae576843 · observablehq/framework · GitHub to
const observer = new ResizeObserver(async ([entry]) => {
const {width, height} = entry.contentRect;
while (div.lastChild) div.lastChild.remove();
if (width > 0) {
const child = await run(width, height);
// prevent feedback loop if height is used
if (run.length !== 1 && isElement(child)) child.style.position = "absolute";
div.append(child);
}
});
Hope this helps!
You don’t want to recompute the buffer every time the window resizes, I expect. So probably better to move the async buffer calculation outside of the render call, and instead pass it in. That way your render function can be sync.
1 Like
To give a complete example of moving the buffer calculation outside of resize:
```js
import * as geo from "npm:geotoolbox";
```
```js
const world = fetch("https://static.observableusercontent.com/files/f00996f6cc84c5676a169075acba6d90cabb161fd36c4c91e5106cb8316028c36183a1e58c5d4260dae229421d91eb8cc15555eea39c4edc1f2a4e897082f2d8?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27countries.json").then((_) => _.json());
```
```js
const dist = view(Inputs.range([-1000, 5000], {label: "Distance", value: 300, step: 100}));
const quadsegs = view(Inputs.range([1, 8], {label: "quadsegs", value: 8, step: 1}));
```
```js
const buffered = geo.buffer(world, {dist, quadsegs});
```
<div class="card">
${resize((width) =>
Plot.plot({
width,
projection: "equal-earth",
marks: [
Plot.geo(world, {stroke: "blue"}),
Plot.geo(buffered, {stroke: "red"})
]
})
)}
</div>
1 Like
If you really want to have an async component (like, you don’t want to declare buffered
as a top-level variable as above), another option is to return an element synchronously, and then add the async content later. That looks like this:
function buffermap(data, {width, dist, quadsegs}) {
const div = document.createElement("div");
geo.buffer(world, {dist, quadsegs}).then((buffered) => {
div.append(Plot.plot({
width,
projection: "equal-earth",
marks: [
Plot.geo(world, {stroke: "blue"}),
Plot.geo(buffered, {stroke: "red"})
]
}));
});
return div;
}
(You could even use a WeakMap to cache the buffered calculation so that you don’t have to recompute it on resize.)
3 Likes