# Building New Visualizations (Widgets) for Sisense
In this tutorial, you will build an Add-on that adds a new type of visualization that can be used in dashboards.
TIP
This guide assumes you're already familiar with the general structure of Sisense Add-ons, as described here.
It is also highly recommended to visit the JavaScript API Reference where you can find detailed descriptions of the various methods and events referenced in this page.
TIP
This tutorial covers the basics of Sisense widgets. Building new widgets for Sisense Mobile sometimes requires a slightly different process, which is described here.
# Step 1: Creating an empty Add-on
Follow the same steps described here. Name your Add-on simpleTable
(both the folder and within plugin.json
). You can skip the "additional code files" step for now.
You should now have the following files:
plugin.json
main.6.js
Now, download/copy this file to your Add-on's folder. You will use this module to render your data as a simple HTML table. It is a stand-in for the more complex 3rd-party visualization modules you would probably use for a real widget, such as various d3.js
or Highcharts visualizations.
Replace the contents of main.6.js
with this code:
import simpleTable from `./simpleTable.6`;
prism.registerWidget('simpleTable', {
// This will be the widget manifest
});
The empty object passed as the 2nd argument to registerWidget
is the widget manifest - a JSON object describing the widget type, containing the information Sisense needs to display and store your visualization. Most of the work in developing new visualizations for Sisense happens within this object.
# Step 2: Creating the widget manifest
In this step, you will start creating the widget manifest object, by defining the simpler, static properties it requires. In the following steps, you will add the more complex parts needed for a complete widget.
# Defining basic properties
There are several simple properties that every widget type has, which determine how it's represented in the new widget wizard or widget editor UI. Add these properties to the widget manifest:
name
: A unique name for your widget type. May be identical to your Add-on name, but doesn't have to be.family
: What type of widget this is - use the valuetable
title
: A title to display in the UIiconSmall
: A root-relative path to a.png
file containing an icon to represent the widget type
Your widget manifest should now look like this:
prism.registerWidget('simpleTable', {
name: 'simpleTable',
family: 'table',
title: 'Simple Table',
iconSmall: '/plugins/simpleTable/widget-24.png',
});
# Defining default sizing
Another property of the widget manifest is sizing
which defines some restrictions on how the widget is positioned and scaled in the dashboard layout.
These settings will prevent the user from stretching or squeezing the widget beyond the limits of its usability.
{
sizing: {
minHeight: 100,
maxHeight: 1000,
minWidth: 100,
maxWidth: 1000
}
}
# Step 3: Widget data
For the widget to be able to retrieve and display data, you need to define how the widget interacts with metadata - which panels can be populated by the user, how the query is built and how results are processed.
# Defining the data panels
Each widget type requires different types and amounts of fields in the data set it can render. When building a widget, you need to define which metadata panels appear in the widget editor UI. You will use the metadata items chosen by the user in each panel to construct your widget's query in a later step.
First, add a data
property to your widget manifest, and within it add an array called panels
:
{
data: {
panels: []
}
}
Each item in the panels
array will generate a metadata panel in the widget editor UI.
There are many optional features of the metadata panel object, which cannot all be covered in this tutorial. In this example, you will add 3 simple panels to your Add-on:
- A "Categories" panel allowing for 1 dimension item
- A "Values" panel allowing for 1 measure item
- A "Filters" panel allowing for unlimited widget filters
Add the following items to the panels
array:
[
{
name: 'Category',
type: 'visible',
metadata: {
types: ['dimensions'],
maxitems: 1
}
},
{
name: 'Value',
type: 'visible',
metadata: {
types: ['measures'],
maxitems: 1
}
},
{
name: 'filters',
type: 'filters',
metadata: {
types: ['dimensions'],
maxitems: -1
}
}
]
Note that all metadata panels are configurable except for the filters
panel. You will copy & paste this exact panel object to every widget you build.
# Building the query
When a fresh query needs to run (for example, when dashboard filters are changed) Sisense will invoke your widget's buildQuery
method, passing in a blank JAQL query object and expecting the method to return a fully formed JAQL query.
You only need to take care of your widget's metadata - Sisense will append dashboard filters automatically.
Add a method called buildQuery
to your data
object, like so:
{
data: {
buildQuery: (widget, query) => {
widget.metadata.panel('Category').items.forEach((item) => {
query.metadata.push($$.object.clone(item, true));
});
widget.metadata.panel('Value').items.forEach((item) => {
query.metadata.push($$.object.clone(item, true));
});
widget.metadata.panel('filters').items.forEach((item) => {
const itemClone = $$.object.clone(item, true);
itemClone.panel = 'scope';
query.metadata.push(itemClone);
});
return query;
}
}
}
# Handling transitions between widgets
When the user changes a widget's type, ideally as much of the existing metadata selected by the user should be preserved and used for the new widget. However, since every widget type has different requirements, this can't be achieved automatically - every widget type needs to define how it handles incoming metadata items from another widget type.
When the user switches to your widget type in the widget editor, Sisense will invoke the populateMetadata
method of your widget, which is defined as part of the widget manifest's data
object.
Sisense provides a helper service, prism.$jaql
to break down the metadata items into different categories. You can read more about this service here.
For this example, you will simply add dimension items to your "Categories" panel, measures to the "Values" panel and filters to the "Filters" panel:
{
data: {
populateMetadata: (widget, items) => {
const breakdown = prism.$jaql.analyze(items);
widget.metadata.panel('Category').push(breakdown.dimensions);
widget.metadata.panel('Value').push(breakdown.measures);
widget.metadata.panel('filters').push(breakdown.filters);
}
}
}
# Step 4: Adding the style panel
Many widget types need to have some configuration, usually for the visual aspects - for example, to let the dashboard designer choose a line chart's line width, or where the widget's legend is displayed.
These settings are usually accessible via the style panel on the right side of the widget editor. This panel's content is defined by each widget type, and in this example we'll add the ability to define a custom label that will be rendered in the widget in the next step.
First, create a blank HTML file in your Add-on's directory. This will be the template for the style panel's UI. In this example, we'll call the file style-panel-template.html
.
Now, specify this file in the widget manifest via the styleEditorTemplate
property:
{
styleEditorTemplate: '/plugins/simpleTable/style-panel-template.html'
}
Next, you need to define where this label's text will be stored within the widget instance object. The widget manifest's style
property is meant exactly for that, and allows you to set default values for these settings.
Add this property to your widget manifest:
{
style: {
label: ''
}
}
You also need to build a controller for this UI, which will handle changes to it and ensure they are reflected in the widget.
Create a file called style-panel-controller.6.js
in your Add-on's folder and paste the following code in it:
// AngularJS controller for the style panel
const controller = ['$scope', ($scope) => {
// Watch for style property changes to redraw the widget
$scope.$watch('widget.style.label', () => {
$scope.$root.widget.redraw();
});
}];
module.exports = controller;
Then import it in your main.6.js
file:
import controllerDefinition from './style-panel-controller.6';
mod.controller('stylerController', controllerDefinition);
The last step is to build the template and use all of the above. Paste this HTML code in the template file you previously created:
<div data-ng-controller="plugin-simpleTable.controllers.stylerController">
<div class="style-control">
<label>Custom Label:</label>
<input type="text" data-ng-model="widget.style.label" />
</div>
</div>
As you can see, the input
DOM element is bound to widget.style.label
which means this field will be updated whenever the user changes the text. This will trigger an event in the controller (which is attached to the template in the first line) which in turn will cause the widget to re-draw.
# Step 5: Rendering the widget
Finally, having defined how your widget interacts with the Sisense application, it is time to write the code which will render the widget, using the sample simpleTable
component imported in step #1.
Add a method called render
to your widget manifest:
{
render: (widget, args) => {
const widgetElement = $(args.element)[0];
const widgetObjectID = widget.oid;
const data = widget.queryResult;
const responseMetadata = widget.rawQueryResult;
// Clear widget element
widgetElement.innerHTML = '';
// Render the table
let tableElement = simpleTable(responseMetadata.headers, data);
widgetElement.appendChild(tableElement);
// Render the custom label
const settingsContainer = document.createElement('div');
settingsContainer.innerText = widget.style.label;
widgetElement.appendChild(settingsContainer);
}
}