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

Horizontal Bar Bounds #2515

Open
AaronVinh opened this issue Jun 12, 2017 · 5 comments
Open

Horizontal Bar Bounds #2515

AaronVinh opened this issue Jun 12, 2017 · 5 comments
Assignees
Labels

Comments

@AaronVinh
Copy link

I believe that the getBarBounds function is bugged. I am trying to draw a gradient layer over each bar by grabbing the bounds for each bar. Here is my code
`
var chartEntries = BarChartDataEntry

    var times = [50,120,150,250,250,350,430,500]
    let shows = ["The Wire \n \n  ","Game of Thrones \n \n  ","The Sopranos \n \n  ","Breaking Bad \n \n  ","Billions \n \n  ","House of Cards \n \n  ","Attack on Titan \n \n  ", "Mad Men \n \n  "]

    for i in 0 ..< times.count  {
        let chartEntry = BarChartDataEntry(x:Double(i), y: Double(times[i]))
        chartEntries.append(chartEntry)
        //categories[i] = shows[i] + "\n" + String(times[i])
    }
    
    let chartDataSet = BarChartDataSet(values: chartEntries, label: "Categories")
    
    let chartData = BarChartData(dataSet: chartDataSet)

    chartData.barWidth = 0.2
    barChart.leftAxis.axisMinimum = 0
    barChart.data = chartData
    barChart.chartDescription?.text = ""
    barChart.legend.enabled = false
    barChart.xAxis.labelPosition = .bottomInside
    barChart.leftAxis.enabled = false
    barChart.rightAxis.enabled = false
    barChart.drawValueAboveBarEnabled = true
    barChart.xAxis.valueFormatter = IndexAxisValueFormatter(values:shows)
    barChart.xAxis.granularity = 1
    barChart.xAxis.drawAxisLineEnabled = false
    barChart.drawGridBackgroundEnabled = false
    for e in chartEntries{
        let bounds  = barChart.getBarBounds(entry: e)
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = bounds
        gradientLayer.colors = [UIColor(red:2, green:201, blue:255).cgColor,UIColor(red: 0, green:30,blue:189).cgColor]
        gradientLayer.locations = [0.5, 1.0]
        barChart.layer.addSublayer(gradientLayer)

    }`

screen shot 2017-06-11 at 5 47 27 pm

you can see in the screenshot the actualy bars in the light blue color and the gradient layer on top of them. The gradient layers' positions are off as well as the wrong size. Am I misunderstanding how the getBounds functions works or is it bugged?

@liuxuan30
Copy link
Member

liuxuan30 commented Jun 12, 2017

Actually getBarBounds is not used by the library...

First, where do you set your chartView.data = chartData? make sure it's after your chart setup or call notifyDataSetChanged()

I compared horizontal bar chart's getBarBounds and prepareBuffer, it had a diff, basically getBarBounds does not handle stacked bar case, and when it's not stacked,

  • Note: bar chart seems fine, there is no +-0.5 in both methods in bar chart.

prepareBuffer is like:

            if !containsStacks || vals == nil
            {
                let bottom = CGFloat(x - barWidthHalf)
                let top = CGFloat(x + barWidthHalf)
                var right = isInverted
                    ? (y <= 0.0 ? CGFloat(y) : 0)
                    : (y >= 0.0 ? CGFloat(y) : 0)
                var left = isInverted
                    ? (y >= 0.0 ? CGFloat(y) : 0)
                    : (y <= 0.0 ? CGFloat(y) : 0)
                
                // multiply the height of the rect with the phase
                if right > 0
                {
                    right *= CGFloat(phaseY)
                }
                else
                {
                    left *= CGFloat(phaseY)
                }
                
                barRect.origin.x = left
                barRect.size.width = right - left
                barRect.origin.y = top
                barRect.size.height = bottom - top
                
                buffer.rects[bufferIndex] = barRect
                bufferIndex += 1
            }

vs. getBarBounds

    open override func getBarBounds(entry e: BarChartDataEntry) -> CGRect
    {
        guard
            let data = _data as? BarChartData,
            let set = data.getDataSetForEntry(e) as? IBarChartDataSet
            else { return CGRect.null }
        
        let y = e.y
        let x = e.x
        
        let barWidth = data.barWidth
        
        let top = x - 0.5 + barWidth / 2.0
        let bottom = x + 0.5 - barWidth / 2.0
        let left = y >= 0.0 ? y : 0.0
        let right = y <= 0.0 ? y : 0.0
        
        var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top)
        
        getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds)
        
        return bounds
    }

I think it's a bug, but I don't know why there is a method for getting bar bounds.
What you can try is override the method with prepareBuffer content to see if it fix your issue, or just remove +- 0.5

@liuxuan30 liuxuan30 added the bug label Jun 12, 2017
@liuxuan30
Copy link
Member

liuxuan30 commented Jun 12, 2017

@danielgindi do you remember why +- 0.5 in horizontal bar chart getBarBounds? legacy code issue? Seems not necessary right now. Don't see other place do this +- 0.5

@AaronVinh can you help verify, if you just remove +-0.5 in getBarBounds, will it fix your issue? If so, seems we have a simple clean fix for this issue.

@AaronVinh
Copy link
Author

After removing the 0.5 the bars appear to be the correct size, but in the wrong place.screen shot 2017-06-11 at 9 34 44 pm

@liuxuan30
Copy link
Member

liuxuan30 commented Jun 14, 2017

After some digging, found out you have to add the isInverted condition as well.

So yes it's buggy using getBarBounds as it's not the same as what in renderer code. I will check how to make a full fix.

    open override func getBarBounds(entry e: BarChartDataEntry) -> CGRect
    {
        guard
            let data = _data as? BarChartData,
            let set = data.getDataSetForEntry(e) as? IBarChartDataSet
            else { return CGRect.null }
        
        let y = e.y
        let x = e.x
        let isInverted = self.isInverted(axis: set.axisDependency)
        
        let barWidth = data.barWidth
        
        let top = x  + barWidth / 2.0
        let bottom = x  - barWidth / 2.0
        let right = isInverted
            ? (y <= 0.0 ? y : 0.0)
            : (y >= 0.0 ? y : 0.0)
        let left = isInverted
            ? (y >= 0.0 ? y : 0.0)
            : (y <= 0.0 ? y : 0.0)

        var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top)
        
        getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds)
        
        return bounds
    }

also, you have to be careful that you must add the layers after the valueToPixelMatrix is fully initialized. if you just put your code right after something like view.chartData = data, it will be wrong position.

You can setup a delay like:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    for (int i = 0; i < yVals.count; i++) {
        ChartDataEntry *entry = [set1 entryForIndex:i];
        if (entry != nil) {
            CGRect bound = [_chartView getBarBoundsWithEntry:entry];
            CAGradientLayer *layer = [CAGradientLayer layer];
            [layer setFrame:bound];
            layer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor greenColor].CGColor];
            layer.startPoint = CGPointMake(0.0, 0.5);
            layer.endPoint = CGPointMake(1.0, 0.5);
            [_chartView.layer addSublayer:layer];
        }
    }
});

or set a observer when the matrix is fully set after calling prepareOffsetMatrix or somewhere, or you just override the renderer code to add the gradient in real time using CoreGraphics (as layers may introduce side effect, I recommend not using CALayer inside the library)

image

@liuxuan30 liuxuan30 self-assigned this Jun 14, 2017
@gustavo-yy
Copy link

is this possible using CombinedChartView()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants