I am working with the new Charts Framework. The axis value labels for the x axis are dates (mm/dd/yy). I have 5 vertical grid lines and am trying to get the dates to be centered below the associated grid line with no success. One additional problem is that the values are in an array that contains 5 dates but only the first 4 get displayed. Any solutions will be appreciated. Below is the code.
struct LineChart: View {
private var localArray: [TradingDayPrices]
init(passedInArray: [TradingDayPrices]) {
self.localArray = passedInArray
}
var body: some View {
let minCloseValue: Double = localArray.map { $0.close }.min()!
let maxCloseValue: Double = localArray.map { $0.close }.max()!
let minDate: Date = localArray[0].timeStamp!
let itemCount: Int = localArray.count - 1
let maxDate: Date = localArray[itemCount].timeStamp!
Spacer()
GroupBox (label: Text("VOO Closing Values For The Past Year")
.font(.system(size: 20))
.fontWeight(.bold)
.frame(width: 700, height: 50, alignment: .center)) {
Chart {
ForEach(localArray) { item in
LineMark (
x: .value("TimeStamp", item.timeStamp!),
y: .value("Close", item.close)
)
.foregroundStyle(Color.blue)
.lineStyle(StrokeStyle(lineWidth: 1.25))
} // end for each
} // end chart
.padding(50)
.chartBackground { item in
Color.white
}
.chartXAxisLabel(position: .bottom, alignment: .center) {
Text("Date")
.font(.system(size: 14))
.foregroundColor(Color.black)
.frame(width: 50, height: 35, alignment: .bottom)
}
.chartYAxisLabel(position: .leading, alignment: .center, spacing: 0) {
Text("Closing Values")
.font(.system(size: 14))
.foregroundColor(Color.black)
}
.chartXAxis {
AxisMarks (values: GetXAxisLabels(min: minDate, max: maxDate)) { value in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
AxisValueLabel() {
if let localDate = value.as(Date.self) {
let formattedDate = dateToStringFormatter.string(from: localDate)
Text(formattedDate)
.font(.system(size: 12))
.foregroundColor(Color.black)
}
} // end axis value label
} // end axis marks
} // end chart x axis
.chartYAxis {
AxisMarks (position: .leading, values: GetYAxisLabels(min: minCloseValue, max: maxCloseValue)) { value in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
AxisValueLabel() {
if let localValue = value.as(Double.self) {
let formattedValue = String(format: "$%.2f", localValue)
Text(formattedValue)
.font(.system(size: 12))
.foregroundColor(Color.black)
}
}
} // end axis marks
} // end chart y axis
.chartXScale(domain: minDate...maxDate)
.chartYScale(domain: minCloseValue...maxCloseValue)
.frame(width: 700, height: 700, alignment: .center)
} // end group box
} // end of body
} // end of structure
func GetXAxisLabels(min: Date, max: Date) -> [Date] {
var xLabels: [Date] = []
let increment: Int = 90
for i in 0...3 {
let value = Calendar.current.date(byAdding: DateComponents(day: (i * increment)), to: min)!
xLabels.append(value)
}
xLabels.append(max)
print("\(xLabels)")
return xLabels
}
func GetYAxisLabels(min: Double, max: Double) -> [Double] {
var yLabels: [Double] = []
let increment: Double = (max - min) / 4
for i in 0...3 {
let value = min + (Double(i) * increment)
yLabels.append(value)
}
yLabels.append(max)
return yLabels
}
let dateToStringFormatter: DateFormatter = {
let result = DateFormatter()
result.dateFormat = "MM/dd/yy"
return result
}()
After some additional research I was able to position values with the AxisValueLabel horizontal and vertical spacing parameters. I was not able to figure out how to get Charts to place a value under the far right vertical grid line, i.e. my 5th date. So I resolved the problem by removing the AxisValueLabel code and using an overlay along with x & y positioning of the values. See updated code below.
struct LineChart: View {
private var localArray: [TradingDayPrices]
private var chartTitle: String
private var numYears: Int
init(passedInArray: [TradingDayPrices], chartTitle: String, numYears: Int) {
self.localArray = passedInArray
self.chartTitle = chartTitle
self.numYears = numYears
}
var body: some View {
let minCloseValue: Double = localArray.map { $0.close }.min()!
let maxCloseValue: Double = localArray.map { $0.close }.max()!
let minDate: Date = localArray[0].timeStamp!
let itemCount: Int = localArray.count - 1
let maxDate: Date = localArray[itemCount].timeStamp!
let dateArray: [Date] = GetXAxisLabels(min: minDate, max: maxDate, numYears: numYears)
Spacer()
GroupBox (label: Text("\(chartTitle)")
.font(Font.custom("Arial", size: 20))
.frame(width: 700, height: 50, alignment: .center)) {
Chart {
ForEach(localArray) { item in
LineMark (
x: .value("TimeStamp", item.timeStamp!),
y: .value("Close", item.close)
)
.foregroundStyle(Color.blue)
.lineStyle(StrokeStyle(lineWidth: 1.25))
} // end for each
} // end chart
.padding(50)
.chartBackground { item in
Color.white
}
.chartXAxisLabel(position: .bottom, alignment: .center, spacing: 25) {
Text("")
}
.chartYAxisLabel(position: .leading, alignment: .center, spacing: 25) {
Text("Closing Value")
.font(.system(size: 14))
.foregroundColor(Color.black)
}
.chartXAxis {
AxisMarks (values: dateArray) { value in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
AxisTick(centered: true, length: 7 , stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
} // end axis marks
} // end chart x axis
.chartYAxis {
AxisMarks (position: .leading, values: GetYAxisLabels(min: minCloseValue, max: maxCloseValue)) { value in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
AxisTick(centered: true, length: 7 , stroke: StrokeStyle(lineWidth: 0.5))
.foregroundStyle(Color.gray)
AxisValueLabel(verticalSpacing: 10) {
if let localValue = value.as(Double.self) {
let formattedValue = String(format: "$%.2f", localValue)
Text(formattedValue)
.font(.system(size: 12))
.foregroundColor(Color.black)
}
}
} // end axis marks
} // end chart y axis
.chartXScale(domain: minDate...maxDate)
.chartYScale(domain: minCloseValue...maxCloseValue)
.frame(width: 700, height: 700, alignment: .center)
.overlay {
let dateValue0 = dateToStringFormatter.string(from: dateArray[0])
Text(dateValue0).position(x: 155, y: 620)
let dateValue1 = dateToStringFormatter.string(from: dateArray[1])
Text(dateValue1).position(x: 275, y: 620)
let dateValue2 = dateToStringFormatter.string(from: dateArray[2])
Text(dateValue2).position(x: 398, y: 620)
let dateValue3 = dateToStringFormatter.string(from: dateArray[3])
Text(dateValue3).position(x: 522, y: 620)
let dateValue4 = dateToStringFormatter.string(from: dateArray[4])
Text(dateValue4).position(x: 654, y: 620)
Text("Date").position(x: 398, y: 655)
.font(.system(size: 14))
.foregroundColor(Color.black)
} // end overlay
} // end group box frame
Spacer().frame(height:65)
} // end of body
} // end of structure