-1

Ist there a proper way to remove weekend gaps from e.g. stock data? See picture.

weekend gaps

The data are Linemarks with

x value : Date

y value : Double

Datapoint class:

public class HistoricalDataPoint : ObservableObject, Hashable, Identifiable, Codable, Comparable  {
public static func < (lhs: HistoricalDataPoint, rhs: HistoricalDataPoint) -> Bool {
    lhs.date < rhs.date
}

var timestamp : Double
let priceLow : Double
let priceHigh: Double
let priceOpen : Double
let priceClose: Double
let volume : Double
let date : Date
let identifier : String
let dataCreatedTimeStamp : Date
let code: String
var normalized = 0.0

public init(timestamp: Double, priceLow: Double, priceHigh: Double, priceOpen: Double, priceClose: Double, volume: Double, dataCreatedTimeStamp: Date, code: String) {
    self.identifier = "\(timestamp)\(priceLow)\(priceHigh)\(priceOpen)\(priceClose)\(volume)"
    self.timestamp = timestamp
    self.priceLow = priceLow
    self.priceHigh = priceHigh
    self.priceOpen = priceOpen
    self.priceClose = priceClose
    self.volume = volume
    self.date = Date(timeIntervalSince1970: TimeInterval(timestamp))
    self.dataCreatedTimeStamp = dataCreatedTimeStamp
    self.code = code
    
}

public static func == (lhs: HistoricalDataPoint, rhs: HistoricalDataPoint) -> Bool {
    return lhs.date == rhs.date
}

public func hash(into hasher: inout Hasher) {
    return hasher.combine(identifier)
}
}

I check the data for the weekday and only add if it's weekdays. But the stock data has no data for weekends anyway:

let gregorian = Calendar(identifier: .gregorian)
var components = gregorian.dateComponents([.weekday], from: date)
if(components.weekday != 1 && components.weekday != 7){
historicalData.append(HistoricalDataPoint(timestamp: date.timeIntervalSince1970, priceLow: Double(low), priceHigh: Double(high), priceOpen: Double(open), priceClose: Double(close), volume: Double(volume), dataCreatedTimeStamp: Date(),code: asset.code))
}

And the ChartView:

Chart(historicalData, id: \.date){
LineMark(
    x: .value("Time", $0.date),
    y: .value("Close", $0.priceClose)
)
.chartYScale(domain: lowY...highY)
.frame(height: height)
stefple
  • 1,007
  • 17
  • 28

1 Answers1

1

You should check your data Charts does not display the weekend if not included.

import SwiftUI
import Charts
struct StockWeekChartView: View {
    let data: [Stock] = (-20...20).map { i in
            .init(timestamp: Calendar.current.date(byAdding: .day, value: i, to: Date())!)//Add data for 40 consecutive days
    }.filter {
        $0.isWeekDay //remove weekends
    }.sorted(using: KeyPathComparator(\.timestamp)) //Sort by date
    
    var body: some View {
        Chart(data, id: \.id){ value in
            LineMark(
                x: .value("Date", value.timestamp),
                y: .value("Price", value.priceClose)
            )
            
        }.chartXAxis {
                AxisMarks { value in
                    AxisValueLabel {
                        Text(
                        data[value.index].desc) //Show the weekday as a sample
                    }
                }
            }
    }
    //Simple model
    struct Stock: Identifiable{
        let id: UUID = .init()
        let timestamp: Date
        
        let priceClose: Double = Double.random(in: 0...300)

        var isWeekDay: Bool{
            !Calendar.current.isDateInWeekend(timestamp)
        }
        var desc: String{
            let f = DateFormatter()
            f.dateFormat = "E"
            return f.string(from: timestamp)
        }
    }
}

struct StockWeekChartView_Previews: PreviewProvider {
    static var previews: some View {
        StockWeekChartView()
    }
}

enter image description here

SwiftUI is heavily dependent on Identifiable, Hashable and Equatable if you override the standard implementation it can cause many issues.

Something else to consider. When you let Apple decides the AxisMarks you may end up with the beginning of the week, month, year, depending many factors such as number of data points or screen size.

The beginning of the week will usually start with Sunday the the beginning of the month will be the 1st of the month and the beginning of the year will the the 1st of January.

The image you posted shows weekly axis labels with the beginning of the week always falling on Monday it seems.

You can change the default calendar's firstWeekday to 2/Monday instead of 1/Sunday.

var calendar: Calendar{
    var c = Calendar.current
    c.firstWeekday = 2 //Monday
    return c
}

Then inject the modified calendar into the Chart

.environment(\.calendar, calendar)

Here is a full sample

import SwiftUI
import Charts
struct StockWeekChartView: View {
    let data: [Stock] = (-20...20).map { i in
            .init(timestamp: Calendar.current.date(byAdding: .day, value: i, to: Date())!)//Add data for 40 consecutive days
    }.filter {
        $0.isWeekDay //remove weekends
    }.sorted(using: KeyPathComparator(\.timestamp)) //Sort by date
    
    var calendar: Calendar{
        var c = Calendar.current
        c.firstWeekday = 2
        return c
    }
    var body: some View {
        Chart(data, id: \.id){ value in
            LineMark(
                x: .value("Date", value.timestamp),
                y: .value("Price", value.priceClose)
            )
            
        }.chartXAxis {
            AxisMarks(values: .stride(by: .weekOfYear)) { value in
                if let n = value.as(Date.self){
                    AxisValueLabel(n.longDesc)
                } else{
                    AxisValueLabel(format: .dateTime.week())
                    
                }
            }
        }.environment(\.calendar, calendar)
    }
    //Simple model
    struct Stock: Identifiable{
        let id: UUID = .init()
        let timestamp: Date
        
        let priceClose: Double = Double.random(in: 0...300)
        
        var isWeekDay: Bool{
            !Calendar.current.isDateInWeekend(timestamp)
        }
        
    }
}

struct StockWeekChartView_Previews: PreviewProvider {
    static var previews: some View {
        StockWeekChartView()
    }
}
extension Date{
    var desc: String{
        let f = DateFormatter()
        f.dateFormat = "E"
        return f.string(from: self)
    }
    var longDesc: String{
        let f = DateFormatter()
        f.dateFormat = "d. MMM\nE"
        return f.string(from: self)
    }
}

enter image description here

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • @stefple did you see the solution? – lorem ipsum May 01 '23 at 23:47
  • Yes. As mentioned: there is no weekend data in the data set. I implemented your check to be sure that mine works, but there is no difference. – stefple May 02 '23 at 06:40
  • I convert my data to your "struct Stock" now and filter the data with your function. I still have the gaps. I reduced it to the minimum. Still the same. The only difference is that I have .chartYScale(domain: lowY...highY) – stefple May 02 '23 at 07:10
  • The only other difference is, that there is more than one data point for the weekdays. That might be the problem. – stefple May 02 '23 at 07:16
  • @stefple try the first version of the graph that should put a label for each datapoint. Is Saturday included in the labels? Maybe there is something going on with time differences? – lorem ipsum May 02 '23 at 09:22