Charts
Charts copied to clipboard
Feature Request:- Add Gradient to Bar chart Bars
Is there a way to add gradient colours in bar chart ? if not can we expect a future version with this functionality ?
I also saw a pending PR regarding the same issue: PR #4411
i also need this feature, finally i found a solution.
enum GradientDirection {
case leftToRight
case rightToLeft
case topToBottom
case bottomToTop
}
class GradientBarChartRenderer: BarChartRenderer {
var gradientColors: [NSUIColor] = []
var gradientDirection: GradientDirection = .topToBottom
typealias Buffer = [CGRect]
fileprivate var _buffers = [Buffer]()
override func initBuffers() {
super.initBuffers()
guard let barData = dataProvider?.barData else { return _buffers.removeAll() }
if _buffers.count != barData.count {
while _buffers.count < barData.count {
_buffers.append(Buffer())
}
while _buffers.count > barData.count {
_buffers.removeLast()
}
}
_buffers = zip(_buffers, barData).map { buffer, set -> Buffer in
let set = set as! BarChartDataSetProtocol
let size = set.entryCount * (set.isStacked ? set.stackSize : 1)
return buffer.count == size
? buffer
: Buffer(repeating: .zero, count: size)
}
}
private func prepareBuffer(dataSet: BarChartDataSetProtocol, index: Int) {
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
let barWidthHalf = CGFloat(barData.barWidth / 2.0)
var bufferIndex = 0
let containsStacks = dataSet.isStacked
let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency)
let phaseY = CGFloat(animator.phaseY)
for i in (0 ..< dataSet.entryCount).clamped(to: 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX))) {
guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
let x = CGFloat(e.x)
let left = x - barWidthHalf
let right = x + barWidthHalf
var y = e.y
if containsStacks, let vals = e.yValues {
var posY = 0.0
var negY = -e.negativeSum
var yStart = 0.0
// fill the stack
for value in vals {
if value == 0.0 && (posY == 0.0 || negY == 0.0) {
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value
yStart = y
} else if value >= 0.0 {
y = posY
yStart = posY + value
posY = yStart
} else {
y = negY
yStart = negY + abs(value)
negY += abs(value)
}
var top = isInverted
? (y <= yStart ? CGFloat(y) : CGFloat(yStart))
: (y >= yStart ? CGFloat(y) : CGFloat(yStart))
var bottom = isInverted
? (y >= yStart ? CGFloat(y) : CGFloat(yStart))
: (y <= yStart ? CGFloat(y) : CGFloat(yStart))
// multiply the height of the rect with the phase
top *= phaseY
bottom *= phaseY
let barRect = CGRect(x: left, y: top,
width: right - left,
height: bottom - top)
_buffers[index][bufferIndex] = barRect
bufferIndex += 1
}
} else {
var top = isInverted
? (y <= 0.0 ? CGFloat(y) : 0)
: (y >= 0.0 ? CGFloat(y) : 0)
var bottom = isInverted
? (y >= 0.0 ? CGFloat(y) : 0)
: (y <= 0.0 ? CGFloat(y) : 0)
var topOffset: CGFloat = 0.0
var bottomOffset: CGFloat = 0.0
if let offsetView = dataProvider as? BarChartView {
let offsetAxis = offsetView.getAxis(dataSet.axisDependency)
if y >= 0 {
// situation 1
if offsetAxis.axisMaximum < y {
topOffset = CGFloat(y - offsetAxis.axisMaximum)
}
if offsetAxis.axisMinimum > 0 {
bottomOffset = CGFloat(offsetAxis.axisMinimum)
}
}
else // y < 0
{
// situation 2
if offsetAxis.axisMaximum < 0 {
topOffset = CGFloat(offsetAxis.axisMaximum * -1)
}
if offsetAxis.axisMinimum > y {
bottomOffset = CGFloat(offsetAxis.axisMinimum - y)
}
}
if isInverted {
// situation 3 and 4
// exchange topOffset/bottomOffset based on 1 and 2
// see diagram above
(topOffset, bottomOffset) = (bottomOffset, topOffset)
}
}
// apply offset
top = isInverted ? top + topOffset : top - topOffset
bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset
// multiply the height of the rect with the phase
// explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520)
if top > 0 + topOffset {
top *= phaseY
} else {
bottom *= phaseY
}
let barRect = CGRect(x: left, y: top,
width: right - left,
height: bottom - top)
_buffers[index][bufferIndex] = barRect
bufferIndex += 1
}
}
}
override func drawDataSet(context: CGContext, dataSet: BarChartDataSetProtocol, index: Int) {
super.drawDataSet(context: context, dataSet: dataSet, index: index)
guard let dataProvider = dataProvider else { return }
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
prepareBuffer(dataSet: dataSet, index: index)
trans.rectValuesToPixel(&_buffers[index])
let buffer = _buffers[index]
for j in buffer.indices {
let barRect = buffer[j]
drawRadianColor(context: context, rect: barRect)
}
}
func drawRadianColor(context: CGContext, rect: CGRect) {
if !gradientColors.isEmpty {
let view = NSUIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
gradientBackground(view: view, colors: gradientColors, direction: gradientDirection)
if let image = self.image(with: view)?.cgImage {
context.draw(image, in: rect)
}
}
}
func gradientBackground(view: NSUIView, colors: [NSUIColor], direction: GradientDirection) {
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = colors
switch direction {
case .leftToRight:
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
case .rightToLeft:
gradient.startPoint = CGPoint(x: 1.0, y: 0.5)
gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
case .topToBottom:
gradient.startPoint = CGPoint(x: 0.5, y: 1.0)
gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
default:
break
}
view.layer.insertSublayer(gradient, at: 0)
}
func image(with view: NSUIView) -> NSUIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}
use it with
chartView.renderer = GradientBarChartRenderer(dataProvider: chartView, animator: chartView.chartAnimator, viewPortHandler: chartView.viewPortHandler)
Hi, I read elsewhere that this was closed, however, I cannot find how to access the bar chart gradient in the latest release 4.0.0 of Sept. 22, 2022. Has it been added to the latest master branch or no?
How do I use this swift class? I created a GradientBarChartRenderer.swift file and copy/pasted the above code into it. Also prepended @objc to the name of the class since I'm using it in an Objective-C project. How do I access the bar gradient rendering if my code with the standard bar chart looks like this:
BarChartDataSet * dataSet = [[BarChartDataSet alloc] initWithEntries:values];
[dataSet setColor:UIColor.blueColor];
NSMutableArray * dataSets = [[NSMutableArray alloc] init];
[dataSets addObject:dataSet];
BarChartData * data = [[BarChartData alloc] initWithDataSets:dataSets];
self.barChartView.data = data;
In addition to the above, added the following code, and the bars just render in solid black:
GradientBarChartRenderer * gradientRenderer = [[GradientBarChartRenderer alloc] initWithDataProvider:self.chartView animator:self.chartView.chartAnimator viewPortHandler:self.chartView.viewPortHandler];
gradientRenderer.gradientColors = @[UIColor.redColor, UIColor.blueColor];
self.chartView.renderer = gradientRenderer;
I tried but not not working for some reason @protosse
Is the latest version of this library has gradientcolor opiton for bar Chart? cause i have not found it. but it is needed in any charts designs now a days. ....
i have found the solution and it s working !! tested
`
import UIKit
import DGCharts
enum GradientDirection {
case leftToRight
case rightToLeft
case topToBottom
case bottomToTop
}
class GradientBarChartRenderer: BarChartRenderer {
var gradientColors: [NSUIColor] = []
var gradientDirection: GradientDirection = .topToBottom
typealias Buffer = [CGRect]
fileprivate var _buffers = [Buffer]()
override func initBuffers() {
super.initBuffers()
guard let barData = dataProvider?.barData else { return _buffers.removeAll() }
if _buffers.count != barData.count {
while _buffers.count < barData.count {
_buffers.append(Buffer())
}
while _buffers.count > barData.count {
_buffers.removeLast()
}
}
_buffers = zip(_buffers, barData).map { buffer, set -> Buffer in
let set = set as! BarChartDataSetProtocol
let size = set.entryCount * (set.isStacked ? set.stackSize : 1)
return buffer.count == size
? buffer
: Buffer(repeating: .zero, count: size)
}
}
private func prepareBuffer(dataSet: BarChartDataSetProtocol, index: Int) {
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
let barWidthHalf = CGFloat(barData.barWidth / 2.0)
var bufferIndex = 0
let containsStacks = dataSet.isStacked
let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency)
let phaseY = CGFloat(animator.phaseY)
for i in (0 ..< dataSet.entryCount).clamped(to: 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX))) {
guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
let x = CGFloat(e.x)
let left = x - barWidthHalf
let right = x + barWidthHalf
var y = e.y
if containsStacks, let vals = e.yValues {
var posY = 0.0
var negY = -e.negativeSum
var yStart = 0.0
// fill the stack
for value in vals {
if value == 0.0 && (posY == 0.0 || negY == 0.0) {
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value
yStart = y
} else if value >= 0.0 {
y = posY
yStart = posY + value
posY = yStart
} else {
y = negY
yStart = negY + abs(value)
negY += abs(value)
}
var top = isInverted
? (y <= yStart ? CGFloat(y) : CGFloat(yStart))
: (y >= yStart ? CGFloat(y) : CGFloat(yStart))
var bottom = isInverted
? (y >= yStart ? CGFloat(y) : CGFloat(yStart))
: (y <= yStart ? CGFloat(y) : CGFloat(yStart))
// multiply the height of the rect with the phase
top *= phaseY
bottom *= phaseY
let barRect = CGRect(x: left, y: top,
width: right - left,
height: bottom - top)
_buffers[index][bufferIndex] = barRect
bufferIndex += 1
}
} else {
var top = isInverted
? (y <= 0.0 ? CGFloat(y) : 0)
: (y >= 0.0 ? CGFloat(y) : 0)
var bottom = isInverted
? (y >= 0.0 ? CGFloat(y) : 0)
: (y <= 0.0 ? CGFloat(y) : 0)
var topOffset: CGFloat = 0.0
var bottomOffset: CGFloat = 0.0
if let offsetView = dataProvider as? BarChartView {
let offsetAxis = offsetView.getAxis(dataSet.axisDependency)
if y >= 0 {
// situation 1
if offsetAxis.axisMaximum < y {
topOffset = CGFloat(y - offsetAxis.axisMaximum)
}
if offsetAxis.axisMinimum > 0 {
bottomOffset = CGFloat(offsetAxis.axisMinimum)
}
}
else // y < 0
{
// situation 2
if offsetAxis.axisMaximum < 0 {
topOffset = CGFloat(offsetAxis.axisMaximum * -1)
}
if offsetAxis.axisMinimum > y {
bottomOffset = CGFloat(offsetAxis.axisMinimum - y)
}
}
if isInverted {
// situation 3 and 4
// exchange topOffset/bottomOffset based on 1 and 2
// see diagram above
(topOffset, bottomOffset) = (bottomOffset, topOffset)
}
}
// apply offset
top = isInverted ? top + topOffset : top - topOffset
bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset
// multiply the height of the rect with the phase
// explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520)
if top > 0 + topOffset {
top *= phaseY
} else {
bottom *= phaseY
}
let barRect = CGRect(x: left, y: top,
width: right - left,
height: bottom - top)
_buffers[index][bufferIndex] = barRect
bufferIndex += 1
}
}
}
override func drawDataSet(context: CGContext, dataSet: BarChartDataSetProtocol, index: Int) {
super.drawDataSet(context: context, dataSet: dataSet, index: index)
guard let dataProvider = dataProvider else { return }
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
prepareBuffer(dataSet: dataSet, index: index)
trans.rectValuesToPixel(&_buffers[index])
let buffer = _buffers[index]
for j in buffer.indices {
let barRect = buffer[j]
drawRadianColor(context: context, rect: barRect)
}
}
func drawRadianColor(context: CGContext, rect: CGRect) {
// Ensure the rect has non-zero dimensions
guard rect.width > 0 && rect.height > 0 else {
return
}
if !gradientColors.isEmpty {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(origin: .zero, size: rect.size) // Set frame to match the size of the rect
let cgColors = gradientColors.map { $0.cgColor }
gradientLayer.colors = cgColors
switch gradientDirection {
case .leftToRight:
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
case .rightToLeft:
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
case .topToBottom:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
case .bottomToTop:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
}
// Create a UIImage from the gradientLayer
UIGraphicsBeginImageContextWithOptions(gradientLayer.bounds.size, gradientLayer.isOpaque, 0.0)
guard let gradientContext = UIGraphicsGetCurrentContext() else { return }
gradientLayer.render(in: gradientContext)
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// Draw the gradientImage onto the context
if let gradientCGImage = gradientImage?.cgImage {
context.draw(gradientCGImage, in: rect)
}
}
}
func image(with view: NSUIView) -> NSUIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}
/*
Use Case
for barChartDataSet
barChartDataSet.colors = [.clear]
let gradientRenderer = GradientBarChartRenderer( dataProvider: barChartView, animator:
barChartView.chartAnimator, viewPortHandler: barChartView.viewPortHandler)
gradientRenderer.gradientColors = [isThisWeekTransactionDetails.value ? .transactionHistoryDetailsBlue :
.transactionDetailsRed, .white]
barChartView.renderer = gradientRenderer
*/
`