Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rounded corners for BarChart #4395

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,17 @@ open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDat
open var barShadowColor = NSUIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0)

/// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn.
open var barBorderWidth : CGFloat = 0.0
open var barBorderWidth: CGFloat = 0.0

/// the color drawing borders around the bars.
open var barBorderColor = NSUIColor.black

/// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque)
open var highlightAlpha = CGFloat(120.0 / 255.0)

/// corners to be rounded
open var roundedCorners: UIRectCorner = []

// MARK: - NSCopying

open override func copy(with zone: NSZone? = nil) -> Any
Expand Down
3 changes: 3 additions & 0 deletions Source/Charts/Data/Interfaces/IBarChartDataSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ public protocol IBarChartDataSet: IBarLineScatterCandleBubbleChartDataSet

/// array of labels used to describe the different values of the stacked bars
var stackLabels: [String] { get set }

/// corners to be rounded
var roundedCorners: UIRectCorner { get set }
}
144 changes: 105 additions & 39 deletions Source/Charts/Renderers/BarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,9 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
// draw the bar shadow before the values
if dataProvider.isDrawBarShadowEnabled
{
for j in stride(from: 0, to: buffer.rects.count, by: 1)
for firstIndexInBar in stride(from: 0, to: buffer.rects.count, by: 1)
{
let barRect = buffer.rects[j]
let barRect = buffer.rects[firstIndexInBar]

if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
Expand All @@ -406,53 +406,81 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
{
context.setFillColor(dataSet.color(atIndex: 0).cgColor)
}

context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.setLineCap(.square)

// In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements
let isStacked = dataSet.isStacked
let stackSize = isStacked ? dataSet.stackSize : 1

for j in stride(from: 0, to: buffer.rects.count, by: 1)
for firstIndexInBar in stride(from: 0, to: buffer.rects.count, by: stackSize)
{
let barRect = buffer.rects[j]
context.saveGState()

let lastIndexInBar = firstIndexInBar + stackSize - 1


if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}
let topRectInBar = findTopRectInBar(barRects: buffer.rects,
firstIndexInBar: firstIndexInBar,
lastIndexInBar: lastIndexInBar)

let path = createBarPath(for: topRectInBar, roundedCorners: dataSet.roundedCorners)

if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
context.addPath(path.cgPath)
context.clip()

if !isSingleColor
{
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: j).cgColor)
for index in firstIndexInBar...lastIndexInBar {

let barRect = buffer.rects[index]

if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}

if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}

if !isSingleColor
{
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: index).cgColor)
}

context.addRect(barRect)
context.fillPath()

if drawBorder {
context.stroke(barRect)
}

// Create and append the corresponding accessibility element to accessibilityOrderedElements
if let chart = dataProvider as? BarChartView
{
let element = createAccessibleElement(withIndex: index,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
}

accessibilityOrderedElements[index/stackSize].append(element)
}

}

context.fill(barRect)
context.restoreGState()

if drawBorder
{
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.stroke(barRect)
}

// Create and append the corresponding accessibility element to accessibilityOrderedElements
if let chart = dataProvider as? BarChartView
{
let element = createAccessibleElement(withIndex: j,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
}

accessibilityOrderedElements[j/stackSize].append(element)
context.addPath(path.cgPath)
context.strokePath()
}
}

Expand Down Expand Up @@ -806,12 +834,22 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
y1 = e.y
y2 = 0.0
}

prepareBarHighlight(x: e.x, y1: e.positiveSum, y2: -e.negativeSum, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect)

prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect)

setHighlightDrawPos(highlight: high, barRect: barRect)
var highlightRect = CGRect()
prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &highlightRect)
setHighlightDrawPos(highlight: high, barRect: highlightRect)

context.fill(barRect)
let path = createBarPath(for: barRect, roundedCorners: set.roundedCorners)

context.saveGState()

context.addPath(path.cgPath)
context.clip()
context.fill(highlightRect)

context.restoreGState()
}
}

Expand Down Expand Up @@ -901,4 +939,32 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer

return element
}

