Step 3 - Creating the view

Skeleton view code

Create a file named View.js and place the following code in it:

define([
  "pentaho/module!_",
  "pentaho/visual/impl/View",
  "d3"
], function(module, BaseView, d3) {
  
  "use strict";

  // Create and return the Bar View class
  return BaseView.extend(module.id, {
    
    _updateAll: function() {
      d3.select(this.domContainer).text("Hello World!");
    }
  });
});

Remarks:

Installing D3

Execute the following:

# Add and install the D3 dependency
# (also set it as a bundled dependency)
npm install d3 --save --save-bundle

Configuring the view as the default view

The new view type must be associated with the model type developed in the previous section. Edit the package.json and add a DefaultView annotation to the model type, like in (the "..." properties stand for omitted content):

{
  "name": "@pentaho/visual-samples-bar-d3",
  
  "...": "...",
  
  "config": {
    "pentaho/modules": {
      "pentaho/visual/samples/barD3/Model": {
        "base": "pentaho/visual/Model",
        "annotations": {
          "pentaho/visual/DefaultView": {
            "module": "./View"
          }
        }
      }
    }
  },
  
  "...": "..."
}

Adapting the HTML sandbox

Edit the sandbox.html file, by replacing the sandbox construction statement with the following one:

var sandbox = new Sandbox({
  id: "pentaho/visual/samples/barD3/Model",
  spec: {
    "data": new Table(datasets.productSales),
    "category": {fields: ["productFamily"]},
    "measure": {fields: ["sales"]}
  },
  container: "viz_div",
  messages: "msg_div"
});

Remarks:

At last, refresh the sandbox.html page in the browser! You should read Hello World!.

Implementing the render code

Let’s now 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 go through the view’s _updateAll code, piece by piece.

Method _updateAll, part 1

In View.js, add the "pentaho/visual/scene/Base" dependency to the module:

define([
  "pentaho/module!_",
  "pentaho/visual/impl/View",
  "d3",
  "pentaho/visual/scene/Base"
], function(module, BaseView, d3, Scene) {
  
  // ...
  
}

Then, replace the code of the _updateAll method with the following:

// _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:

Method _updateAll, part 2

Now, add the following adapted D3 code:

// ViewD3.js
// _updateAll:
function() {
  // Part 1
  // ...
    
  // Part 2
  container.selectAll("*").remove();
  
  var margin = {top: 50, right: 30, bottom: 30, left: 75};

  var width = model.width - margin.left - margin.right;
  var height = model.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", model.width)
    .attr("height", model.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", model.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:

Now, you’ll make a small detour to create the new __getRoleLabel method.

Method __getRoleLabel

Add a property __getRoleLabel, after _updateAll, and give it the following code:

// ViewD3.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:

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

Continue to Styling the view.