mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-27 13:48:49 +08:00
More validation for the scales.
This commit is contained in:
parent
bdd0290c98
commit
a3bed458d6
@ -85,7 +85,10 @@ func (y *Y) Details(cvsHeight int, maxWidth int) (*YDetails, error) {
|
||||
if req := y.RequiredWidth(); maxWidth < req {
|
||||
return nil, fmt.Errorf("the received maxWidth %d is smaller than the reported required width %d", maxWidth, req)
|
||||
}
|
||||
scale := NewYScale(y.min.Value, y.max.Value, cvsHeight, nonZeroDecimals)
|
||||
scale, err := NewYScale(y.min.Value, y.max.Value, cvsHeight, nonZeroDecimals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// See how the labels would look like on the entire maxWidth.
|
||||
maxLabelWidth := maxWidth - yAxisWidth
|
||||
|
@ -65,7 +65,7 @@ func TestY(t *testing.T) {
|
||||
maxWidth: 2,
|
||||
want: &YDetails{
|
||||
Width: 2,
|
||||
Scale: NewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Scale: mustNewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Labels: []*Label{
|
||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||
{NewValue(1.72, nonZeroDecimals), image.Point{0, 0}},
|
||||
@ -81,7 +81,7 @@ func TestY(t *testing.T) {
|
||||
maxWidth: 5,
|
||||
want: &YDetails{
|
||||
Width: 5,
|
||||
Scale: NewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Scale: mustNewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Labels: []*Label{
|
||||
{NewValue(0, nonZeroDecimals), image.Point{3, 1}},
|
||||
{NewValue(1.72, nonZeroDecimals), image.Point{0, 0}},
|
||||
@ -97,7 +97,7 @@ func TestY(t *testing.T) {
|
||||
maxWidth: 6,
|
||||
want: &YDetails{
|
||||
Width: 5,
|
||||
Scale: NewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Scale: mustNewYScale(0, 3, 2, nonZeroDecimals),
|
||||
Labels: []*Label{
|
||||
{NewValue(0, nonZeroDecimals), image.Point{3, 1}},
|
||||
{NewValue(1.72, nonZeroDecimals), image.Point{0, 0}},
|
||||
|
@ -98,7 +98,10 @@ func TestYLabels(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
scale := NewYScale(tc.min, tc.max, tc.cvsHeight, nonZeroDecimals)
|
||||
scale, err := NewYScale(tc.min, tc.max, tc.cvsHeight, nonZeroDecimals)
|
||||
if err != nil {
|
||||
t.Fatalf("NewYScale => unexpected error: %v", err)
|
||||
}
|
||||
t.Logf("scale step: %v", scale.Step.Rounded)
|
||||
got, err := yLabels(scale, tc.labelWidth)
|
||||
if (err != nil) != tc.wantErr {
|
||||
|
@ -41,7 +41,16 @@ type YScale struct {
|
||||
// NewYScale calculates the scale of the Y axis, given the boundary values and
|
||||
// the height of the canvas. The nonZeroDecimals dictates rounding of the
|
||||
// calculated scale, see NewValue for details.
|
||||
func NewYScale(min, max float64, cvsHeight, nonZeroDecimals int) *YScale {
|
||||
// Max must be greater or equal to min. The cvsHeight must be a positive
|
||||
// number.
|
||||
func NewYScale(min, max float64, cvsHeight, nonZeroDecimals int) (*YScale, error) {
|
||||
if max < min {
|
||||
return nil, fmt.Errorf("max(%v) cannot be less than min(%v)", max, min)
|
||||
}
|
||||
if min := 1; cvsHeight < min {
|
||||
return nil, fmt.Errorf("cvsHeight cannot be less than %d, got %d", min, cvsHeight)
|
||||
}
|
||||
|
||||
brailleHeight := cvsHeight * braille.RowMult
|
||||
usablePixels := brailleHeight - 1 // One pixel reserved for value zero.
|
||||
|
||||
@ -53,7 +62,7 @@ func NewYScale(min, max float64, cvsHeight, nonZeroDecimals int) *YScale {
|
||||
Step: step,
|
||||
CvsHeight: cvsHeight,
|
||||
brailleHeight: brailleHeight,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PixelToValue given a Y coordinate of the pixel, returns its value according
|
||||
@ -88,6 +97,9 @@ func (ys *YScale) ValueToPixel(v float64) (int, error) {
|
||||
if min, max := ys.Min.Value, ys.Max.Rounded; v < min || v > max {
|
||||
return 0, fmt.Errorf("invalid value %v, must be in range %v <= v <= %v", v, min, max)
|
||||
}
|
||||
if ys.Step.Rounded == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if ys.Min.Value < 0 {
|
||||
diff := -1 * ys.Min.Rounded
|
||||
@ -137,6 +149,8 @@ type XScale struct {
|
||||
// points in the series and the width on the canvas that is available to the X
|
||||
// axis. The nonZeroDecimals dictates rounding of the calculated scale, see
|
||||
// NewValue for details.
|
||||
// The numPoints must be zero or positive number. The axisWidth must be a
|
||||
// positive number.
|
||||
func NewXScale(numPoints int, axisWidth, nonZeroDecimals int) (*XScale, error) {
|
||||
if numPoints < 0 {
|
||||
return nil, fmt.Errorf("numPoints cannot be negative, got %d", numPoints)
|
||||
@ -150,6 +164,9 @@ func NewXScale(numPoints int, axisWidth, nonZeroDecimals int) (*XScale, error) {
|
||||
|
||||
const min float64 = 0
|
||||
max := float64(numPoints - 1)
|
||||
if max < 0 {
|
||||
max = 0
|
||||
}
|
||||
diff := max - min
|
||||
step := NewValue(diff/float64(usablePixels), nonZeroDecimals)
|
||||
return &XScale{
|
||||
@ -188,6 +205,9 @@ func (xs *XScale) ValueToPixel(v int) (int, error) {
|
||||
if min, max := xs.Min.Value, xs.Max.Rounded; fv < min || fv > max {
|
||||
return 0, fmt.Errorf("invalid value %v, must be in range %v <= v <= %v", v, min, max)
|
||||
}
|
||||
if xs.Step.Rounded == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return int(numbers.Round(fv / xs.Step.Rounded)), nil
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,24 @@ import (
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
// mustNewYScale returns a new YScale or panics.
|
||||
func mustNewYScale(min, max float64, cvsHeight, nonZeroDecimals int) *YScale {
|
||||
s, err := NewYScale(min, max, cvsHeight, nonZeroDecimals)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// mustNewXScale returns a new XScale or panics.
|
||||
func mustNewXScale(numPoints int, axisWidth, nonZeroDecimals int) *XScale {
|
||||
s, err := NewXScale(numPoints, axisWidth, nonZeroDecimals)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestYScale(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
@ -29,7 +47,24 @@ func TestYScale(t *testing.T) {
|
||||
cvsHeight int
|
||||
nonZeroDecimals int
|
||||
want *YScale
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "fails when max is less than min",
|
||||
min: 0,
|
||||
max: -1,
|
||||
cvsHeight: 4,
|
||||
nonZeroDecimals: 2,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when canvas height too small",
|
||||
min: 0,
|
||||
max: 1,
|
||||
cvsHeight: 0,
|
||||
nonZeroDecimals: 2,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "min and max are zero",
|
||||
min: 0,
|
||||
@ -90,7 +125,13 @@ func TestYScale(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := NewYScale(tc.min, tc.max, tc.cvsHeight, tc.nonZeroDecimals)
|
||||
got, err := NewYScale(tc.min, tc.max, tc.cvsHeight, tc.nonZeroDecimals)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("NewYScale => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Errorf("NewYScale => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
@ -176,6 +217,22 @@ func TestYScaleMethods(t *testing.T) {
|
||||
{4, nil, true},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "works without data points",
|
||||
min: 0,
|
||||
max: 0,
|
||||
cvsHeight: 1,
|
||||
nonZeroDecimals: 2,
|
||||
pixelToValueTests: []pixelToValueTest{
|
||||
{0, 0, false},
|
||||
},
|
||||
valueToPixelTests: []valueToPixelTest{
|
||||
{0, 0, false},
|
||||
},
|
||||
cellLabelTests: []cellLabelTest{
|
||||
{0, NewValue(0, 2), false},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "integer scale",
|
||||
min: 0,
|
||||
@ -302,7 +359,11 @@ func TestYScaleMethods(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
scale := NewYScale(test.min, test.max, test.cvsHeight, test.nonZeroDecimals)
|
||||
scale, err := NewYScale(test.min, test.max, test.cvsHeight, test.nonZeroDecimals)
|
||||
if err != nil {
|
||||
t.Fatalf("NewYScale => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("PixelToValue:%s", test.desc), func(t *testing.T) {
|
||||
for _, tc := range test.pixelToValueTests {
|
||||
got, err := scale.PixelToValue(tc.pixel)
|
||||
@ -415,6 +476,21 @@ func TestXScaleMethods(t *testing.T) {
|
||||
{2, nil, true},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "works without data points",
|
||||
numPoints: 0,
|
||||
axisWidth: 1,
|
||||
nonZeroDecimals: 2,
|
||||
pixelToValueTests: []pixelToValueTest{
|
||||
{0, 0, false},
|
||||
},
|
||||
valueToPixelTests: []valueToPixelTest{
|
||||
{0, 0, false},
|
||||
},
|
||||
cellLabelTests: []cellLabelTest{
|
||||
{0, NewValue(0, 2), false},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "integer scale, all points fit",
|
||||
numPoints: 6,
|
||||
|
Loading…
x
Reference in New Issue
Block a user