Search code examples
iosiphonecore-plotgraphing

iOS CorePlot point conversion


I am experimenting with Core Plot. I am trying to add a custom "goal" label over the graph at x: 2.0, y: 50.0 - basically label over the y == 50, its in a separate view, which means I need to convert my point from Core Plot to my UIView bounds. I have not found a combination of points conversions from layer/views that works across all iPhones screen sizes. In my pictures below iPhone 6s is the closest.

iPhone 6s+: iphone 6s+

iPhone 6s: iphone 6s

iPhone 5s: iphone 5s

My view layout: View layout

Here is my class:

class BarChartViewController: UIViewController, CPTBarPlotDataSource
{
    private var barGraph : CPTXYGraph? = nil
    @IBOutlet weak var textBox: UILabel!
    @IBOutlet weak var graphHostingView: CPTGraphHostingView!
    @IBOutlet weak var textBoxView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationController?.setNavigationBarHidden(false, animated: false)

        self.title = "My results"

        let newGraph = CPTXYGraph(frame: CGRectZero)
        let theme = CPTTheme(named: kCPTPlainWhiteTheme)

        newGraph.applyTheme(theme)

        let hostingView = graphHostingView
        hostingView.hostedGraph = newGraph
        if let frameLayer = newGraph.plotAreaFrame
        {
            // Border
            frameLayer.borderLineStyle = nil
            frameLayer.cornerRadius = 0.0
            frameLayer.masksToBorder = false

            // Paddings
            newGraph.paddingLeft = 0.0
            newGraph.paddingRight = 0.0
            newGraph.paddingTop = 0.0
            newGraph.paddingBottom = 0.0

            frameLayer.paddingLeft = 70.0
            frameLayer.paddingTop = 20.0
            frameLayer.paddingRight = 20.0
            frameLayer.paddingBottom = 80.0
        }

        // Plot space
        let plotSpace = newGraph.defaultPlotSpace as? CPTXYPlotSpace
        plotSpace?.yRange = CPTPlotRange(location: 0.0, length: 100.0)
        plotSpace?.xRange = CPTPlotRange(location: 0.0, length: 4.0)

        let axisSet = newGraph.axisSet as? CPTXYAxisSet

        if let x = axisSet?.xAxis {
            x.axisLineStyle = nil
            x.majorTickLineStyle = nil
            x.minorTickLineStyle = nil
            x.majorIntervalLength = 5.0
            x.orthogonalPosition = 0.0
            x.title = "X Axis"
            x.titleLocation = 7.5
            x.titleOffset = 55.0

            // Custom labels
            x.labelRotation = CGFloat(M_PI_4)
            x.labelingPolicy = .None

            let customTickLocations = [0.5, 1.5, 2.5]
            let xAxisLabels = ["Label A", "Label B", "Label C"]

            var labelLocation = 0
            var customLabels = Set<CPTAxisLabel>()
            for tickLocation in customTickLocations
            {
                let newLabel = CPTAxisLabel(text: xAxisLabels[labelLocation], textStyle: x.labelTextStyle)
                labelLocation += 1
                newLabel.tickLocation = tickLocation
                newLabel.offset = x.labelOffset + x.majorTickLength
                newLabel.rotation = 0 //CGFloat(M_PI_4)
                customLabels.insert(newLabel)
            }

            x.axisLabels = customLabels
        }

