The Chart Floorplan is a reusable component for visualizing analytic data. Users can interact with categories in multiple data series, view data summaries, and drill-down to data slices. Multiple chart types are supported: column, bar, line, and combination (column & line).
Two use cases for the Chart Floorplan are supported: full-screen (FUIChartFloorplanViewController
, and partial-screen (FUIChartFloorplanTableViewCell
). See the Fiori Design Guidelines for details of how each should be used within the application.
Usage
The core API of both the UIViewController
and UITableViewCell
variants is the FUIChartFloorplanView
. The FUIChartFloorplanView
exposes a set of title properties directly, for filling in content like the floorplan title
, subtitle
, status
, seriesTitles
, valuesAxisTitle
, and categoryAxisTitle
. It also exposes a FUIChartView
, and FUIChartSummaryView
which have their own sub-APIs.
class AnalyticFloorplanComboChartVC: FUIChartFloorplanViewController {
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - configuring view controller & chart view properties
self.titleText.text = "Monthly APE"
self.status.text = "Updated 20 min ago"
self.subtitle.text = "Month Target: 200k"
self.chartView.chartType = .combo
self.chartView.categoryAxis.labelLayoutStyle = .range
self.chartView.dataSource = self
self.valuesAxisTitle.text = "APE"
self.categoryAxisTitle.text = "Month"
self.seriesTitles = chartSeriesTitles().map { FUIText(stringLiteral: $0)}
// MARK: - configuring summary view properties & setting 'aggregate' summary item
self.headerView.dataSource = self
self.headerView.delegate = self
self.headerView.seriesTitleText = chartSeriesTitles().map { FUIText(stringLiteral: $0)}
let item = FUIChartSummaryItem()
item.categoryIndex = -1 // will pin to left
item.isEnabled = false // is not tappable
let values: [Double] = {
var values: [Double] = []
let seriesCount = chartData().count
for i in 0..<seriesCount {
values.append(chartData()[i].map({ $0 * 1000 }).suffix(from: 1).reduce(0, +))
}
return values
}()
item.valuesText = values.map { formattedTitleForDouble($0)! }
item.title.text = "TTM"
self.headerView.addItem(item)
FUIChartViewDataSource
The FUIChartViewDataSource
supplies data to the FUIChartView
. Data is partioned into series (e.g. all data from 2016
, and all data from 2017
), which are then partioned into categories (e.g. all data from January
, and all data from February
).
The data source supplies the values and formatted meta-text to the chart, for the given number of series & categories.
E.g.:
extension AnalyticFloorplanComboChartVC: FUIChartViewDataSource {
// MARK: - FUIChartViewDataSource methods
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]
}
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]
}
// MARK: - helper methods for generating & formatting sample dat
func chartSeriesTitles() -> [String] {
return ["Actual", "Target"]
}
func chartCategoryTitles() -> [String] {
return ["Jan-17", "Feb-17", "Mar-17", "Apr-17", "May-17", "Jun-17", "Jul-17", "Aug-17", "Sep-17", "Oct-17", "Nov-17", "Dec-17", "Jan-18"]
}
func chartData() -> [[Double]] {
return [[170, 140, 150, 161, 190, 168, 150, 170, 145, 167, 210, 220, 180], [150, 160, 160, 160, 160, 160, 160, 180, 180, 180, 200, 200, 200]]
}
func formattedTitleForDouble(_ value: Double) -> String? {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currencyAccounting
numberFormatter.maximumFractionDigits = 0
return numberFormatter.string(from: value as NSNumber)
}
}
Note: Developers should configure the chart view, then set the
dataSource
property–or, should invokereloadData()
after modifying the configurations of the chart view.
FUIChartSummaryDataSource
The FUIChartSummaryDataSource
supplies an optional FUIChartSummaryItem
, to be displayed or updated when the user taps on a category in the chart. This summary item may be set to tappable using the isEnabled: Bool
property, so that a user can select the summary item to drill-down into the data slice for the category. These summary items may also display trend information across the different series captured in the category. Empty text is permitted, and an emptyText: FUIText
value may be filled-in for nil data values.
extension AnalyticFloorplanComboChartVC: FUIChartSummaryDataSource {
func chartView(_ chartView: FUIChartView, summaryItemForCategory categoryIndex: Int) -> FUIChartSummaryItem? {
let item = FUIChartSummaryItem()
item.categoryIndex = categoryIndex
item.isEnabled = true
let values: [Double] = {
var values: [Double] = []
let seriesCount = chartData().count
for i in 0..<seriesCount {
values.append(chartData()[i][categoryIndex] * 1000.0)
}
return values
}()
let deltaPercent = values.first! / values.last! - 1
let red = UIColor.preferredFioriColor(forStyle: .negative, background: .lightBackground)
let green = UIColor.preferredFioriColor(forStyle: .positive, background: .lightBackground)
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
item.valuesText = values.map { formattedTitleForDouble($0)! }
item.title.text = chartCategoryTitles()[categoryIndex]
item.trend.text = deltaPercent != 0 ? percentFormatter.string(from: deltaPercent as NSNumber)! : nil
switch deltaPercent {
case ..<0:
item.trendImage = FUIIconLibrary.analytics.trendDown.withRenderingMode(.alwaysTemplate)
item.trendSemanticColor = red
case 0:
item.trendImage = nil
item.trendSemanticColor = nil
case 0.0001...:
item.trendImage = FUIIconLibrary.analytics.trendUp.withRenderingMode(.alwaysTemplate)
item.trendSemanticColor = green
default:
break
}
return item
}
}
FUIChartSummaryDelegate
The FUIChartSummaryDelegate
is invoked, when the user taps on an enabled FUIChartSummaryItemView
. The developer should push a new FUIChartFloorplanViewController
, to render the data slice for the selected category.
extension AnalyticFloorplanComboChartVC : FUIChartSummaryDelegate {
func chartView(_ chartView: FUIChartView, didSelectCategorySummaryItem summaryItem: FUIChartSummaryItem) {
let floorplan = SalespersonAnalyticsViewController(month: summaryItem.categoryIndex)
self.navigationController?.pushViewController(floorplan, animated: true)
}
}