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: 
1,346

Introduction


During the last week, I was working on a project where the majority of the features were around the analytics and graphs. I started exploring the SAPUI5 SDK and Documentation but couldn't find any suitable chart according to the requirement to show a comparison of various Counties and their respective data having


  • X-axis: Years




  • Y-axis: Product Sales across different Countries.





I started exploring with D3.js, the amazing visualization library that helps you to get different perspectives having different visualizations on the basis of data. I have gone through various blogs but found a kind of workaround everyone has done using the HTML template but I wanted to use the control inside XML Views like other SAPUI5 library Controls to re-use the same across various views where I can control various things by properties and data binding. Here, The control also should be user interactive with a nice look and feel.

To achieve the above requirement, after exploration I got to know we can define/develop our own control in SAPUI5 and develop our own library by extending the D3.js Charts. In this blog, I will explain the step by step develop of SAPUI5 Custom Control - MultiLineGraph by extending the D3.js.

 

Environment:



  • SAP Full Stack WebIDE


Step by Step Process:




  • Create a SAPUI5 Project from File menu >> New >> Project from Template.




  • Select the SAPUI5




  • Fill the Project Name, View Name with View Type: XML




  • Create a new folder under webapp with name lib >> customsdk.






  • Create a File "library.js" under customsdk
    sap.ui.define([
    'jquery.sap.global',
    'sap/ui/core/library'
    ], function(jQuery, library) {

    "use-strict";
    /**
    * Suite controls library.
    *
    * @namespace
    * @name customlib.graph
    * @author aroralove7@gmail.com
    * @version ${version}
    * @public
    */

    var mylib={};
    sap.ui.getCore().initLibrary({
    name : "customlib.graph",
    noLibraryCSS: true,
    version: "${version}",
    dependencies : ["sap.ui.core", "sap.m"],
    types: [],
    interfaces: [],
    controls: [
    "customlib.graph.MultiLineGraph"
    ],
    elements: []
    });

    return customlib.graph;

    }, /* bExport= */ false);
    ​​




  • Create a File "MultiLineGraphRenderer.js" under customsdk
    sap.ui.define([],
    function() {
    "use strict";

    /**
    * @namespace customlib.graph
    */
    var MultiLineGraphRenderer = {};

    /**
    * Renders the HTML for the control, using the provided {@link sap.ui.core.RenderManager}.
    *
    * @param {sap.ui.core.RenderManager} oRm RenderManager object
    * @param {sap.ui.core.Control} oControl An object representation of the control that will be rendered
    */
    MultiLineGraphRenderer.render = function(oRm, oControl) {
    oRm.write("<div");
    oRm.writeControlData(oControl);
    oRm.writeClasses();
    oRm.writeStyles();
    oRm.write(">");
    };

    return MultiLineGraphRenderer;

    }, /* bExport= */ true);




  • Create a File "MultiLineGraph.js" under customsdk

    /*
    * A simple UI5 control wrapping the HTML5 media API
    * allowing the library user to easily take Pictures in javascript
    * very easily. The control renders a Video preview element
    * (technically a video html tag). When clicked the image is grabbed
    * as a base64 encoded PNG. In the future would be nice to have the
    * format configurable.
    */
    sap.ui.define([
    'jquery.sap.global',
    'sap/ui/core/Control',
    'com/sample/custom/graph/D3_Graphs_Exension/lib/customD3/core/d3.v4.min'
    ],
    function (jQuery, Control) {
    "use strict";

    /**
    * Constructor for a new MultiLine Graph control.
    *
    * @param {string} [sId] id for the new control, generated automatically if no id is given
    * @param {object} [mSettings] initial settings for the new control
    *
    * @class
    *
    * @public
    * @alias customlib.graph.multilinegraph
    */
    var graph = Control.extend("customlib.graph.MultiLineGraph", {
    /**
    * Control API
    */
    metadata: {
    properties: {

    "id": {
    type: "string",
    defaultValue: ""
    },

    "width": {
    type: "string",
    defaultValue: "640"
    },

    /**
    * Height of the preview window in pixels
    */
    "height": {
    type: "string",
    defaultValue: "480"
    }

    },
    events: {

    }
    },

    /**
    * Lifecycle hook to initialize the control
    */
    init: function () {
    var that = this;
    this._data = {}; // Is the control displaying video at the moment?
    },

    initializeData: function (oData) {
    this._data = oData;
    },

    renderGraph: function (data) {
    var width = 500;
    var height = 300;
    var margin = 50;
    var duration = 250;

    var lineOpacity = "0.25";
    var lineOpacityHover = "0.85";
    var otherLinesOpacityHover = "0.1";
    var lineStroke = "1.5px";
    var lineStrokeHover = "2.5px";

    var circleOpacity = '0.85';
    var circleOpacityOnLineHover = "0.25"
    var circleRadius = 3;
    var circleRadiusHover = 6;


    /* Format Data */
    var parseDate = d3.timeParse("%Y");
    data.forEach(function (d) {
    d.values.forEach(function (d) {
    d.date = parseDate(d.date);
    d.value = +d.value;
    });
    });


    /* Scale */
    var xScale = d3.scaleTime()
    .domain(d3.extent(data[0].values, d => d.date))
    .range([0, width - margin]);

    var yScale = d3.scaleLinear()
    .domain([0, d3.max(data[0].values, d => d.value)])
    .range([height - margin, 0]);

    var color = d3.scaleOrdinal(d3.schemeCategory10);

    /* Add SVG */
    var svg = d3.select("#"+this.getId()).append("svg")
    .attr("width", (width + margin) + "px")
    .attr("height", (height + margin) + "px")
    .append('g')
    .attr("transform", `translate(${margin}, ${margin})`);


    /* Add line into SVG */
    var line = d3.line()
    .x(d => xScale(d.date))
    .y(d => yScale(d.value));

    let lines = svg.append('g')
    .attr('class', 'lines');

    lines.selectAll('.line-group')
    .data(data).enter()
    .append('g')
    .attr('class', 'line-group')
    .on("mouseover", function (d, i) {
    svg.append("text")
    .attr("class", "title-text")
    .style("fill", color(i))
    .text(d.name)
    .attr("text-anchor", "middle")
    .attr("x", (width - margin) / 2)
    .attr("y", 5);
    })
    .on("mouseout", function (d) {
    svg.select(".title-text").remove();
    })
    .append('path')
    .attr('class', 'line')
    .attr('d', d => line(d.values))
    .style('stroke', (d, i) => color(i))
    .style('opacity', lineOpacity)
    .on("mouseover", function (d) {
    d3.selectAll('.line')
    .style('opacity', otherLinesOpacityHover);
    d3.selectAll('.circle')
    .style('opacity', circleOpacityOnLineHover);
    d3.select(this)
    .style('opacity', lineOpacityHover)
    .style("stroke-width", lineStrokeHover)
    .style("cursor", "pointer");
    })
    .on("mouseout", function (d) {
    d3.selectAll(".line")
    .style('opacity', lineOpacity);
    d3.selectAll('.circle')
    .style('opacity', circleOpacity);
    d3.select(this)
    .style("stroke-width", lineStroke)
    .style("cursor", "none");
    });


    /* Add circles in the line */
    lines.selectAll("circle-group")
    .data(data).enter()
    .append("g")
    .style("fill", (d, i) => color(i))
    .selectAll("circle")
    .data(d => d.values).enter()
    .append("g")
    .attr("class", "circle")
    .on("mouseover", function (d) {
    d3.select(this)
    .style("cursor", "pointer")
    .append("text")
    .attr("class", "text")
    .text(`${d.value}`)
    .attr("x", d => xScale(d.date) + 5)
    .attr("y", d => yScale(d.value) - 10);
    })
    .on("mouseout", function (d) {
    d3.select(this)
    .style("cursor", "none")
    .transition()
    .duration(duration)
    .selectAll(".text").remove();
    })
    .append("circle")
    .attr("cx", d => xScale(d.date))
    .attr("cy", d => yScale(d.value))
    .attr("r", circleRadius)
    .style('opacity', circleOpacity)
    .on("mouseover", function (d) {
    d3.select(this)
    .transition()
    .duration(duration)
    .attr("r", circleRadiusHover);
    })
    .on("mouseout", function (d) {
    d3.select(this)
    .transition()
    .duration(duration)
    .attr("r", circleRadius);
    });


    /* Add Axis into SVG */
    var xAxis = d3.axisBottom(xScale).ticks(5);
    var yAxis = d3.axisLeft(yScale).ticks(5);

    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", `translate(0, ${height - margin})`)
    .call(xAxis);

    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append('text')
    .attr("y", 15)
    .attr("transform", "rotate(-90)")
    .attr("fill", "#000")
    .text("Total values");
    },

    onAfterRendering: function () {
    var data = this._data;
    if (data.length > 0) {
    this.renderGraph(data);
    } else {
    this.renderGraphNoData();
    }
    }
    });
    return graph;
    });


  • Now Load the library which we have developed for the application inside manifest.json under "sap.ui5"
    "resourceRoots": {
    "customlib.graph":"./lib/customsdk"

    }​



 

  • In XML View
    <mvc:View controllerName="com.sample.custom.graph.D3_Graphs_Exension.controller.RenderGraph" xmlns:graph="customlib.graph" xmlns:mvc="sap.ui.core.mvc" displayBlock="true"
    xmlns="sap.m">
    <Shell id="shell">
    <App id="app">
    <pages>
    <Page id="page" title="Custom MultiLine Chart">
    <content>
    <graph:MultiLineGraph id="mychart" />
    </content>
    </Page>
    </pages>
    </App>
    </Shell>
    </mvc:View>​


  • In Controller
    var data = [
    {
    name: "India",
    values: [
    {date: "2000", value: "100"},
    {date: "2001", value: "110"},
    {date: "2002", value: "145"},
    {date: "2003", value: "241"},
    {date: "2004", value: "101"},
    {date: "2005", value: "90"},
    {date: "2006", value: "10"},
    {date: "2007", value: "35"},
    {date: "2008", value: "21"},
    {date: "2009", value: "201"}
    ]
    },
    {
    name: "Germany",
    values: [
    {date: "2000", value: "200"},
    {date: "2001", value: "120"},
    {date: "2002", value: "33"},
    {date: "2003", value: "21"},
    {date: "2004", value: "51"},
    {date: "2005", value: "190"},
    {date: "2006", value: "120"},
    {date: "2007", value: "85"},
    {date: "2008", value: "221"},
    {date: "2009", value: "101"}
    ]
    },
    {
    name: "USA",
    values: [
    {date: "2000", value: "50"},
    {date: "2001", value: "10"},
    {date: "2002", value: "5"},
    {date: "2003", value: "71"},
    {date: "2004", value: "20"},
    {date: "2005", value: "9"},
    {date: "2006", value: "220"},
    {date: "2007", value: "235"},
    {date: "2008", value: "61"},
    {date: "2009", value: "10"}
    ]
    }
    ];
    this.getView().byId('mychart').initializeData(data);​



 

 

  • Add CSS classes for nice colors!
    svg {
    font-family: Sans-Serif, Arial;
    }
    .line {
    stroke-width: 2;
    fill: none;
    }

    .axis path {
    stroke: black;
    }

    .text {
    font-size: 12px;
    }

    .title-text {
    font-size: 12px;
    }​


  • Now all configurations are done and we are ready!!


Result




Github


You can find the project setup and code here.

 

References



 

SAPUI5 is a flexible framework to extend and develop the controls according to our needs. D3 is one of the best library for implementing graphs with best user experience.

I hope this blog will help you all to implement custom control by extending D3 charts as SAPUI5 control to provide best user experience with minimal efforts.

 

Happy Learning!

Love Arora.
4 Comments