Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
JayThvV
Product and Topic Expert
Product and Topic Expert

Over the last couple of months I have been playing with D3.js, the JavaScript visualization library that allows you to programmatically create SVG visualizations on the basis of a data set. I love how this allows me to think up how to visualize a particular dataset without any constraints of the capabilities of any charts and graph package included in whatever tool you would otherwise have used. I have since built out custom extensions in Lumira using D3.js, and when I started looking at SAPUI5, one of the first things I wanted to do was create my own D3.js SAPUI5 custom controls.

There have been other articles on including D3.js in SAPUI5 (most notably here and here), but they used an HTML template, rather than include it in a custom control that could be used largely like any other SAPUI5 control. Well, after a bit of puzzling and some help from a colleague who had built custom controls based on other SAPUI5 controls for a different project, I managed to figure it out, and through this blog, I hope to help you create your own.

This is one of the controls I created. You see it here included in a page, embedded within a <Panel> tag. It receives a data set of target, currently booked revenue and a forecast, and then shows the percentage of booked revenue and forecast against the target. We will focus here largely on the SAPUI5 custom control parts rather than the D3.js code, but the control is available for download at the bottom of this blog.

Building the Chart Control

Let’s create our custom control: create a new folder called “controls” inside the WebContent folder of your SAPUI5 application, and create a new JavaScript file. We’ll use the attached D3ChartTemplate.js as an example. In your own controls you should change the class names (and filename) to something a bit more descriptive so you know which is which, and typically you’d call your JavaScript file the same as the control (i.e. BarChart.js would contain BarChart and BarChartItem classes).

We start with a requirement to load the built-in third party D3 library and a declaration of the chart itself:

jQuery.sap.require("sap/ui/thirdparty/d3");

jQuery.sap.declare("sap.jaysdk.Chart");

Then, we need to define our ChartItem. This is used to map between what the chart expects and the dataset you are trying to visualize. You’ll see this once we get to instantiating the chart.

