Moving to Visualization API 3.0
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 is application independent. It also excludes any functionality not related with data visualization. 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, 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 });
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.