-1

I am trying to convert JSON from an API into an array of cgpoints that I can use in core graphics.

The JSON looks like:

{"date":"2017-01-05","open":119.34,"high":120.2,"low":119.1,"close":119.7,"adjusted_close":109.7062,"volume":4261070},{"date":"2017-01-06","open":118.93,"high":121.5,"low":118.52,"close":120.76,"adjusted_close":110.6777,"volume":4089186},{"date":"2017-01-09","open":120.76,"high":121.06,"low":120.33,"close":120.43,"adjusted_close":110.3754,"volume":3021844}

I am interested in the date and close fields and I'd like to convert them into this format

 dataPoints:@[xy(val0,119.7),xy(val1,120.76),xy(val2,120.43)];

where val0...val3 are dates mapped to the x dimension of the graph and I have defined:

 #define xy(__x,__y) [NSValue valueWithCGPoint:CGPointMake(__x,__y)]

I am not wedded to the #define. It's just the code that CoreGraphics is using now. If there is another way to do it a struct or something that would be great.

I know how to decode the JSON into an array using:

struct StockReport: Codable {
    let date: String
    let stockDatumOpen, high, low, close: Double
    let adjustedClose: Double
    let volume: Int

    enum CodingKeys: String, CodingKey {
        case date
        case stockDatumOpen = "open"
        case high, low, close
        case adjustedClose = "adjusted_close"
        case volume
    }
}

typealias StockData = [StockReport]

let stockData = try? newJSONDecoder().decode(StockData.self, from: jsonData)

Beyond that, however, I'm not sure how to create into an array of CGpoints.

I imagine I can convert the date strings into seconds since 1970 (epoch) and then scale those to the coordinate system. Or I can use the index of the array as an integer corresponding to each day but that's as far as I've gotten. Should I loop through the array stockData and create points one by one? Is there a more elegant way?

Thanks in advance for any suggestions.

user6631314
  • 1,751
  • 1
  • 13
  • 44
  • How (and why) do you want to convert the date into the x of a CGPoint? – Joakim Danielson Jul 11 '20 at 17:11
  • The end product is a graph with dates on X-axis and values on the Y axis--common for stock graphs.. Therefore the X-coordinates need to map to dates. So there needs to be some intermediate relation to map the coordinate system of the dates to cg point values. Have edited question to clarify – user6631314 Jul 11 '20 at 17:57
  • I understand what you are trying to achieve here but I don't understand how and why you want to fit a date into a CGPoint. Wouldn't it be better to just have a counter (int) for the x value and then have the dates in a separate array and use them for labels on the x-line? – Joakim Danielson Jul 11 '20 at 18:01
  • It might. I'm not used to working with CGPoints and am confused about how to work with an array of them. In fact, the data points array is not even an array of CGpoints. It's something else..I guess an array of xy's or dataPoints which are NSValues – user6631314 Jul 11 '20 at 18:10
  • So each date in your collection would be enumerated like 1,2,3 and used as the x value while the date time interval since the reference date would be used to represent the y value? – Leo Dabus Jul 11 '20 at 18:17
  • Yes for x value. It is a series like 1,2,3 that map to sequential dates. The y value is the stock price. In the starting JSON string or object it is the value for the key close e.g. 120.76 – user6631314 Jul 11 '20 at 18:30

1 Answers1

0

Assuming you are receiving a valid json string (the string you have posted it is not a valid json string. You can sort your objects by its date (if they are not already sorted), enumerate the elements and initialize a CGPoint as follow:


let json = """
    [{"date":"2017-01-05",
     "open":119.34,
     "high":120.2,
     "low":119.1,
     "close":119.7,
     "adjusted_close":109.7062,
     "volume":4261070},
    {"date":"2017-01-06",
     "open":118.93,
     "high":121.5,
     "low":118.52,
     "close":120.76,
     "adjusted_close":110.6777,
     "volume":4089186},
    {"date":"2017-01-09",
     "open":120.76,
     "high":121.06,
     "low":120.33,
     "close":120.43,
     "adjusted_close":110.3754,
     "volume":3021844}]
"""

do {
    let stocks = try JSONDecoder().decode([StockReport].self, from: Data(json.utf8))
    let points = stocks.sorted{$0.date < $1.date}.enumerated().map {
        CGPoint(x: Double($0.offset), y: $0.element.close)
    }
    print(points)
} catch {
    print(error)
}

This will print

[(0.0, 119.7), (1.0, 120.76), (2.0, 120.43)]

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 2
    Just voting down an answer without a reason won't help the OP and or future readers. Leave a comment about whats wrong with my answer so that I can try to improve and or fixed iit if that is something wrong with it. – Leo Dabus Jul 11 '20 at 19:00
  • 1
    This worked like a charm. Very efficient and elegant answer to the question. – user6631314 Jul 11 '20 at 19:15