        if let y = axisSet?.yAxis
        {
            y.axisLineStyle = nil
            y.majorGridLineStyle = CPTMutableLineStyle()
            var style = y.majorTickLineStyle?.mutableCopy() as? CPTMutableLineStyle
            //style!.lineColor = CPTColor(CGColor: UIColor.blackColor())   //UIColor.blackColor())
            style!.lineWidth = 10.0
            y.minorGridLineStyle = CPTMutableLineStyle()
            style = y.minorTickLineStyle?.mutableCopy() as? CPTMutableLineStyle
            //style.lineColor = UIColor.redColor()
            style!.lineWidth = 10.0
            style!.lineCap = .Round
            y.majorTickLength = 10.0
            y.majorIntervalLength = 50.0
            y.orthogonalPosition = 0.0
            y.title = "Y Axis"
            y.titleOffset = 45.0
            y.titleLocation = 150.0

            y.labelRotation = 0
            y.labelingPolicy = .None


            let customTickLocations = [50, 100]
            let yAxisLabels = ["50", "100"]

            var labelLocation = 0
            var customLabels = Set<CPTAxisLabel>()
            for tickLocation in customTickLocations
            {
                let newLabel = CPTAxisLabel(text: yAxisLabels[labelLocation], textStyle: y.labelTextStyle)
                labelLocation += 1
                newLabel.tickLocation = tickLocation
                newLabel.offset = y.labelOffset + y.majorTickLength
                newLabel.rotation = 0 //CGFloat(M_PI_4)
                customLabels.insert(newLabel)
            }

            y.axisLabels = customLabels
            var nums = Set<NSNumber>()
            nums.insert(NSNumber(double: 50.0))
            y.majorTickLocations = nums
        }

        // First bar plot
        let barPlot1 = CPTBarPlot.tubularBarPlotWithColor(CPTColor.yellowColor(), horizontalBars: false)
        barPlot1.baseValue = 0.0
        barPlot1.dataSource = self
        //barPlot1.barOffset = 0.5
        barPlot1.identifier = "Bar Plot 1"
        let textStyle = CPTMutableTextStyle()
        textStyle.color = CPTColor.redColor()
        textStyle.fontSize = 10.0
        barPlot1.labelTextStyle = textStyle
        newGraph.addPlot(barPlot1, toPlotSpace: plotSpace)

        self.barGraph = newGraph
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        self.view.layoutIfNeeded()

        let point: [Double] = [2.0, 50.0]
        let plotPoint = UnsafeMutablePointer<Double>(point)
        var dataPoint = self.barGraph?.defaultPlotSpace?.plotAreaViewPointForDoublePrecisionPlotPoint(plotPoint, numberOfCoordinates: 2)
        //dataPoint = graphHostingView.layer.convertPoint(dataPoint!, fromLayer: self.barGraph!.plotAreaFrame!.plotArea)
        dataPoint = graphHostingView.layer.convertPoint(dataPoint!, toLayer: graphHostingView.layer.superlayer)
        //dataPoint = barGraph?.convertPoint(dataPoint!, fromLayer: graphHostingView.layer)
        dataPoint = self.textBoxView.convertPoint(dataPoint!, fromView: graphHostingView)

        print(dataPoint!)
        for item in (self.textBoxView?.constraints)!
        {
            if let id = item.identifier
            {
                if id == "goalX"
                {
                    print(item.constant)
                    item.constant = CGFloat((dataPoint?.x)!) - item.constant
                }
                else if id == "goalY"
                {
                    print(item.constant)
                    item.constant = CGFloat((dataPoint?.y)!) - item.constant
                }
            }
        }
        barGraph?.titleDisplacement = CGPoint(x: dataPoint!.x * -1, y: dataPoint!.y * -1)
        barGraph?.titlePlotAreaFrameAnchor = .TopLeft
        self.textBoxView?.layoutIfNeeded()
   }
}

Based on some other questions I have seen about CorePlot I know I need to convert it from CorePlot layer to my new view. I left some of my experiments in viewWillAppear() to see what I have tried. None of the solutions on SO seemed to work and I am new to iOS so probably missing something. Any ideas of what conversions I need to do to get my label to show up properly across all screen sizes?


Solution

  • You're on the right track with the point conversion. This is how I would do it:

    // Update layer layout if needed
    self.view.layoutIfNeeded()
    graphHostingView.layer.layoutIfNeeded()
    
    // Point in data coordinates
    let point: [Double] = [2.0, 50.0]
    
    // Data point in plot area layer coordinates
    var dataPoint = self.barGraph?.defaultPlotSpace?.plotAreaViewPointForPlotPoint(point)
    
    // Convert data point to graph hosting view coordinates
    dataPoint = graphHostingView.layer.convertPoint(dataPoint!, fromLayer: self.barGraph!.plotAreaFrame!.plotArea)
    

    Use this to update the constraints on the text box relative to the graph hosting view.