Responsive Visualizations Using D3.js and Bootstrap

In this article by Christoph Körner, the author of the book Learning Responsive Data Visualization, we will design and implement a responsive data visualization using Bootstrap and Media Queries based on real data. We will cover the following topics:

  • Absolute and relative units in the browsers
  • Drawing charts with percentage values
  • Adapting charts using JavaScript event listeners
  • Learning to adapt the resolution of the data
  • Using bootstrap's Media Queries
  • Understanding how to use Media Queries in CSS, LESS and JavaScript
  • Learning how to use bootstrap's grid system

(For more resources related to this topic, see here.)

First, we will discuss the most important absolute and relative units that are available in modern browsers. You will learn the difference of absolute pixels, relative percentages, em, rem, and many more.

In the next section, we will take a look at what is really needed for a chart to be responsive. Adapting the width to the parent element is one of the requirements, and you will learn about two different ways to implement this. After this section, you will know when to use percentage values or JavaScript event listeners. We will also take a look at adapting the data resolution, which is another important property of responsive visualizations.

In the next section, we will explore Media Queries, and understand how we can use them to make viewport depended responsive charts. We will take the advantage of Bootstrap's definitions of Media Queries for the most common device resolutions to integrate them into our responsive chart using CSS or LESS. Finally, we will also see how to include Media Queries into JavaScript.

In the last section, we will take a look at Bootstrap's grid system and learn how to seamlessly integrate it with the charts. This will give us not only some great flexibility but will also make it easier to combine multiple charts to one big dashboard application.

Units and lengths in the browser

Creating a responsive design, bet it website or graphics, depends strongly on the units and lengths that a browser can interpret. We can easily create an element that fills the entire width of a container using the percentage values that are relative to the parent container; whereas, achieving the same result width absolute values could be very tricky. Thus, mastering responsive graphics also means knowing all the absolute and relative units that are available in the browser.

Units for Absolute lengths

The most convenient and popular way in web design and development is to define and measure lengths and dimensions in absolute units, usually in pixels. The reason for this is that designers and developers often want to exactly specify the exact dimensions of an object. The pixel unit called px has been introduced as a visual unit based on a physical measurement to read from a device in the distance of approximately one arm length; however, all the modern browsers can also allow the definitions of lengths based on physical units. The following list shows the most common absolute units and their relations to each other:

  • cm: centimeters (1 cm = 96px/2.54)
  • mm: millimeters (1 mm = 1/10th of 1 cm)
  • in: inches (1in = 2.54 cm = 96 px)
  • pt: points (1 pt = 1/72th of 1 in)
  • px: pixels (1 px = 1/96th of 1 in)

More information on the origin and meaning of the pixel unit can be found in the CSS3 Specification available at http://www.w3.org/TR/css3-values/#viewport-relative-lengths.

Units for Relative lengths

In addition to Absolute lengths, relative lengths that are expressed as the percentage of the width or height of a parent element has also been a common technique to the style dynamic elements of web pages. Traditionally, the % unit has always been the unit of choice for the preceding reason. However, with CSS3, a couple of additional relative units have found their way into the browsers, for example, to define a length relative to the font size of the element.

Here is a list of the relative length units that have been specified in the CSS3 specifications and will soon be available in modern browsers:

  • %: the percentage of the width/height of the absolute container
  • Em: the factor of font size of the element
  • Rem: the factor of font size of the root element
  • Vw: 1% of viewport's width
  • vh: 1% viewport's height
  • vmin: 1% of the viewport's smaller dimension (either vw or vh)
  • vmax: 1% of the viewport's larger dimension (either vw or vh)

I am aware that as a web developer, we cannot really take the advantage of any technology that will be supported soon; however, I want to point out one unit that will play an important role for feature web developers: the rem unit. The rem unit defines the length of an element based on the font size of the root node (the html node in this case), rather than the font size of the current element such as em.

This rem unit is very powerful if we use it to define the lengths and spacings of a layout because the layout can also adapt when the user increases the font size of the browser (that is, for readability). I want to mention that Bootstrap 4 will replace all absolute pixel units for Media Queries by the rem units because of this reason.

