More test coverage for linechart.

This commit is contained in:
Jakub Sobon 2019-01-13 01:38:39 -05:00
parent 1db0cfc7f1
commit bc911a3cd6
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
10 changed files with 411 additions and 48 deletions

View File

@ -165,7 +165,9 @@ type XDetails struct {
// of the provided area. The yStart is the point where the Y axis starts.
// The numPoints is the number of points in the largest series that will be
// plotted.
func NewXDetails(numPoints int, yStart image.Point, cvsAr image.Rectangle) (*XDetails, error) {
// customLabels are the desired labels for the X axis, these are preferred if
// provided.
func NewXDetails(numPoints int, yStart image.Point, cvsAr image.Rectangle, customLabels map[int]string) (*XDetails, error) {
if min := 3; cvsAr.Dy() < min {
return nil, fmt.Errorf("the canvas isn't tall enough to accommodate the X axis, its labels and the line chart, got height %d, minimum is %d", cvsAr.Dy(), min)
}
@ -180,7 +182,7 @@ func NewXDetails(numPoints int, yStart image.Point, cvsAr image.Rectangle) (*XDe
// One point horizontally for the Y axis.
// Two points vertically, one for the X axis and one for its labels.
graphZero := image.Point{yStart.X + 1, cvsAr.Dy() - 3}
labels, err := xLabels(scale, graphZero)
labels, err := xLabels(scale, graphZero, customLabels)
if err != nil {
return nil, err
}

View File

@ -142,13 +142,14 @@ func TestY(t *testing.T) {
func TestNewXDetails(t *testing.T) {
tests := []struct {
desc string
numPoints int
yStart image.Point
cvsWidth int
cvsAr image.Rectangle
want *XDetails
wantErr bool
desc string
numPoints int
yStart image.Point
cvsWidth int
cvsAr image.Rectangle
customLabels map[int]string
want *XDetails
wantErr bool
}{
{
desc: "fails when numPoints is negative",
@ -209,7 +210,7 @@ func TestNewXDetails(t *testing.T) {
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got, err := NewXDetails(tc.numPoints, tc.yStart, tc.cvsAr)
got, err := NewXDetails(tc.numPoints, tc.yStart, tc.cvsAr, tc.customLabels)
if (err != nil) != tc.wantErr {
t.Errorf("NewXDetails => unexpected error: %v, wantErr: %v", err, tc.wantErr)
}

View File

@ -157,14 +157,16 @@ func (xs *xSpace) Sub(size int) error {
// Labels are returned in an increasing value order.
// Returned labels shouldn't be trimmed, their count is adjusted so that they
// fit under the width of the axis.
func xLabels(scale *XScale, graphZero image.Point) ([]*Label, error) {
// The customLabels map value positions in the series to the desired custom
// label. These are preferred if present.
func xLabels(scale *XScale, graphZero image.Point, customLabels map[int]string) ([]*Label, error) {
space := newXSpace(graphZero, scale.GraphWidth)
const minSpacing = 3
var res []*Label
next := 0
for haveLabels := 0; haveLabels <= int(scale.Max.Value); haveLabels = len(res) {
label, err := colLabel(scale, space)
label, err := colLabel(scale, space, next, customLabels)
if err != nil {
return nil, err
}
@ -200,14 +202,20 @@ func xLabels(scale *XScale, graphZero image.Point) ([]*Label, error) {
// colLabel returns a label placed either at the beginning of the space.
// The space is adjusted according to how much space was taken by the label.
// Returns nil, nil if the label doesn't fit in the space.
func colLabel(scale *XScale, space *xSpace) (*Label, error) {
pos := space.Relative()
v, err := scale.CellLabel(pos.X)
if err != nil {
return nil, fmt.Errorf("unable to determine label value for column %d: %v", pos.X, err)
func colLabel(scale *XScale, space *xSpace, labelNum int, customLabels map[int]string) (*Label, error) {
var val *Value
if custom, ok := customLabels[labelNum]; ok {
val = NewTextValue(custom)
} else {
pos := space.Relative()
v, err := scale.CellLabel(pos.X)
if err != nil {
return nil, fmt.Errorf("unable to determine label value for column %d: %v", pos.X, err)
}
val = v
}
labelLen := len(v.Text())
labelLen := len(val.Text())
if labelLen > space.Remaining() {
return nil, nil
}
@ -218,7 +226,7 @@ func colLabel(scale *XScale, space *xSpace) (*Label, error) {
}
return &Label{
Value: v,
Value: val,
Pos: abs,
}, nil
}

View File

@ -151,12 +151,13 @@ func TestYLabels(t *testing.T) {
func TestXLabels(t *testing.T) {
const nonZeroDecimals = 2
tests := []struct {
desc string
numPoints int
graphWidth int
graphZero image.Point
want []*Label
wantErr bool
desc string
numPoints int
graphWidth int
graphZero image.Point
customLabels map[int]string
want []*Label
wantErr bool
}{
{
desc: "only one point",
@ -246,6 +247,50 @@ func TestXLabels(t *testing.T) {
{NewValue(3, nonZeroDecimals), image.Point{94, 3}},
},
},
{
desc: "custom labels provided",
numPoints: 4,
graphWidth: 100,
graphZero: image.Point{0, 1},
customLabels: map[int]string{
0: "a",
1: "b",
2: "c",
3: "d",
},
want: []*Label{
{NewTextValue("a"), image.Point{0, 3}},
{NewTextValue("b"), image.Point{31, 3}},
{NewTextValue("c"), image.Point{62, 3}},
{NewTextValue("d"), image.Point{94, 3}},
},
},
{
desc: "only some custom labels provided",
numPoints: 4,
graphWidth: 100,
graphZero: image.Point{0, 1},
customLabels: map[int]string{
0: "a",
3: "d",
},
want: []*Label{
{NewTextValue("a"), image.Point{0, 3}},
{NewValue(1, nonZeroDecimals), image.Point{31, 3}},
{NewValue(2, nonZeroDecimals), image.Point{62, 3}},
{NewTextValue("d"), image.Point{94, 3}},
},
},
{
desc: "not displayed if custom labels don't fit",
numPoints: 2,
graphWidth: 6,
graphZero: image.Point{0, 1},
customLabels: map[int]string{
0: "a very very long custom label",
},
want: []*Label{},
},
{
desc: "more points than pixels",
numPoints: 100,
@ -265,7 +310,7 @@ func TestXLabels(t *testing.T) {
t.Fatalf("NewXScale => unexpected error: %v", err)
}
t.Logf("scale step: %v", scale.Step.Rounded)
got, err := xLabels(scale, tc.graphZero)
got, err := xLabels(scale, tc.graphZero, tc.customLabels)
if (err != nil) != tc.wantErr {
t.Errorf("xLabels => unexpected error: %v, wantErr: %v", err, tc.wantErr)
}

View File

@ -36,6 +36,9 @@ type Value struct {
// NonZeroDecimals indicates the rounding precision used, it is provided on
// a call to newValue.
NonZeroDecimals int
// text value if this value was constructed using NewTextValue.
text string
}
// String implements fmt.Stringer.
@ -55,8 +58,20 @@ func NewValue(v float64, nonZeroDecimals int) *Value {
}
}
// NewTextValue constructs a value out of the provided text.
func NewTextValue(text string) *Value {
return &Value{
Value: math.NaN(),
Rounded: math.NaN(),
text: text,
}
}
// Text returns textual representation of the value.
func (v *Value) Text() string {
if v.text != "" {
return v.text
}
if math.Ceil(v.Rounded) == v.Rounded {
return fmt.Sprintf("%.0f", v.Rounded)
}

View File

@ -115,3 +115,12 @@ func TestText(t *testing.T) {
})
}
}
func TestNewTextValue(t *testing.T) {
const want = "foo"
v := NewTextValue(want)
got := v.Text()
if got != want {
t.Errorf("v.Text => got %q, want %q", got, want)
}
}

View File

@ -19,11 +19,12 @@ import (
"errors"
"fmt"
"image"
"log"
"sort"
"sync"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/braille"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/draw"
"github.com/mum4k/termdash/numbers"
"github.com/mum4k/termdash/terminalapi"
@ -39,6 +40,12 @@ type seriesValues struct {
min float64
// max is the largest value, zero if values is empty.
max float64
seriesCellOpts []cell.Option
// The custom labels provided on a call to Series and a bool indicating if
// the labels were provided. This allows resetting them to nil.
xLabelsSet bool
xLabels map[int]string
}
// newSeriesValues returns a new seriesValues instance.
@ -76,6 +83,9 @@ type LineChart struct {
// opts are the provided options.
opts *options
// xLabels that were provided on a call to Series.
xLabels map[int]string
}
// New returns a new line chart widget.
@ -88,10 +98,47 @@ func New(opts ...Option) *LineChart {
}
}
// SeriesOption is used to provide options to Series.
type SeriesOption interface {
// set sets the provided option.
set(*seriesValues)
}
// seriesOption implements SeriesOption.
type seriesOption func(*seriesValues)
// set implements SeriesOption.set.
func (so seriesOption) set(sv *seriesValues) {
so(sv)
}
// SeriesCellOpts sets the cell options for this series.
// Note that the braille canvas has resolution of 2x4 pixels per cell, but each
// cell can only have one set of cell options set. Meaning that where series
// share a cell, the last drawn series sets the cell options. Series are drawn
// in alphabetical order based on their name.
func SeriesCellOpts(co ...cell.Option) SeriesOption {
return seriesOption(func(opts *seriesValues) {
opts.seriesCellOpts = co
})
}
// SeriesXLabels is used to provide custom labels for the X axis.
// The argument maps the positions in the provided series to the desired label.
// The labels are only used if they fit under the axis.
// Custom labels are property of the line chart, since there is only one X axis,
// providing multiple custom labels overwrites the previous value.
func SeriesXLabels(labels map[int]string) SeriesOption {
return seriesOption(func(opts *seriesValues) {
opts.xLabelsSet = true
opts.xLabels = labels
})
}
// Series sets the values that should be displayed as the line chart with the
// provided label.
// Subsequent calls with the same label replace any previously provided values.
func (lc *LineChart) Series(label string, values []float64) error {
func (lc *LineChart) Series(label string, values []float64, opts ...SeriesOption) error {
if label == "" {
return errors.New("the label cannot be empty")
}
@ -100,6 +147,21 @@ func (lc *LineChart) Series(label string, values []float64) error {
defer lc.mu.Unlock()
series := newSeriesValues(values)
for _, opt := range opts {
opt.set(series)
}
if series.xLabelsSet {
for i, t := range series.xLabels {
if i < 0 {
return fmt.Errorf("invalid key %d -> %q provided in SeriesXLabels, keys must be positive", i, t)
}
if t == "" {
return fmt.Errorf("invalid label %d -> %q provided in SeriesXLabels, values cannot be empty", i, t)
}
}
lc.xLabels = series.xLabels
}
lc.series[label] = series
lc.yAxis = axes.NewY(series.min, series.max)
return nil
@ -116,7 +178,7 @@ func (lc *LineChart) Draw(cvs *canvas.Canvas) error {
return fmt.Errorf("lc.yAxis.Details => %v", err)
}
xd, err := axes.NewXDetails(lc.maxPoints(), yd.Start, cvs.Area())
xd, err := axes.NewXDetails(lc.maxPoints(), yd.Start, cvs.Area(), lc.xLabels)
if err != nil {
return fmt.Errorf("NewXDetails => %v", err)
}
@ -133,7 +195,7 @@ func (lc *LineChart) drawAxes(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YD
{Start: yd.Start, End: yd.End},
{Start: xd.Start, End: xd.End},
}
if err := draw.HVLines(cvs, lines); err != nil {
if err := draw.HVLines(cvs, lines, draw.HVLineCellOpts(lc.opts.axesCellOpts...)); err != nil {
return fmt.Errorf("failed to draw the axes: %v", err)
}
@ -141,13 +203,14 @@ func (lc *LineChart) drawAxes(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YD
if err := draw.Text(cvs, l.Value.Text(), l.Pos,
draw.TextMaxX(yd.Start.X),
draw.TextOverrunMode(draw.OverrunModeThreeDot),
draw.TextCellOpts(lc.opts.yLabelCellOpts...),
); err != nil {
return fmt.Errorf("failed to draw the Y labels: %v", err)
}
}
for _, l := range xd.Labels {
if err := draw.Text(cvs, l.Value.Text(), l.Pos); err != nil {
if err := draw.Text(cvs, l.Value.Text(), l.Pos, draw.TextCellOpts(lc.opts.xLabelCellOpts...)); err != nil {
return fmt.Errorf("failed to draw the X labels: %v", err)
}
}
@ -158,12 +221,19 @@ func (lc *LineChart) drawAxes(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YD
func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDetails) error {
// The area available to the graph.
graphAr := image.Rect(yd.Start.X+1, yd.Start.Y, cvs.Area().Max.X, xd.End.Y)
log.Printf("graphAr:%v", graphAr)
bc, err := braille.New(graphAr)
if err != nil {
return fmt.Errorf("braille.New => %v", err)
}
for name, sv := range lc.series {
var names []string
for name := range lc.series {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
sv := lc.series[name]
if len(sv.values) <= 1 {
continue
}
@ -189,10 +259,11 @@ func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.
return fmt.Errorf("failure for series %v[%d], yd.Scale.ValueToPixel => %v", name, i, err)
}
start := image.Point{startX, startY}
end := image.Point{endX, endY}
log.Printf("start:%v, end:%v", start, end)
if err := draw.BrailleLine(bc, image.Point{startX, startY}, image.Point{endX, endY}); err != nil {
if err := draw.BrailleLine(bc,
image.Point{startX, startY},
image.Point{endX, endY},
draw.BrailleLineCellOpts(sv.seriesCellOpts...),
); err != nil {
return fmt.Errorf("draw.BrailleLine => %v", err)
}
prev = v

View File

@ -22,6 +22,7 @@ import (
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/braille/testbraille"
"github.com/mum4k/termdash/canvas/testcanvas"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/draw"
"github.com/mum4k/termdash/draw/testdraw"
"github.com/mum4k/termdash/terminal/faketerm"
@ -39,13 +40,29 @@ func TestLineChartDraws(t *testing.T) {
wantErr bool
}{
{
desc: "write fails without name for the series",
desc: "series fails without name for the series",
canvas: image.Rect(0, 0, 3, 4),
writes: func(lc *LineChart) error {
return lc.Series("", nil)
},
wantWriteErr: true,
},
{
desc: "series fails when custom label has negative key",
canvas: image.Rect(0, 0, 3, 4),
writes: func(lc *LineChart) error {
return lc.Series("series", nil, SeriesXLabels(map[int]string{-1: "text"}))
},
wantWriteErr: true,
},
{
desc: "series fails when custom label has empty value",
canvas: image.Rect(0, 0, 3, 4),
writes: func(lc *LineChart) error {
return lc.Series("series", nil, SeriesXLabels(map[int]string{1: ""}))
},
wantWriteErr: true,
},
{
desc: "draw fails when canvas not wide enough",
canvas: image.Rect(0, 0, 2, 4),
@ -78,6 +95,66 @@ func TestLineChartDraws(t *testing.T) {
return ft
},
},
{
desc: "sets axes cell options",
canvas: image.Rect(0, 0, 3, 4),
opts: []Option{
AxesCellOpts(
cell.BgColor(cell.ColorRed),
cell.FgColor(cell.ColorGreen),
),
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
// Y and X axis.
lines := []draw.HVLine{
{Start: image.Point{1, 0}, End: image.Point{1, 2}},
{Start: image.Point{1, 2}, End: image.Point{2, 2}},
}
testdraw.MustHVLines(c, lines, draw.HVLineCellOpts(cell.BgColor(cell.ColorRed), cell.FgColor(cell.ColorGreen)))
// Zero value labels.
testdraw.MustText(c, "0", image.Point{0, 1})
testdraw.MustText(c, "0", image.Point{2, 3})
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "sets label cell options",
canvas: image.Rect(0, 0, 3, 4),
opts: []Option{
XLabelCellOpts(
cell.BgColor(cell.ColorYellow),
cell.FgColor(cell.ColorBlue),
),
YLabelCellOpts(
cell.BgColor(cell.ColorRed),
cell.FgColor(cell.ColorGreen),
),
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
// Y and X axis.
lines := []draw.HVLine{
{Start: image.Point{1, 0}, End: image.Point{1, 2}},
{Start: image.Point{1, 2}, End: image.Point{2, 2}},
}
testdraw.MustHVLines(c, lines)
// Zero value labels.
testdraw.MustText(c, "0", image.Point{0, 1}, draw.TextCellOpts(cell.BgColor(cell.ColorRed), cell.FgColor(cell.ColorGreen)))
testdraw.MustText(c, "0", image.Point{2, 3}, draw.TextCellOpts(cell.BgColor(cell.ColorYellow), cell.FgColor(cell.ColorBlue)))
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "two Y and X labels",
canvas: image.Rect(0, 0, 20, 10),
@ -111,6 +188,74 @@ func TestLineChartDraws(t *testing.T) {
return ft
},
},
{
desc: "custom X labels",
canvas: image.Rect(0, 0, 20, 10),
writes: func(lc *LineChart) error {
return lc.Series("first", []float64{0, 100}, SeriesXLabels(map[int]string{
0: "start",
1: "end",
}))
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
// Y and X axis.
lines := []draw.HVLine{
{Start: image.Point{5, 0}, End: image.Point{5, 8}},
{Start: image.Point{5, 8}, End: image.Point{19, 8}},
}
testdraw.MustHVLines(c, lines)
// Value labels.
testdraw.MustText(c, "0", image.Point{4, 7})
testdraw.MustText(c, "51.68", image.Point{0, 3})
testdraw.MustText(c, "start", image.Point{6, 9})
// Braille line.
graphAr := image.Rect(6, 0, 20, 8)
bc := testbraille.MustNew(graphAr)
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0})
testbraille.MustCopyTo(bc, c)
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "sets series cell options",
canvas: image.Rect(0, 0, 20, 10),
writes: func(lc *LineChart) error {
return lc.Series("first", []float64{0, 100}, SeriesCellOpts(cell.BgColor(cell.ColorRed), cell.FgColor(cell.ColorGreen)))
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
// Y and X axis.
lines := []draw.HVLine{
{Start: image.Point{5, 0}, End: image.Point{5, 8}},
{Start: image.Point{5, 8}, End: image.Point{19, 8}},
}
testdraw.MustHVLines(c, lines)
// Value labels.
testdraw.MustText(c, "0", image.Point{4, 7})
testdraw.MustText(c, "51.68", image.Point{0, 3})
testdraw.MustText(c, "0", image.Point{6, 9})
testdraw.MustText(c, "1", image.Point{19, 9})
// Braille line.
graphAr := image.Rect(6, 0, 20, 8)
bc := testbraille.MustNew(graphAr)
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0}, draw.BrailleLineCellOpts(cell.BgColor(cell.ColorRed), cell.FgColor(cell.ColorGreen)))
testbraille.MustCopyTo(bc, c)
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "multiple Y and X labels",
canvas: image.Rect(0, 0, 20, 11),
@ -217,13 +362,44 @@ func TestLineChartDraws(t *testing.T) {
return ft
},
},
{
desc: "draw multiple series with different cell options, last series wins where they cross",
canvas: image.Rect(0, 0, 20, 10),
writes: func(lc *LineChart) error {
if err := lc.Series("first", []float64{0, 50, 100}, SeriesCellOpts(cell.FgColor(cell.ColorRed))); err != nil {
return err
}
return lc.Series("second", []float64{100, 0}, SeriesCellOpts(cell.FgColor(cell.ColorBlue)))
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
// Sets axis colors.
// Sets label colors on Y axis.
// Sets label colors on X axis.
// Sets series color.
// Multiple series, same color.
// Multiple series, different color.
// Y and X axis.
lines := []draw.HVLine{
{Start: image.Point{5, 0}, End: image.Point{5, 8}},
{Start: image.Point{5, 8}, End: image.Point{19, 8}},
}
testdraw.MustHVLines(c, lines)
// Value labels.
testdraw.MustText(c, "0", image.Point{4, 7})
testdraw.MustText(c, "51.68", image.Point{0, 3})
testdraw.MustText(c, "0", image.Point{6, 9})
testdraw.MustText(c, "1", image.Point{12, 9})
testdraw.MustText(c, "2", image.Point{19, 9})
// Braille line.
graphAr := image.Rect(6, 0, 20, 8)
bc := testbraille.MustNew(graphAr)
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{27, 0}, draw.BrailleLineCellOpts(cell.FgColor(cell.ColorRed)))
testdraw.MustBrailleLine(bc, image.Point{0, 0}, image.Point{13, 31}, draw.BrailleLineCellOpts(cell.FgColor(cell.ColorBlue)))
testbraille.MustCopyTo(bc, c)
testcanvas.MustApply(c, ft)
return ft
},
},
}
for _, tc := range tests {

View File

@ -22,6 +22,7 @@ import (
"time"
"github.com/mum4k/termdash"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/container"
"github.com/mum4k/termdash/draw"
"github.com/mum4k/termdash/terminal/termbox"
@ -51,7 +52,18 @@ func playLineChart(ctx context.Context, lc *linechart.LineChart, delay time.Dura
case <-ticker.C:
i = (i + 1) % len(inputs)
rotated := append(inputs[i:], inputs[:i]...)
if err := lc.Series("sine", rotated); err != nil {
if err := lc.Series("first", rotated,
linechart.SeriesCellOpts(cell.FgColor(cell.ColorBlue)),
linechart.SeriesXLabels(map[int]string{
0: "zero",
}),
); err != nil {
panic(err)
}
i2 := (i + 100) % len(inputs)
rotated2 := append(inputs[i2:], inputs[:i2]...)
if err := lc.Series("second", rotated2, linechart.SeriesCellOpts(cell.FgColor(cell.ColorWhite))); err != nil {
panic(err)
}
@ -70,7 +82,11 @@ func main() {
const redrawInterval = 25 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background())
lc := linechart.New()
lc := linechart.New(
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
linechart.XLabelCellOpts(cell.FgColor(cell.ColorCyan)),
)
go playLineChart(ctx, lc, redrawInterval/3)
c := container.New(
t,

View File

@ -14,6 +14,8 @@
package linechart
import "github.com/mum4k/termdash/cell"
// options.go contains configurable options for LineChart.
// Option is used to provide options to New().
@ -24,6 +26,9 @@ type Option interface {
// options stores the provided options.
type options struct {
axesCellOpts []cell.Option
xLabelCellOpts []cell.Option
yLabelCellOpts []cell.Option
}
// newOptions returns a new options instance.
@ -43,8 +48,23 @@ func (o option) set(opts *options) {
o(opts)
}
// Foo ...
func Foo() Option {
// AxesCellOpts set the cell options for the X and Y axes.
func AxesCellOpts(co ...cell.Option) Option {
return option(func(opts *options) {
opts.axesCellOpts = co
})
}
// XLabelCellOpts set the cell options for the labels on the X axis.
func XLabelCellOpts(co ...cell.Option) Option {
return option(func(opts *options) {
opts.xLabelCellOpts = co
})
}
// YLabelCellOpts set the cell options for the labels on the Y axis.
func YLabelCellOpts(co ...cell.Option) Option {
return option(func(opts *options) {
opts.yLabelCellOpts = co
})
}