Skip to Content

In the SDK release of 2.1 there are new controls introduced for visualizing business data in the form of charts. Visualizing business data is a powerful way to gain insights into the underlying data. Line charts over a period of time is also an effective way to easily see trends in business data.

Currently, SAP Fiori for iOS supports four chart types: horizontal bar, column, line and combination (column + line) charts.

Depending on the business scenario, a particular chart type may be selected when building an application. The classes in the SDK used for working with charts and analytics data are categorized under Chart Floorplan and Analytics Cards. The classes start with FUIChart and FUIKPI prefixes.

In this blog, we will describe how to use the FUIChartFloorplanViewController for adding a Bar Chart.  The following process will be used for adding the Bar Chart:

  1. We will generate an Master Detail application from Assistant connecting an API endpoint on Business Hub.
  2. We will create a FUIChartFloorplanViewController for a Bar Chart that has sample data to chart.
  3. We will add a cell to the collection that will invoke the Bar Chart ViewController when selected
  4. We will change the Collections Table View to invoke the Bar Chart ViewController when the cell is selected.

Step 1:

  1. Generate the Master Detail application using the SDK Assistant for S/4 HANA Cloud Platform with Product Allocation Object endpoint
  2. Use the SDK Assistant to create an application using an endpoint on the SAP API Business Hub (as shown in screenshot below)

  1. Select the S/4 HANA Cloud API product and select the API for Product Allocation Object (as shown in the screenshot below)
  2. Complete the Assistant flow with the defaults in the succeeding pages and build the XCode project. The application should run with the following initial view.

Step 2:

  1. In this step we will add a cell below in the above view to display a Bar Chart with sample data.
  2. First, create the Swift files for FUIChartFloorplanViewController to display Bar Chart as well as sample data classes to chart.

The snippet of code of demonstrating how a Bar Chart is created with sample data is given below.

//

//  Copyright © 2018 SAP SE. All rights reserved.

//

// The following snippet shows the sample data being setup for the Bar chart

// The ViewController is now defined being derived from FUIChartSummaryDataSource

extension SalespersonAnalyticsViewController: FUIChartSummaryDataSource {

func chartView(_ chartView: FUIChartView, summaryItemForCategory categoryIndex: Int) -> FUIChartSummaryItem? {

let item = FUIChartSummaryItem()

item.categoryIndex = categoryIndex

item.isEnabled = true

item.isPreservingTrendHeight = false



let values: [Double] = {

var values: [Double] = []

for series in chartView.series {

values.append(series.valueForCategory(categoryIndex, dimension: 0)!)

}

return values

}()

item.valuesText = values.map { formattedTitleForDouble($0)! }

item.title.text = chartCategoryTitles()[categoryIndex]

return item

}

}

extension SalespersonAnalyticsViewController: FUIChartViewDataSource {

func formattedTitleForDouble(_ value: Double) -> String? {

let numberFormatter = NumberFormatter()

numberFormatter.numberStyle = .none

numberFormatter.maximumFractionDigits = 0

let formattedNumber = numberFormatter.string(from: value as NSNumber)!

return "$\(formattedNumber)k"

}



func chartSeriesTitles() -> [String] {

return ["Actual", "Target"]

}

func chartCategoryTitles() -> [String] {

return ["Adam Humprey", "Jimmy Patrick", "Franck Syren", "Alex Kilgo", "Kim Kilgo", "Sean Long", "Flash Ek-Ularnpun", "Lili Lin", "Luka Ning", "Rodhan Hickey", "Natasha Girotra", "Megan Zurcher", "Joan Wood", "Stanley Thomas Stadelman Jr."]

}

func chartData() -> [[Double]] {

return [[2.5, 2.2, 1.6, 2.8, 1.7, 0.9, 0.8, 1.95, 1.75, 1.33, 2.44, 1.4, 1.25, 1.8]]

}

func numberOfSeries(in: FUIChartView) -> Int {

return chartData().count

}

func chartView(_ chartView: FUIChartView, numberOfValuesInSeries seriesIndex: Int) -> Int {

return chartData()[seriesIndex].count

}

func chartView(_ chartView: FUIChartView, valueForSeries series: Int, category categoryIndex: Int, dimension dimensionIndex: Int) -> Double? {

return chartData()[series][categoryIndex] * 100.0

}

func chartView(_ chartView: FUIChartView, formattedStringForValue value: Double, axis: FUIChartAxisId) -> String? {

return formattedTitleForDouble(value)

}

func chartView(_ chartView: FUIChartView, titleForCategory categoryIndex: Int, inSeries seriesIndex: Int) -> String? {

return chartCategoryTitles()[categoryIndex]

}

}