If we look at the following figure, we also see that rem units are already supported in all major browsers. I recommend you to start replacing all your absolute pixel units of your layout and spacing by the rem units:

Learning Responsive Data Visualization

Cross-browser compatibility of rem units

However, percentage units are not dead; we can still use them when they are appropriate. We will use them later to draw SVG elements with dimensions based on their parent elements' dimensions.

Units for Resolution

To round up the section of relative and absolute units, I want to mention that we can also use resolutions in different units. These resolution units can be used in Media Queries together with the min-resolution or max-resolution attribute:

  • Dpi: dots per inch
  • dpcm: dots per centimeter
  • dppx: dots per px unit

Mathematical Expressions

We often have the problem of dealing with rational numbers or expressions in CSS; just imagine defining a 3-column grid with a width of 33% per column, or imagine the need of computing a simple expression in the CSS file. CSS3 provides a simple solution for this:

  • calc(exp): This computes the mathematical expression called exp, which can consist of lengths, values, and the operators called +, -, /, and *

Note that the + and -operators must be surrounded by whitespace. Otherwise, they will be interpreted as a sign of the second number rather than the operator. Both the other operators called * and / don't require a whitespace, but I encourage you to add them for consistency.

We can use these expression in the following snippets.

.col-4 {
width: calc(100%/3);
}
.col-sp-2 {
width: calc(50% - 2em);
}

The preceding examples look great; however, as we can see in the following figure, we need to take care of the limitations of the browser compatibility:

Learning Responsive Data Visualization

Cross-browser compatibility of the calc() expression

Responsive charts

Now that we know some basics about absolute and relative units, we can start to define, design, and implement responsive charts. A responsive chart is a chart that automatically adapts its look and feel to the resolution of the user's device; thus, responsive charts need to adapt the following properties:

  • The dimension (width and height)
  • The resolution of data points
  • The interactions and interaction areas.

Adapting the dimensions is most obvious. The chart should always scale and adapt to the width of its parent element. In the previous section, you learned about relative and absolute lengths, so one might think that simply using relative values for the chart's dimensions would be enough. However, there are multiple ways with advantages and disadvantages to achieve this; in this section, we will discuss three of them.

Adapting the resolution of the data is a little less obvious and often neglected. The resolution of data points (the amount of data point per pixel) should adapt, so that we can see more points on a device with a higher resolution and less points on a low resolution screen. In this section we will see that this can only be achieved using JavaScript event listeners and by redrawing/updating the whole chart manually.

Adapting interactions and interaction areas is important for not just using different screen resolutions but also different devices. We interact differently with a TV than a computer, and we use different input devices on a desktop and mobile phone. However, the chart will allow interactions and interaction areas that are appropriate for a given device and screen resolution..

Using Relative Lengths in SVG

The first and most obvious solution for adapting the dimensions of a chart to its parent container is the use of relative values for lengths and coordinates. This means that when we define the chart once with relative values and the browser takes care of recomputing all the values when the dimension of the parent container has changed, there is no manual redrawing of the chart required.

First, we will add some CSS styles to scale the SVG element to the full width of the parent container:

.chart {
height: 16rem;
position: relative;
}
.chart > svg {
width: 100%;
height: 100%;
}

Next, we modify all our scales to work on a range at [0, 100] and subtract a padding from both sides:

var xScale = d3.scale.ordinal()
.domain(flatData.map(xKey))
.rangeBands([padding, 100 - 2*padding]);

var yScale = d3.scale.linear()
.domain([0, d3.max(flatData, yKey)])
.range([100 - 2*padding, padding]);

Finally, we can draw the chart as before, but simply adding percentage signs % at the end of the attributes—to indicate the use of percentage units:

$$bars
.attr('x', function(d) { return (xScale(d.x) + i*barWidth ) + '%'; })
.attr('y', function(d) { return yScale(d.y) + '%'; })
.attr('height', function(d) { return (yScale(0) - yScale(d.y)) + '%'; })
.attr('width', barWidth + '%')
.attr('fill', colors(data.key));

Observe that we only slightly modified the code to plot the bars in the bar chart in order to use percentage values as coordinates for the attributes. However, the effect of this small change is enormous. In the following figure, we can see the result of the chart in a browser window:

