16.4 Widget sizing

In the spirit of HTML widgets working just like plots in R, it is important that HTML widgets intelligently size themselves to their container, be it the RStudio Viewer, a figure in a knitr document, or a UI panel within a Shiny application. The htmlwidgets framework provides a rich mechanism for specifying the sizing behavior of widgets.

This sizing mechanism is designed to address the following constraints that affect the natural size of a widget:

  • The kind of widget it is. Some widgets may only be designed to look good at small, fixed sizes (like sparklines) while other widgets may want every pixel that can be spared (like network graphs).

  • The context into which the widget is rendered. While a given widget might look great at 960px by 480px in an R Markdown document, the same widget would look silly at that size in the RStudio Viewer pane, which is typically much smaller.

Widget sizing is handled in two steps:

  1. First, a sizing policy is specified for the widget. This is done via the sizingPolicy argument to the createWidget function. Most widgets can accept the default sizing policy (or override only one or two aspects of it) and get satisfactory sizing behavior (see details below).

  2. The sizing policy is used by the framework to compute the correct width and height for a widget given where it is being rendered. This size information is then passed to the factory and resize methods of the widget’s JavaScript binding. It is up to the widget to forward this size information to the underlying JavaScript library.

16.4.1 Specifying a sizing policy

The default HTML widget sizing policy treats the widget with the same sizing semantics as an R plot. When printed at the R console, the widget is displayed within the RStudio Viewer and sized to fill the Viewer pane (modulo any padding). When rendered inside an R Markdown document, the widget is sized based on the default size of figures in the document.

Note that for most widgets the default sizing behavior is fine, and you will not need to create a custom sizing policy. If you need a slightly different behavior than the default, you can also selectively override the default behavior by calling the sizingPolicy() function and passing the result to createWidget(). For example:

htmlwidgets::createWidget(
  "sigma",
  x,
  width = width,
  height = height,
  sizingPolicy = htmlwidgets::sizingPolicy(
    viewer.padding = 0,
    viewer.paneHeight = 500,
    browser.fill = TRUE
  )
)

Below are two examples:

  • The networkD3 package uses custom sizing policies for all of its widgets. The simpleNetwork widget eliminates padding (as D3.js is already providing padding), and specifies that it wants to fill up as much space as possible when displayed in a standalone web browser:

    sizingPolicy(padding = 0, browser.fill = TRUE)
  • The sankeyNetwork widget requires much more space than is afforded by the RStudio Viewer or a typical knitr figure, so it disables those automatic sizing behaviors. It also provides a more reasonable default width and height for knitr documents:

    sizingPolicy(viewer.suppress = TRUE,
                 knitr.figure = FALSE,
                 browser.fill = TRUE,
                 browser.padding = 75,
                 knitr.defaultWidth = 800,
                 knitr.defaultHeight = 500)

Table 16.1 shows the various options that can be specified within a sizing policy. Note that the default width, height, and padding will be overridden if their values for a specific viewing context are provided (e.g., browser.defaultWidth will override defaultWidth when the widget is viewed in a web browser). Also note that when you want a widget a fill a viewer, the padding is still applied.

TABLE 16.1: Options that can be specified within a sizing policy.
Option Description
defaultWidth Default widget width in all contexts (browser, viewer, and knitr).
defaultHeight Similar to defaultWidth, but for heights instead.
padding The padding (in pixels) in all contexts.
viewer.defaultWidth Default widget width within the RStudio Viewer.
viewer.defaultHeight Similar to viewer.defaultWidth.
viewer.padding Padding around the widget in the RStudio Viewer (defaults to 15 pixels).
viewer.fill When displayed in the RStudio Viewer, automatically size the widget to the viewer dimensions. Default to TRUE.
viewer.suppress Never display the widget within the RStudio Viewer (useful for widgets that require a large amount of space for rendering). Defaults to FALSE.
viewer.paneHeight Request that the RStudio Viewer be forced to a specific height when displaying this widget.
browser.defaultWidth Default widget width within a standalone web browser.
browser.defaultHeight Similar to browser.defaultWidth.
browser.padding Padding in a standalone browser (defaults to 40 pixels).
browser.fill When displayed in a standalone web browser, automatically size the widget to the browser dimensions. Defaults to FALSE.
browser.external Always use an external browser (via browseURL()). Defaults to FALSE, which will result in the use of an internal browser within RStudio v1.1 and higher.
knitr.defaultWidth Default widget width within documents generated by knitr (e.g., R Markdown).
knitr.defaultHeight Similar to knitr.defaultWidth.
knitr.figure Apply the default knitr fig.width and fig.height to the widget rendered in R Markdown. Defaults to TRUE.

16.4.2 JavaScript resize method

Specifying a sizing policy allows htmlwidgets to calculate the width and height of your widget based on where it is being displayed. However, you still need to forward this sizing information on to the underlying JavaScript library for your widget.

Every JavaScript library handles dynamic sizing a bit differently. Some do it automatically, some have a resize() call to force a layout, and some require that size be set only along with data and other options. Whatever the case it is, the htmlwidgets framework will pass the computed sizes to both your factory function and resize function. Here is a sketch of a JavaScript binding:

HTMLWidgets.widget({

  name: "demo",

  type: "output",

  factory: function(el, width, height) {

    return {
      renderValue: function(x) {

      },

      resize: function(width, height) {

      }
    };
  }
});

What you do with the passed width and height is up to you, and depends on the re-sizing semantics of the underlying JavaScript library. A couple of illustrative examples are included below:

  • In the dygraphs widget (https://rstudio.github.io/dygraphs), the implementation of re-sizing is relatively simple, since the dygraphs library includes a resize() method to automatically size the graph to its enclosing HTML element:

    resize: function(width, height) {
      if (dygraph)
        dygraph.resize();
    }
  • In the forceNetwork widget (https://christophergandrud.github.io/networkD3/#force), the passed width and height are applied to the <svg> element that hosts the D3 network visualization, as well as forwarded on to the underlying D3 force simulation object:

    factory: function(el, width, height) {
    
      var force = d3.layout.force();
    
      d3.select(el).append("svg")
        .attr("width", width)
        .attr("height", height);
    
      return {
        renderValue: function(x) {
          // implementation excluded
        },
    
        resize: function(width, height) {
    
          d3.select(el).select("svg")
            .attr("width", width)
            .attr("height", height);
    
          force.size([width, height]).resume();
        }
      };
    }

As you can see, re-sizing is handled in a wide variety of fashions in different JavaScript libraries. The resize method is intended to provide a flexible way to map the automatic sizing logic of htmlwidgets directly into the underlying library.