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:

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:

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:

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:

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:

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

Continue to Styling the view.