From 3096d61d5573b083fe53ec658d18a32d33aba59c Mon Sep 17 00:00:00 2001 From: Mosaic Engineering Date: Thu, 16 Apr 2020 17:11:52 -0700 Subject: [PATCH] + Bar Chart: Add rounded corners --- .../Swift/Demos/BarChartViewController.swift | 3 +- .../Standard/BarChartDataSet.swift | 7 +- .../Data/Interfaces/IBarChartDataSet.swift | 5 + .../Charts/Renderers/BarChartRenderer.swift | 144 +++++++++++++----- .../HorizontalBarChartRenderer.swift | 126 ++++++++++----- 5 files changed, 208 insertions(+), 77 deletions(-) diff --git a/ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift index 5400646822..697e596409 100644 --- a/ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift @@ -133,7 +133,8 @@ class BarChartViewController: DemoBaseViewController { set1 = BarChartDataSet(entries: yVals, label: "The year 2017") set1.colors = ChartColorTemplates.material() set1.drawValuesEnabled = false - + set1.roundedCorners = [.topLeft, .topRight] + set1.cornerRadius = 4 let data = BarChartData(dataSet: set1) data.setValueFont(UIFont(name: "HelveticaNeue-Light", size: 10)!) data.barWidth = 0.9 diff --git a/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift index 2478aeccf8..25a086d4b1 100644 --- a/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift @@ -141,7 +141,7 @@ 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 @@ -149,6 +149,11 @@ 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 = [] + + open var cornerRadius: CGFloat = 8.0 + // MARK: - NSCopying open override func copy(with zone: NSZone? = nil) -> Any diff --git a/Source/Charts/Data/Interfaces/IBarChartDataSet.swift b/Source/Charts/Data/Interfaces/IBarChartDataSet.swift index b90b4dc0c2..54df47329b 100644 --- a/Source/Charts/Data/Interfaces/IBarChartDataSet.swift +++ b/Source/Charts/Data/Interfaces/IBarChartDataSet.swift @@ -39,4 +39,9 @@ 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 } + + var cornerRadius: CGFloat { get set } } diff --git a/Source/Charts/Renderers/BarChartRenderer.swift b/Source/Charts/Renderers/BarChartRenderer.swift index c2ff0b108b..13c1b6c7db 100644 --- a/Source/Charts/Renderers/BarChartRenderer.swift +++ b/Source/Charts/Renderers/BarChartRenderer.swift @@ -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)) { @@ -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, cornerRadius: dataSet.cornerRadius) - 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() } } @@ -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, cornerRadius: set.cornerRadius) + + context.saveGState() + + context.addPath(path.cgPath) + context.clip() + context.fill(highlightRect) + + context.restoreGState() } } @@ -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, cornerRadius: CGFloat) -> UIBezierPath { + + let cornerRadius = cornerRadius + + let path = UIBezierPath(roundedRect: rect, + byRoundingCorners: roundedCorners, + cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) + + return path + } } diff --git a/Source/Charts/Renderers/HorizontalBarChartRenderer.swift b/Source/Charts/Renderers/HorizontalBarChartRenderer.swift index 36f4a6c846..a0363a252b 100644 --- a/Source/Charts/Renderers/HorizontalBarChartRenderer.swift +++ b/Source/Charts/Renderers/HorizontalBarChartRenderer.swift @@ -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, cornerRadius: dataSet.cornerRadius) - 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() } } @@ -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, cornerRadius: CGFloat) -> 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 + } }