Learning Responsive Data Visualization

Bar chart using relative lengths

If we now increase the size of the browser, the bar chart scales nicely to the full width of the parent container. We can see the scaled chart in the following figure:

Learning Responsive Data Visualization

Scaled bar chart using relative lengths

If you are not impressed by it now, you better be. This is awesome in my opinion because it leaves all the hard work of recomputing the SVG element dimensions to the browser. We don't have to care about them, and these native computations give us some maximal performance.

The previous example shows how to use percentage values to create a simple bar chart. However, what we didn't explain so far is why we didn't add any axis and labels to the chart. Well, despite the idea that we can exploit native rescaling of the browser, we need to face the limitations of this technique. Relative values are only allowed in standard attributes, such as width, height, x, y, cx, cy—but not in SVG paths or transform functions.

Conclusion about using Relative Lengths

While this sounds like an excellent solution (and indeed it is wonderful for certain use cases), it has two major drawbacks:

  • The percentage values are not accepted for the SVG transform attributes and for the d attribute in the path elements, only in standard attributes
  • Due to the fact that the browser is recomputing all the values automatically, we cannot adapt the resolution of the data of the charts

The first point is the biggest drawback, which means we can only position elements using the standard attributes called width, height, x, y, cx, cy, and more. However, we can still draw a bar chart that seamlessly adapts according to the parent elements without the use of JavaScript event listeners.

The second argument doesn't play a huge role anymore compared to the first one, and it can be circumvented using additional JavaScript event listeners, but I am sure you get the point.

Using the JavaScript Resize event

The last option is to use JavaScript event handlers and redraw the chart manually when the dimensions of the parent container change. Using this technique, we can always measure the width of the parent container (in absolute units) and use this length to update and redraw the chart accordingly. This gives us great flexibility over the data resolution, and we can adapt the chart to a different aspect ratio when needed as well.

The Native Resize event

Theoretically, this solution sounds brilliant. We simply watch the parent container or even the SVG container itself (if it uses a width of 100%) for the resize events, and then redraw the chart when the dimensions of the element change. However, there does not exist a native resize event on the div or svg element; modern browsers only support the resize events on the window element. Hence, it triggers only if the dimensions of the browser window changes. This means also that we need to clean up listeners once we remove a chart from the page.

Although this is a limitation, in most cases, we can still use the windowresize event to adapt the chart to its parent container; we have to just keep this in our mind.

Let's always use the parent container's absolute dimensions for drawing and redrawing the chart; we need to define the following things inside a redraw function:

var width = chart.clientWidth;
var height = width / aspectRatio;

Now, we can add a resize event listener to the window element and call the redraw function whenever the window dimensions change:

window.addEventListener('resize', function(event){
redraw();
});

The benefit of this solution is that we can do everything that we want in the redraw function, for example, modifying the aspect ratio, adapting the labels of the axis, or modifying the number of displayed elements.

The following figure shows a resized version of the previous chart; we observe that this time, the axis ticks adapt nicely and don't overlap anymore. Moreover, the axis ticks now take the full available space:

Learning Responsive Data Visualization

Resized chart with adapted axis ticks

Adapting the Resolution of the Data

However, there is another problem that can be nicely solved using these types of manual redraws—the problem of data resolution. How much data should be displayed in a small chart and how much in a bigger chart?

Learning Responsive Data Visualization

Small chart with high data resolution

I think you agree that in the preceding figure, we display too much data for the size of the graphic. This is bad and makes the chart useless. Moreover, we should really adapt the resolution of the data according to the small viewport in the redrawing process.

Let's implement a function that returns only ever i-th element of an array:

function adaptResolution(data, resolution) {
resolution = resolution ? Math.ceil(resolution) : 1;
return data.filter(function(d, i) {
return i % resolution === 0;
});
}

Great, let's define a width depended data resolution and filter the data accordingly:

var pixelsPerData = 20;
var resolution = pixelsPerData * (flatData.length) / width;

In the previous code, we observed that we can now define the minimum amount of pixel that one data point should have and remove the amount of values accordingly by calling the following:

