Responsive Design and Embedded Tableau Vizes – responsive_scaling_tableau.js

Web sites have to work on an incredibly wide variety of displays these days. The work to make a single page look as good as possible on all those displays often falls under the heading of “Responsive Design”.

When embedding a Tableau viz into another application, the actual “view area” for the Tableau viz is just one component of a page. In this situation, the view we pick from Tableau really needs to fill the most space of the area it has been allotted on the page. So let’s think a little more deeply about proportions and how we can best fit our embedded vizes given the tools available, and how to tie them all together to give the best possible experience to the end user, with the least effort for our Tableau viz designers.

Tableau recognizes the need for Responsive Design with a feature called Device Designer. It allows you to specify three different layouts, which are called in the system Desktop, Tablet, and Phone. These map well to the situations where you are looking at Tableau Server directly or in the Tableau phone app, and the Tableau viz is assumed to take the full width of the viewing area (and often most of the height). If different layouts exist, Tableau Server will automatically pick a layout based on the view space dimensions it detects.

However, when a Tableau viz is embedded into another page, it lives in a regular HTML div which can be styled using CSS3 and changed dynamically using JavaScript. Web designers regularly use these features to make page elements respond to changes in the viewing area (hence Responsive Design). Modern web browsers can actually scale up or down whatever is in a div without needing the content of the div to change.

Given all these capabilities, what is the best way to get our Tableau content to look its best in a page, regardless of the size or device it loads on?

Summary of Best Practices

  • Use Device Designer to specify several Dashboard layouts in different proportions
  • Use CSS / JavaScript to detect the proportions of the area allocated for the Tableau Viz, then manually control which Tableau Dashboard Device Layout is loaded
  • Use CSS3 transform-scale to maximize the fit of the chosen Device Layout to the available space on the page
  • Use break-point detection to reload the Tableau Viz using a different Device Layout once the available space would be better served by a Viz in different proportions

Now let’s dig into how to make all of this happen.

The Code Library – responsive_scaling_tableau.js

There’s a whole article down below explaining the how and why, but you probably just want to get things moving, so I’ll start with the end in mind. On GitHub, there is a file called responsive_scaling_tableau.js (this file, or a very slight variant of it, is what is used on https://embedded.tableau.com).

The library handles everything around the HTML / CSS / JS side of making your Tableau Viz objects behave responsively within the page. You will still need to design the Dashboards appropriately in Tableau Desktop, using Device Designer in a specific and targeted way – more on that below.

The basic process the library goes through is:

  1. Find the Ratio that will best fit the shape of the space available for the Tableau Viz
  2. Load the Viz using a height and width of the best matching ratio
  3. Scale the Viz to fit the actual available space using the CSS transform / scale properties

When it’s all set up right, the process above happens on the first load of the Viz and also any time the user resizes the page in a way that changes the available space ratio enough.

Let’s walk through how it works:

ratioBreakpoints

The very first thing in the library is a data structure called ratioBreakpoints. It is used to define different dimensions and values used for loading Vizes into the page (and adjusting their iframes) at the right size / ratio.

There is some commenting about the actual common sizes of physical devices and also the “realized” pixels vs actual (much more below). Basically, you can tailor the ratio breakpoints to give yourself some margin-of-error — your web design may work better keeping the Viz more rectangular, or in a mobile situation you may want to keep it more square or narrow unless it’s absolutely a very wide display space.

The tableauDeviceName property matches up the strings for the “device” property that can be used in the JS API Viz options object.

Variables to Set

After the breakpoints, there is a set of variables which help tie the library to your page and other Tableau JS API code.

var postResizeVizInitializationFunction;
var postResizeVizOptionsObject;

These variables will be used in whatever page you actually load a Viz object. In that page, you will have an options object, and then some function which encapsulates creating the Viz object using that options object. Once you define those two things, you also assign them to these variables like this code, taken from a page that actually embeds a Viz:

postResizeVizOptionsObject = localVizOptions; // localVizOptions is already declared as an object with different properties
function loadTableauContent() {
        initializeViz(placeholderDivId, vizUrl, localVizOptions, viz); // initializeViz does a few other things but basically wraps the standard JS API viz initializer
}
postResizeVizInitializationFunction = loadTableauContent;

If you aren’t super familiar with JavaScript, this all works because variables are just names referencing something actual in memory — so two variables names can refer to the same thing, in different places. We are still using variables in the “global scope” (i.e. not defined directly within variables) because so much of the code is Asynchronous or run in callbacks, and we want to make sure we are referencing the same actual thing, vs. an offshoot in another context (you can go down a long path learning about context in JavaScript, here’s a start) .

Next are variables that you set within responsive_scaling_tableau.js:

// The iframe adjustment function adds or removes a class to the iframe. This specifics the name of that class
var iframeWorksheetAdjustmentClassName = 'iframe-with-worksheet';

// Specifies the DIV to use to determine the space available to show the Tableau viz
// Not the vizDiv itself, but the DIV that it belong to
var nameOfOuterDivContainingTableauViz = 'outer-main-div';

// Pixel amounts to subtract from the calculated size of the OuterDivContainingTableauViz
// Without these offsets, the Viz will size to fill the full space, but typically you want slight margins
// Used within the scaleDiv() function below
var additionalWidthMargin = 30;
var additionalHeightMargin = 60;

iframeWorksheetAdjustmentClassName lets you define a CSS class which will be added to the iframe if the content is a Tableau Worksheet (rather than Dashboard). You can read down below why this is necessary, but the long and short is (1) a Worksheet by itself can export the full set of data to a PDF so it is useful (2) There is an amount of whitespace rendered around the viz which looks bad if your background is not white, so there is need to adjust the iframe to hide this whitespace to fit into a lot of page layouts).

