diff --git a/widgets/linechart/internal/axes/axes.go b/widgets/linechart/internal/axes/axes.go index 01ee686..60e6a52 100644 --- a/widgets/linechart/internal/axes/axes.go +++ b/widgets/linechart/internal/axes/axes.go @@ -72,6 +72,8 @@ type YProperties struct { ReqXHeight int // ScaleMode determines how the Y axis scales. ScaleMode YScaleMode + // ValueFormatter is the formatter used to format numeric values to string representation. + ValueFormatter valueFormatter } // NewYDetails retrieves details about the Y axis required to draw it on a @@ -85,7 +87,7 @@ func NewYDetails(cvsAr image.Rectangle, yp *YProperties) (*YDetails, error) { } graphHeight := cvsHeight - yp.ReqXHeight - scale, err := NewYScale(yp.Min, yp.Max, graphHeight, nonZeroDecimals, yp.ScaleMode) + scale, err := NewYScale(yp.Min, yp.Max, graphHeight, nonZeroDecimals, yp.ScaleMode, yp.ValueFormatter) if err != nil { return nil, err } diff --git a/widgets/linechart/internal/axes/axes_test.go b/widgets/linechart/internal/axes/axes_test.go index 87f83c4..8f974f4 100644 --- a/widgets/linechart/internal/axes/axes_test.go +++ b/widgets/linechart/internal/axes/axes_test.go @@ -22,6 +22,10 @@ import ( "github.com/kylelemons/godebug/pretty" ) +var ( + testValueFormatter = func(float64) string { return "test" } +) + type updateY struct { minVal float64 maxVal float64 @@ -82,7 +86,7 @@ func TestY(t *testing.T) { Width: 2, Start: image.Point{1, 0}, End: image.Point{1, 2}, - Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored), + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, nil), Labels: []*Label{ {NewValue(0, nonZeroDecimals), image.Point{0, 1}}, {NewValue(1.72, nonZeroDecimals), image.Point{0, 0}}, @@ -103,7 +107,7 @@ func TestY(t *testing.T) { Width: 2, Start: image.Point{1, 0}, End: image.Point{1, 2}, - Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored), + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, nil), Labels: []*Label{ {NewValue(0, nonZeroDecimals), image.Point{0, 1}}, {NewValue(1.72, nonZeroDecimals), image.Point{0, 0}}, @@ -124,7 +128,7 @@ func TestY(t *testing.T) { Width: 2, Start: image.Point{1, 0}, End: image.Point{1, 2}, - Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored), + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, nil), Labels: []*Label{ {NewValue(0, nonZeroDecimals), image.Point{0, 1}}, {NewValue(1.72, nonZeroDecimals), image.Point{0, 0}}, @@ -145,7 +149,7 @@ func TestY(t *testing.T) { Width: 2, Start: image.Point{1, 0}, End: image.Point{1, 2}, - Scale: mustNewYScale(1, 6, 2, nonZeroDecimals, YScaleModeAdaptive), + Scale: mustNewYScale(1, 6, 2, nonZeroDecimals, YScaleModeAdaptive, nil), Labels: []*Label{ {NewValue(1, nonZeroDecimals), image.Point{0, 1}}, {NewValue(3.88, nonZeroDecimals), image.Point{0, 0}}, @@ -165,7 +169,7 @@ func TestY(t *testing.T) { Width: 5, Start: image.Point{4, 0}, End: image.Point{4, 2}, - Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored), + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, nil), Labels: []*Label{ {NewValue(0, nonZeroDecimals), image.Point{3, 1}}, {NewValue(1.72, nonZeroDecimals), image.Point{0, 0}}, @@ -185,13 +189,35 @@ func TestY(t *testing.T) { Width: 5, Start: image.Point{4, 0}, End: image.Point{4, 2}, - Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored), + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, nil), Labels: []*Label{ {NewValue(0, nonZeroDecimals), image.Point{3, 1}}, {NewValue(1.72, nonZeroDecimals), image.Point{0, 0}}, }, }, }, + { + desc: "success for formatted labels scale", + yp: &YProperties{ + Min: 1, + Max: 3, + ReqXHeight: 2, + ScaleMode: YScaleModeAnchored, + ValueFormatter: testValueFormatter, + }, + cvsAr: image.Rect(0, 0, 3, 4), + wantWidth: 2, + want: &YDetails{ + Width: 2, + Start: image.Point{1, 0}, + End: image.Point{1, 2}, + Scale: mustNewYScale(0, 3, 2, nonZeroDecimals, YScaleModeAnchored, testValueFormatter), + Labels: []*Label{ + {NewFormattedValue(0, nonZeroDecimals, testValueFormatter), image.Point{0, 1}}, + {NewFormattedValue(1.72, nonZeroDecimals, testValueFormatter), image.Point{0, 0}}, + }, + }, + }, } for _, tc := range tests { diff --git a/widgets/linechart/internal/axes/label_test.go b/widgets/linechart/internal/axes/label_test.go index 1216efe..35221de 100644 --- a/widgets/linechart/internal/axes/label_test.go +++ b/widgets/linechart/internal/axes/label_test.go @@ -129,7 +129,7 @@ func TestYLabels(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - scale, err := NewYScale(tc.min, tc.max, tc.graphHeight, nonZeroDecimals, YScaleModeAnchored) + scale, err := NewYScale(tc.min, tc.max, tc.graphHeight, nonZeroDecimals, YScaleModeAnchored, nil) if err != nil { t.Fatalf("NewYScale => unexpected error: %v", err) } diff --git a/widgets/linechart/internal/axes/scale.go b/widgets/linechart/internal/axes/scale.go index 63b2448..dfd74a2 100644 --- a/widgets/linechart/internal/axes/scale.go +++ b/widgets/linechart/internal/axes/scale.go @@ -66,6 +66,10 @@ type YScale struct { GraphHeight int // brailleHeight is the height of the braille canvas based on the GraphHeight. brailleHeight int + + // valueFormatter is the value formatter used for the labels + // represented by the values on the scale. + valueFormatter valueFormatter } // String implements fmt.Stringer. @@ -78,7 +82,7 @@ func (ys *YScale) String() string { // calculated scale, see NewValue for details. // Max must be greater or equal to min. The graphHeight must be a positive // number. -func NewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode) (*YScale, error) { +func NewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, valueFormatter valueFormatter) (*YScale, error) { if max < min { return nil, fmt.Errorf("max(%v) cannot be less than min(%v)", max, min) } @@ -114,11 +118,12 @@ func NewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMo diff := max - min step := NewValue(diff/float64(usablePixels), nonZeroDecimals) return &YScale{ - Min: NewValue(min, nonZeroDecimals), - Max: NewValue(max, nonZeroDecimals), - Step: step, - GraphHeight: graphHeight, - brailleHeight: brailleHeight, + Min: yScaleNewValue(min, nonZeroDecimals, valueFormatter), + Max: yScaleNewValue(max, nonZeroDecimals, valueFormatter), + Step: step, + GraphHeight: graphHeight, + brailleHeight: brailleHeight, + valueFormatter: valueFormatter, }, nil } @@ -188,7 +193,18 @@ func (ys *YScale) CellLabel(y int) (*Value, error) { if err != nil { return nil, err } - return NewValue(v, ys.Min.NonZeroDecimals), nil + return yScaleNewValue(v, ys.Min.NonZeroDecimals, ys.valueFormatter), nil +} + +// yScaleNewValue is a helper method to get new values for the y scale +// that selects the correct value factory method depending on the passed +// arguments. +func yScaleNewValue(value float64, nonZeroDecimals int, valueFormatter valueFormatter) *Value { + if valueFormatter != nil { + return NewFormattedValue(value, nonZeroDecimals, valueFormatter) + } + + return NewValue(value, nonZeroDecimals) } // XScale is the scale of the X axis. diff --git a/widgets/linechart/internal/axes/scale_test.go b/widgets/linechart/internal/axes/scale_test.go index cff8199..bc43011 100644 --- a/widgets/linechart/internal/axes/scale_test.go +++ b/widgets/linechart/internal/axes/scale_test.go @@ -22,8 +22,8 @@ import ( ) // mustNewYScale returns a new YScale or panics. -func mustNewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode) *YScale { - s, err := NewYScale(min, max, graphHeight, nonZeroDecimals, mode) +func mustNewYScale(min, max float64, graphHeight, nonZeroDecimals int, mode YScaleMode, formatter valueFormatter) *YScale { + s, err := NewYScale(min, max, graphHeight, nonZeroDecimals, mode, formatter) if err != nil { panic(err) } @@ -766,7 +766,7 @@ func TestYScale(t *testing.T) { } for _, test := range tests { - scale, err := NewYScale(test.min, test.max, test.graphHeight, test.nonZeroDecimals, test.mode) + scale, err := NewYScale(test.min, test.max, test.graphHeight, test.nonZeroDecimals, test.mode, nil) if (err != nil) != test.wantErr { t.Errorf("NewYScale => unexpected error: %v, wantErr: %v", err, test.wantErr) }