sap.ui.core.Element.extend("sap.jaysdk.ChartItem", { metadata : {

       properties : {

              "region" : {type : "string", group : "Misc", defaultValue : null},

              "budget" : {type : "string", group : "Misc", defaultValue : null},

              "bw" : {type : "string", group : "Misc", defaultValue : null},

              "forecast" : {type : "string", group : "Misc", defaultValue : null

       }

}});


We then set up the definition of the Chart itself:

sap.ui.core.Control.extend("sap.jaysdk.Chart", {

       metadata : {

              properties: {

                     "title": {type : "string", group : "Misc", defaultValue : "Chart Title"}

              },

              aggregations : {

                     "items" : { type: "sap.jaysdk.ChartItem", multiple : true, singularName : "item"}

              }

              ,

              defaultAggregation : "items",

              events: {

                     "onPress" : {},

                     "onChange":{}       

              }                   

       },

       …

}

You see here the ChartItem referenced. Once data is passed in, these will become our “items”.

We need to split the setup of the chart separately from the D3.js code, since we need to create the placeholder first, while the D3.js code will only run after rendering the HTML through the Chart’s onAfterRendering() function. So, let’s create the chart:

createChart : function() {

              /*

         * Called from renderer

         */

        console.log("sap.jaysdk.Chart.createChart()");

              var oChartLayout = new sap.m.VBox({alignItems:sap.m.FlexAlignItems.Center,justifyContent:sap.m.FlexJustifyContent.Center});

              var oChartFlexBox = new sap.m.FlexBox({height:"180px",alignItems:sap.m.FlexAlignItems.Center});

              /* ATTENTION: Important

         * This is where the magic happens: we need a handle for our SVG to attach to. We can get this using .getIdForLabel()

         * Check this in the 'Elements' section of the Chrome Devtools:

         * By creating the layout and the Flexbox, we create elements specific for this control, and SAPUI5 takes care of

         * ID naming. With this ID, we can append an SVG tag inside the FlexBox

         */

              this.sParentId=oChartFlexBox.getIdForLabel();

        oChartLayout.addItem(oChartFlexBox);

           

              return oChartLayout;

    },

We create a VBox and place a FlexBox inside of it. Then, as the comments already suggest, we get the magic. Rather than using a <div> tag or <span> tag that we write deliberately in an HTML file, we’re going to leverage the identifier of the FlexBox to attach our SVG to. This is what it looks like in the Chrome DevTools “Elements” section:


The “__box2” id is what we will use and is stored in this.sParentId.

We have a pretty standard renderer function, with the only difference that we call the createChart function.

renderer : function(oRm, oControl) {

              var layout = oControl.createChart();

        oRm.write("<div");

        oRm.writeControlData(layout); // writes the Control ID and enables event handling - important!

        oRm.writeClasses(); // there is no class to write, but this enables

              // support for ColorBoxContainer.addStyleClass(...)

           

        oRm.write(">");

        oRm.renderControl(layout);

        oRm.addClass('verticalAlignment');

        oRm.write("</div>");

    

   },


Finally, the Chart is completed in the onAfterRendering() function:

onAfterRendering: function(){

        console.log("sap.jaysdk.Chart.onAfterRendering()");

        console.log(this.sParentId);

              var cItems = this.getItems();

              var data = [];

              for (var i=0;i<cItems.length;i++){

                     var oEntry = {};

                     for (var j in cItems[i].mProperties) {

                oEntry[j]=cItems[i].mProperties[j];

            }                                

            data.push(oEntry);

        }

              //console.log("Data:");

              //console.log(data);

           

              /*

         * ATTENTION: See .createChart()

         * Here we're picking up a handle to the "parent" FlexBox with the ID we got in .createChart()

         * Now simply .append SVG elements as desired

         * EVERYTHING BELOW THIS IS PURE D3.js

         */

           

              var vis = d3.select("#" + this.sParentId);

           

              //Your D3.js code HERE

                     

       }

We get the “items” that were passed in containing our data, and read it into the data array. Once that has run, you have the dataset from your model in the chart and you are ready to build out your SVG. We can now attach to the <div> tag with the id we retrieved before using d3.select. After that, it is just standard D3.js code!

Using the Chart control

We can now try to use the chart in our SAPUI5 pages. We start with creating a container inside the Xxx.view.xml file:

<Panel>

<headerToolbar>

              <Toolbar>

                     <Label text="YTD Booked/Won and Forecast to Budget (Percentage)" />

              </Toolbar>

       </headerToolbar>

       <FlexBox id="ChartHolder" alignItems="Start" justifyContent="Center">

</FlexBox>

</Panel>

This creates the location that we will place our chart in. Now we need to add the chart to our Xxx.controller.js. We need start with declaring the Chart and the ChartItem. The resource path should be the path to your custom control relative to index.html and its name without the js extension. Notice that since the ChartItem is embedded in the controls/Chart.js file, its module path is controls/Chart. Again, for your own controls, use the same convention, but obviously a more descriptive name for your custom chart.


jQuery.sap.registerModulePath("sap.jaysdk.Chart", "controls/Chart");

jQuery.sap.require("sap.jaysdk.Chart");

jQuery.sap.registerModulePath("sap.jaysdk.ChartItem", "controls/Chart");

jQuery.sap.require("sap.jaysdk.ChartItem");

Next, we instantiate our chart, inside the onBeforeRendering() function (or a separate function call from within that event function). We find the ChartHolder FlexBox, create a ChartItem that maps the fields from the data in your model to the chart, and create a new chart (here with a path included). We then get a handle on the model and set the model to the chart. Finally, we add the chart to the FlexBox container.

var oChartHolder = this.byId("ChartHolder");

var oChartItem = new sap.jaysdk.ChartItem({region:"{region}", budget:"{budget}", bw:"{bw_ytd}", forecast:"{forecast}"});

/* new  chart */

var oChart = new sap.jaysdk.Chart({

       items: {path : "/regions", template : oChartItem}

});

           

var oModel = sap.ui.getCore().getModel("chart");

oChart.setModel(oModel);

oChartHolder.addItem(oChart);

And that’s it! Your chart should now be visible in your application, and can be embedded like any other control.

Attached below you'll find the D3ChartTemplate as well as the PerformanceToTargetComparison chart of which a screenshot is shown at the top of this blog. I have also included a JSON file with the sample data.

SAPUI5 is an excellent framework to quickly develop business applications, and using the techniques described here, you can now add any fancy visualization you would like. Happy hacking!

14 Comments