var flatDataRes = adaptResolution(flatData, resolution);

The following image shows a small chart with a low number of values, which is perfectly readable even though it is very small:

Learning Responsive Data Visualization

Small chart with a proper data resolution

In the next figure, we can see the same chart based on the same data drawn with a bigger container. We immediately observe that also the data resolutions adapts accordingly; and again, the chart looks nice:

Learning Responsive Data Visualization

Big chart with a proper data resolution

Conclusion of using Resize events

This is the most flexible solution, and therefore, in many situations, it is the solution of choice. However, you need to be aware that there are also drawbacks in using this solution:

  • There is no easy way to listen for resize events of the parent container
  • We need to add event listeners
  • We need to make sure that event listeners are removed properly
  • We need to manually redraw the chart

Using Bootstrap's Media Queries

Bootstrap is an awesome library that gets you started quickly with new projects. It not just includes a huge amount of useful HTML components but also normalized amd standardized CSS styles. One particular style is the implementation of Media Queries for four typical device types (five types in Bootstrap 4). In this section, we will take a look at how to make use of these Media Queries in our styles and scripts. The great thing about Bootstrap is that it successfully standardizes typical device dimensions for web developers thus, beginners can simply use them without rethinking over and over which pixel width could be the most common one for tablets.

Media Queries in CSS

The quickest way to use Bootstrap's Media Queries is to simply copy them from the compiled source code. The queries are here:

/* Extra small devices (phones, etc. less than 768px) */
/* No media query since this is the default in Bootstrap */

/* Small devices (tablets, etc.) */
@media (min-width: 768px) { ... }

/* Medium devices (desktops, 992px and up) */
@media (min-width: 992px) { ... }

/* Large devices (large desktops, 1200px and up) */
@media (min-width: 1200px) { ... }

We can easily add these queries to our CSS styles and define certain properties and styles for our visualizations, such as four predefined widths, aspect ratios, spacing, and so on in order to adapt the chart appearance to the device type of the user.

Bootstrap 4 is currently in alpha; however, I think you can already start using the predefined device types in your CSS. The reason I am strongly arguing for Bootstrap 4 is because of its shift towards the em units instead of pixels:

// Extra small devices (portrait phones, etc.)
// No media query since this is the default in Bootstrap

// Small devices (landscape phones, etc.)
@media (min-width: 34em) { ... }

// Medium devices (tablets, etc.)
@media (min-width: 48em) { ... }

// Large devices (desktops, etc.)
@media (min-width: 62em) { ... }

// Extra large devices (large desktops, etc.)
@media (min-width: 75em) { ... }

Once again, the huge benefit of this is that the layout can adapt when the user increases the font size of the browser, for example, to enhance readability.

Media Queries in LESS/SASS

In Bootstrap 3, you can include Media Query mixins to your LESS file, which then gets compiled to plain CSS.

To use these mixins, you have to create a LESS file instead of CSS and import the Bootstrap variables.less file. In this file, Bootstrap defines all its dimensions, colors, and other variables. Let's create a style.less file and import variables.less:

// style.less
@import "bower_components/bootstrap/less/variables.less";

Perfect, that's all. Now, we can go ahead and start using Bootstrap's device types in our LESS file.

/* Extra small devices (phones, etc. less than 768px) */
/* No media query since this is the default in Bootstrap */

/* Small devices (tablets, etc.) */
@media (min-width: @screen-sm-min) { ... }

/* Medium devices (desktops, etc.) */
@media (min-width: @screen-md-min) { ... }

/* Large devices (large desktops, etc.) */
@media (min-width: @screen-lg-min) { ... }

Finally, we need to use a LESS compiler to transform our style.less file to plain CSS. To achieve this, we run the following command from the terminal:

lessc styles.less styles.css

As we can see, the command requires the LESS compiler called lessc being installed. If it's not yet installed on your system, go ahead and install it using the following command:

npm install -g less

If you are new to LESS, I recommend you to read through the LESS documentation on http://lesscss.org/. Once you check out of LESS, you can also look at the very similar SASS format, which is favored by Bootstrap 4. You can find the SASS documentation at http://sass-lang.com/.

