Vizception: Viz in a viz & native d3.js integration on Tableau Server

gif found on giphy

Concept & background

This project started from this set of tweets.

@tableau this is amazing work @tfoldi my brain hurts from the number of doors you just opened...

— Chris DeMartini (@demartsc) August 30, 2016

Originating with the work that Tamas Foldi has done and building some real examples out from there. Tamas pinged me on Twitter with the idea and we were on our way. Please be sure to take a look at his post as well, found here.

We wanted to show a real example of how to bring more to Tableau Server without additional hardware or hosting needed. That’s right, no additional server purchase requisition requests needed. There are really too many use cases to count when it comes to this, the details herein are only the tip of the iceberg.

We had a few short discussions and landed on the following example. This includes not only native d3.js integration but also an example of viz in a viz (in a viz) on Tableau Server. As a starting point, we used the twitter network graphs that Keith Helfrich and I recently shared, which were showcased on the Tableau blog last month.

Collaboration

My favorite part of these projects is collaborating with people across the Tableau community (and the world). Being able to work with Tamas on this one was really fun, there is a reason why Tableau (and the community) know him as the ‘white-hat hacker’ of Tableau Server.

Get to it already, what is really required to make this work?

Here is a list of what you need to mimic what we have done in this post. The items which are add-ons to do specific things that we tried to achieve in this example are noted as optional all others are needed for our implementation of d3.js integration.

  • Tableau Desktop
  • Tableau Server
  • Tableau JS API, d3.js and other js libraries which will vary depending on your use case
  • Access to save files to the weddataconnectors folder on the server (sorry Tableau Public users :( )
    • Associated html, css and js files used for integration will need to be placed in this folder (or sub-folders).
    • HTML, CSS & JavaScript
    • jQuery (optional)
    • jQuery UI (optional)
    • Bootstrap (optional)
    • Determination and stubbornness (not optional, but hopefully this post can help reduce some of the need for that!).

Native d3.js integration on Tableau Server

As I mentioned, the method we are describing here was originally developed by Tamas. We have taken that work and built a use case on top of it to (1) test it out and (2) provide a working example of it in action to the community. This method should be adaptable to incorporate almost any d3.js visualization into your Tableau Dashboard on Tableau Server (we haven't tested them all so the caveats of “should” and “almost” stand).

The code we developed is written for this specific use case, but there is nothing to stop us, or you, other than time, from writing packaged js functions that can be called on for this type of integration.

Here we go...

If we start with the easiest part, that is the Tableau workbook and just including a webpage object referencing the html file we have created and placed on Tableau Server, like so…

vizception-2

It may not look like much in desktop, but it will look great once published onto server! Note: you need your embedded page and parent server to have the same domain to avoid some security issues (which shouldn’t be an issue since we are talking only about hosting directly on Tableau Server here anyways).

Next we can take a look at our html file that we just embedded. We added some bootstrap elements (and other stuff) to give the view a tabbed tooltip feel. But if we ignore all that fanciness you are left with just a few divs and an svg element in your initial DOM setup.

The d3.js visualization in this example is the force layout. I am not going to go into detail on how to build a d3 force layout in this post. There are a bunch of great resources and examples already available from d3.js community (you can also review the API reference here).

So we have our Tableau viz (referred to as the “parent viz” going forward) and our html file with html DOM elements and d3 force layout code, next we need some data to feed to d3.

What better place to get the data from then our parent viz? With the v10 JS API, we now have the getUnderlyingDataAsync() call available to us. We have to do a few things to really make this work though…

  1. We need to find the parent viz in the DOM (which we then store in a variable for later use).
  2. We have to call getUnderlyingDataAsync() to get the data from the parent viz.
  3. We have to manipulate the data structure on the fly using JavaScript functions so that we can feed it into our d3 force layout. Ultimately the data is then stored in a variable for later use (I also cache it locally to speed things up).

Note: For those of you out there who are not on v10 (or will not be for some time), there is a hack using getAppliedValues() (from a categorical filter) that can be used to mimic the v10 getData(), not ideal, not as many options, but it does work.

If you were to combine the first two steps into one our JavaScript code will look something like this (just writing the data to the console here):

parent.parent.tableau.VizManager.getVizs()[0].getWorkbook().getActiveSheet().getWorksheets()[1].getUnderlyingDataAsync().then(function(a){console.log(a);});

Refer to the Tableau JS API reference for the various options that you can call with getUnderlyingDataAsync(). Here is how we went about it. Notice the use of “.then()” to handle the returned promise and either continue our process or fire an error.

vizception-4

Note: I had to create a work around for the fact that the hive plot uses data densification and had underlying data that was null for densified marks. This is why I chose “ignoreSelection: true” in my options above. I then use a mark selection event listener on the parent viz and more JavaScript to filter down the entire dataset that is returned by the getData() call process. If you don’t have densification in play, you can likely change this to false and save yourself that trouble.

As mentioned, we store this result into a variable and from there we can start manipulating the data with JavaScript. I will hold off on going into that detail as it could be its own blog post, all of this is included and can be reverse engineered from the code we are sharing as part of this project though, let me know if you have questions.

At this point we are technically done with native d3.js integration on Tableau Server. Meaning that we have a parent viz, which is feeding data to our child d3.js viz. We have interactivity when a mark is selected that updates the child d3.js… pretty cool.

Viz in a viz (in a viz)

Creating the tooltip modal window is easier than you think it would be. We leverage the floating webpage object on our Tableau dashboard and then we just manipulate that using HTML/CSS/JavaScript/jQuery. As noted above, these files can all be stored on the Tableau Server.

To get the look we are using a combination of CSS and Bootstrap to create the modal window. This is all coded directly within the same HTML file that we discussed above. As an example, we can access the modal window, push it forward or back and/or make it fade using code like the following (there are many ways to do this):

vizception-7

Note: You can find the correct div id (here we have “tabZoneId127”) by inspecting your published Tableau viz and drilling through the DOM elements. Yes, you most likely can automate finding this reference as well. :)

Why stop there? That would not be very DataBlick of me. We took it a step further (turned it up to 11?!?) and leveraged the Tableau JS API once more to embed a Tableau tooltip within the embedded d3.js view (hence the blog name “Vizception”). The main trick here was integration of the Tableau JS API into the d3.js view. I found this function online which helped facilitate the call to the Tableau JS API from within d3.js (sorry having trouble finding the source link in my notes, but it is something I found online, and I will update with source link if I can dig it up).

vizception-5

From within our d3 script we can then use this function to assist with calling the Tableau JS API (where we filter the Tableau viz based on the d3 DOM element the user has hovered over). Here is how that looks (see the highlighted line).

vizception-6

So now we have (1) our parent tableau viz, (2) our child d3.js viz with data sourced from the parent viz (or its selected marks) and (3) a sub-child reference within the d3.js viz back to the parent tableau viz. All interactive and all working directly off of Tableau Server.

Conclusion

We did a lot, I learned A LOT, and I had a hard time trying to condense it into this post (which I failed miserably at yet again). I personally am really excited about this and where we go from here. Let me know your thoughts as well.

Click on the above image to interact directly with the Vizception workbook.

Click on the above image to interact directly with the Vizception workbook.

Files

You can find the files and code used to make this happen here.