ChartsOrg / Charts

Beautiful charts for iOS/tvOS/OSX! The Apple side of the crossplatform MPAndroidChart.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Adding a gradient to bar charts

ricburton opened this issue · comments

Hi everyone,

Really loving this library. I want to create a bar chart that looks like this:

screen shot 2016-05-23 at 17 03 40

I have searched through all of the issues, read through the API, and I looked around GitHub for examples .

Really appreciate your time!

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

commented

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.