We can use the Bootstrap 4 Media Queries in a SASS file by the following mixins:

@include media-breakpoint-up(xs) { ... }
@include media-breakpoint-up(sm) { ... }
@include media-breakpoint-up(md) { ... }
@include media-breakpoint-up(lg) { ... }
@include media-breakpoint-up(xl) { ... }

In my opinion, including Bootstrap's LESS/SASS mixins to the styles of your visualization is the cleanest solution because you always compile your CSS from the latest Bootstrap source, and you don't have to copy CSS into your project.

Media Queries in JavaScript

Another great possibility of using Bootstrap's Media Queries to adapt your visualization to the user's device is to use them directly in JavaScript. The native window.matchMedia (mediaQuery) function gives you the same control over your JavaScript as Media Queries gives us over CSS.

Here is a little example on how to use it:

if (window.matchMedia("(min-width: 1200px)").matches) {
/* the viewport is at least 1200 pixels wide */
} else {
/* the viewport is less than 1200 pixels wide */
}

In the preceding code, we see that this function is quite easy to use and adds almost infinite customization possibilities to our visualization.

More information about the matchMedia function can be found on the Mozilla Website https://developer.mozilla.org/de/docs/Web/API/Window/matchMedia.

However, apart from using the watchMedia function directly, we could also use a wrapper around the native API call. I can really recommend the enquire.js library by Nick Williams, which allows you to declare event listeners for viewport changes. It can be installed via the package manager bower by running the following command from the terminal:

bower install enquire

Then, we need to add enquire.js to the website and use in the following snippet:

enquire.register("screen and (min-width:1200px)", {

// triggers when the media query matches.
match : function() {
/* the viewport is at least 1200 pixels wide */
},      

// optional; triggers when the media query transitions
unmatch : function() {
/* the viewport is less than 1200 pixels wide */
},    
});

In the preceding code, we see that we can now can add the match and unmatch listeners almost in the same way as listening for resize events—just much more flexible.

More information about require.js can be found on the GitHub page of the project at https://github.com/WickyNilliams/enquire.js.

If we would like to use the Bootstrap device types, we could easily implement them (as needed) with enquire.js and trigger events for each device type. However, I prefer being very flexible and using the bare wrapper.

Using Bootstrap's Grid System

Another great and quick way of making your charts responsive and play nicely together with Bootstrap is to integrate them into Bootstrap's gird system. It is the best and cleanest integration however, is to separate concerns—and make the visualization as general and adaptive as possible.

Let's take our bar chart example with the custom resize events and integrate it into a simple grid layout. As usual, you can find the full source code of the example in the code examples:

<div class="container">
<div class="row">
<div class="col-md-8">
<div class="chart" data-url="…" …>
</div>
</div>
<div class="col-md-4">
<h2>My Dashboard</h2>
<p>This is a simple dashboard</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="chart" data-url="…" …>
</div>
</div>
<div class="col-md-4">
<div class="chart" data-url="…" …>
</div>
</div>
<div class="col-md-4">
<div class="chart" data-url="…" …>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="chart" data-url="…" …>
</div>
</div>
<div class="col-md-6">
<div class="chart" data-url="…" …>
</div>
</div>
</div>

We observe that by making use of the parent containers' width, we can simply add the charts as the div elements in the columns of the grid. This is the preferred integration where two components play together nicely but are not depended on each other.

In the following figure, we can see a screenshot from the simple dashboard that we just built. We observe that the visualizations already fit nicely into our grid layout, which makes it easy to compose them together:

Learning Responsive Data Visualization

A simple dashboard using Bootstrap's grid layout

Summary

In this article, you learned the essentials about absolute and relative units to define lengths in a browser. We remember that the em and rem unit plays an important role because it allows a layout to adapt when a user increases the font size of the web site.

Then, you learned about how to use relative units and JavaScript resize events to adapt the chart size and the data resolution according to the current container size. We looked into Media Queries in CSS, LESS, und JavaScript.

Finally, we saw how to integrate charts with Bootstrap's grid system and implemented a simple Google Analytics-like dashboard with multiple charts.

You've been reading an excerpt of:

Learning Responsive Data Visualization

Explore Title
comments powered by Disqus