champagne anarchist | armchair activist

Embedding D3.js charts in a responsive website - a better solution

I often use D3.js to create charts which I embed on my website (the chart below is included merely as an illustration; it was copied from here). Normally you set the width and height of the embedded page in the embed code, but with a responsive layout it’s not so simple. The challenge is to adapt the iframe width to varying screen sizes and change the height so that the chart still fits.

After struggling with this issue for quite a while I thought I had come across the solution and wrote an article about it. However, this solution has two problems:

  • You have to define the aspect ratio in both the embed code and the D3 code; ideally, you shouldn’t have to do that in more than one place;
  • More importantly, it doesn’t take the height into account of any title and captions that are not part of the D3-created svg. You could handle this by making the title and caption part of the svg itself, but this is a bit awkward, especially with multiline captions.

A while ago, I came across a different approach which uses HTML5’s postMessage. The embedded page posts a message containing it’s own height to the parent page. The parent page picks up the message and changes the iframe height accordingly.

A smart variant has the embedded page not only send its height, but also its url to the parent page. That way, you can identify the corresponding iframe by its src attribute and thus make sure the right iframe gets updated - which is nice if you have more than one iframe on a web page.

Here’s how it works. In the D3 code, set the width of the chart to the width of the div the svg is attached to and use the aspect ratio to calculate the chart height. Also add the following code to the embedded page. It will send its height and url to the parent page:

function sendHeight() {
  var height = $('body').height();
  window.parent.postMessage({
    'height': height,
    'location': window.location.href
  }, "*");
}
 
$(window).on('resize', function() {
  sendHeight();
}).resize();

And here’s the code for the parent page. It will pick up the message, identify the corresponding iframe and update its height (note that Drupal requires jQuery instead of $):

window.addEventListener('message', function(event) {
    if (event.origin !== 'https://dirkmjk.nl') return;
    var data = event.data;
    var height = data.height + 32;
    jQuery('iframe[src^="' + data.location + '"]').css('height', height + 'px');
}, false);

In the second line, the domain should be replaced with the domain where the embedded page is hosted (the line checks for the origin of the posted message for security reasons).

I haven’t extensively checked this but it works on iOS and Android. Since it uses postMessage, it will not work on some older browsers. Then again, D3.js won’t work on some older browsers either.

Credits go to thomax and Jan Werkhoven.

Tags: 

Follow this blog:
Twitter (English) | Twitter (Nederlands) | RSS data blog (English) | RSS dirkmjk (Nederlands)