This is the CSS class we use (5px seems to work most of the time i.e. it’s not always right but it’s unclear what the full set of situations are when it’s only 4px or occasionally even more)

/* used only when a Worksheet is displayed, not a Dashboard */
.iframe-with-worksheet {
    position: relative;
    top: -5px;
    left: -5px;
}

The next variable nameOfOuterDivContainingTableauViz lets you define what space within your page the Tableau viz should be fitting within. If you imagine the set of nested boxes going on when you embed a Tableau Viz, it will look like:

  • Outer Containing Div: A div in the page that will have the Tableau content somewhere within it
    • vizDiv: DIV that is passed to the Tableau viz constructor
      • iframe: The iframe that the Tableau JS API creates connecting to the Tableau Server

Because your actual web application may have all sorts of different layouts, you set this variable to tell Tableau what to look at to determine the space and proportions it has to work within. If the user resizes their browser, this is the DIV where the scaling functions will look to for new sizing information (along with the overall browser edges).

Lastly, the additionalWidthMargin and additionalHeightMargin values are integers that get subtracted from the real values retrieved from the Outer Containing DIV. This accounts for the fact that you most likely want some slight margin between the containing DIV and the Tableau Viz. The lower you set these, the closer the Tableau Viz will be sized to the edges of the containing div.

getPageSpaceWidthHeight()

To make any determinations about scaling or which device type to load the Viz as, we need to determine the dimensions of the space within the page that the Viz object will appear in. If you just have a titlebar and then the viz will fill all of the other space, this wouldn’t be that complex. But modern responsive webpages may have a variety of layouts, often with either one sidebar or two, where the main content goes within a central column. Page elements and their sizes also can change dynamically.

Dashboard and Worksheet Sizing in Tableau

If you’re looking at this blog, you may already know this about Tableau, but it’s worth a refresher on terms before we dive in. There is a great guide now to all of this and how it pertains to Dashboard design itself, but mainly we’re looking at how the choices a user can make will realize within the published version on Tableau Server.

Tableau object types

Tableau’s basic component is a Worksheet, which is one particular visualization (or Viz).

Worksheets can be put together into Dashboards. When a Worksheet is part of a Dashboard, it is a live object connected to its existing Worksheet self — changes made within the Dashboard actually change the Worksheet.

It is possible to hide a Worksheet once it belongs to a Dashboard, so that it won’t be see in Desktop or Web Edit without going to it from a Dashboard. This happens in Tableau Desktop, at the design phase. You can also hide Worksheets and Dashboards when you Publish to Tableau Server. These two actions are different in a few cases, but both result in the same thing to the end Tableau Server user.

Once either a Worksheet or Dashboard is published to Tableau Server it becomes a View or Sheet within the Workbook on Tableau Server. Whether it is called View or Sheet depends on the particular API and documentation. The View/Sheet is also rendered as a Tab, so any options around “Sheets as Tabs” or APIs about switching tabs, are also referring to this object. For ease of writing, I will just write View from here on to refer to a View/Sheet/Tab.

Lastly, Tableau can also be in Web Edit mode or be embedding Ask Data. We’ll try and cover off all the use cases.

How Tableau Objects are sized

Worksheet Fit

Worksheets have a Fit property, accessed in the top toolbar , which specifies how the Viz will treat the space. There are four options, each

  • Standard: Different viz layouts / mark types have their own internal rules for which way they work by default. Could behave like any of the other options
  • Fit Width: Scrolls vertically, but compresses all content into the fixed horizontal space
  • Fit Height: Scrolls horizontally, but compresses all content in the fixed vertical space
  • Entire View: No scrolling, compress (or stretch) all content both horizontally and vertically.