extension NumberFormatter {

func string(fromOptional from: NSNumber?) -> String {

guard let value = from,

let string = self.string(from: value as NSNumber) else {

return ""

}

return string

}

}

import UIKit

import SAPFiori

class SalespersonAnalyticsViewController: FUIChartFloorplanViewController {

override func viewDidLoad() {

super.viewDidLoad()

self.title = "Sales Chart"

self.chartView.chartType = .bar

self.chartView.numberOfGridlines = 4

self.chartView.dataSource = self

self.headerView.dataSource = self

self.titleText.text = "Total APE ($) by Salesperson"

self.status.text = "Updated 20m ago"

self.categoryAxisTitle.text = "Salesperson"

self.valuesAxisTitle.text = "Total APE ($)"

let item = FUIChartSummaryItem()

item.categoryIndex = -1

item.isEnabled = false

item.isPreservingTrendHeight = false

let values: [Double] = {

var values: [Double] = []

for series in chartView.series {

let categoriesUpperBound = series.numberOfValues - 1

if let valuesInSeries = series.valuesInCategoryRange((0...categoriesUpperBound), dimension: 0) {

values.append(valuesInSeries.flatMap({ $0 }).reduce(0.0, +))

}

}

return values

}()



let numberFormatter  = NumberFormatter()

numberFormatter.numberStyle = .currency

numberFormatter.maximumFractionDigits = 0

item.valuesText = values.map { "\(numberFormatter.string(from: $0 as NSNumber)!)k" }

item.title.text = "Team ($) APE"

self.headerView.addItem(item)

}

}

 

Step3:

Add a TableView cell to the collection that will invoke the Bar Chart ViewController when selected.

Also change the Collections Table View to invoke the Bar Chart ViewController when the cell is selected.

The relevant snippets of code are given below:

In the CollectionsViewController.swift file replace the tableView function for rendering the cells with the following. This new function adds an additional cell to invoke the Row Chart.

 

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {



let cell = tableView.dequeueReusableCell(withIdentifier: FUIObjectTableViewCell.reuseIdentifier, for: indexPath) as! FUIObjectTableViewCell



switch (indexPath.section, indexPath.row) {

case (0, _) :



cell.headlineLabel.text = self.collections[indexPath.row].rawValue

cell.accessoryType = !self.isPresentedInSplitView ? .disclosureIndicator : .none

cell.isMomentarySelection = false

return cell

default:

cell.textLabel?.text = "Row Chart Floorplan"

return cell

}

}

 

To display two sections change the TableView function to the following functions:

// Return two sections now

