Originally Published at:

Displaying data in using graphics like Bar charts, Stacked charts, Pyramids, Maps are more appealing as well as fun to work with. Recently, I came across this great library D3.js which is based on Javascript. The library D3.js is very powerful and provides so many different type of graphical options which we can use.

Introduction

After doing quite a lot research I came across the tutorial on creating the Bar charts. The example works excellent as long as data is embedded or you get the data and pass it to your HTML page. As first thought, I started looking into exposing SAP data using the WebServices. But, I haven’t yet able to make it work, yet. So, I selected second best option which I knew would work – Using the RESTful web service to build the entire page alongwith the data as the response of the Service.

Intro to D3.js

D3 is supports HTML5 which is much more powerful flavor of HTML. D3 has so many API which can be used to create fancy graphics using HTML5 tags, CSS and JavaScript. To be able use and view the output generated using D3.js, your browser must support HTML5 like Chrome, Mozilla, Opera and IE 8 onwards. I will use SVG component of HTML5. D3 definitely supports traditional HTML but it would be much more easier with HTML5.  To start with you need to include the D3.js library in your HTML page. After that you need to bind some data in JS. You can assign the data directly to an array and start using the APIs. I am assigning this data into JSON format first as I am planning to expose the data in JSON from SAP after calling it from ABAP. I would then parse the JSON to build up my required arrays of data.

Example to create Vertical Bar Charts

This code would create rect elements calculating height and width. Add values on top of the bar charts. Add this line underneath the chart as a base. Add labels below the line to complete the chart.

var obj = eval ("(" + document.getElementById("json-data").innerHTML + ")");
var jdata = obj.cdata;
var data = new Array();
var datal = new Array();
for (i=0; i<jdata.length;i++){
          data[i] = jdata[i].VALUE;
          datal[i] = jdata[i].LABEL;
}
console.log(datal);
var w = 30,
    h = 400;
var max_data = Math.max.apply( Math, data );
max_data = max_data + 1;
var x = d3.scale.linear()
    .domain([0, 1])
    .range([0, w]);
var y = d3.scale.linear()
    //.domain([0, 25])    // min and max of the data set
    .domain([0, max_data])    // min and max of the data set
    .rangeRound([0, h]);
var chart = d3.select("body").append("svg")
     .attr("class", "chart")
     .attr("width", w * data.length - 1)
     .attr("height", function(){return h + 40;});
chart.selectAll("rect")
     .data(data)
   .enter().append("rect")
     .attr("x", function(d, i) { return x(i) - .5; })
     .attr("y", function(d) { return h - y(d) - .5 ; })
     .attr("width", w)
     .attr("height", function(d) { return y(d); });
chart.append("line")
     .attr("x1", 0)
     .attr("x2", w * data.length)
     .attr("y1", h - .5)
     .attr("y2", h - .5)
     .style("stroke", "#000");
//values on top
chart.selectAll("text")
     .data(data)
   .enter().append("text")
     .attr("x", function(d, i) { return x(i) + w / 2 + 5; })
     .attr("y", function(d) { return h - y(d) - 10; })
     .attr("dx", -3) // padding-right
     .attr("dy", ".35em") // vertical-align: middle
     .attr("text-anchor", "end") // text-align: right
           .style("fill", "blue")
     .text(String);
// labels at bottom
chart.selectAll("text1")
     .data(datal)
   .enter().append("text")
      .attr("x", function(d, i) { return x(i) + w / 2 + 10; })
     .attr("y", function(){ return h + 20;})
     .attr("dx", -3) // padding-right
     .attr("dy", ".35em") // vertical-align: middle
     .attr("text-anchor", "end") // text-align: right
     .text(String);

The logic would create a output like this. If you don’t see this output, you might NOT be using the HTML5 compatible browser. This not an image but a IFRAME running a test page to generate bar chart

.

I have used the example code for this tutorial which is available at Bar Chart – 1 & Bar Chart – 2.

Introduction to RESTful WS

RESTful WS is service implemented using HTTP using REST principles. In RESTful WS, you pass arguments in the URI itself like http://something.com/PARAM1. You extract this parameters or set of parameters  (PARAM1/SUBparam1/Text1) and perform desired operation – GET, POST, PUT, DELETE. In GET, you get the data and send back the response. Using POST action, you try to POST the data within the system where RESTful WS is implemented.  Read more on RESTful WS and Real Web Service with REST and ICF  

Step by step guide on Creating RESTful web service

