Sunday, September 24, 2017

User-Generated Batch Printing in Tableau with JavaScript

I love using Tableau to publish interactive data visuals, but if you publish Tableau visuals in a workplace, sooner or later you're going to get a request for batch-printing.

If you have Tableau server, this can be done using tabcmd, but it requires work by the BI team for each request. It's okay for a few one-off requests, but if you have hundreds of users who want on-demand or customizable batch-printing, it's not a great solution. Instead, we can allow the user to do their own batch printing using JavaScript.

The basic plan is:
  1. Make a URL that generates Tableau dashboard PDFs
  2. Make a dashboard that can be filtered to a list of the URLs
  3. Use the JavaScript API to get the list of URLs
  4. Use JavaScript to batch-download the dashboard PDFs
If you'd like to skip the tutorial, here's a working html file that contains the code and you can skip to the bottom for a demonstration. Even if you read the tutorial, the html file is useful to see all the code in the right order, and the JavaScript libraries used.

Step 1: Make a URL that generates Tableau dashboard PDFs

Tableau will generate a pdf of a dashboard view if you include ".pdf" at the end of the workbook name in the url. Tableau can also receive filter values as url parameters, which will allow the user to customize the view. Andy Kriebel has a nice blog post on url parameters here. Andy's post generates PNG files, but PDF is the same concept.

In my case, I created a one-page of home price data by major US city, using Zillow data. Here's an example using my city:

The url to create this pdf is:

https://public.tableau.com/views/HomePriceOneSheet_0/HomePriceProfile.pdf?:showVizHome=no&CityState=AtlantaGA

To switch to a pdf of Chicago, and change the time comparison to five years:

https://public.tableau.com/views/HomePriceOneSheet_0/HomePriceProfile.pdf?:showVizHome=no&CityState=ChicagoIL&TimeComparison=FiveYear

Once you have your URL, make a calculated field in Tableau that generates the URL, given the user selection. In my case:

Step 2: Make a dashboard that can be filtered to a list of URLs

Make a worksheet that contains only the URL variable and your filter variables:

"Time Comparison" doesn't appear in my filter list because it's a parameter. Next, make this pretty by putting the worksheet on a small dashboard with filters showing and the URL list hidden.



Step 3: Use the JavaScript API to get the list of URLs

I adapted this code from Tableau's github page on the getData() functions. First, we initialize the visual:
var viz, sheet, table;  
function initViz() {  
     var containerDiv = document.getElementById("vizContainer"),  
          url = "https://public.tableau.com/views/HomePriceOneSheet_0/BatchPrint", 
          //replace with your tableau url  
          options = {  
               hideTabs: true,  
               hideToolbar: true,  
               onFirstInteractive: function() {  
                    document.getElementById('getData').disabled = false;  
          }};  
     viz = new tableau.Viz(containerDiv, url, options);  
 }  
Next, we use the getSummaryData() function to get the urls and reshape the data array. This command will be called in our html from a button click (below), after the user has made their selection in the Tableau visual.
function getSummaryData() {  
      sheet = viz.getWorkbook().getActiveSheet().getWorksheets().get("URLs");   
      //replace "URLs" with the name of your worksheet containing the URLs  
      options = {  
           maxRows: 0, // Max rows to return. Use 0 to return all rows  
           ignoreAliases: false,  
           ignoreSelection: true,  
      };  
      sheet.getSummaryDataAsync(options).then(function(t) {  
           table = t;  
           var tgt = document.getElementById("dataTarget");                      
           var data = table.getData()  
           var value_array = new Array();  
           //reshape array to fit download code  
           for (var i=0;i<data.length;i++){  
           value_array[i] = new Array (data[i][0].value);  
           }  
           //call download function from above  
           downloadAll( value_array )  
      });}  
Step 4: Use JavaScript to batch-download the dashboard PDFs

The last function in the above code, downloadAll() has not been defined. This function loops through the urls and downloads each pdf. I adapted downloadAll() from this code. Unfortunately this code only works in Chrome; other browsers will just show a single PDF.
function downloadAll(files){  
      if(files.length == 0) return;  
      file = files.pop();  
      var theAnchor = $('<a />')  
           .attr('href', file[0])  
           .attr('download',file[0])  
           .appendTo('body');  
      theAnchor[0].click();   
      theAnchor.remove();  
      downloadAll(files);  
      }  
Last, we use html to call our JavaScript functions:
<body onload="initViz();">  
      <div id="vizContainer" style="width:100%; height:300px;"></div>  
   <div class="page-header">  
     <button id="getData" onclick="getSummaryData()" class="btn" disabled>Get Reports</button>  
   </div>  
      <div id="dataTarget"></div>  
 </body>  
Here's the code in action below. The files might take a few seconds to download because the Tableau Public server has to generate each one.

Home Price Profile by City: Batch Printing


A great next step for this project is to get the multiple file download to work in browsers other than Chrome. 

It would also be helpful to rename the PDF after the city instead of the dashboard name. However, JavaScript can't rename an external file, so this would require downloading the PDF to a web server, renaming, and then downloading to the client.

Another next step is to stitch together the pdfs. This also can't be done in JavaScript and would require an intermediate download to a web server.

Let me know in the comments if you use this code, or let me know on twitter.