The Looker Visualization API is a pure-JavaScript API that runs in a sandboxed iframe and is hosted within the Looker application.
The same visualization code can provide a visualization anywhere in Looker: Explores, Looks, dashboards, embeds, or in PDF or rendered images.
Each visualization represents a view of a single Looker query. Looker handles running the query, and passes it to your visualization code. You'll also get passed a DOM element that your visualization code can draw into.
- Some knowledge of JavaScript and web development is necessary.
- Looker Admin access is required to create and update manifests, but otherwise is not required.
Let's walk thorough creating a simple visualization script.
For more details on each parameter consult the Visualization API Reference.
We'll create a simple "Hello World" visualization that displays the first dimension of a given query. The final result should look like this:
To develop and test a visualization in Looker, you need to host your visualization over https and create a manifest with a "Main" file pointing at your IP address and hosting port.
pip install pyhttps
to install a simple https server.pyhttps
in whichever folder you wish to develop.- In Looker, navigate to the Admin page. In the left-hand navigation pane, find the "Platform" section and select "Visualizations".
- Click "Add Visualization" to create a new manifest.
- Add a unique id, a label for your visualization (we suggest prefixing it with DEV ONLY so no one creates and saves content with it).
- Finally, your "Main" file should point at
https://localhost:4443/hello_world.js
.
Now let's actually create hello_world.js
.
If you want to jump to the final source code for this example, that's available here.
In the folder you are hosting via pyhttps
open a new blank JavaScript file on your computer and call it hello_world.js
.
You register a new custom visualization with Looker by calling the looker.plugins.visualizations.add
function and passing it a visualization object. This object contains the entire definition of your visualization and its configuration UI.
Here's the skeleton of our visualization with all the required properties filled out - just a create
and updateAsync
function where we'll write our visualization code:
looker.plugins.visualizations.add({
create: function(element, config) {
},
updateAsync: function(data, element, config, queryResponse, details, done) {
}
})
This is a perfectly valid Looker visualization, but it's not very visual yet. It'll just look like a blank box. But hey, it's a start.
Let's look at the create
function now. Note it has two arguments: element
and config
. We'll just worry about the first one for now.
Looker gives us an element
, which is the DOM Element that Looker would like us to put our visualization into. Looker will build this element for you and make it the proper size, you just need to put stuff in there.
The create
function gives us the opportunity to do the initial setup of our element. For our example, we want to create a chart that will look kind of like this HTML:
<style>
// some styles for our chart
</style>
<div class="hello-world-vis">
<div>Data Goes Here</div>
</div>
Using JavaScript, we can start to insert stuff into our DOM Element to match the structure we're looking for above.
Here's what that looks like fleshed out:
create: function(element, config) {
// Insert a <style> tag with some styles we'll use later.
var css = element.innerHTML = `
<style>
.hello-world-vis {
// Vertical centering
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
</style>
`;
// Create a container element to let us center the text.
var container = element.appendChild(document.createElement("div"));
container.className = "hello-world-vis";
// Create an element to contain the text.
this._textElement = container.appendChild(document.createElement("div"));
},
So we've now got some CSS in there, and we've made our hello-world-vis
container, and a div inside there.
We've also assigned this._textElement
to the container we want to render into. That's a convenience so we don't have to look it up again later when the chart begins to render.
That's all we need to do in our create
function – it's just a convenient place to do setup that only needs to happen once.
It's time to visualize! We'll flesh out our updateAsync
method now. This method gets called any time the chart is supposed to visualize changes, or when any other event happens that might affect how your chart is rendered. (For example, the chart may have been resized or a configuration option changed.)
In our case, we only need to find the first dimension in the first cell. We can do so using data
, which is an array of every row of the dataset, and queryResponse
, which contains metadata about the query, such as field names and types.
We can also use a helper method called LookerCharts.Utils.htmlForCell
to give us the proper HTML representation of the data point in that cell, which automatically handles things like drill links, formatting, and data actions:
updateAsync: function(data, element, config, queryResponse, details, done) {
// Grab the first cell of the data.
var firstRow = data[0];
var firstCell = firstRow[queryResponse.fields.dimensions[0].name];
// Insert the data into the page.
this._textElement.innerHTML = LookerCharts.Utils.htmlForCell(firstCell);
// Always call done to indicate a visualization has finished rendering.
done()
}
🎉 And we're done! That's all we need to do to have a fully-functioning custom visualization.
Let's press on though, and improve the user experience a bit...
Our updateAsync
code is grabbing the first row of data, but what happens if the query returns no results or doesn't contain any dimensions?
Well, currently you'll get an ugly JavaScript error in the browser console, and most users will be confused when the chart doesn't render.
However, it's easy for charts to display custom error messages if they encounter problems while trying to render. Looker will add two functions to your visualization object: addError
and clearErrors
that can be used to display a nice error message to the user.
Because they're added to the visualization object, they're available from the context of your updateAsync
function via the this
object.
We can just modify the beginning of our updateAsync
method to detect an error condition, let the user know there's an issue, and bail out instead of trying to render:
updateAsync: function(data, element, config, queryResponse, details, done) {
// Clear any errors from previous updates.
this.clearErrors();
// Throw some errors and exit if the shape of the data isn't what this chart needs.
if (queryResponse.fields.dimensions.length == 0) {
this.addError({title: "No Dimensions", message: "This chart requires dimensions."});
return;
}
// ... the rest of the update code here ...
That's it! If the user creates a query that only has measures, they'll now see this:
The final step is to allow users to customize aspects of their visualization.
That's really easy – in addition to the other properties of your visualization object, there's a special property called options
.
We can use that to specify what kind of options the chart needs:
looker.plugins.visualizations.add({
options: {
font_size: {
type: "string",
label: "Font Size",
values: [
{"Large": "large"},
{"Small": "small"}
],
display: "radio",
default: "large"
}
},
// ... rest of visualization object ...
Here we've created a font_size
option that will display as radio buttons, letting users choose between "small" and "large" font sizes.
There are lots of parameters available for making more complicated options available, but we'll just look at the basics here.
Specifying the options:
property is all you need to add the option to the visualization picker.
So how do we use it?
Recall that in the updateAsync
method there's a config
parameter that gets passed in. This will contain the currently selected options:
updateAsync: function(data, element, config, queryResponse, details, done) {
That config
object looks something like this:
{font_size: "large"}
So when we're updating the chart we can easily check that and do stuff in response to it. Here's a check we can add to the end of the update method to implement the font size changes:
if (config.font_size == "small") {
this._textElement.className = "hello-world-text-small";
} else {
this._textElement.className = "hello-world-text-large";
}
We should also update the <style>
tag we added in the create
method to define the hello-world-text-small
and hello-world-text-large
CSS classes:
.hello-world-text-large {
font-size: 72px;
}
.hello-world-text-small {
font-size: 18px;
}
Because the updateAsync
function gets called any time the config changes, and Looker automatically keeps track of saving the configuration for you, this is all you need to do to respond to any configuration settings you like.
And that's the basics of how to create a custom visualization using the Visualization API. You can check out the complete source code to this "Hello World" visualization here.
There's a bunch of additional functionality you can use as well, so definitely take a look at the Visualization API Reference for more information.
Happy visualizing!