We can think of these as combinations of CSS width and height options along with x-overflow and y-overflow.

Worksheet Fit is an available property when the Worksheet is on a Dashboard, and it is how you get each individual Worksheet to fill its container or scroll as expected.

Worksheet Sizing: Always Automatic

When you publish a Worksheet separately from a dashboard, it has no fixed dimensions. It behaves like the “Automatic” setting for a Dashboard — it is expected that a height and width will be passed in at load time in the browser so that Tableau Server can return the appropriately sized viz.

A note about Worksheets

One quirk of Worksheets outside of Dashboards is that when embedded into a page, they have either a 4px or 5px white border around them. While this isn’t noticeable on pages with white or light backgrounds, it definitely is on darker backgrounds or if you apply borders around you viz container.

The most obvious workaround is to embed the Worksheet within a Dashboard. Unfortunately, once a Worksheet is on a Dashboard, PDF and Image Export no longer will export the full output of a vertically-scrolling view.

BEST PRACTICE: Only publish and embed a Worksheet by itself if it is a Long Horizontally Scrolling List View, which will need to be exported in full.

REAL LIFE: Someone won’t listen, and then you will be stuck with a view that doesn’t look right. How can we fix for it?

Automatically Adjusting the Div and Iframe to account for Worksheets

Because there is both an outer DIV and the iframe, we can do some tricky CSS so the user doesn’t ever see the white borders.

This technique requires that the DIV, which surrounds the IFRAME, have a specified width and height, and also have the CSS property “overflow: hidden;” . With overflow set to hidden, anything in the iframe that is outside the visible space just won’t appear to the user — this lets us “cut off” the excess white border.

We want to remove 10px from the width and the height of the div, which requires us read the current values and then adjust using JavaScript (shown in the function below).

But this is not enough — we need to also shift the iframe itself within the space so that it lines up 5px up and over to the left from how it naturally would lie.

In this example, we’ve defined a class that can be added and removed to the iframe:

/* used only when a Worksheet is displayed, not a Dashboard */
.iframe-with-worksheet {
    position: relative;
    top: -5px;
    left: -5px;
}

But you could handle setting these CSS properties yourself in JavaScript each time as well. Notice that position:relative; is also part of the formula — it is necessary for top and left to work.

This function is included in the overall scaling library, and this variation is intended for running during the onFirstInteractive callback function.

function adjustForWorksheetOrDashboard(e){
 var viz = e.getViz();
    var wb = viz.getWorkbook();
    var activeSheet = wb.getActiveSheet();
    var sheetType = activeSheet.getSheetType();
    // Because getParentElement() grabs the DOM node of the Div specified in the constructor
    var vizDiv = viz.getParentElement();
    // And then you get the iframe of the Viz, which will be inside that particular div
    var iframe = vizDiv.querySelectorAll('iframe')[0];

    if (sheetType == 'worksheet') {
        // Fix up the Div
        vizDiv.style.overflow = 'hidden';
        var oHeight = vizDiv.style.height;
        var oWidth = vizDiv.style.width;

        // Remove PX from ending to do some math
        var oHeightInt = oHeight.split('px')[0];
        var oWidthInt = oWidth.split('px')[0];

        var nHeight = (oHeightInt - 10);
        var nWidth = (oWidthInt - 10);

        // Styles need 'px' at the end
        vizDiv.style.height = nHeight + "px";
        vizDiv.style.width = nWidth + "px";

        // Now apply the adjustment style the iframe
        iframe.classList.add('iframe-with-worksheet');
    }
    else {
        if (iframe.classList.contains('iframe-with-worksheet')){
            iframe.classList.remove('iframe-with-worksheet');
        }
    }
}

Dashboard Sizing