override func numberOfSections(in _: UITableView) -> Int {

        return 2

override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {

switch section {

   case 0:

      return self.collections.count

   case 1:

      return 1

   default:

      return 2

}

}


After this, modify the TableView cell to display the Bar Chart when the ‘Row Chart Floorplan’ cell is selected:

 

 

override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {



let sectionIndex = indexPath.section

if (sectionIndex == 1) {

let vc = SalespersonAnalyticsViewController()

self.navigationController?.pushViewController(vc, animated: true)

}

else {

self.selectedIndex = indexPath

self.collectionSelected(at: indexPath)

}

}

Now build the project and run. It should display TableView as shown below:

Select the Row Chart Floorplan cell and now the resulting Bar chart will be displayed as given below:

 

In a similar way other Chart controls introduced in this release can be used to display business data in an app.

Check below resources for more information:

Help Documentation

What’s new in SAP Cloud Platform SDK for iOS 2.0 SP01

Latest changes in SAP Cloud Platform SDK for iOS Assistant and its generated apps

SAP Translation Integration – SAP Cloud Platform SDK for iOS 2.0

SAP API Business Hub Integration – SAP Cloud Platform SDK for iOS 2.0

From API to App: Assistant tool generates mobile app scaffolding from a Backend API

 

Krishna Sunkammurali,

Product Management, SAP Cloud Platform User Experience

SAP Labs, Palo Alto

To report this post you need to login first.

5 Comments

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

  1. Florian Pfeffer

    Hi Krishna,

    thanks for writing that blog post and for giving some more insights into the new chart functionality.

    However I have some issues with the documentation of the functionality. Yesterday I had a look on it and I didn’t find no information about the chart functionality (beside the release notes and API reference) in the official documentation.

    Then I had just a look to the API reference, but that kind of documentation (which is just the jazzy generated documentation) does not give any information on how the components are strutured and how the components can be used. With the help of the iOS Fiori Design Guidelines it can be assumed, that the charts can be used (using the corresponding artifacts) in a header, table view cell, collection view cell and as view controller. But is it also supported and recommended that a FUIChartView can be used without using one of the provided container objects?

    Also some pieces are not part of the API reference, e.g. the FUIChartViewDataSource protocol, which you used above, or the FUIChartViewDelegate.

    From my point of view it is great to have such a functionality now, but the usage/implementation time could be much faster with a better documentation. At the moment it is more a trial and error scenario until the undocumented pieces are clear. I think having a better description as part of the documentation would save many people a lot of hours. The guides in the “Other Guides” section of the API reference are going into the right direction, but they are available just for a little part of the provided functionality. Tutorials on the SAP Developer Tutorials area are also ok, but from my experience, they in most cases they are just describing the functionality on a high level with a simple example.

    Best Regards,
    Florian

    (1) 
    1. Alexandre Giguere

      good remark, I think SAP should provide good api documentation before release an SDK, we also need image information, this is very helpful

      I’m also playing around with this API, good potential

      thanks

      alex

       

      (0) 
  2. Krishna Sunkammurali Post author

    Hi Florian,

    Thanks for your comments. We are continuously working on enhancing our documentation. In the interim we will continue to have blogs and tutorials to cover the gaps in our documentation. This blog is an attempt to fill in some of the gaps. Also, please feel free to reach out to me if there is any specific area you need more help or more tutorials/sample code.

    (1) 
  3. Former Member

    Hi Krishna,

     

    Thanks for providing such a tutorial. It’s really exciting to see there are resources available for the new features. However, when I followed the code example, I couldn’t get the additional cell to display on the table.

     

    In the switch statement for returning the table cell, I tried to inspect the indexPath passed in and there is no instance that will fallback to the default case.

    print("section and row: \(indexPath.section, indexPath.row)")
    
    switch (indexPath.section, indexPath.row) {
      case (0, _) :
        cell.headlineLabel.text = self.collections[indexPath.row].rawValue
        cell.accessoryType = !self.isPresentedInSplitView ? .disclosureIndicator : .none
        cell.isMomentarySelection = false
        return cell
      default:
        cell.textLabel?.text = "Row Chart Floorplan"
        return cell
    

    The printed console log is as below. I believe those 5 rows are the original, auto-generated rows by the template.

    section and row: (0, 0)
    section and row: (0, 1)
    section and row: (0, 2)
    section and row: (0, 3)
    section and row: (0, 4)

    Am I missing anything from your code snippets to add the cell with

    indexPath.section == 1?

     

    Thank you very much,

    Jerry

     

    (0) 
    1. Krishna Sunkammurali Post author

      Hi Jerry,

      thanks for your feedback. there is another section that needs to be added to the TableView and the case to handle. Here is the code snippet below ( I have also updated the blog):

      override func numberOfSections(in _: UITableView) -> Int {

      return 2

      override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {

      switch section {

      case 0:

      return self.collections.count

      case 1:

      return 1

      default:

      return 2

      }

      }

       

      Let me know if you run into any other issues.

      Thanks!

      Krishna

      (0) 

Leave a Reply