Chart: How to achieve a specific X-axis (an awkward log scale)

I'm trying to determine how best to achieve a desired result within Swift Charts.

I'm trying to make a sort-of "reversed" log chart. The use case here is for driving into the details of the percentiles from a series latency values stored within a histogram.

The X axis percentile values I was hoping to achieve was something along the lines of:

10, 50, 90, 99, 99,9, 99.99, 99.999

With those values being fairly evenly distributed across the chart. If I look at this from an inverted sequence of:

( 1 - (some value) ).reversed() where the values are:

0.00001, 0.0001, 0.0001, 0.001, 0.01, 0.5, 0.1

But I've been struggling with how to start with those values, scale the X axis using a 1-value kind of setup, and then overlaying the values that are more human readable to achieve the end result.

Any suggestions on how to tackle this scenario with customizing a chart axis?

Accepted Reply

I found a solution, although I had to go tweaking my origin data to enable it.

I switched the percentiles reported out of my data structure so that they represented 1.0 - the_original_percentile to get numbers into a log range that could be represented. Following that, the keys were figuring out chartXScale and chartXAxis with a closure to tweak the values presented as labels using AxisValueLabel.

For anyone else trying to make a service latency review chart and wanting to view the outliers at wacky high percentiles, here's the gist:

Chart {
    ForEach(Converter.invertedPercentileArray(histogram), id: \.0) { stat in
        LineMark(
            x: .value("percentile", stat.0),
            y: .value("value", stat.1)
        )
        // Use curved line to join points
        .interpolationMethod(.monotone)
    }
}
.chartXScale(
    domain: .automatic(includesZero: false, reversed: true),
    type: .log
)
.chartXAxis {
    AxisMarks(values: Converter.invertedPercentileArray(histogram).map{ $0.0 }
    ) { value in
        AxisGridLine()
        AxisValueLabel(centered: true, anchor: .top) {
            if let invertedPercentile = value.as(Double.self) {
                Text("\( ( (1 - invertedPercentile)*100.0).formatted(.number.precision(.significantDigits(1...5)))) ")
            }
        }
    }
}

Although of note - at least in Xcode Version 14.3 beta 2 (14E5207e), using "reversed:true" in the ScaleDomain caused the simulator to crash. (filed as FB12035575) In a macOS app, there wasn't any issue

Resulting chart:

Replies

I found a solution, although I had to go tweaking my origin data to enable it.

I switched the percentiles reported out of my data structure so that they represented 1.0 - the_original_percentile to get numbers into a log range that could be represented. Following that, the keys were figuring out chartXScale and chartXAxis with a closure to tweak the values presented as labels using AxisValueLabel.

For anyone else trying to make a service latency review chart and wanting to view the outliers at wacky high percentiles, here's the gist:

Chart {
    ForEach(Converter.invertedPercentileArray(histogram), id: \.0) { stat in
        LineMark(
            x: .value("percentile", stat.0),
            y: .value("value", stat.1)
        )
        // Use curved line to join points
        .interpolationMethod(.monotone)
    }
}
.chartXScale(
    domain: .automatic(includesZero: false, reversed: true),
    type: .log
)
.chartXAxis {
    AxisMarks(values: Converter.invertedPercentileArray(histogram).map{ $0.0 }
    ) { value in
        AxisGridLine()
        AxisValueLabel(centered: true, anchor: .top) {
            if let invertedPercentile = value.as(Double.self) {
                Text("\( ( (1 - invertedPercentile)*100.0).formatted(.number.precision(.significantDigits(1...5)))) ")
            }
        }
    }
}

Although of note - at least in Xcode Version 14.3 beta 2 (14E5207e), using "reversed:true" in the ScaleDomain caused the simulator to crash. (filed as FB12035575) In a macOS app, there wasn't any issue

Resulting chart: