TheoAbiel
TheoAbiel

Reputation: 33

Vega chart - how to make the chart responsive

I'm trying to create a simple line chart using Vega chart library. My problem is that I can't make it to be responsive. I've started from the example that they provided, but I can't make the chart to dimension relative to the window size. Probably this is not an out of the box functionality of Vega chart library.

Could you help me to achieve this? Or do you have any suggestions on how can I create a Vega chart that is auto-resize based on the screen size?

Thanks in advance!

UPDATE: Here is the changes that I did so far:

window.onresize = function (event) {
  view.signal('width', event.target.innerWidth - 50)
      .signal('height', event.target.innerHeight - 50)
      .run('enter');
}

Link to fiddle: https://jsfiddle.net/TheoAbiel/ehtu6xkj/18/

This isn't quite ok and I need to improve it. Do you have other suggestions?

Upvotes: 3

Views: 4896

Answers (1)

Native Dev
Native Dev

Reputation: 7402

So long as there aren't certain interactions that depend on the math from the original view dimensions, or there aren't multiple nested charts this example should help.

Note: autosize:{type:'fit',resize:true} in the spec helps with multiple nested graphs, but causes some jumpy behavior on hover as seen in this example.

This snippet supports SVG & Canvas. There are also two specs to choose from.

Run the demo below and resize your browser! The responsiveness is based on the parent element, and in this case, the parent element is 50% of the body. Note that the DOM will be cleared after 10 seconds!

// const spec={$schema:'https://vega.github.io/schema/vega/v4.json',width:200,height:200,autosize:{type:'fit',resize:true,},signals:[{name:'startAngle',value:0,bind:{input:'range',min:0,max:6.29,step:0.01},},{name:'endAngle',value:6.29,bind:{input:'range',min:0,max:6.29,step:0.01},},{name:'padAngle',value:0,bind:{input:'range',min:0,max:0.1},},{name:'innerRadius',value:0,bind:{input:'range',min:0,max:90,step:1},},{name:'cornerRadius',value:0,bind:{input:'range',min:0,max:10,step:0.5},},{name:'sort',value:false,bind:{input:'checkbox'},},],data:[{name:'table',values:[{id:1,field:4},{id:2,field:6},{id:3,field:10},{id:4,field:3},{id:5,field:7},{id:6,field:8},],transform:[{type:'pie',field:'field',startAngle:{signal:'startAngle'},endAngle:{signal:'endAngle'},sort:{signal:'sort'},},],},],scales:[{name:'color',type:'ordinal',range:{scheme:'category20'}},],marks:[{type:'arc',from:{data:'table'},encode:{enter:{fill:{scale:'color',field:'id'},x:{signal:'width/2'},y:{signal:'height/2'},},update:{startAngle:{field:'startAngle'},endAngle:{field:'endAngle'},padAngle:{signal:'padAngle'},innerRadius:{signal:'innerRadius'},outerRadius:{signal:'width/2'},cornerRadius:{signal:'cornerRadius'},},},},],};

const spec={$schema:'https://vega.github.io/schema/vega/v4.json',width:400,height:200,autosize:{type:'fit',resize:true,},data:[{name:'table',values:[{category:'A',amount:28},{category:'B',amount:55},{category:'C',amount:43},{category:'D',amount:91},{category:'E',amount:81},{category:'F',amount:53},{category:'G',amount:19},{category:'H',amount:87},],},],signals:[{name:'tooltip',value:{},on:[{events:'rect:mouseover',update:'datum'},{events:'rect:mouseout',update:'{}'},],},],scales:[{name:'xscale',type:'band',domain:{data:'table',field:'category'},range:'width',padding:0.05,round:true,},{name:'yscale',domain:{data:'table',field:'amount'},nice:true,range:'height',},],axes:[{orient:'bottom',scale:'xscale'},{orient:'left',scale:'yscale'},],marks:[{type:'rect',from:{data:'table'},encode:{enter:{x:{scale:'xscale',field:'category'},width:{scale:'xscale',band:1},y:{scale:'yscale',field:'amount'},y2:{scale:'yscale',value:0},},update:{fill:{value:'steelblue'},},hover:{fill:{value:'red'},},},},{type:'text',encode:{enter:{align:{value:'center'},baseline:{value:'bottom'},fill:{value:'#333'},},update:{x:{scale:'xscale',signal:'tooltip.category',band:0.5},y:{scale:'yscale',signal:'tooltip.amount',offset:-2},text:{signal:'tooltip.amount'},fillOpacity:[{test:'datum===tooltip',value:0},{value:1},],},},},],};

// Vega object
const vegaDemo = {
  renderType: 'svg', // 'canvas',
  responsive: true,
  widthHeightRatio: spec.width / spec.height,
  wrapperElem: document.querySelector('#vega-view'),
};

// Create the view
vegaDemo.view = new vega.View(vega.parse(spec))
  .renderer(vegaDemo.renderType)
  .resize()
  .width(vegaDemo.wrapperElem.offsetWidth)
  .height(vegaDemo.wrapperElem.offsetWidth / vegaDemo.widthHeightRatio)
  .initialize(vegaDemo.wrapperElem)
  .run();

// If responsive
if (vegaDemo.responsive) {
  if (vegaDemo.renderType === 'canvas') {
    // For Canvas views
    vegaDemo.resize = () => {
      const width = vegaDemo.wrapperElem.offsetWidth;
      vegaDemo.view
      .width(width)
      .height(width / vegaDemo.widthHeightRatio)
      .run();
    };
    window.addEventListener('resize', vegaDemo.resize);
  } else {
    // For SVG views
    function responsive() {
      // Remove width/height attributes of Vega SVG and let viewBox handle the responsive behavior
      const viewDom = vegaDemo.wrapperElem.firstElementChild;
      viewDom.removeAttribute('width');
      viewDom.removeAttribute('height');
      viewDom.style.width = '100%';
    }
    responsive();
  }
}

// Destroy before changing your view on a SPA app
vegaDemo.destroy = () => {
  vegaDemo.view.finalize();
  if (vegaDemo.resize) {
    window.removeEventListener('resize', vegaDemo.resize);
  } else if (vegaDemo.responsive instanceof MutationObserver) {
    vegaDemo.responsive.disconnect();
  }
  vegaDemo.wrapperElem.innerHTML = ''; // Empty the wrapper if you aren't navigating away and simply want to remove the Vega view
};

// Destroy everything after 10 seconds
window.setTimeout(vegaDemo.destroy, 10000);
*,
*:before,
*:after {
  box-sizing: border-box;
}

article {
  width: 50%;
}
<!doctype html>
<html>
  <head>
    <title>Vega Responsive</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
  </head>
  <body>
    <p>Resize the browser!</p>
    <ul>
      <li>Specs taken from <a href="https://vega.github.io/vega/examples/" target="_blank">https://vega.github.io/vega/examples/</a>.</li>
      <li>This supports SVG &#38; Canvas (See the vegaDemo.renderType prop).</li>
      <li>This demo uses the wrapper's width...in this case, 50% of the body.</li>
      <li>The demo will be destroyed and removed after 10 seconds.</li>
    </ul>
    <article>
      <div id="vega-view"></div>
    </article>
  </body>
</html>

FYI: containerSize tied to an event in signals can help too. An example from Banu Prakash on https://groups.google.com/forum/#!topic/vega-js/YovF3RvlnRg may help.

Upvotes: 4

Related Questions