This documentation targets the API shipped with Pentaho 7.1. Click here for the updated version shipped with Pentaho 8.3.
Skeleton view code
Create a file named view-d3.js
and place the following code in it:
define([
"module",
"pentaho/visual/base/view",
"./model",
"d3"
], function(module, baseViewFactory, barModelFactory, d3) {
"use strict";
return function(context) {
var BaseView = context.get(baseViewFactory);
var BarView = BaseView.extend({
type: {
id: module.id,
props: [
{
name: "model",
type: barModelFactory
}
]
},
_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/view-d3
). - Inherits directly from the base visualization view, pentaho/visual/base/view.
- The inherited model property is overridden so that its type 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.
npm install d3 --save
# or: yarn add d3
Adapting the HTML sandbox
Edit the index.html
file and place the following code in it:
<html>
<head>
<style>
.pentaho-visual-base {
border: solid 1px #005da6;
}
</style>
<script type="text/javascript" src="node_modules/requirejs/require.js"></script>
<script type="text/javascript" src="node_modules/@pentaho/viz-api/dev-bootstrap.js"></script>
<script>
// Needed only in a sandbox environment.
require.config({
paths: {
"pentaho/visual/samples/bar": ".",
"d3": "./node_modules/d3/build/d3"
}
});
</script>
<script>
require([
"pentaho/type/Context",
"pentaho/data/Table",
"pentaho/visual/base/view",
"pentaho/visual/samples/bar/model",
"json!./sales-by-product-family.json"
], function(Context, Table, baseViewFactory, barModelFactory, dataSpec) {
// Setup up a VizAPI context.
var context = new Context({application: "viz-api-sandbox"});
// Create the visualization model.
var modelSpec = {
"data": new Table(dataSpec),
"category": {attributes: ["productFamily"]},
"measure": {attributes: ["sales"]}
};
var BarModel = context.get(barModelFactory);
var model = new 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.
var BaseView = context.get(baseViewFactory);
BaseView.createAsync(viewSpec).then(function(view) {
// Render the visualization.
view.update();
});
});
</script>
</head>
<body>
<div id="viz_div"></div>
</body>
</html>
Remarks:
- A script block was added with the AMD/RequireJS configuration of the Bar and D3 packages. 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/model
. - The model now contains visual role mappings for the
category
andmeasure
visual roles. - The dimensions of the visualization were increased.
Now, refresh the index.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 categoryAttribute = model.category.attributes.at(0).name;
var measureAttribute = model.measure.attributes.at(0).name;
var categoryColumn = dataTable.getColumnIndexByAttribute(categoryAttribute);
var measureColumn = dataTable.getColumnIndexByAttribute(measureAttribute);
var scenes = this.__buildScenes(dataTable, categoryColumn, measureColumn);
var container = d3.select(this.domContainer);
// ...
}
Remarks:
- this.model gives you access to the visualization model object.
- Both the visual roles are required, so it is safe to directly read the first mapped attribute.
- Most data table methods accept column indexes, so attribute names are converted into column indexes.
- 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.
- this.domContainer gives you access to the DIV where rendering should take place.
Now, you’ll make a small detour to create that new __buildScenes
method.
Method __buildScenes
Add a property __buildScenes
, after _updateAll
, and give it the following code:
// view-d3.js
// __buildScenes:
function(dataTable, categoryColumn, measureColumn) {
var scenes = [];
for(var i = 0, R = dataTable.getNumberOfRows(); i < R; i++) {
scenes.push({
category: dataTable.getValue(i, categoryColumn),
categoryLabel: dataTable.getFormattedValue(i, categoryColumn),
measure: dataTable.getValue(i, measureColumn),
rowIndex: i
});
}
return scenes;
}
Remarks:
- Note the data table methods which can be used to traverse it: getNumberOfRows, getValue and getFormattedValue.
- In the X axis, you’ll be displaying the value of
categoryLabel
, but the value ofcategory
(and ofrowIndex
) will be useful, later, for adding interactivity to the visualization.
Method _updateAll
, part 2
Having prepared the data for rendering, you’ll now add the 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(d) { return d.categoryLabel; }));
y.domain([0, d3.max(scenes, function(d) { return d.measure; })]);
var svg = container.append("svg")
.attr("width", this.width)
.attr("height", this.height);
// Title
var title = dataTable.getColumnLabel(measureColumn) +
" per " +
dataTable.getColumnLabel(categoryColumn);
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 bar = g.selectAll(".bar")
.data(scenes)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.categoryLabel) + barOffset; })
.attr("y", function(d) { return y(d.measure); })
.attr("width", barWidth)
.attr("height", function(d) { return height - y(d.measure); });
}
Remarks:
- The view dimensions are available through this.width and this.height.
- The chart title is build with the labels of the mapped attributes, by calling getColumnLabel.
- The Bar model’s
barSize
property is being used to limit the width of bars.
Now, refresh the index.html
page in the browser, and you should finally see a Bar chart!
Continue to Styling the view.