Skip to main content
Pentaho Documentation

Moving to Visualization API 3.0

Parent article

Visualization API 3.0 is a significant upgrade from Visualization API 2.0. While the previous version worked only within Pentaho Analyzer, Visualization API 3.0 works as an independent application. It also excludes any non-data visualization APIs. Like most of the current Pentaho platform, Visualization API 3.0 uses AMD/RequireJS as the module technology. Previously, the API used a global variable paradigm.

See Moving to Visualization API 3.0 in Analyzer for examples on how Visualization API 3.0 differs from Visualization API 2.0 specifically for Pentaho Analyzer.

Metadata and registration conversion

The following sections show the metadata and registration of a visualization before and after converting to Visualization API 3.0.

Before Visualization API 3.0

Before Visualization API 3.0, the following code defined metadata:

pentaho.visualizations.push({ 
  id: 'pentaho_sample_KPI',      // Unique identifier
  name: 'Example KPI',           // Visible name, this will come from a properties file, eventually 
  type: 'kpi',                   // Generic type id 
  source: 'Example',             // Id of the source library 
  'class': 'pentaho.sample.KPI', // Type of the Javascript object to instantiate
  menuOrdinal: 10001,
  menuSeparator: true,
  maxValues: [1000, 2000, 3000], 
  args: {                        // Arguments to provide to the Javascript object 
    aggregate: 'AVG'             //  this allows a single class to act as multiple visualizations
  },
  dataReqs: [                    // dataReqs describes the data requirements of this visualization 
    { 
      name: 'Default', 
      reqs : 
        [ 
          { 
            id: 'rows',               // ID of the data element 
            dataType: 'string',       // data type - 'string', 'number', 'date', 
                                      // 'boolean', 'any' or a comma separated list 
            dataStructure: 'column',  // 'column' or 'row' - only 'column' supported so far 
            caption: 'Level',         // visible name 
            required: true,           // true or false 
            allowMultiple: false, 
            ui: { 
              group: 'data' 
            } 
          }, 
          {
            id: 'measures', 
            dataType: 'number', 
            dataStructure: 'column', 
            caption: 'Measure', 
            required: true, 
            allowMultiple: false, 
            ui: { 
              group: "data" 
            } 
          }, 
          { 
            id: 'aggregate', 
            dataType: 'string', 
            values: ['MIN', 'MAX', 'AVG'], 
            ui: { 
              labels: ['Minimum', 'Maximum', 'Average'], 
              group: 'options', 
              type: 'combo',  // combo, checkbox, slider, textbox, gem, gemBar, and button are valid ui types
              caption: 'Aggregation' 
            }
          }
        ]
    }
  ]
});

The pentaho.visualizations is a global array where visualizations’ metadata is placed. The visualization has an identifier of pentaho_sample_KPI and is presented in the UI as Example KPI. The visualization itself is a JavaScript class which should be published globally in the path pentaho.sample.KPI.

Then, the visualization needed to be registered with Pentaho Analyzer. The following content would be placed in an Analyzer plugin file:

// example_analyzer_plugin.js
var analyzerPlugins = analyzerPlugins || [];

analyzerPlugins.push({
  init: function() { 
	  // Register visualizations to display in Analyzer 
    cv.pentahoVisualizations.push(pentaho.visualizations.getById('pentaho_sample_KPI')); 

    // Helpers contain code that knows about the Analyzer specific context. The one 
    // function that's required "generateOptionsFromAnalyzerState" is called so the 
    // visualization can set its own options based on Analyzer's current report.  
    cv.pentahoVisualizationHelpers['pentaho_sample_KPI'] = { 
      // Use one of Analyzer's stock placeholder images. 
      placeholderImageSrc: CONTEXT_PATH + 'content/analyzer/images/viz/VERTICAL_BAR.png', 

      // This method allows a visualization to generate visualization specific 
      // options based on Analyzer’s report definition. In the following example, 
      // this visualisation is setting a background color using the same background 
      // color setting in Chart Options. You can figure out the existing chart 
      // options by looking at the report XML by clicking the XML link in Analyzer.    
      // return a hash object containing the custom state of your visualization.
      generateOptionsFromAnalyzerState: function(report) { 
        return {myBackgroundColor: report.reportDoc.getChartOption("backgroundColor")}; 
      }
    };

    // LayoutConfig objects manage the interaction between Analyzer's Layout Panel 
    // and the visualization's settings.

    // Declare a new class which extends the built-in version from Analyzer. 
    dojo.declare("SampleConfig", [analyzer.LayoutConfig], { 
      onModelEvent: function(config, item, eventName, args) {
        if(eventName == "value") {
          this.report.visualization.args['aggregate'] = config.byId('aggregate').value; 
  
          // Add a report state item to the undo/redo history stack. 
          this.report.history.add(new cv.ReportState("Update KPI Aggregation"));
  
          // Trigger a report refresh so that the visualization is updated with the change.
          this.report.refreshReport();
        }
        
        this.inherited(arguments);  
      }
    });

    // Register the Layout Panel Configuration Manager. 
    // Note that the string entry matches 'JSON_' plus the visualization id 
    // defined earlier.
    analyzer.LayoutPanel.configurationManagers['JSON_pentaho_sample_KPI'] = SampleConfig; 
  } // init
});
After converting to Visualization API 3.0

