Skip to content

heatmap(_:colorScale:) ignores the interpolation on the scale it is given, so .workoutStepped renders linear #4

Description

@elmbg-sam

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions