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

Custom XAxisRenderer labels are shifting around when scrolling #3193

Closed
MananPatel95 opened this issue Jan 18, 2018 · 1 comment
Closed

Custom XAxisRenderer labels are shifting around when scrolling #3193

MananPatel95 opened this issue Jan 18, 2018 · 1 comment

Comments

@MananPatel95
Copy link

MananPatel95 commented Jan 18, 2018

I have a LineChartView that I am applying a custom XAxisRenderer to draw on top of the chart to. I am having issues with the drawn labels shifting around when I am scrolling: Link to imgur vid like so. It seems to appear once the data point leaves the viewPortHandler that it shifts the labels, but I am unsure what to do to fix this. Using Charts 3.0.5, Swift 4.

Here is the code to setup the LineChartView

    func setupLineChart() {
        
        // Set up the chart properties
        HourlyLineChart.chartDescription = nil
        HourlyLineChart.dragEnabled = true
        HourlyLineChart.scaleYEnabled = false
        HourlyLineChart.scaleXEnabled = true
        HourlyLineChart.pinchZoomEnabled = false
        HourlyLineChart.legend.enabled = false
        HourlyLineChart.doubleTapToZoomEnabled = false
        //HourlyLineChart.zoom(scaleX: 0, scaleY: 0, x: 0, y: 0)
        HourlyLineChart.zoom(scaleX: 4, scaleY: 1, x: 0, y: 0)
        // Make a frame for drawable space with a portoffset with borders
        HourlyLineChart.setViewPortOffsets(left: 0, top: -10, right: 0, bottom: -40)
        HourlyLineChart.animate(xAxisDuration: 1, yAxisDuration: 1, easingOption: .easeInBounce)
        HourlyLineChart.rightAxis.enabled = false
        HourlyLineChart.delegate = self
        HourlyLineChart.noDataFont.withSize(17)
        
        // Set up expandable view properties
        hourlyExpandableLabel.isHidden = true
        hourlyExpandableView.isHidden = true
        hourlyExpandableView.backgroundColor = UIColor(red: 99/255, green: 140/255, blue: 189/255, alpha: 1/1)
        hourlyExpandableView.layer.cornerRadius = 8
        hourlyExpandableHeight.constant = 0
        
        // Set Y-Axis properties
        let yAxis = HourlyLineChart.leftAxis
        yAxis.enabled = true
        yAxis.drawAxisLineEnabled = false
        yAxis.drawLabelsEnabled = false
        yAxis.drawGridLinesEnabled = false
        yAxis.spaceTop = 1
        yAxis.spaceBottom = 1.5
        
        // Set Custom XAXis renderers
        HourlyLineChart.xAxis.enabled = true
        HourlyLineChart.xAxis.drawGridLinesEnabled = false
        HourlyLineChart.xAxis.drawLabelsEnabled = true
        HourlyLineChart.xAxis.labelPosition = .top
        HourlyLineChart.xAxis.labelTextColor = .white
        HourlyLineChart.xAxis.granularityEnabled = true
        HourlyLineChart.xAxis.granularity = 1
        HourlyLineChart.xAxis.avoidFirstLastClippingEnabled = true
        HourlyLineChart.xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 12.0)!
        
        dataSet.highlightEnabled = true
        dataSet.drawHorizontalHighlightIndicatorEnabled = false
        dataSet.highlightColor = UIColor(red: 236/255, green: 239/255, blue: 241/255, alpha: 0.1/1)
        dataSet.highlightLineWidth = 50
        dataSet.mode = .cubicBezier
        dataSet.circleColors = [UIColor(hex: "fafafa")]
        dataSet.circleRadius = 4
        dataSet.fillColor = UIColor.white
        dataSet.drawCircleHoleEnabled = false
        dataSet.drawFilledEnabled = false
        dataSet.lineWidth = 1.0
        dataSet.colors = [UIColor.white]
        dataSet.drawValuesEnabled = true
        dataSet.valueFont = UIFont(name: "HelveticaNeue-Light", size: 14.0)!
        dataSet.axisDependency = .left
    }

Here is the code that updates data

