Selecting the Values of All Marks using the JavaScript API

It’s been a long-standing question how to select all the marks in a viz using the JavaScript API. The great news is that there are two solutions: (1) A workaround using selectMarksAsync that will work in any version (2) The getData() methods in Tableau 10 allow direct access to the mark values

selectMarksAsync: the pre-10 workaround

Sometimes this question is posed as “Selecting All Marks.” While there is no “Select All Marks” method (not even in 10.0), it is possible to select marks based on any Dimension that is including in the viz. The workaround is to add a Dimension that will have the same value on every mark.

For example, I created a calculated field in my workbook called “Select All”, and just set its value to 1. I moved that to be a Dimension. I then added it onto the Detail part of the Marks card. Now that field is available for filtering using the selectMarksAsync() method.

Here’s a working implementation that will return an array of all the values in the viz for a given field. The “selector_field_name” and the “selector_field_value” are from the Calculated Field that you built. The “field_name_with_values” is Field Name from which you want all values to return.


function selectAllFromView(sheet_name_in_dashboard, selector_field_name, selector_field_value, field_name_with_values){
workbook = viz.getWorkbook();
sheet = workbook.getActiveSheet();
console.log(sheet.getSheetType());
switch (sheet.getSheetType()) {
case 'worksheet':
console.log('I am a worksheet');
current_sheet = sheet;

break;
case 'dashboard':
console.log('I am a dashboard');
db_objects = sheet.getObjects();
console.log(db_objects);
worksheets = sheet.getWorksheets();
console.log(worksheets);
for(i=0;i<worksheets.length;i++){
console.log(worksheets[i]);
console.log(worksheets[i].getName());
if (worksheets[i].getName() == sheet_name_in_dashboard){
console.log(worksheets[i]);
console.log(worksheets[i].getName());
current_sheet = worksheets[i];
}
}
case 'story':
console.log('I am a story, not going to do anything');
return false;
break;
}

current_sheet.selectMarksAsync(selector_field_name, selector_field_value,
tableau.SelectionUpdateType.REPLACE).then(
function() {
console.log('Selection updated');
current_sheet.getSelectedMarksAsync().then(
function(marks){
console.log('Got the selected marks');
console.log(marks);
value_type = 'value';
var value_array = new Array();
for(k=0;k<marks.length;k++){
pairs = marks[k].getPairs();
console.log(marks[k].getPairs());
console.log("Selected Mark " + k + " , " + pairs.length + " pairs of data");
for(j=0;j<pairs.length;j++){
// Pair has three properties: fieldName, formattedValue, value, accessed DIRECTLY, without setter / getter method
/* Enable for debugging
console.log(
"Pair " + j + " -- " +
"Field Name: " + pairs[j].fieldName + " , " +
"Value: " + pairs[j].value + " , " +
"Formatted Value: " + pairs[j].formattedValue
);*/
if( pairs[j].fieldName === field_name_with_values ) {
if(value_type == 'value'){
value_array.push( pairs[j].value );
}
if(value_type == 'formatted value') {
value_array.push( pairs[j].formattedValue ) ;
}
}
}
}
console.log(value_array);
console.log('Clearing the Marks selection');
current_sheet.clearSelectedMarksAsync();
}
);
}
);
}

There are three parts to this code:

  1. Select All of the Marks based on the Selector Field and its value (which all of the items will have based on your worksheet design)
  2. Retrieve the Values for all of the Selected Marks. These come in an array of Marks objects, which include both a value and a formatted_value. Get all of those (whichever of the two you want) and put them in an array)
  3. Clear all of the selected marks

Now, you might see a slight visual update as this all happens, but it does leave the viz as it was.

getData() / getSummaryDataAsync() in 10.0 and beyond

In Tableau 10, there is a whole set of functionality referred to as getData() . In essence, you can get the values underlying a viz, exactly like Show Underlying Data or Show Summary Data, but directly into JavaScript. In fact, getData() is really two methods:

  • getSummaryDataAsync(options)
  • getUnderlyingDataAsync(options)

If you want the values as they are showing in the viz, getSummaryDataAsync will do it. Here’s an equivalent to the function to the pre-10 workaround method, without any need to do anything special in the worksheet design:


function selectAllGetSummaryData(sheet_name_in_dashboard, field_name_with_values){
workbook = viz.getWorkbook();
sheet = workbook.getActiveSheet();
console.log(sheet.getSheetType());
// Set whether you return values or formatted values
var value_type = 'value';
switch (sheet.getSheetType()) {
case 'worksheet':
console.log('I am a worksheet');
current_sheet = sheet;
break;
case 'dashboard':
console.log('I am a dashboard');
db_objects = sheet.getObjects();
console.log(db_objects);
worksheets = sheet.getWorksheets();
console.log(worksheets);
for(i=0;i<worksheets.length;i++){
console.log(worksheets[i]);
console.log(worksheets[i].getName());
// Set the current_sheet variable to the sheet in the dashboard
if (worksheets[i].getName() == sheet_name_in_dashboard){
current_sheet = worksheets[i];
}
}
break;
case 'story':
console.log('I am a story, not going to do anything');
return false;
break;
}

options = {
maxRows: 0, // Max rows to return. Use 0 to return all rows
ignoreAliases: false,
ignoreSelection: true
};
current_sheet.getSummaryDataAsync(options).then(function(t){
console.log(t);
console.log("Here's the row count");
console.log(t.getTotalRowCount());
console.log("Here them columns");
columns = t.getColumns();
console.log(columns);
var field_position;
// Find the position the desired field is in
for(j=0;j<columns.length;j++){
console.log(columns[j]);
name = columns[j].getFieldName();
if (name == field_name_with_values){
index = columns[j].getIndex();
field_position = index;
console.log( name + " is in position " + index);
}
}
data = t.getData();
console.log("Here that data");
console.log(data);

var value_array = new Array();
// Data is returned as Rows, then with with a second array of the columns
for (var i=0;i<data.length;i++){
if(value_type == 'value'){
value_array.push( data[i][field_position].value );
}
if(value_type == 'formatted value') {
value_array.push( data[i][field_position].formattedValue ) ;
}
}

console.log(value_array);
});
}

Not only do you not have to specify any fields, but you can do whatever you want with this in the background without any visual change to the viz.

I’ve put up a working page on my GitHub that can be used a basis and framework for getting this to work.

 

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s