1. Create RESTful WS in SAPYou can create the service in transaction SICF. Create a new service underneath the node default_host/sap/bc. You may want to create a new node, whenever you are creating a new RESTful WS as you can create different service underneath that node. Don’t use the node default_host/sap/bc/srt/ as it would required to have SOAMANAGER Configuration, which we are not going to have for our service. 

Place cursor on desired node and select New Sub-Element. Enter the name of the Service in next Popup. In subsequent screen, enter the description.

WS_initial_popup.jpg

2 Enter the Logon Data: Select the tab Logon data and enter the required User credentials

WS_User_Credentials.png

3 Enter the Request Handler: In the handler tab, assign a class which would be used to handle the http request coming from the web. This class need to implement the interface IF_HTTP_EXTENSION in order to gain access of the request and also the required method. Press F1 to know more about the handler class.

Class_F1_Help.png

Add the interface IF_HTTP_EXTENSION in your handler class Implement the method and activate the class.

Handler_Class.png

Add the class in the Handler tab of the Service definition. You can create as many as handler class and assign them in the handler tab. All the classes here would be accessed in the sequence.

WS_Handler_Class.png

Locate your service in the service tree and activate it.

Web Service Testing

Once the class is active, Put an external break point in the method implementation. Locate your service and select Test Service from context menu. System will stop at your break-point.

WS_Initial_Test.png

You can note down the URI or URL and call the Service directly. Make sure you remove the client from the URL.  For now we would add this code in our handler class to interpret the request and send our response back. This would send response in HTML with text Hello SCN from Restful Ws.  At high level, code does this:

  • Extract the Parameters from URI
  • Do the logic
  • Build the response HTML
 

  DATA:

  lv_path TYPE string,

  lv_cdata TYPE string,

  lv_param TYPE string.

  DATA: lt_request TYPE STANDARD TABLE OF string.

* get the request attributes

  lv_path = server->request->get_header_field( name = '~path_info' ).

  SHIFT lv_path LEFT BY 1 PLACES.

  SPLIT lv_path AT '/' INTO TABLE lt_request.

* build the response

  CONCATENATE

  '<head>'

  '<title>Success</title>'

  '</head>'

  '<body>'

  `<h1>Hello `  lv_param ` from Restful WS</h1>`

  '</body>'

  '</html>'

  INTO lv_cdata.

* Send the response back

  server->response->set_cdata( data = lv_cdata ).

Putting all together

Lets use the RESTful web service created in the previous step. You need to implement below logic to prepare the entire HTML with Data and D3.js JavaScript code and send it as request.

Parse the URL

Get the value from the URL. You can use the method GET_HEADER_FIELD of object SERVER attribute REQUEST. For the Demo, I would pass an integer as the URI parameter. I’ll use this integer to create number of required bars.

* get the request attributes

  lv_path = server->request->get_header_field( name = '~path_info' ).

  SHIFT lv_path LEFT BY 1 PLACES.

  SPLIT lv_path AT '/' INTO TABLE lt_request.

  READ TABLE lt_request INTO lv_param INDEX 1.

HTML template

First all you need to load the HTML template. Creating an HTML tag from scratch in ABAP would need lot of concatenation. Instead you load the file in SMW0. Get this HTML content to make up full output HTML. You place some place holder in the template file. You would need to replace this placeholder with your JSON data. The template file has everything – CSS, JavaScript to load D3.js, API calls to D3.js. So, if any change is required to HTML output other than data needs to be done in HTML. You can definitely create this from scratch or put more place holders to make it more dynamic. 

Load the template d3_bar_chart using transaction code SMW0. Use the option “HTML templates for WebRFC application”. Use FM WWW_GET_SCRIPT_AND_HTML to get the HTML template content.

Exposing Data as JSON

For demo purpose, I would just create some random data. But you can definitely prepare actual data and convert that to JSON. To build a json, I have used a utility json4abap. Download the class include as a local class in the HTTP request handler. Prepare your data and convert the data to json.

Build Final HTML

Replace the placeholder with the JSON data in the HTML template. You would need to convert the JSON data to the table compatible to the HTML data. After that, FIND and REPLACE the placeholder with JSON data. Generate the HTML string and send it back to the request. 

Code Snippet

Method IF_HTTP_EXTENSION~HANDLE_REQUEST of the class ZCL_TEST_D3_DEMO_HANDLER