This is just a quick summary of what is covered in-depth in the reference guide here. For our purposes, Dashboards can have 3 types of sizing:

  • Automatic: the Viz will be rendered to fill whatever height and width is specified at load time by the ‘height’ and ‘width’ properties (through embedding URL or JavaScript API options object.
  • Fixed: the Viz will always be rendered at the specified height and width, regardless of the size of the div or iframe specified to load into. This can result in some parts of the Viz being cutoff with scrollbars visible
  • Range: Works like Automatic, but you can specific Maximum and Minimum sizes. If the height and width options at load time exceed the Max or Min values, it will behave like Fixed and render at the specified size. Anything in between the values, will render like Automatic to the size that was passed in.

Historically, Fixed has resulted in the fastest performance, because more about the layout and rendering can be cached on the Tableau Server and reused. For Automatic and Range, each request to Tableau Server requires more rendering and processing — however the differences in load or render time may not matter more than the flexibility you need.

All Tableau Viz sizing happens at the time the Viz is loaded into a given div on the page. After that point, any changes in page size, div size, etc. will not result in a change to the Tableau viz without a reload of the Viz object.

Web Edit Sizing

The Web Editor view is always rendered in a 16:10 proportion, regardless of the sizing of the sheet content itself. It will create its own internal scrollbars if you are editing a Dashboard that is wider than the editor UI.

Ask Data Sizing

Ask Data also has some of its own “UI” when embedded, so it should be assumed to be in a 16:10 proportion overall. <Experiment with the smallest proportions that will work>

Device Designer: How we define different “ratios”

Device Designer is the feature that allows for specifying up to 3 different Dashboard layouts for the same Dashboard object. At load time, the size of the iframe the viz loads into determines which Device Designer layout is used, unless you override with a parameter before the Viz loads.

The library expects that you define the following layouts for your Dashboard in Device Designer. You can adjust these ratios if you make changes to the layout expectations in the library settings. In general we use the Default / Desktop layout for a wide Rectangular ratio, Tablet for a narrow but more square ratio, and Phone for a very narrow and tall layout:

  • Default: Generic Desktop (1366 x 768px)
  • Tablet: Fit All
  • Phone: Height 1700px (put possibly you want to half this). You want to end up with a fixed narrow layout.

So far we’ve discussed Tableau. Now let’s move to devices themselves and how web designers deal with the vast variations in sizes and proportions.

Device Proportions and Physical Sizes

Before we get started on this section, always remember that proportions are written as WIDTH:HEIGHT. Also take into consideration that the pixels themselves are bigger on a monitor with bigger physical dimensions but the same resolution.

Monitor sizes have evolved a lot over the years, with the trend continually going for wider and wider. The most common sizes now are 16:9 or 16:10.

On the other hand, the most common iPad has the more classic 4:3 portion (which equates to 16:12) when positioned landscape (wide), but instead reverses to a 3:4 (12:16) when held upright, which is more normal in standard browsing.

Phones also have a screen proportions, typically either 9:16 or 10:16 on more recent models. However, their actual pixel dimensions tend to be half that of standard monitors, so you can’t fit as much in and still have it be legible. For example, an iPhone 8 has a resolution of 1080 x 1920 (per Apple) — and consider how much smaller those pixels are compared to the pixels on a 27″ monitor.

One last wonderful aspect to consider is that many phones, most Apple computers, and 4K and larger monitors are often run with scaling, where the physical display has double the number of physical pixels than what is reported to the software. When Apple tells us the iPhone 8 above has 1080 x 1920 pixels, it is reporting what the web browser will think, but the display itself probably is 2160 x 3840 “natively”. The pixels themselves are so small though, that an iPhone will never render at this resolution. A 4K monitor on the other hand might – if it is misconfigured and the user doesn’t mind squinting very hard.

Proportions of Areas within a typical Web Page Layout

Just because screens have become wider and wider doesn’t mean content fills the whole space. In general, most web pages tend to have some type of top “title” area, and then at least one sidebar (possibly two). Even if there is no sidebar at all, then it’s very common have a single central area which is much narrower than the overall monitor.

In general, we could say that the most common proportions of “main content” actually map pretty well to:

  1. Phone vertical: Narrow and tall, 9:16
  2. Tablet vertical / centered content: Boxy, slightly taller than wide: 12:16 / 3:4
  3. Desktop minus top and sidebar / Tablet horizontal: 4:3 or 16:9

Now we have basic guidelines for what to build using Tableau’s Device Designer.

“Automatic Sized Dashboards” vs. Scaling with CSS

As mentioned earlier, Tableau Dashboards set to “Automatic” require more load time, because elements cannot be cached as readily, since each user viewing the dashboard might be looking at a different size. Once the Viz is loaded into the page, it won’t change size if the user changes the size of the browser window – it is only “automatically determined” at load time, then fixed after that point.

For the best responsive experience, we’d want to apply CSS3 transform-scale in case the browser window changes size within use, even if we used Automatic sizing on the Tableau Dashboard. Scaling algorithms always do better scaling down than up (you can always reduce what exists, but creating what doesn’t exist involves a lot of guessing)

Given that, the best option is:

  • Fixed sizing on Tableau Dashboards
  • Using known Fixed Proportions
  • At the largest reasonable resolution for that proportion, so that we are scaling down in most cases (or only up slightly)

CSS Scaling a Tableau Viz – the Pioneering Implementations

John Hegele, one of Tableau’s fantastic Sales Engineers, came up with a library called TabScale which uses CSS properties to scale up or down the rendered Tableau viz within a page so that it fills up the page, regardless of the arbitrary size the user has chosen for the browser window. John was kind enough to explain how the library works (along with making all his code available) — the following is just an expansion on how to use the same technique within more complex page structures. I’d also like to mention Timo Tautenhahn’s working demo of the basic concepts of responsive design, if you want to see a quick proof of this all in action.

TabScale itself deals with scaling one of the three device designer views to fit, while Timo shows how to take advantage of two of the device designer views. Based on the discussion above, we can use all three views. Let’s try and put it all together into a working model.

Best Practice Implementation

Deciding on Default Proportions for each Device Layout

  • Actual proportions / resolutions we want to use:
    • In Tableau for our Fixed Widths
    • As defaults to pass into the options object of the Viz at load time
  • Breakpoints of available space on the web page to determine which of the proportions to pick

Discovering the Space to Embed In: Load Time and Page Resizes

There are several different events where we want to discover the proportions and space to embed within:

  • Prior to Viz Load
  • Browser window resizes
  • The View changes (due to action within workbook, JS API command or tab bar selection): Each Dashboard or Viz can have its own proportions, so we may need to reset at this action as well.

The logic we’ll use should be the same at all of these points in time, so the code we write should separate out the detection and size / resizing choices from the events themselves.

We’re looking for the “Available Space to Embed In”, which is more difficult than simply getting the total browser size.

Defining Defaults for Automatic Sized Dashboards

Automatic sized Vizes won’t even load if they aren’t sent a width and height property at load time. It makes sense — how can it size to an unknown space? But this means we need to define defaults to send into options object of the Viz constructor at all times.

You may be saying, “But we’ve said everyone should build using Fixed Sizes” — even if that’s your process, we want to take care to get the best results even if an Automatic sized Dashboard comes through. And all Worksheets loaded by themselves behave as “Automatic”, so there’s always reason to protect and define a default size.

Choosing the Device Designer layout at load (and other options)

When you build a Viz object, you pass an options object which specifies how the Viz will load up. Here’s a very simple example:

var defaultVizOptions = {     
width: '1440px', // We scale to fit
height: '900px', // We scale to fit
device: 'desktop', // Override if necessary based on proportions
hideTabs: true,
hideToolbar: true,
onFirstInteractive: defaultOnFirstInteractive
}

If you don’t specify device here, and you choose a width that is below the automatic threshold for loading the Phone layout, you may be sitting for a long time puzzling on why an “automatic” layout isn’t sizing correctly.

You’ll also note both the hideTabs and hideToolbar options are set to true . Both the toolbar (27px) and tab bar (23px) take up space within the rendered content, deep inside the iframe. The viz itself will be sized based on the height passed in the options, but then the extra space taken by the toolbar or tab bar will push the total rendered content inside the iframe to be larger than the iframe space itself, resulting in scrollbars.

Checking the Tableau Viz sizing options and Matching the DIV to the IFRAME

I may love to explain things, but we didn’t go over the differences between Automatic, Fixed, and Range sizing for no reason above. The choices the designer made in Tableau Desktop or Web Edit must be detected for the scaling algorithms to work correctly.

Why?

The Tableau JavaScript API constructor, which loads the Viz, takes a div as an argument, but what it actually does is create an iframe and put it into that div. The iframe is what is actually connected to Tableau Server, but we want to be styling the div rather than the iframe. For the CSS styling properties to work most appropriately, the div and the iframe should have matching dimensions.

If the Viz has Fixed sizing, this is easy enough to accomplish: There is an

VizOptions.onFirstVizSizeKnown(VizResizeEvent)

property which allows for defining a callback function. The VizResizeEvent object which comes through has a method for retrieving a SheetSize object:

var sheetSize = VizResizeEvent.getVizSize().sheetSize;

The contents of this object vary depending on which Dashboard Sizing mode was chosen.

What happens with Automatic sizing?

Adjusting iframe Height for Toolbar or Tab Bar

As discussed above, the simplest way to ensure the viz inside the iframe is the size you specified is to hide the toolbar and the tab bar. But doing this will force you to implement those functionalities in your own application. If you need the toolbar or the tab bar, you can instead adjust the size of the iframe to account for them.

Add to your iframe height per the following rules:

  • Tab Bar: add 23px
  • Toolbar: add 27px
  • Both: add 50px

Leave a comment