This documentation targets the API shipped with Pentaho 8.1. Click here for the updated version shipped with Pentaho 8.3.
Step 3 - Creating the view
Skeleton view code
Create a file named view-d3.js
and place the following code in it:
define(["module", "d3", "pentaho/visual/scene/Base"], function(module, d3, Scene) {
"use strict";
return [
"pentaho/visual/base/view",
"./model",
function(BaseView, Model) {
// Create the Bar View subclass
var BarView = BaseView.extend({
$type: {
id: module.id,
props: [
// Specialize the inherited model property to the Bar model type
{
name: "model",
valueType: Model
}
]
},
_updateAll: function() {
d3.select(this.domContainer).text("Hello World!");
}
});
return BarView;
}
];
});
Remarks:
- Defines a visualization view whose id is the file’s AMD module identifier
(depending on how AMD is configured, it can be, for example:
pentaho-visual-samples-bar-d3/view-d3
). - Inherits directly from the base visualization view, pentaho/visual/base/view.
- The inherited model property is overridden so that its valueType is the Bar model you previously created.
- The _updateAll
method is where the code that fully renders the visualization must go,
and, for now, it simply uses D3 to output
"Hello World!"
in the view’s DOM element,domContainer
.
Installing D3
Execute the following:
# Add and install the D3 dependency
# (also set it as a bundled dependency)
npm install d3 --save --save-bundle
Adapting the HTML sandbox
Edit the sandbox.html
file and place the following code in it:
<!doctype html>
<html>
<head>
<style>
.pentaho-visual-base-model {
border: solid 1px #005da6;
}
</style>
<!-- load requirejs -->
<script type="text/javascript" src="node_modules/requirejs/require.js"></script>
<!-- load the VizAPI dev bootstrap helper -->
<script type="text/javascript" src="node_modules/@pentaho/viz-api/dev-bootstrap.js"></script>
<!-- configure AMD for the sample visualization -->
<script>
require([
"vizapi-dev-init",
"json!./package.json"
], function (devInit, package) {
devInit(package);
require.config({
paths: {
"d3": "./node_modules/d3/build/d3"
}
});
require([
"pentaho/type/Context",
"pentaho/data/Table",
"json!./sandbox-data.json"
], function (Context, Table, dataSpec) {
// Setup up a VizAPI context.
Context.createAsync({application: "viz-api-sandbox"})
.then(function (context) {
// Get the model and base view types
return context.getDependencyAsync({
BarModel: "pentaho-visual-samples-bar-d3/model",
BaseView: "pentaho/visual/base/view"
});
})
.then(function (types) {
// Create the visualization model.
var modelSpec = {
"data": new Table(dataSpec),
"category": {fields: ["productFamily"]},
"measure": {fields: ["sales"]}
};
var model = new types.BarModel(modelSpec);
// Create the visualization view
var viewSpec = {
width: 700,
height: 300,
domContainer: document.getElementById("viz_div"),
model: model
};
// These are responsibilities of the visualization container application:
// 1. Mark the container with the model's CSS classes, for styling purposes.
viewSpec.domContainer.className = model.$type.inheritedStyleClasses.join(" ");
// 2. Set the DOM container dimensions.
viewSpec.domContainer.style.width = viewSpec.width + "px";
viewSpec.domContainer.style.height = viewSpec.height + "px";
// Create the visualization view.
return types.BaseView.createAsync(viewSpec);
})
.then(function (view) {
// Handle the execute action.
view.on("pentaho/visual/action/execute", {
"do": function (action) {
alert("Executed " + action.dataFilter.$contentKey);
}
});
// Handle the select action.
view.on("pentaho/visual/action/select", {
"finally": function (action) {
document.getElementById("messages_div").innerText =
"Selected: " + view.model.selectionFilter.$contentKey;
}
});
// Render the visualization.
return view.update();
});
});
});
</script>
</head>
<body>
<!-- div that will contain the visualization -->
<div id="viz_div"></div>
<!-- div that will display messages -->
<div id="messages_div"></div>
</body>
</html>
Remarks:
- A script block was added with the AMD/RequireJS configuration of the D3 path. This step is only needed in a sandbox environment. When inside the Pentaho platform, these configurations are provided automatically, built from the web package information.
- The used visualization model is now
pentaho-visual-samples-bar-d3/model
(or other, if you choose a different package name on step 1). - The model now contains visual role mappings for the
category
andmeasure
visual roles. - The dimensions of the visualization were increased.
Now, refresh the sandbox.html
page in the browser, and you should read Hello World!
.
Implementing the render code
Let’s now finally implement the rendering of a Bar chart in D3. To make it easy, we’ll adapt the code of following D3 block: https://bl.ocks.org/mbostock/3885304.
We’ll now go through the view’s _updateAll code, piece by piece.
Method _updateAll
, part 1
In view-d3.js
, replace the code of the _updateAll
method with the following:
// view-d3.js
// _updateAll:
function() {
// Part 1
var model = this.model;
var dataTable = model.data;
var scenes = Scene.buildScenesFlat(this).children;
var container = d3.select(this.domContainer);
// ...
}
Remarks:
- this.model gives you access to the visualization model object.
- The data in the data table needs to be converted into an “array of plain objects” form, so that then it can be directly consumed by D3; to that end, the pentaho.visual.scene.Base helper class is used.
- this.domContainer gives you access to the DIV where rendering should take place.
Method _updateAll
, part 2
Now, add the following adapted D3 code:
// view-d3.js
// _updateAll:
function() {
// Part 1
// ...
// Part 2
container.selectAll("*").remove();
var margin = {top: 50, right: 20, bottom: 30, left: 75};
var width = this.width - margin.left - margin.right;
var height = this.height - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);
x.domain(scenes.map(function(scene) { return scene.vars.category.toString(); }));
y.domain([0, d3.max(scenes, function(scene) { return scene.vars.measure.value; })]);
var svg = container.append("svg")
.attr("width", this.width)
.attr("height", this.height);
// Title
var title = this.__getRoleLabel(model.measure) +
" per " +
this.__getRoleLabel(model.category);
svg.append("text")
.attr("class", "title")
.attr("y", margin.top / 2)
.attr("x", this.width / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(title);
// Content
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// X axis
g.append("g")
.attr("class", "axis axis-x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Y axis
g.append("g")
.attr("class", "axis axis-y")
.call(d3.axisLeft(y).ticks(10));
// Bars
var bandWidth = x.bandwidth();
var barWidth = Math.min(model.barSize, bandWidth);
var barOffset = bandWidth / 2 - barWidth / 2 + 0.5;
var selectColor = function(scene) {
return model.palette.colors.at(scene.index % model.palette.colors.count).value;
};
var bar = g.selectAll(".bar")
.data(scenes)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", selectColor)
.attr("stroke", selectColor)
.attr("x", function(scene) { return x(scene.vars.category.toString()) + barOffset; })
.attr("y", function(scene) { return y(scene.vars.measure.value); })
.attr("width", barWidth)
.attr("height", function(scene) { return height - y(scene.vars.measure.value); });
}
Remarks:
- The view dimensions are available through this.width and this.height.
- The dynamic chart title is built with the help of the
__getRoleLabel
method, which will be introduced below. - The chart title is build with the labels of the mapped fields, by calling getColumnLabel.
- The Bar model’s
barSize
property is being used to limit the width of bars. - The scene objects, previously built by the
pentaho.visual.scene.Base helper class,
contain variables, one for each visual role; each variable has a value and a formatted value,
which is obtained by calling the variable’s
toString
method. - Scene objects have an
index
property which is being used to cycle through and select the bar color from thepalette
property.
Now, you’ll make a small detour to create that new __getRoleLabel
method.
Method __getRoleLabel
Add a property __getRoleLabel
, after _updateAll
, and give it the following code:
// view-d3.js
// __getRoleLabel:
function(mapping) {
if(!mapping.hasFields) {
return "";
}
var data = this.model.data;
var columnLabels = mapping.fieldIndexes.map(function(fieldIndex) {
return data.getColumnLabel(fieldIndex);
});
return columnLabels.join(", ");
}
Remarks:
- The visual role mapping object’s fieldIndexes, property conveniently gives you the indexes of the fields mapped to a visual role.
- The label of a field is obtained from the data table’s getColumnLabel method.
Now, refresh the sandbox.html
page in the browser, and you should finally see a Bar chart!
Continue to Styling the view.