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 partitioned into series (for example, all data from 2016, and all data from 2017), which are then partioned into categories (for example, 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.

For example:

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 data

    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 invoke reloadData() 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)
    }
}