Skip to Content
Technical Articles
Author's profile photo Ferry Djaja

Network Graph Visualization in SAP Analytics Cloud Analytic App

In this quick tutorial, we will create an SAC Analytic App custom widget to visualize network graph force layout.

Network Graph

As highlighted in Fiori Design Guidelines, the network graph displays a large amount of data by highlighting the relationships between individual records. Records are displayed as nodes, and connectors (lines) show the relationships between them. Below is the screenshot of the network graph with force layout algorithms.


Prepare the Data

Let’s prepare the data in CSV format. I am getting the data from the SAPUI5 sample Force-Based Layout with Static Nodes. Save it to “data.csv”.

0,0,1,Iron Man,1,Error,sap-icon://key-user-settings,Release date,"May 2, 2008",Director,Jon Favreau
1,1,5,Iron Man 2,1,Error,sap-icon://key-user-settings,Release date,"May 7, 2010",Director,Jon Favreau
2,2,5,The Incredible Hulk,1,Error,sap-icon://theater,Release date,"June 13, 2008",Director,Louis Leterrier
3,3,5,Thor,1,Warning,sap-icon://wrenc,Release date,"May 6, 2011",Director,Kenneth Branagh
4,4,5,Captain America: The First Avenger,1,Success,sap-icon://unfavorite,Release date,"July 22, 2011",Director,Joe Johnston
5,5,6,Marvel's The Avengers,1,Error,sap-icon://text-color,Release date,"May 4, 2012",Director,Joss Whedon
6,5,7,Marvel's The Avengers,1,Error,sap-icon://text-color,Release date,"May 4, 2012",Director,Joss Whedon
7,5,8,Marvel's The Avengers,1,Error,sap-icon://text-color,Release date,"May 4, 2012",Director,Joss Whedon
8,5,19,Marvel's The Avengers,1,Error,sap-icon://text-color,Release date,"May 4, 2012",Director,Joss Whedon
9,6,10,Iron Man 3,2,Error,sap-icon://key-user-settings,Release date,"May 3, 2013",Director,Shane Black
10,7,10,Thor: The Dark World,2,Warning,sap-icon://wrench,Release date,"November 8, 2013",Director,Alan Taylor
11,8,10,Captain America: The Winter Soldier,2,Success,sap-icon://unfavorite,Release date,"April 4, 2014",Director,Anthony & Joe Russo
12,9,12,Doctor Strange,3,Error,sap-icon://activate,Release date,"November 4, 2016",Director,Scott Derrickson
13,10,12,Avengers: Age of Ultron,2,Error,sap-icon://text-color,Release date,"May 1, 2015",Director,Joss Whedon
14,10,13,Avengers: Age of Ultron,2,Error,sap-icon://text-color,Release date,"May 1, 2015",Director,Joss Whedon
15,10,14,Avengers: Age of Ultron,2,Error,sap-icon://text-color,Release date,"May 1, 2015",Director,Joss Whedon
16,10,19,Avengers: Age of Ultron,2,Error,sap-icon://text-color,Release date,"May 1, 2015",Director,Joss Whedon
17,,,Ant-Man and the Wasp,3,Error,sap-icon://chain-link,Release date,"July 6, 2018",Director,Peyton Reed
18,12,20,Thor: Ragnarok,3,Warning,sap-icon://wrench,Release date,"November 3, 2017",Director,Taika Waititi
19,13,11,Ant-Man,2,Error,sap-icon://chain-link,Release date,"July 17, 2015",Director,Peyton Reed
20,13,14,Ant-Man,2,Error,sap-icon://chain-link,Release date,"July 17, 2015",Director,Peyton Reed
21,14,16,Captain America: Civil War,3,Success,sap-icon://unfavorite,Release date,"May 6, 2016",Director,Anthony & Joe Russo
22,14,17,Captain America: Civil War,3,Success,sap-icon://unfavorite,Release date,"May 6, 2016",Director,Anthony & Joe Russo
23,15,18,Guardians of the Galaxy,2,Error,sap-icon://shield,Release date,"August 1, 2014",Director,James Gunn
24,16,20,Spider-Man: Homecoming,3,Error,sap-icon://tree,Release date,"July 7, 2017",Director,Jon Watts
25,17,20,Black Panther,3,Error,sap-icon://circle-task-2,Release date,"February 16, 2018",Director,Ryan Coogler
26,18,20,Guardians of the Galaxy Vol. 2,3,Error,sap-icon://shield,Release date,"May 5, 2017",Director,James Gunn
27,,,Avengers 4,3,Error,sap-icon://text-color,Release date,"May 3, 2019",Director,Anthony & Joe Russo
28,20,19,Avengers: Infinity War,3,Error,sap-icon://text-color,Release date,"April 27, 2018",Director,Anthony & Joe Russo

Expose the Data

Write an R script with Plumber library to expose the data as an REST API, so we can call it from the SAC custom widget app.


