Summary
BodyView.heatmap(_:colorScale:) accepts a HeatmapColorScale and then discards the interpolation stored on it, substituting the interpolation from the view's HeatmapConfiguration (default .linear).
The practical result: .workoutStepped — whose only difference from .workout is that it carries .step(count: 5) — renders identically to .workout when passed to this overload. The parameter is accepted, the call site reads as if it took effect, and nothing reports otherwise.
Version: 1.6.4.
The code
// Sources/MuscleMap/Views/BodyView.swift
public func heatmap(_ data: [MuscleIntensity], colorScale: HeatmapColorScale = .workout) -> BodyView {
var copy = self
let config = copy.heatmapConfig ?? .default
let effectiveScale = HeatmapColorScale(
colors: colorScale.colors, // <- colors are taken from the argument
interpolation: config.interpolation // <- interpolation is not
)
...
}
colorScale.interpolation is never read. Since HeatmapConfiguration.default.interpolation is .linear, any stepped or eased scale handed to this overload is silently flattened unless the caller has separately set a configuration.
Reproduction
import SwiftUI
import MuscleMap
struct ContentView: View {
let data: [MuscleIntensity] = [
.init(muscle: .chest, intensity: 0.1),
.init(muscle: .biceps, intensity: 0.3),
.init(muscle: .quadriceps, intensity: 0.5),
.init(muscle: .abs, intensity: 0.7),
.init(muscle: .forearm, intensity: 0.9),
]
var body: some View {
HStack {
// These two render identically — no banding in either.
BodyView(gender: .male, side: .front).heatmap(data, colorScale: .workout)
BodyView(gender: .male, side: .front).heatmap(data, colorScale: .workoutStepped)
// This one bands as expected, which is the giveaway.
BodyView(gender: .male, side: .front)
.heatmap(data, configuration: HeatmapConfiguration(
colorScale: .workoutStepped,
interpolation: .step(count: 5)
))
}
}
}
The first two are pixel-identical; the third shows the five discrete bands.
Why this is worth fixing rather than documenting
.workoutStepped exists as a preset for exactly one reason — it is .workout plus a step interpolation — and the most obvious way to use it is the overload that ignores the thing that makes it different. I shipped a build believing the app rendered banded intensity for a day before noticing the colours were continuous.
Suggested fix
Prefer the scale's own interpolation, falling back to the configuration only when the scale does not specify one — or, less disruptively, use the argument's interpolation whenever the caller passed an explicit colorScale:
let effectiveScale = HeatmapColorScale(
colors: colorScale.colors,
interpolation: copy.heatmapConfig?.interpolation ?? colorScale.interpolation
)
That keeps heatmapInterpolation(_:) as the explicit override while making the preset behave as its name implies. Happy to send a PR if you would like it in this shape.
Related
Summary
BodyView.heatmap(_:colorScale:)accepts aHeatmapColorScaleand then discards theinterpolationstored on it, substituting the interpolation from the view'sHeatmapConfiguration(default.linear).The practical result:
.workoutStepped— whose only difference from.workoutis that it carries.step(count: 5)— renders identically to.workoutwhen passed to this overload. The parameter is accepted, the call site reads as if it took effect, and nothing reports otherwise.Version: 1.6.4.
The code
colorScale.interpolationis never read. SinceHeatmapConfiguration.default.interpolationis.linear, any stepped or eased scale handed to this overload is silently flattened unless the caller has separately set a configuration.Reproduction
The first two are pixel-identical; the third shows the five discrete bands.
Why this is worth fixing rather than documenting
.workoutSteppedexists as a preset for exactly one reason — it is.workoutplus astepinterpolation — and the most obvious way to use it is the overload that ignores the thing that makes it different. I shipped a build believing the app rendered banded intensity for a day before noticing the colours were continuous.Suggested fix
Prefer the scale's own interpolation, falling back to the configuration only when the scale does not specify one — or, less disruptively, use the argument's interpolation whenever the caller passed an explicit
colorScale:That keeps
heatmapInterpolation(_:)as the explicit override while making the preset behave as its name implies. Happy to send a PR if you would like it in this shape.Related
Muscle.rhomboids/Muscle.rotatorCuffhave no drawable pathBodyRendererconstructed per draw, so itsPathCachenever hits