diff --git a/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift index 2478aeccf8..9ccae27461 100644 --- a/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift @@ -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 diff --git a/Source/Charts/Data/Interfaces/IBarChartDataSet.swift b/Source/Charts/Data/Interfaces/IBarChartDataSet.swift index b90b4dc0c2..0571a98d5a 100644 --- a/Source/Charts/Data/Interfaces/IBarChartDataSet.swift +++ b/Source/Charts/Data/Interfaces/IBarChartDataSet.swift @@ -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 } } diff --git a/Source/Charts/Renderers/BarChartRenderer.swift b/Source/Charts/Renderers/BarChartRenderer.swift index c2ff0b108b..38ecdf2788 100644 --- a/Source/Charts/Renderers/BarChartRenderer.swift +++ b/Source/Charts/Renderers/BarChartRenderer.swift @@ -12,6 +12,10 @@ import Foundation import CoreGraphics +#if !os(OSX) + import UIKit +#endif + #if canImport(UIKit) import UIKit #endif @@ -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() } } @@ -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() } } @@ -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 + } } diff --git a/Source/Charts/Renderers/HorizontalBarChartRenderer.swift b/Source/Charts/Renderers/HorizontalBarChartRenderer.swift index 36f4a6c846..ec13314e6d 100644 --- a/Source/Charts/Renderers/HorizontalBarChartRenderer.swift +++ b/Source/Charts/Renderers/HorizontalBarChartRenderer.swift @@ -12,6 +12,10 @@ import Foundation import CoreGraphics +#if !os(OSX) + import UIKit +#endif + #if canImport(UIKit) import UIKit #endif @@ -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() } } @@ -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 + } }