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:

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:

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:

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:

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:

Now, refresh the index.html page in the browser, and you should finally see a Bar chart!

Continue to Styling the view.