Skip to content

Commit

Permalink
Recreate rounded bar chart patch found at:
Browse files Browse the repository at this point in the history
https://github.com/life360/Charts/pull/1/files (Pull request into forked repository)
ChartsOrg#3754 (Pull request into original repository)
ChartsOrg#1066 (Feature request in original repository)
  • Loading branch information
tigloo committed Nov 6, 2019
1 parent a5fda8f commit a06902f
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDat
/// 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 }
}
141 changes: 105 additions & 36 deletions Source/Charts/Renderers/BarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import Foundation
import CoreGraphics

#if !os(OSX)
import UIKit
#endif

#if canImport(UIKit)
import UIKit
#endif
Expand Down Expand Up @@ -406,53 +410,80 @@ 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)

if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
let path = createBarPath(for: topRectInBar, roundedCorners: dataSet.roundedCorners)

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)
context.addPath(path.cgPath)
context.clip()

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 @@ -807,11 +838,21 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
y2 = 0.0
}

prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect)
prepareBarHighlight(x: e.x, y1: e.positiveSum, y2: -e.negativeSum, 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 +942,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: 92 additions & 34 deletions Source/Charts/Renderers/HorizontalBarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import Foundation
import CoreGraphics

#if !os(OSX)
import UIKit
#endif

#if canImport(UIKit)
import UIKit
#endif
Expand Down Expand Up @@ -243,53 +247,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
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]

if (!viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height))
{
break
}
context.saveGState()

if (!viewPortHandler.isInBoundsBottom(barRect.origin.y))
{
continue
}
let lastIndexInBar = firstIndexInBar + stackSize - 1

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

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

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 {

context.fill(barRect)
let barRect = buffer.rects[index]

if drawBorder
{
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.stroke(barRect)
}
if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}

// 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
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)
}

accessibilityOrderedElements[j/stackSize].append(element)
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.restoreGState()

if drawBorder
{
context.addPath(path.cgPath)
context.strokePath()
}
}

Expand Down Expand Up @@ -629,4 +660,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
}
}

0 comments on commit a06902f

Please sign in to comment.