METHOD if_http_extension~handle_request.

  DATA:

  lv_path TYPE string,

  lv_cdata TYPE string,

  lv_param TYPE string.

  DATA: lt_request TYPE STANDARD TABLE OF string.

  DATA: lv_times TYPE i.

* get the request attributes

  lv_path = server->request->get_header_field( name = '~path_info' ).

  SHIFT lv_path LEFT BY 1 PLACES.

  SPLIT lv_path AT '/' INTO TABLE lt_request.

  READ TABLE lt_request INTO lv_param INDEX 1.

* convert to number

  TRY.

      lv_times = lv_param.

      if lv_times ge 20.    " avoid misuse

        lv_times = 20.

      endif.

    CATCH cx_root.

      lv_times = 5.

  ENDTRY.

* Get HTML data

  lv_cdata = me->prepare_html( lv_times ).

*

* Send the response back

  server->response->set_cdata( data = lv_cdata ).

ENDMETHOD.

Method PREPARE_HTML of the class PREPARE_HTML

METHOD prepare_html.

  TYPE-POOLS: swww.

  TYPES: BEGIN OF ty_data,

           label TYPE char5,

           value TYPE i,

         END OF ty_data.

  DATA: ls_data TYPE ty_data,

        lt_data TYPE TABLE OF ty_data.

  DATA: lr_json TYPE REF TO json4abap,

        l_json TYPE string.

  DATA: lt_json TYPE soli_tab.

  DATA: template        TYPE swww_t_template_name,

        html_table      TYPE TABLE OF w3html.

  DATA: ls_result TYPE match_result.

  DATA: lt_html_final TYPE TABLE OF w3html.

  DATA: lv_to_index TYPE i.

  DATA: lv_total    TYPE i.

  DATA: ls_html LIKE LINE OF lt_html_final.

* Some dummy data. This can be real data based on the parameters

* added in the URL

  CALL FUNCTION 'RANDOM_INITIALIZE'.

  DO 3 TIMES.

    CALL FUNCTION 'RANDOM_I4'

      EXPORTING

        rnd_min = 0

        rnd_max = 20.

  ENDDO.

  DO iv_times TIMES.

    ls_data-label = sy-index + 70.

    CONDENSE ls_data-label.

    CALL FUNCTION 'RANDOM_I4'

      EXPORTING

        rnd_min   = 0

        rnd_max   = 20

      IMPORTING

        rnd_value = ls_data-value.

    APPEND ls_data TO lt_data.

  ENDDO.

* Create JSON

  CREATE OBJECT lr_json.

  l_json = lr_json->json( abapdata = lt_data

                          name = 'cdata' ).

* convert string to 255

  lt_json = cl_bcs_convert=>string_to_soli( l_json ).

* Get template data from the SMW0

  template = 'ZDEMO_D3_BAR_CHART'.

  CALL FUNCTION 'WWW_GET_SCRIPT_AND_HTML'

    EXPORTING

      obj_name         = template

    TABLES

      html             = html_table

    EXCEPTIONS

      object_not_found = 1.

* Merge data into output table

  FIND FIRST OCCURRENCE OF '&json_data_holder&'

    IN TABLE html_table

    RESULTS ls_result.

  lv_to_index = ls_result-line - 1.

  lv_total = LINES( html_table ).

  APPEND LINES OF html_table FROM 1 TO lv_to_index TO lt_html_final.

  APPEND LINES OF lt_json TO lt_html_final.

  ls_result-line = ls_result-line + 1.

  APPEND LINES OF html_table FROM ls_result-line TO lv_total TO lt_html_final.

  LOOP AT lt_html_final INTO ls_html.

    CONCATENATE rv_html_string ls_html-line

      INTO rv_html_string.

  ENDLOOP.

ENDMETHOD.

Output

When you execute the URL with any integer value, you will get output like this. Doesn’t it look great?

Testing_1.png

You can play around with the numbers in the URI to generate different number of vertical bars.

I’m on Google+. Follow me on Google+ .

To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Octav Onu

    Very nice Naimesh . It is exactly what i was looking for !

    Can you manage <svg> tags as well ? My SAP web server cannot understand these tags..what am I doing wrong ?

    Thank you once more !

    (0) 
  2. Arthur Silva

    Great job Naimesh,

    Is it possible to attach a handler in a existing service? For example, a Fiori App has a service enabled in SICF, and a handler redirects the data to a chart in order to show the amount of documents in a certain period.

    Kindly regards,

    Arthur Silva

    (0) 

Leave a Reply