#' @filter cors
cors <- function(req, res) {
  res$setHeader("Access-Control-Allow-Origin", "*")
  if (req$REQUEST_METHOD == "OPTIONS") {
    res$setHeader("Access-Control-Allow-Headers", req$HTTP_ACCESS_CONTROL_REQUEST_HEADERS)
    res$status <- 200
  } else {

#* @param df data frame of variables
#* @get /hierarchy
#* @serializer unboxedJSON
function() {
    data <- read.csv("data.csv")
    dataset <- sqldf("SELECT * FROM data")
    totalrow = nrow(dataset)
    node <- list()
    line <- list()
    group <- list()
    count_node <-1
    count <- 1
    # Getting nodes
    for(REC in 1:totalrow) {
      KEY <- dataset[REC,"KEY"]
      FROM <- dataset[REC,"FROM"]
      TO <- dataset[REC,"TO"]
      TITLE <- dataset[REC,"TITLE"]
      GROUP <- dataset[REC, "GROUP"]
      STATUS <- dataset[REC, "STATUS"]
      ICON <- dataset[REC, "ICON"]
      LABEL0 <- dataset[REC, "LABEL0"]
      VALUE0 <- dataset[REC, "VALUE0"]
      LABEL1 <- dataset[REC, "LABEL1"]
      VALUE1 <- dataset[REC, "VALUE1"]
      # Get attributes
      attr1 <- list()
      attr1 <- c(attr1, label=LABEL0)
      attr1 <- c(attr1, value=VALUE0)
      attr2 <- list()
      attr2 <- c(attr2, label=LABEL1)
      attr2 <- c(attr2, value=VALUE1)
      attrF <- list()
      attrF <- c(attrF, list(attr1))
      attrF <- c(attrF, list(attr2))     
      data <- list()
      data <- c(data, key=count_node-1)
      data <- c(data, title=TITLE)
      data <- c(data, group=GROUP)
      data <- c(data, status=STATUS)
      data <- c(data, icon=ICON)
      data <- c(data, attributes=list(attrF))

      if(REC > 1) {
        x <- list.find(node, title==TITLE, 1)
        if(length(x) == 0 ) {
          node[count_node] <- list(data)
          count_node <- count_node + 1
      } else {
        node[count_node] <- list(data)
        count_node <- count_node + 1
      # Get lines
      if(! {
        data_line <- list()
        data_line <- c(data_line, from=FROM)
        data_line <- c(data_line, to=TO)
        line[count] <- list(data_line)
        count <- count + 1
    group1 <- list()
    group1 <- c(group1, key=1)
    group1 <- c(group1, title="Phase One")
    group1 <- list(group1)
    group2 <- list()
    group2 <- c(group2, key=2)
    group2 <- c(group2, title="Phase Two")
    group2 <- list(group2)
    group3 <- list()
    group3 <- c(group3, key=3)
    group3 <- c(group3, title="Phase Three")
    group3 <- list(group3)
    group <- list()
    group <- c(group, group1)
    group <- c(group, group2)
    group <- c(group, group3)
    # Getting final result
    dataF <- list()
    dataF <- c(dataF, nodes=list(node))
    dataF <- c(dataF, lines=list(line))
    dataF <- c(dataF, groups=list(group))
  }, error = function(err){

Let’s execute the script to get the result.

SAC Analytic App Custom Widget

Finally, we’ll create a custom widget with SAPUI5 library sap.ui.core.mvc.XMLView and  sap.suite.ui.commons.networkgraph.Graph. For more detailed steps how to create, you can refer to the blog in the “Reference” section.

On onInit() function, we execute the Ajax “GET” to request the data from the REST API.

onInit: function() {
    var this_ = this;

    if (that._firstConnection === 0) {

        that._firstConnection = 1;

            url: '', //R Plumber REST API URL
            type: 'GET',
            async: true,
            timeout: 0,
            contentType: 'application/json',
            success: function(data) {
                var oGraph,
                    oModel = new JSONModel(data[0]);

                this_.getView().setModel(oModel, that.widgetName);

                this_.oModelSettings = new JSONModel({
                    maxIterations: 200,
                    maxTime: 500,
                    initialTemperature: 200,
                    coolDownStep: 1
                this_.getView().setModel(this_.oModelSettings, "settings");

                this_.oGraph = this_.byId("graph_" + widgetName);
                this_.oGraph._fZoomLevel = 0.75;
            error: function(e) {
                console.log("error: " + e);

Create an Analytic app, load the widget and run it. You should see the output as shown below.








Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Patrick Perrier
      Patrick Perrier


      Very cool! And I like the Marvel Universe dataset 🙂

      Possible to share a full screenshot of what the finish product looks like?


      Author's profile photo Mina Bjelic
      Mina Bjelic

      Hello Ferry,

      Thanks for this great idea and explanation. Unfortunately all seems to work on my side until the part where I add the widget to an App and run it afterwards:


      Is there a chance that something has been changed in the meantime?

      Thanks for your answer.

      Kind regards!