diff --git a/palette/moreland/luminance.go b/palette/moreland/luminance.go index 0baced27..001e6d3d 100644 --- a/palette/moreland/luminance.go +++ b/palette/moreland/luminance.go @@ -179,7 +179,13 @@ func (l luminance) Palette(n int) palette.Palette { var v float64 c := make([]color.Color, n) for i := range n { - v = l.min + float64(delta*float64(i)) + if i == n-1 { + // Avoid potential overflow on last element + // due to floating point error. + v = l.max + } else { + v = l.min + float64(delta*float64(i)) + } var err error c[i], err = l.At(v) if err != nil { diff --git a/palette/moreland/luminance_test.go b/palette/moreland/luminance_test.go index a691c4df..6e4c0312 100644 --- a/palette/moreland/luminance_test.go +++ b/palette/moreland/luminance_test.go @@ -161,3 +161,47 @@ func BenchmarkLuminance_At(b *testing.B) { }) } } + +// See https://github.com/gonum/plot/issues/798 +func TestIssue798Kindlmann(t *testing.T) { + for _, test := range []struct { + n int + min, max float64 + }{ + 0: {n: 2, min: 0, max: 1}, + 1: {n: 15, min: 0.3402859786606234, max: 15.322841335211892}, + } { + t.Run("", func(t *testing.T) { + defer func() { + r := recover() + if r != nil { + t.Errorf("unexpected panic with n=%d min=%f max=%f: %v", test.n, test.min, test.max, r) + } + }() + colors := Kindlmann() + colors.SetMin(test.min) + colors.SetMax(test.max) + col := colors.Palette(test.n).Colors() + min, err := colors.At(test.min) + if err != nil { + t.Fatalf("unexpected error calling colors.At(min): %v", err) + } + if !sameColor(min, col[0]) { + t.Errorf("unexpected min color %#v != %#v", min, col[0]) + } + max, err := colors.At(test.max) + if err != nil { + t.Fatalf("unexpected error calling colors.At(max): %v", err) + } + if !sameColor(max, col[len(col)-1]) { + t.Errorf("unexpected max color %#v != %#v", max, col[len(col)-1]) + } + }) + } +} + +func sameColor(a, b color.Color) bool { + ar, ag, ab, aa := a.RGBA() + br, bg, bb, ba := b.RGBA() + return ar == br && ag == bg && ab == bb && aa == ba +} diff --git a/palette/moreland/smooth.go b/palette/moreland/smooth.go index 3b7057a3..427b6742 100644 --- a/palette/moreland/smooth.go +++ b/palette/moreland/smooth.go @@ -171,9 +171,16 @@ func (p smoothDiverging) Palette(n int) palette.Palette { p.SetMax(1) } delta := (p.max - p.min) / float64(n-1) + var v float64 c := make([]color.Color, n) for i := range c { - v := p.min + float64(delta*float64(i)) + if i == n-1 { + // Avoid potential overflow on last element + // due to floating point error. + v = p.max + } else { + v = p.min + float64(delta*float64(i)) + } var err error c[i], err = p.At(v) if err != nil { diff --git a/palette/moreland/smooth_test.go b/palette/moreland/smooth_test.go index ae128c69..06e7ab90 100644 --- a/palette/moreland/smooth_test.go +++ b/palette/moreland/smooth_test.go @@ -246,3 +246,41 @@ func similar(a, b color.Color, tolerance float64) bool { } return true } + +// See https://github.com/gonum/plot/issues/798 +func TestIssue798SmoothBlueRed(t *testing.T) { + for _, test := range []struct { + n int + min, max float64 + }{ + 0: {n: 2, min: 0, max: 1}, + 1: {n: 15, min: 0.3402859786606234, max: 15.322841335211892}, + } { + t.Run("", func(t *testing.T) { + defer func() { + r := recover() + if r != nil { + t.Errorf("unexpected panic with n=%d min=%f max=%f: %v", test.n, test.min, test.max, r) + } + }() + colors := SmoothBlueRed() + colors.SetMin(test.min) + colors.SetMax(test.max) + col := colors.Palette(test.n).Colors() + min, err := colors.At(test.min) + if err != nil { + t.Fatalf("unexpected error calling colors.At(min): %v", err) + } + if !sameColor(min, col[0]) { + t.Errorf("unexpected min color %#v != %#v", min, col[0]) + } + max, err := colors.At(test.max) + if err != nil { + t.Fatalf("unexpected error calling colors.At(max): %v", err) + } + if !sameColor(max, col[len(col)-1]) { + t.Errorf("unexpected max color %#v != %#v", max, col[len(col)-1]) + } + }) + } +}