This documentation targets the API shipped with Pentaho 8.2. Click here for the updated version shipped with Pentaho 8.3.

Step 3 - Creating the view

Skeleton view code

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

define([
  "pentaho/module!_",
  "pentaho/visual/base/View",
  "./Model",
  "d3",
  "pentaho/visual/scene/Base"
], function(module, BaseView, BarModel, d3, Scene) {
  
  "use strict";

  // Create and return the Bar View class
  return BaseView.extend({
    $type: {
      id: module.id,
      props: [
        // Specialize the inherited model property to the Bar model type
        {
          name: "model",
          valueType: BarModel
        }
      ]
    },
  
    _updateAll: function() {
      d3.select(this.domContainer).text("Hello World!");
    }
  })
  .configure({$type: module.config});
});

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

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 the VizAPI dev context -->
  <script type="text/javascript" src="node_modules/@pentaho/viz-api/webcontext.js"></script>

  <script type="text/javascript">

    /* globals require */

    require.config({
      paths: {
        "d3": "./node_modules/d3/build/d3"
      }
    });

    require([
      "pentaho-visual-samples-bar-d3/Model",
      "pentaho/visual/base/View",
      "pentaho/data/Table",
      "json!./sandbox-data.json"
    ], function(BarModel, BaseView, Table, dataSpec) {

      // Create the visualization model.
      var modelSpec = {
        "data": new Table(dataSpec),
        "category": {fields: ["productFamily"]},
        "measure": {fields: ["sales"]}
      };

      var model = new BarModel(modelSpec);

      // Create the visualization view.
      var viewSpec = {
        width: 1400,
        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.
      BaseView.createAsync(viewSpec)
        .then(function(view) {
          // Handle the execute action.
          view.on("pentaho/visual/action/Execute", {
            "do": function(event, action) {
              alert("Executed " + action.dataFilter.$contentKey);
            }
          });

          // Handle the select action.
          view.on("pentaho/visual/action/Select", {
            "finally": function(event, action) {
              document.getElementById("messages_div").innerText =
                "Selected: " + view.model.selectionFilter.$contentKey;
            }
          });

          // Render the visualization.
          return view.update();
        }, onError);
    }, onError);

    function onError(error) {
      alert(error.message);
    }
  </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:

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 ViewD3.js, replace the code of the _updateAll method with the following:

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

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

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:

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