Make a custom chart responsive
A responsive chart fills its available space and redraws when that space changes: when a user resizes the widget on a dashboard, switches to a phone, or drags the panel divider in a report. A chart with a fixed pixel size, by contrast, stays the same size no matter how much room it has, leaving empty gutters or clipping at the edges.
Holistics automatically resizes every custom chart to fit its widget and redraws it when the widget changes, for both Vega-Lite (@vgl) and Vega (@vg). You do not wire up any resize handling. The work, when there is any, is making sure the chart looks right at every size, which mostly means not pinning its contents to fixed pixel values. The details differ slightly between the two languages.
Make a Vega-Lite chart responsive
Vega-Lite charts are responsive out of the box. Holistics tells Vega-Lite to fit the widget, so as long as you do not hard-code a size, the chart stretches to fill the widget and redraws when it changes. To keep that working:
-
Omit
widthandheightfrom the spec. Holistics already fits the chart to the widget. The moment you set"width": 400, the chart locks to 400px and stops stretching. The same goes forheight. Most of the library's@vglcharts (like the diverging bar chart) set no size at all. -
Use
"width": "container"if you copied a sized spec. Many examples from the Vega-Lite gallery ship with a fixed size. Either delete those properties or set them to"container"so the chart reads its widget's dimensions:template: @vgl {"width": "container","height": "container","data": { "values": @{values} },"mark": "bar","encoding": { ... }};;
Limitation: Multi-view Vega-Lite layouts (facet, row, column, concat, repeat) lay out their panels at a natural size and do not stretch to the widget, so they do not fill it. For a responsive grid of small multiples, build it in Vega instead (the faceted sparkline does exactly this).
Make a Vega chart responsive
Holistics keeps a @vg chart's width and height signals in sync with the widget (resizing on both window and container changes) and adds an autosize that keeps the axes inside, so the canvas already tracks the widget. The chart relayouts to fit as long as its scales and marks read those signals. It looks stuck only when its scale ranges use fixed numbers, so the marks stay one size while the canvas around them resizes.
The starting point: a fixed-size chart
Here is a plain Vega bar chart whose scales use fixed pixel ranges. Holistics still resizes the canvas to the widget, but the bars stay pinned to 400px wide because the ranges are hard-coded numbers.
CustomChartDef responsive_bar {
label: 'Responsive Bar'
fields {
field category { label: 'Category' type: 'dimension' }
field value { label: 'Value' type: 'measure' }
}
template: @vg {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 300,
"data": [
{
"name": "table",
"values": @{values},
"transform": [
{ "type": "formula", "expr": "datum['@{fields.category.name}']", "as": "category" },
{ "type": "formula", "expr": "datum['@{fields.value.name}']", "as": "amount" }
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"domain": { "data": "table", "field": "category" },
"range": [0, 400],
"padding": 0.1
},
{
"name": "y",
"type": "linear",
"nice": true,
"domain": { "data": "table", "field": "amount" },
"range": [300, 0]
}
],
"axes": [
{ "orient": "bottom", "scale": "x" },
{ "orient": "left", "scale": "y" }
],
"marks": [
{
"type": "rect",
"from": { "data": "table" },
"encode": {
"update": {
"x": { "scale": "x", "field": "category" },
"width": { "scale": "x", "band": 1 },
"y": { "scale": "y", "field": "amount" },
"y2": { "scale": "y", "value": 0 }
}
}
}
]
};;
}
The highlighted lines are the problem: each scale's range is a fixed number, so the marks cannot grow. (Holistics overrides the top-level width/height above, so they do not pin the chart, but it is cleaner to drop them.)
Make it responsive
Bind each scale's range to the widget with Vega's "width" and "height" shorthand instead of fixed numbers:
"scales": [
{
"name": "x",
"type": "band",
"domain": { "data": "table", "field": "category" },
"range": "width",
"padding": 0.1
},
{
"name": "y",
"type": "linear",
"nice": true,
"domain": { "data": "table", "field": "amount" },
"range": "height"
}
]
"width" and "height" are the signals Holistics drives from the widget, so the bars now follow it and redraw on resize. (The "height" shorthand also flips the y range to run bottom-to-top for you, so larger values sit higher.) You can also drop the top-level "width"/"height" while you are here, since Holistics overrides them. Save the file and resize the widget on a dashboard to see it reflow.
Here is the full definition after both changes:
Complete responsive definition
CustomChartDef responsive_bar {
label: 'Responsive Bar'
fields {
field category { label: 'Category' type: 'dimension' }
field value { label: 'Value' type: 'measure' }
}
template: @vg {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"data": [
{
"name": "table",
"values": @{values},
"transform": [
{ "type": "formula", "expr": "datum['@{fields.category.name}']", "as": "category" },
{ "type": "formula", "expr": "datum['@{fields.value.name}']", "as": "amount" }
]
}
],
"scales": [
{
"name": "x",
"type": "band",
"domain": { "data": "table", "field": "category" },
"range": "width",
"padding": 0.1
},
{
"name": "y",
"type": "linear",
"nice": true,
"domain": { "data": "table", "field": "amount" },
"range": "height"
}
],
"axes": [
{ "orient": "bottom", "scale": "x" },
{ "orient": "left", "scale": "y" }
],
"marks": [
{
"type": "rect",
"from": { "data": "table" },
"encode": {
"update": {
"x": { "scale": "x", "field": "category" },
"width": { "scale": "x", "band": 1 },
"y": { "scale": "y", "field": "amount" },
"y2": { "scale": "y", "value": 0 }
}
}
}
]
};;
}
Adjust the layout with derived signals (optional)
The shorthand stretches the plot to the full widget. When you need a margin around the plot, or a layout you compute from the size, you do not read the widget yourself. Holistics already maintains the width and height signals, so reference them from your own derived signals:
"signals": [
{ "name": "plot_width", "update": "width - 50" }, // leave room for a wide y-axis
{ "name": "cell_width", "update": "width / 3" } // a 3-column small-multiple grid
]
Use those signals in your scale ranges or mark positions. The faceted sparkline builds its whole grid this way, deriving a per-cell size from width and height.
Test that it actually resizes
A chart can look fine at its default size and still remain fixed. To confirm it is responsive:
- Add the chart to a dashboard and drag the widget's corner to resize it. The chart should follow, not leave a gap or clip at the edges.
- Preview the dashboard at a narrow width (or open it on a phone) and check that nothing overflows horizontally.
- If the bars or marks do not resize (in
@vg), look for scale ranges or mark sizes that use fixed numbers instead of the"width"/"height"shorthand.
Next steps
- Style a custom chart to match the look of Holistics's built-in charts.
- Make a custom chart interactive so users can click and drag to filter.
- Browse the Custom Chart Library for ready-made responsive templates you can copy.