In the Visualization API 3.0, a visualization is identified by its model class. The model class plays the double role of concentrating the metadata of a visualization and of serving as the runtime object used by applications and visualizations to write and read options from.

After converting to Visualization API 3.0, the metadata is now defined in the model.js file, as shown in the following code example:

// Model.js
define([
  "pentaho/module!_",
  "pentaho/visual/Model",
  "pentaho/i18n!model"
], function(module, BaseModel, bundle) {

  "use strict";
  
  var operDomain = bundle.structured.operation.domain;

  return BaseModel.extend({
    $type: {
      id: module.id,
      props: [
        {
          name: "rows",
          base: "pentaho/visual/role/Property",
          modes: [{dataType: "string"}],
          fields: {isRequired: true}
        },
        {
          name: "measures",
          base: "pentaho/visual/role/Property",
          modes: [{dataType: "number"}],
          fields: {isRequired: true}
        },
        {
          name: "aggregate",
          valueType: "string",
          domain: [
            {v: "min", f: operDomain.min.f},
            {v: "max", f: operDomain.max.f},
            {v: "avg", f: operDomain.avg.f}
          ],
          defaultValue: "avg"
        },
        {
          name: "backgroundColor",
          valueType: "string"
        }
      ]
    }
  })
  .localize({$type: bundle.structured.type})
  .configure();
});

It defines a model class, which inherits from the base model class, pentaho.visual.Model. The identifier of the visualization is that of its model class’ AMD module, which could be pentaho/visual/samples/KPI/Model.

The registration is now defined by the following code:

{
  "name": "@pentaho/visual-samples-kpi",
  "version": "0.0.1",
  "paths": {
    "pentaho/visual/samples/KPI": "/"
  },
  "config": {
    "pentaho/modules": {
      "pentaho/visual/samples/KPI/Model": {
        "base": "pentaho/visual/Model",
        "annotations": {
          "pentaho/visual/DefaultView": {
            "module": "./View"
          }
        }
      },
      "pentaho/visual/samples/KPI/config": {
        "type": "pentaho/config/spec/IRuleSet"
      }
    }
  }
}

This definition configures AMD for the code contained within the package, to be exposed under the module name pentaho/visual/samples/KPI. Then, the contained Model module is declared to export a value which is a subclass of pentaho/visual/Model, the base class of all visualization models. This declaration allows any interested party to discover existing visualizations in the system.

Another important piece of information is the pentaho/visual/DefaultView annotation. It states the module identifier of the default view class to use to render a pentaho/visual/samples/KPI/Model visualization model. In the Visualization API 3.0, a visualization is composed of a model and a view, which roughly correspond to the previous API’s metadata definition and visualization class.

In Visualization API 3.0, visualizations are not registered with specific applications. Visualizations can be configured to be presented or not in certain applications. Most applications will opt to offer all visualizations registered with the system.

For Visualization API 3.0, a contained config module is registered as exporting an instance of pentaho/config/spec/IRuleSet, the system type which represents a set of configurations. The next section presents the configuration file.

Configuration

Some options in the Visualization API 2.0 were specific to Analyzer. Visualization API 3.0 uses the rule-based configuration system, as shown in the following example:

// config.js
define({
  rules: [
    {
      select: {
        module: "./Model",
        application: "pentaho/analyzer"
      },
      apply: {
        category: "kpi",
        ordinal: 10001
      }
    },
    {
      select: {
        module: "./Model",
        application: "pentaho/analyzer",
        annotation: "pentaho/analyzer/visual/Options"
      },
      apply: {
        maxValues: [1000, 2000, 3000],
        generateOptionsFromAnalyzerState: function(report) { 
          return {
            backgroundColor: report.reportDoc.getChartOption("backgroundColor")
          };
        }
      }
    }
  ]
});

Converting the visualization class

In Visualization API 2.0, visually rendering the data and handling user interaction was accomplished in the visualization class, as shown in the following sample code:

// Define a namespace for this sample to live in.
pentaho.sample = {};

