Horizontal Bar Chart with X-Axis Custom Renderer
I decided to write an post about Charts Library,
Specifically about how to create a x-axis custom renderer for horizontal bar chart. If we implement
horizontal bar chart without x-axis custom renderer, Alignment Issue shown in Actual Result
will be observed.
Inorder to resolve this alignment issue, we need to use x-axis custom renderer
(Refer Expected Result
for screenshots after using x-axis custom renderer).
Actual Result :
Expected Result :
Assuming you have already installed Charts Library by adding to Podfile of your project and imported Charts to required UIViewConntroller.
Let’s start by creating required categories and associated values for both segments i.e. First
& Second
.
let unitsSold = [0.1, 0.7, 0.9, 11.1, 11.6, 13.5, 20.3, 41.2]
let unitsSold2 = [0.5, 2.0, 2.1, 2.3, 15.1, 15.5, 18.4, 44.1]
let singleLineCategory1 = ["Small Category1", "Medium Category 11", "Medium Category 12", "Medium Category 13", "Small Category2", "Medium Category 14", "Medium Category 15", "Very very Big Category 1"]
let singleLineCategory2 = ["Medium Category 01", "Medium Category 02", "Small Category1", "Very very Big Category 1", "Small Category2", "Small Category3", "Medium Category 03", "Small Category4"]
Next we need to create and connect IBOutlets of HorizontalBarChartView
and UISegmentedControl
.
Also, Update viewDidLoad()
method with target for segmentControl
:
@IBOutlet var hBarChartView: HorizontalBarChartView!
@IBOutlet var segmentControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
segmentControl.addTarget(self, action: #selector(segmentedControlDidChange(_:)), for: .valueChanged)
}
Now, create setupGraphSettings()
method to define required graph Configurations/Settings (they are self-explanatory):
func setupGraphSettings() {
hBarChartView.extraLeftOffset = 30.0
hBarChartView.extraTopOffset = 0.0
hBarChartView.extraBottomOffset = 0.0
hBarChartView.extraRightOffset = 30.0
hBarChartView.pinchZoomEnabled = false
hBarChartView.doubleTapToZoomEnabled = false
hBarChartView.scaleXEnabled = false
hBarChartView.scaleYEnabled = false
// Graph Config
hBarChartView.legend.enabled = false
let xAxis = hBarChartView.xAxis
xAxis.enabled = true
xAxis.labelPosition = .bottom
xAxis.drawAxisLineEnabled = false
xAxis.drawGridLinesEnabled = false
xAxis.drawLabelsEnabled = true
xAxis.axisMinimum = 0
xAxis.axisMaximum = 8
xAxis.granularityEnabled = true
xAxis.granularity = 1
let leftAxis = hBarChartView.leftAxis
leftAxis.enabled = true
leftAxis.drawLabelsEnabled = false
leftAxis.axisMinimum = 0
hBarChartView.rightAxis.enabled = false
// Graph Color & Font settings
hBarChartView.backgroundColor = .white
leftAxis.zeroLineColor = UIColor.clear
leftAxis.axisLineColor = UIColor.clear
leftAxis.gridColor = UIColor.black.withAlphaComponent(0.2)
xAxis.labelFont = UIFont.systemFont(ofSize: 14)
xAxis.labelTextColor = UIColor.black.withAlphaComponent(0.5)
}
We need to create 2 helper method dataSetWith()
to create BarChartDataSet
and setupPercentValueFormatter
for formatting values with “%” suffix:
func dataSetWith(entries: [BarChartDataEntry],
colors: [UIColor] = [.black],
highlightColor: UIColor,
label: String = "") -> BarChartDataSet {
let barDataSet = BarChartDataSet(entries: entries, label: label)
barDataSet.valueFont = UIFont.systemFont(ofSize: 14)
barDataSet.drawValuesEnabled = true
barDataSet.colors = colors
barDataSet.highlightColor = highlightColor
return barDataSet
}
func setupPercentValueFormatter(barData : BarChartData) {
let percentAxisFormatter = NumberFormatter()
percentAxisFormatter.numberStyle = .decimal
percentAxisFormatter.positiveSuffix = "%"
percentAxisFormatter.maximumFractionDigits = 2
barData.setValueFormatter(DefaultValueFormatter(formatter: percentAxisFormatter))
barData.setValueFont(UIFont.systemFont(ofSize: 12))
barData.setValueTextColor(UIColor.black.withAlphaComponent(0.6))
}
Now we are ready to create methods for updating Chart data based on selected UISegmentControl
.
We have 2 Segments, namely First
& Second
. When user taps on First
segment setDataForSegmentFirst()
method will be executed and When user taps on Second
segment setDataForSegmentSecond()
method will be executed.
We start by resetting chart data i.e. hBarChartView.data = nil
in each of these methods and use singleLineCategory1
categories in case of First
&
singleLineCategory2
categories in case of Second
to format X-axis values of Chart. Next we create arrEntries
for Chart entries and
append unitsSold
values for First
segment & unitsSold2
for Second
segment.
After creating BarChartData
from BarChartDataSet
we notify ChartView of data modification with hBarChartView.data?.notifyDataChanged()
:
Note : This is horizontal bar chart so X-axis are configured to bottom
. You can change xAxis.labelPosition = .bottom
within setupGraphSettings()
method to configure position as per your needs.
func setDataForSegmentFirst() {
hBarChartView.data = nil
hBarChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: singleLineCategory1)
var arrEntries = [BarChartDataEntry]()
for interval in 0..<unitsSold.count {
arrEntries.append(BarChartDataEntry(x: Double(interval), y: Double(unitsSold[interval])))
}
let arrarDataSet = dataSetWith(entries: arrEntries,
colors: [UIColor.cyan],
highlightColor: UIColor.black,
label: "label1")
let barData = BarChartData(dataSet: arrarDataSet)
setupPercentValueFormatter(barData: barData)
barData.barWidth = 0.2
hBarChartView.data = barData
hBarChartView.data?.notifyDataChanged()
}
func setDataForSegmentSecond() {
hBarChartView.data = nil
hBarChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: singleLineCategory2)
var arrEntries = [BarChartDataEntry]()
for interval in 0..<unitsSold2.count {
arrEntries.append(BarChartDataEntry(x: Double(interval), y: Double(unitsSold2[interval])))
}
let arrarDataSet = dataSetWith(entries: arrEntries,
colors: [UIColor.blue],
highlightColor: UIColor.black,
label: "label2")
let barData = BarChartData(dataSet: arrarDataSet)
setupPercentValueFormatter(barData: barData)
barData.barWidth = 0.2
hBarChartView.data = barData
hBarChartView.data?.notifyDataChanged()
}
Next, We create setupChart()
method which will inturn setup Graph with required Configurations/Settings
as well as update Horizontal bar chart with data for First
i.e. default Segment. Also we need to update viewDidLoad()
by adding setupChart()
method
so that on intialisation itself Horizontal Bar chart is first configured with required settings & then loaded with data of First
segment.
func setupChart() {
setupGraphSettings()
setDataForSegmentFirst()
}
@objc func segmentedControlDidChange(_ sender:UISegmentedControl) {
let index = sender.selectedSegmentIndex
switch index {
case 0:
setDataForSegmentFirst()
default:
setDataForSegmentSecond()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
segmentControl.addTarget(self, action: #selector(segmentedControlDidChange(_:)), for: .valueChanged)
setupChart()
}
If you compile the code and run you will observe the alignment issue mentioned at the start of this post (Refer ‘Actual Result’ for screenshots). Now, lets amend the code to use Renderer so that we can get the expected result.
Inorder to create CustomHorizontalXAxisRenderer
we need to first jump to the definition of XAxisRendererHorizontalBarChart
for drawLabels
method.
Next create a new class of CustomHorizontalXAxisRenderer
which inherits from XAxisRendererHorizontalBarChart
so we can override the behaviour of how labels are drawn.
Lastly update the logic by adding following three lines before drawLabel
call : -
let maxWidth:CGFloat = 136// Update as per your needs
let actualSize = label.size(withAttributes: [.font: xAxis.labelFont])
let newPositionX = pos - (maxWidth - actualSize.width)
Add below line to setupGraphSettings()
method to start using the custom renderer.
hBarChartView.xAxisRenderer = CustomHorizontalXAxisRenderer(
viewPortHandler:hBarChartView.viewPortHandler,
xAxis: xAxis,
transformer:hBarChartView.getTransformer(forAxis: .left),
chart: hBarChartView)
That’s it. Compile the code you will observe the expected behaviour in simulator. (Refer ‘Expected Result’ for screenshots)
Hope above post was informative and useful. You can check the source code on github : HERE