Adding a gradient to bar charts
ricburton opened this issue · comments
Hi @ricburton
Seems your need gradient color bar, recently I got an same requirement like that, so I wrote GradientBarChartRenderer for that function, hope this can help you or give you some prompt 😄
GradientBarChartRenderer.swift
import UIKit
public class GradientBarChartRenderer: BarChartRenderer {
public var fillColors:CFArray = [UIColor.whiteColor().colorWithAlphaComponent(0.0).CGColor,
UIColor.whiteColor().colorWithAlphaComponent(0.0).CGColor];
public var locations:[CGFloat] = [0, 1];
override init(dataProvider: BarChartDataProvider?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) {
super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler);
}
public override func drawDataSet(context context: CGContext, dataSet: IBarChartDataSet, index: Int)
{
guard let
dataProvider = dataProvider,
barData = dataProvider.barData,
animator = animator
else { return }
CGContextSaveGState(context)
let trans = dataProvider.getTransformer(dataSet.axisDependency)
let drawBarShadowEnabled: Bool = dataProvider.isDrawBarShadowEnabled
let dataSetOffset = (barData.dataSetCount - 1)
let groupSpace = barData.groupSpace
let groupSpaceHalf = groupSpace / 2.0
let barSpace = dataSet.barSpace
let barSpaceHalf = barSpace / 2.0
let containsStacks = dataSet.isStacked
let isInverted = dataProvider.isInverted(dataSet.axisDependency)
let barWidth: CGFloat = 0.5
let phaseY = animator.phaseY
var barRect = CGRect()
var barShadow = CGRect()
let borderWidth = dataSet.barBorderWidth
let borderColor = dataSet.barBorderColor
let drawBorder = borderWidth > 0.0
var y: Double
// do the drawing
for j in 0 ..< Int(ceil(CGFloat(dataSet.entryCount) * animator.phaseX))
{
guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue }
// calculate the x-position, depending on datasetcount
let x = CGFloat(e.xIndex + e.xIndex * dataSetOffset) + CGFloat(index)
+ groupSpace * CGFloat(e.xIndex) + groupSpaceHalf
var vals = e.values
if (!containsStacks || vals == nil)
{
y = e.value
let left = x - barWidth + barSpaceHalf
let right = x + barWidth - barSpaceHalf
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)
// multiply the height of the rect with the phase
if (top > 0)
{
top *= phaseY
}
else
{
bottom *= phaseY
}
barRect.origin.x = left
barRect.size.width = right - left
barRect.origin.y = top
barRect.size.height = bottom - top
trans.rectValueToPixel(&barRect)
if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}
if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
// if drawing the bar shadow is enabled
if (drawBarShadowEnabled)
{
barShadow.origin.x = barRect.origin.x
barShadow.origin.y = viewPortHandler.contentTop
barShadow.size.width = barRect.size.width
barShadow.size.height = viewPortHandler.contentHeight
CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor)
CGContextFillRect(context, barShadow)
}
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
//CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor)
//CGContextFillRect(context, barRect)
CGContextAddPath(context, UIBezierPath(roundedRect: barRect, cornerRadius: 2).CGPath)
if drawBorder
{
CGContextSetStrokeColorWithColor(context, borderColor.CGColor)
CGContextSetLineWidth(context, borderWidth)
CGContextStrokeRect(context, barRect)
}
}
else
{
var posY = 0.0
var negY = -e.negativeSum
var yStart = 0.0
// if drawing the bar shadow is enabled
if (drawBarShadowEnabled)
{
y = e.value
let left = x - barWidth + barSpaceHalf
let right = x + barWidth - barSpaceHalf
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)
// multiply the height of the rect with the phase
if (top > 0)
{
top *= phaseY
}
else
{
bottom *= phaseY
}
barRect.origin.x = left
barRect.size.width = right - left
barRect.origin.y = top
barRect.size.height = bottom - top
trans.rectValueToPixel(&barRect)
barShadow.origin.x = barRect.origin.x
barShadow.origin.y = viewPortHandler.contentTop
barShadow.size.width = barRect.size.width
barShadow.size.height = viewPortHandler.contentHeight
CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor)
CGContextFillRect(context, barShadow)
}
// fill the stack
for k in 0 ..< vals!.count
{
let value = vals![k]
if value >= 0.0
{
y = posY
yStart = posY + value
posY = yStart
}
else
{
y = negY
yStart = negY + abs(value)
negY += abs(value)
}
let left = x - barWidth + barSpaceHalf
let right = x + barWidth - barSpaceHalf
var top: CGFloat, bottom: CGFloat
if isInverted
{
bottom = y >= yStart ? CGFloat(y) : CGFloat(yStart)
top = y <= yStart ? CGFloat(y) : CGFloat(yStart)
}
else
{
top = y >= yStart ? CGFloat(y) : CGFloat(yStart)
bottom = y <= yStart ? CGFloat(y) : CGFloat(yStart)
}
// multiply the height of the rect with the phase
top *= phaseY
bottom *= phaseY
barRect.origin.x = left
barRect.size.width = right - left
barRect.origin.y = top
barRect.size.height = bottom - top
trans.rectValueToPixel(&barRect)
if (k == 0 && !viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
// Skip to next bar
break
}
// avoid drawing outofbounds values
if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
//CGContextSetFillColorWithColor(context, dataSet.colorAt(k).CGColor)
//CGContextFillRect(context, barRect)
CGContextAddPath(context, UIBezierPath(roundedRect: barRect, cornerRadius: 2).CGPath)
if drawBorder
{
CGContextSetStrokeColorWithColor(context, borderColor.CGColor)
CGContextSetLineWidth(context, borderWidth)
CGContextStrokeRect(context, barRect)
}
}
}
}
CGContextClip(context);
let gradient:CGGradientRef;
let colorspace:CGColorSpaceRef;
colorspace = CGColorSpaceCreateDeviceRGB()!;
gradient = CGGradientCreateWithColors(colorspace, fillColors as CFArrayRef, locations)!;
//Vertical Gradient
let startPoint : CGPoint = CGPointMake(0.0, viewPortHandler.contentBottom);
let endPoint : CGPoint = CGPointMake(0.0, viewPortHandler.contentTop);
//Horizontal Gradient
//let startPoint : CGPoint = CGPointMake(0.0, 0.0);
//let endPoint : CGPoint = CGPointMake(viewPortHandler.contentLeft, 0.0);
CGContextDrawLinearGradient(context, gradient,
startPoint, endPoint, CGGradientDrawingOptions.DrawsBeforeStartLocation);
CGContextRestoreGState(context)
}
}
How to used
Objective C++:
GradientBarChartRenderer *renderer = [[GradientBarChartRenderer alloc] initWithDataProvider:_chartView animator:_chartView._animator viewPortHandler:_chartView.viewPortHandler];
[renderer setLocations:@[@0.0, @1.0]];
NSArray *gradientColors = [NSArray arrayWithObjects:
(id)[UIColor whiteColor].CGColor, (id)[UIColor blackColor].CGColor, nil];
[renderer setFillColors:(CFArrayRef)gradientColors];
_chartView.renderer = renderer;
Swift
let renderer:GradientBarChartRenderer = GradientBarChartRenderer(dataProvider: _chartView, animator: _chartView._animator, viewPortHandler: _chartView._viewPortHandler);
renderer.locations = [0, 1];
renderer.fillColors = [UIColor().whiteColor.CGColor, UIColor().blackColor.CGColor]
chartWillExpired.renderer = renderer;
also you can check this commit on my Charts repo:
https://github.com/wjacker/ios-charts/commit/050cf5b70138223c3c439cb18b7113ad83168995
-Jack
hmm, this seems requires us to refactor the bar rect logic. Make it as a single func so we can add rounding corer or gradients?
Wow thanks so much! I will play with this.
It works fine in @wjacker 's solution.
Hi @wjacker ,
Can you please update GradientBarChartRenderer ,which is not working now.
Thanks,
RAHUL
implemented in #3533
That code is not working as an extension, It is giving me a lot of error.