func updateHourlyData() {
        HourlyLineChart.clear()
        lineChartEntries.removeAll()
        hourlyTimes.removeAll()
        hourlyTemperaturesStr.removeAll()
        hourlyPrecip.removeAll()
        hourlyIcons.removeAll()
        
      if selectedLocationData?.hourlyFcst?.hourly != nil {
            for index in (selectedLocationData?.hourlyFcst?.hourly)! {
                hourlyTimes.append(index.time!)
                hourlyTemperaturesStr.append((index.temperature?.getValue())!)
                hourlyIcons.append(UIImage(named: "\(index.iconCode!)_main")!)
                hourlyPrecip.append(index.precip!)
            }
        }
        
        if selectedLocationData?.hourlyFcst?.hourly != nil && hourlyTimes.count > 0 {
            hourlyGraphHeight.constant = 250
            
            // Populate chartDataEntries
            for index in hourlyTimes.indices {
                let xVal = Double(index).rounded()
                var yVal = Double(index)
                let yValStr = hourlyTemperaturesStr[index]
                if  !yValStr.isEmpty {
                    yVal = Double(yValStr)!
                }
                lineChartEntries.append(ChartDataEntry(x: xVal, y: yVal, data: selectedLocationData?.hourlyFcst?.hourly![index]))
            }
            
            // Set up a dataset for data model
            dataSet.values = lineChartEntries
            
            // Setup a valueformatter to get rid of decimals in the graph
            let format = NumberFormatter()
            format.numberStyle = .none
            let formatter = HourTempFormatter(numberFormatter: format)
            dataSet.valueFormatter = formatter
        
            // Set the data using the above dataSet for the chart
            let data = LineChartData(dataSet: dataSet)
            data.setDrawValues(true)
            data.setValueTextColor(UIColor(hex: "fafafa"))


            HourlyLineChart.data = data
            
            // Custom renederers for icons, precipitation
            let customXAxisRenderer = XAxisCustomRenderer(viewPortHandler: HourlyLineChart.viewPortHandler, xAxis: HourlyLineChart.xAxis, transformer: HourlyLineChart.getTransformer(forAxis: YAxis.AxisDependency.left), icons: hourlyIcons, precipitationArray: hourlyPrecip)
            HourlyLineChart.xAxisRenderer = customXAxisRenderer
            
            // Custom renderer for time
            HourlyLineChart.xAxis.valueFormatter = IndexAxisValueFormatter(values: hourlyTimes)
            
            // Backup renderer for times (CHARTS 3.0.4 -> 3.0.5) ISSUE
            //HourlyLineChart.xAxis.valueFormatter = DefaultAxisValueFormatter(block: {(index, _)  in
            //    return self.hourlyTimes[Int(index)]
            //})
        }
        
        // ELSE BLOCK TO COLLAPSE CHART IF NO DATA
        //else {
//            hourlyGraphHeight.constant = 0
//        }
        HourlyLineChart.notifyDataSetChanged()
    }

And here is the custom xAxisRenderer:

