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.
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.
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.
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.
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
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.
2 Enter the Logon Data: Select the tab Logon data and enter the required User credentials
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.
Add the interface IF_HTTP_EXTENSION in your handler class Implement the method and activate the class.
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.
Locate your service in the service tree and activate it.
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.
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:
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 ).
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.
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.
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.
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.
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.
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.
When you execute the URL with any integer value, you will get output like this. Doesn't it look great?
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+ .
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
2 | |
2 | |
2 | |
2 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 |