3

In this code I declare a Chart with an x scale domain between two doubles:

Chart(points, id: \.self) { point in
    LineMark(
        x: .value("time/s", point.timestamp),
        y: .value("potential/mV", point.potential)
    )
    .foregroundStyle(by: .value("Electrode", point.electrode.symbol))
}
.chartXScale(domain: timeWindow)
// ...

where timeWindow is declared as follows:

var timeWindow: ClosedRange<Double> {
    doc.contents.time(at: window.lowerBound)...doc.contents.time(at: window.upperBound)
}

and time is defined as:

func time(at index: Int) -> Double {
    return Double(index) * (1 / sampleRate)
}

However, the chart x-axis this produces does not have a start and end at the lower and upper bounds of the ClosedRange<Double> I have provided, rather there is a section of mystery space on either the left and right side of the plots:

Empty space in the Chart on the right of the LineMark plot.

How can I get the chart to adhere to the domain I have provided (the bounds are also displayed in the text view of the bottomBar ToolbarItemGroup, using the code Text("\(timeWindow.lowerBound.format()) s to \(timeWindow.upperBound.format()) s"))? And how can I get the y-axis to render on the left of the chart?

Tahmid Azam
  • 566
  • 4
  • 16
  • 2
    Does this answer your question https://stackoverflow.com/a/72902724/12299030? (similar question was for Y-axis) – Asperi Aug 04 '22 at 12:09
  • 1
    Yes it does, thank you, but now confused as to what chartXScale is actually meant to do :/ – Tahmid Azam Aug 04 '22 at 19:40
  • @Tahmid My impression is that chartXScale and chartYScale are only used as a guideline and Chart loosely goes by them when generating the chart. Maybe that will improve in future versions. – Marcy Aug 04 '22 at 20:06
  • You do lose the automatic mark stride by length by using the axis marks method, but since im using the chart to sort of ‘scroll’ through data, it makes sense to use it and make it explicit. – Tahmid Azam Aug 04 '22 at 21:23

1 Answers1

5

The solution is to create an array of values, enabling the construction of axis content by using AxisMarks:

var xValues: [Double] {
    stride(from: timeWindow.lowerBound, to: timeWindow.upperBound, by: (timeWindow.upperBound - timeWindow.lowerBound) / 5).map { $0 }
}

And then in the chart modifiers:

Chart(points, id: \.self) { point in
    LineMark(
        x: .value("time/s", point.timestamp),
        y: .value("potential/mV", point.potential)
    )
    .foregroundStyle(by: .value("Electrode", point.electrode.symbol))
}
.chartXScale(domain: ClosedRange(uncheckedBounds: (lower: timeWindow.lowerBound, upper: timeWindow.upperBound)))
.chartXAxis {
    AxisMarks(values: xValues)
}
    \\ ...

N.B.: I have found that it is still necessary to include the chartXScale(domain:) modifier, as otherwise the x-axis seems to always begin at zero, even if that is not included in the axis marks.

This produces the intended behaviour for the 'scrollable' plot:

Intended scrollable plot.

However, there is a drawback of now losing the automatic method of determining mark spacing in the axis, but this is a compromise I will have to deal with, and construct a better algorithm for creating them instead of hard coded values like the 5 used above.

Tahmid Azam
  • 566
  • 4
  • 16
  • Hi. Sorry, i do ont understand the chartXAxis part and chartXScale. My case scenario is a date object so that's .chartXScale(domain: ClosedRange(uncheckedBounds: (lower: $0.date.lowerBound, upper: $0.date.upperBound))) .chartXAxis { AxisMarks(values: stride(from: $0.date.lowerBound, to: $0.date.upperBound, by: ($0.date.upperBound - $0.date.lowerBound) / 5).map { $0 }) } ? – chitgoks Jan 09 '23 at 02:10