private func findTopRectInBar(barRects: [CGRect], firstIndexInBar: Int, lastIndexInBar: Int) -> CGRect {
var topRectInBar = barRects[firstIndexInBar]
if barRects[lastIndexInBar].origin.y < topRectInBar.origin.y {
topRectInBar = barRects[lastIndexInBar]
}

var height: CGFloat = 0
for index in firstIndexInBar...lastIndexInBar {
height += barRects[index].height
}

topRectInBar.size.height = height

return topRectInBar
}

/// Creates path for bar in rect with rounded corners
internal func createBarPath(for rect: CGRect, roundedCorners: UIRectCorner) -> UIBezierPath {

let cornerRadius = rect.width / 2.0

let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: roundedCorners,
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))

return path
}
}
126 changes: 90 additions & 36 deletions Source/Charts/Renderers/HorizontalBarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,53 +243,80 @@ open class HorizontalBarChartRenderer: BarChartRenderer
{
context.setFillColor(dataSet.color(atIndex: 0).cgColor)
}

context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.setLineCap(.square)

// In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements
// In case the chart is stacked, we need to accomodate individual bars within accessibilityOrderedElements
let isStacked = dataSet.isStacked
let stackSize = isStacked ? dataSet.stackSize : 1

for j in stride(from: 0, to: buffer.rects.count, by: 1)
for firstIndexInBar in stride(from: 0, to: buffer.rects.count, by: stackSize)
{
let barRect = buffer.rects[j]
context.saveGState()

if (!viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height))
{
break
}
let lastIndexInBar = firstIndexInBar + stackSize - 1

let leftRectInBar = findMostLeftRectInBar(barRects: buffer.rects,
firstIndexInBar: firstIndexInBar,
lastIndexInBar: lastIndexInBar)

let path = createBarPath(for: leftRectInBar, roundedCorners: dataSet.roundedCorners)

if (!viewPortHandler.isInBoundsBottom(barRect.origin.y))
{
continue
}
context.addPath(path.cgPath)
context.clip()

if !isSingleColor
{
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: j).cgColor)
for index in firstIndexInBar...lastIndexInBar {

let barRect = buffer.rects[index]

if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}

if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}

if !isSingleColor
{
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: index).cgColor)
}

context.addRect(barRect)
context.fillPath()

if drawBorder {
context.stroke(barRect)
}

// Create and append the corresponding accessibility element to accessibilityOrderedElements
if let chart = dataProvider as? BarChartView
{
let element = createAccessibleElement(withIndex: index,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
}

accessibilityOrderedElements[index/stackSize].append(element)
}

}

context.fill(barRect)

context.restoreGState()
if drawBorder
{
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.stroke(barRect)
}

// Create and append the corresponding accessibility element to accessibilityOrderedElements (see BarChartRenderer)
if let chart = dataProvider as? BarChartView
{
let element = createAccessibleElement(withIndex: j,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
}

accessibilityOrderedElements[j/stackSize].append(element)
context.addPath(path.cgPath)
context.strokePath()
}
}

Expand Down Expand Up @@ -629,4 +656,31 @@ open class HorizontalBarChartRenderer: BarChartRenderer
{
high.setDraw(x: barRect.midY, y: barRect.origin.x + barRect.size.width)
}

override internal func createBarPath(for rect: CGRect, roundedCorners: UIRectCorner) -> UIBezierPath {

let cornerRadius = rect.height / 2.0

let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: roundedCorners,
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))

return path
}

private func findMostLeftRectInBar(barRects: [CGRect], firstIndexInBar: Int, lastIndexInBar: Int) -> CGRect {
var leftRectInBar = barRects[firstIndexInBar]
if barRects[lastIndexInBar].origin.x < leftRectInBar.origin.x {
leftRectInBar = barRects[lastIndexInBar]
}

var width: CGFloat = 0
for index in firstIndexInBar...lastIndexInBar {
width += barRects[index].width
}

leftRectInBar.size.width = width

return leftRectInBar
}
}
8 changes: 8 additions & 0 deletions Tests/Charts/BarChartTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,12 @@ class BarChartTests: FBSnapshotTestCase
chart.notifyDataSetChanged()
ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance)
}

func testRoundedCorners() {
let dataEntries = setupDefaultValuesDataEntries()
let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries)
let chart = setupDefaultChart(dataSets: [dataSet])
dataSet.roundedCorners = [.allCorners]
FBSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), tolerance: Snapshot.tolerance)
}
}