public class XAxisCustomRenderer: XAxisRenderer {
    // Dict to hold the weather icons array
    private var icons: [UIImage]
    // Dict to hold the preciptation mapping
    private var precipitationArray: [String]
    // Array to hold the time string
    //private var timesArray: [String]
    
    
    init(viewPortHandler: ViewPortHandler, xAxis: XAxis, transformer: Transformer, icons: [UIImage], precipitationArray: [String]) {
        
        self.icons = icons
        self.precipitationArray = precipitationArray
        //self.timesArray = times
        
        super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer)
    }
    
    override public func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint){
        guard
            let xAxis = self.axis as? XAxis,
            let transformer = self.transformer
            else { return }
        
        #if os(OSX)
            let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        #else
            let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        #endif
        paraStyle.alignment = .center
        
        let labelAttrs: [NSAttributedStringKey : Any] = [NSAttributedStringKey.font: xAxis.labelFont,
                                                         NSAttributedStringKey.foregroundColor: xAxis.labelTextColor,
                                                         NSAttributedStringKey.paragraphStyle: paraStyle]
        let FDEG2RAD = CGFloat(Double.pi / 180.0)
        
        let labelRotationAngleRadians = xAxis.labelRotationAngle * FDEG2RAD
        
        let centeringEnabled = xAxis.isCenterAxisLabelsEnabled
        
        let valueToPixelMatrix = transformer.valueToPixelMatrix
        
        var position = CGPoint(x: 0.0, y: 0.0)
        
        var labelMaxSize = CGSize()
        
        if xAxis.isWordWrapEnabled
        {
            labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a
        }
        
        let entries = xAxis.entries
        
        for i in stride(from: 0, to: entries.count, by: 1)
        {
            if centeringEnabled
            {
                position.x = CGFloat(xAxis.centeredEntries[i])
            }
            else
            {
                position.x = CGFloat(entries[i])
            }
            
            position.y = 0.0
            position = position.applying(valueToPixelMatrix)
            
            if viewPortHandler.isInBoundsX(position.x)
            {
                let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i], axis: xAxis) ?? ""
                
                let labelns = label as NSString
                
                if xAxis.isAvoidFirstLastClippingEnabled
                {
                    // avoid clipping of the last
                    if i == xAxis.entryCount - 1 && xAxis.entryCount > 1
                    {
                        let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
                        
                        if width > (viewPortHandler.offsetRight) * 2.0
                            && position.x + width > viewPortHandler.chartWidth
                        {
                            position.x -= width / 2.0
                        }
                    }
                    else if i == 0
                    { // avoid clipping of the first
                        let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
                        position.x += width / 2.0
                    }
                }
                
                let rawIcon: UIImage = icons[Int(entries[i])].fixedOrientation().imageRotatedByDegrees(degrees: 180)
                let icon: CGImage = rawIcon.cgImage!
                
                
                // Draw the time labels
                drawLabel(
                    context: context,
                    formattedLabel: label,
                    x: position.x,
                    y: pos + 33,
                    attributes: labelAttrs,
                    constrainedToSize: labelMaxSize,
                    anchor: anchor,
                    angleRadians: labelRotationAngleRadians)
                
//                let time = timesArray[Int(i)]
                
                //drawLabel(context: context, formattedLabel: time, x: position.x + 8, y: viewPortHandler.chartHeight - 2, attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
                
                
                // Draw the icons
                context.draw(icon, in: CGRect(x: position.x - 15, y: viewPortHandler.chartHeight - 47, width: CGFloat(30), height: CGFloat(27)))
                
                // Draw the precipitation value
                let precip = precipitationArray[Int(i)]
                //if( !(precip.starts(with: "0"))) {
                
                // Draw the precip string
                drawLabel(context: context, formattedLabel: precip, x: position.x + 8, y: viewPortHandler.chartHeight - 2, attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
                
                // Draw the precip image
                let precipitationIcon: CGImage = (UIImage(named: "rainCloud")?.fixedOrientation().imageRotatedByDegrees(degrees: 180).cgImage)!
                context.draw(precipitationIcon, in: CGRect(x: position.x - 19, y: viewPortHandler.chartHeight - 15, width: 15, height: 15))
                
                let axisLine: CGImage = (UIImage(named: "axisLine")?.cgImage)!
                
                // Draw top axis
                context.draw(axisLine,in: CGRect(x: -20, y: 0, width: viewPortHandler.chartWidth + 50, height: 50))
                
                //let arrowIconRight: CGImage = (UIImage(named: "arrow")?.fixedOrientation().imageRotatedByDegrees(degrees: 180).alpha(alpha: 0.2)!.cgImage)!
                //let arrowIconLeft: CGImage = (UIImage(named: "arrow")?.fixedOrientation().imageRotatedByDegrees(degrees: 0).alpha(alpha: 0.2)!.cgImage)!
                //context.draw(arrowIconRight, in: CGRect(x: viewPortHandler.chartWidth - 30 , y: viewPortHandler.chartHeight/2.5, width: 15, height: 15))
                //context.draw(arrowIconLeft, in: CGRect(x: 10 , y: viewPortHandler.chartHeight/2.5, width: 15, height: 15))
                
                // Draw bottom axis
                context.draw(axisLine,in: CGRect(x: -20 , y: viewPortHandler.chartHeight - 80, width: viewPortHandler.chartWidth + 50, height: 50))
            }
        }
    }
    
}
@MananPatel95
Copy link
Author

MananPatel95 commented Jan 18, 2018

I found the issue that was causing it, so I removed:
HourlyLineChart.xAxis.avoidFirstLastClippingEnabled = true

and addding in:

HourlyLineChart.xAxis.centerAxisLabelsEnabled = true

and added this to format the starting and ending x position of the graph

HourlyLineChart.xAxis.spaceMin = 0.45
HourlyLineChart.xAxis.spaceMax = 0.5

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

No branches or pull requests

1 participant