Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a gradient to bar charts #1065

Open
ricburton opened this issue May 24, 2016 · 7 comments
Open

Adding a gradient to bar charts #1065

ricburton opened this issue May 24, 2016 · 7 comments

Comments

@ricburton
Copy link

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!

@wjacker
Copy link
Contributor

wjacker commented May 24, 2016

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

@liuxuan30
Copy link
Member

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?

@ricburton
Copy link
Author

Wow thanks so much! I will play with this.

@pmairoldi pmairoldi added this to To Do in Gradients via automation Nov 23, 2017
@WinstonRay
Copy link

It works fine in @wjacker 's solution.

@rahulvatakara
Copy link

Hi @wjacker ,
Can you please update GradientBarChartRenderer ,which is not working now.

Thanks,
RAHUL

@larryonoff
Copy link
Contributor

implemented in #3533

@mayankgoyal3
Copy link

That code is not working as an extension, It is giving me a lot of error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Gradients
  
Releated Issues and PRs
Development

No branches or pull requests

7 participants