// Define the KPI Class, which renders a single KPI.
pentaho.sample.KPI = function(canvasElement) {
  this.canvasElement = canvasElement;
  this.numSpan = document.createElement("span"); 
  this.numSpan.style.fontSize = "42px"; 
  this.numSpan.style.position = "relative"; 
  this.canvasElement.appendChild(this.numSpan);
};

// Calculate the location of the KPI relative to the canvas.
pentaho.sample.KPI.prototype.resize = function(width, height) { 
  this.numSpan.style.left = ((this.canvasElement.offsetWidth - this.numSpan.offsetWidth) / 2) + 'px'; 
  this.numSpan.style.top = ((this.canvasElement.offsetHeight - this.numSpan.offsetHeight) / 2) + 'px'; 
};

// Render the KPI.
pentaho.sample.KPI.prototype.draw = function(datView, vizOptions) { 
  // Extract the values from the result set.
  var rows = datView.dataTable.jsonTable.rows;
  var dataArray = []; 
  for(var i = 0; i < rows.length; i++){ 
    dataArray.push(rows[i].c[1].v);
  } 

  // Calculate the KPI to display.
  var value = 0; 

  // Note that the vizOptions contains an aggregate option,
  // this is a custom property specific for this visualization type.
  switch(vizOptions.aggregate) { 
    case "MAX": 
      value = Number.MIN_VALUE;
      for(var i = 0; i < dataArray.length; i++) { 
        value = Math.max(value, dataArray[i]); 
      } 
      break;
      
    case "MIN": 
      value = Number.MAX_VALUE;
      for(var i = 0; i < dataArray.length; i++) { 
        value = Math.min(value, dataArray[i]); 
      } 
      break;
      
    case "AVG": 
      var total = 0;
      for(var i = 0; i < dataArray.length; i++) { 
        total += dataArray[i]; 
      } 
      value = total / dataArray.length; 
      break; 
  }
  
  // Update the background color.
  this.canvasElement.style.backgroundColor = vizOptions['myBackgroundColor'];
  
  // Write the KPI value to the screen.
  this.numSpan.innerHTML = value;
  
  this.resize(); 
}

In Visualization API 3.0, the visualization class in now defined in the view.js file, as shown in the following sample code:

// View.js
define([
  "pentaho/visual/impl/View",
  "pentaho/i18n!view"
], function(BaseView, bundle) {

  "use strict";

  return BaseView.extend({

    constructor: function(viewSpec) {

      this.base(viewSpec);

      var numSpan = document.createElement("span");
      numSpan.style.fontSize = "42px";
      numSpan.style.position = "relative";

      this.domContainer.appendChild(numSpan);
    },

    // Called the first time and when more than the size has changed.
    _updateAll: function() {

      var result = this.__calculate();

      this.domContainer.firstChild.innerHTML = bundle.get("result", [result.toFixed(2)]);
      
      // Update the background color.
      this.domContainer.style.backgroundColor = this.model.backgroundColor || "";
      
      this._updateSize();
    },

    // Called when only size has changed.
    _updateSize: function() {

      var element = this.domContainer.firstChild;

      // Center the span
      var width  = this.model.width;
      var height = this.model.height;
      element.style.left = ((width - element.offsetWidth) / 2) + "px";
      element.style.top  = ((height - element.offsetHeight) / 2) + "px";
    },

    // ---------

    __calculate: function() {
      var dataTable = this.model.data;
      var rowCount = dataTable.getNumberOfRows();
      var measureIndex = this.model.measure.fieldIndexes[0];

      var getValue = function(i) {
        var v = dataTable.getValue(i, measureIndex);
        return !isNaN(v) && v != null ? v : null;
      };

      var aggregatedValue = null;
      var rowIndex;
      var value;

      /* eslint default-case: 0 */
      switch(this.model.aggregate) {
        case "max":
          for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
            if((value = getValue(rowIndex)) != null)
              aggregatedValue = aggregatedValue == null ? value : Math.max(aggregatedValue, value);
          break;

        case "min":
          for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
            if((value = getValue(rowIndex)) != null)
              aggregatedValue = aggregatedValue == null ? value : Math.min(aggregatedValue, value);
          break;

        case "avg":
          var total = aggregatedValue = 0;
          if(rowCount) {
            for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
              if((value = getValue(rowIndex)) != null)
                total += value;
            aggregatedValue = total / rowCount;
          }
          break;
      }

      return aggregatedValue;
    }
  });
});

This view class is based on the optional base class pentaho/visual/impl/View. The base class handles calling the _updateXyz protected method whenever the associated model changes, depending on the model properties that have changed. The view reads data and options from the model object, which is available through the model property. If you prefer, you can also directly implement the pentaho/visual/IView interface.

Packaging

With Visualization API 3.0, you now only need to create an NPM-like package and drop it in the special Karaf deploy folder.

Learn more