I’m having trouble making vega-lite plots adapt to the size of the browser viewport. The only thing that works reliably to set the plot physical size is to set the width and height properties to specific values in the plot spec; but then, if the browser viewport is too small I don’t even get a horizontal scrollbar, the plot stays at its fixed size and just gets cut off.
Not sure if you’re using notebooks or Observable Framework, but here’s how I would do it in Framework:
const chart = await vl.render({
spec: {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": -1,
"height": 250,
"autosize": {"type": "fit", "contains": "padding"},
"data": {"url": "https://vega.github.io/vega-lite/data/cars.json"},
"mark": "bar",
"encoding": {
"x": {"field": "Origin"},
"y": {"aggregate": "count", "title": "Number of Cars"}
}
}
});
display(resize((width) => {
chart.value.width(width);
chart.value.run();
return chart;
}));
View live: Vega-Lite responsive bar chart | Observable Framework
Unfortunately the "width": "container"
built-in to Vega-Lite doesn’t work because it’s limited to resize events on the window rather than using ResizeObserver, but I filed a feature request for the latter so maybe it’ll get better in the future.
Thanks for responding. Yes, I was frustrated with width: "container"
not working.
Is there any way to use this code in a js module where I put functions that generate my plots (essentially from a couple of templates), so I won’t have to repeat the display()
call verbatim for each plot?
Sure, you can do something like this:
async function vlresize(spec) {
const chart = await vl.render({spec});
return resize((width) => {
chart.value.width(width);
chart.value.run();
return chart;
});
}
And then to use:
vlresize({
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": -1,
"height": 250,
"autosize": {"type": "fit", "contains": "padding"},
"data": {"url": "https://vega.github.io/vega-lite/data/cars.json"},
"mark": "bar",
"encoding": {
"x": {"field": "Origin"},
"y": {"aggregate": "count", "title": "Number of Cars"}
}
})
But the vlresize()
definition must be copied into each md file that needs it, rather than be imported from a module - otherwise I will get resize is not a function
, right? Or am I missing something?
You can import resize
from the Observable standard library. So you can make a vlresize.js
module like this (I also added the default autosize
and initial width
for convenience):
import {resize} from "npm:@observablehq/stdlib";
import vl from "observablehq:stdlib/vega-lite";
export async function vlresize(
{autosize = {type: "fit", contains: "padding"}, ...spec},
{minWidth = 0, maxWidth = Infinity} = {}
) {
const chart = await vl.render({spec: {...spec, width: -1, autosize}});
return resize((width) => {
chart.value.width(Math.max(minWidth, Math.min(maxWidth, width)));
chart.value.run();
return chart;
});
}
And then import it into your page like this:
import {vlresize} from "./vlresize.js";
And call it like this:
vlresize({
"height": 250,
"data": {"url": "https://vega.github.io/vega-lite/data/cars.json"},
"mark": "bar",
"encoding": {
"x": {"field": "Cylinders"},
"y": {"aggregate": "count", "title": "Number of cars"}
}
})
Yes, I’ve been trying something along these lines. But, for some reason it does not work for me, unless I wrap the plot in a <div style:"width=100%">
. Without the div the plot stays at the vega-lite default width afaics. With the div it works as expected.
BTW, why the two different forms of sources in the imports at the top of your example?
Hard to say without seeing the code. Maybe you could share a reproduction? Be aware that paragraph (p
) elements have a default max-width of 640px. But as you can see from the live example, the code I posted works as advertised. I guess I forgot to mention I updated the live example here:
https://observablehq.observablehq.cloud/framework-example-vega-responsive/
What do you mean by different forms of imports? The first npm:
import is how you import from npm in Framework. (You can also use bare module specifiers if you want to import from node_modules
, but then you’ll have to install things yourself using a package manager rather than letting Framework install it for you on use.) You can use an npm:
import for Vega-Lite too, it’s just a bit more verbose because you need to import three separate things and string them together like so:
import * as vega from "npm:vega";
import * as vegaLite from "npm:vega-lite";
import * as vegaLiteApi from "npm:vega-lite-api";
const vl = vegaLiteApi.register(vega, vegaLite);
This is described here:
https://observablehq.com/framework/lib/vega-lite
I didn’t want to repeat this boilerplate so I cheated a little and used the definition of the vl
built-in supplied by the Observable standard library, which is defined here:
They’re exactly equivalent so use whichever you prefer.
Okay, thanks for clarifying. So, how do you make your example plot not be a part of a <p>
element? Other than wrapping it in some other html? Nb. the view-source link in your example doesn’t work (404).
What I meant about the imports is that import { resize } from "observablehq:stdlib"
works just as well, so I was wondering why not write it like that.
I use a fenced code block (```js
) rather than an inline expression (${…}
).
If you want to use a top-level inline expression and you don’t want it to be in a paragraph, then you must make an HTML block in Markdown by wrapping it in some HTML such as a <div>
tag. So this is implicitly a paragraph:
${…}
But this is a DIV:
<div>${…}</div>
Of course if you don’t need anything around your content then I’d just use a fenced code block (```js
).
Regarding importing observablehq:stdlib
, that’s not documented and currently intended for internal use. The documented way to import the Observable standard library is to import npm:@observablehq/stdlib
. Both will work though, and are equivalent. Maybe we’ll formally support observablehq:
in the future to represent these built-in libraries.
Our intent here was to favor a more standard way of importing the libraries so that you can import e.g. @observablehq/stdlib
in a Node.js program and it works the same way. But we have an outstanding issue #852 where we haven’t actually published a number of these libraries to npm yet…
Great, that pretty much explains all I was missing. When I have further questions (and I’m sure I will), I guess it would make more sense to open another thread. Thanks again.