mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Merge branch 'devel' into sparkline
This commit is contained in:
commit
38c288b077
299
widgets/barchart/barchart.go
Normal file
299
widgets/barchart/barchart.go
Normal file
@ -0,0 +1,299 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package barchart implements a widget that draws multiple bars displaying
|
||||
// values and their relative ratios.
|
||||
package barchart
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"sync"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
// BarChart displays multiple bars showing relative ratios of values.
|
||||
//
|
||||
// Each bar can have a text label under it explaining the meaning of the value
|
||||
// and can display the value itself inside the bar.
|
||||
//
|
||||
// Implements widgetapi.Widget. This object is thread-safe.
|
||||
type BarChart struct {
|
||||
// values are the values provided on a call to Values(). These are the
|
||||
// individual bars that will be drawn.
|
||||
values []int
|
||||
// max is the maximum value of a bar. A bar having this value takes all the
|
||||
// vertical space.
|
||||
max int
|
||||
|
||||
// mu protects the BarChart.
|
||||
mu sync.Mutex
|
||||
|
||||
// opts are the provided options.
|
||||
opts *options
|
||||
}
|
||||
|
||||
// New returns a new BarChart.
|
||||
func New(opts ...Option) *BarChart {
|
||||
opt := newOptions()
|
||||
for _, o := range opts {
|
||||
o.set(opt)
|
||||
}
|
||||
return &BarChart{
|
||||
opts: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws the BarChart widget onto the canvas.
|
||||
// Implements widgetapi.Widget.Draw.
|
||||
func (bc *BarChart) Draw(cvs *canvas.Canvas) error {
|
||||
bc.mu.Lock()
|
||||
defer bc.mu.Unlock()
|
||||
|
||||
for i, v := range bc.values {
|
||||
r, err := bc.barRect(cvs, i, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Dy() > 0 { // Value might be so small so that the rectangle is zero.
|
||||
if err := draw.Rectangle(cvs, r,
|
||||
draw.RectCellOpts(cell.BgColor(bc.barColor(i))),
|
||||
draw.RectChar(bc.opts.barChar),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if bc.opts.showValues {
|
||||
if err := bc.drawText(cvs, i, fmt.Sprint(bc.values[i]), bc.valColor(i), insideBar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l, c := bc.label(i)
|
||||
if l != "" {
|
||||
if err := bc.drawText(cvs, i, l, c, underBar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// textLoc represents the location of the drawn text.
|
||||
type textLoc int
|
||||
|
||||
const (
|
||||
insideBar textLoc = iota
|
||||
underBar
|
||||
)
|
||||
|
||||
// drawText draws the provided text inside or under the i-th bar.
|
||||
func (bc *BarChart) drawText(cvs *canvas.Canvas, i int, text string, color cell.Color, loc textLoc) error {
|
||||
// Rectangle representing area in which the text will be aligned.
|
||||
var barCol image.Rectangle
|
||||
|
||||
r, err := bc.barRect(cvs, i, bc.max)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch loc {
|
||||
case insideBar:
|
||||
// Align the text within the bar itself.
|
||||
barCol = r
|
||||
case underBar:
|
||||
// Align the text within the entire column where the bar is, this
|
||||
// includes the space for any label under the bar.
|
||||
barCol = image.Rect(r.Min.X, cvs.Area().Min.Y, r.Max.X, cvs.Area().Max.Y)
|
||||
}
|
||||
|
||||
start, err := align.Text(barCol, text, align.HorizontalCenter, align.VerticalBottom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return draw.Text(cvs, text, start,
|
||||
draw.TextCellOpts(cell.FgColor(color)),
|
||||
draw.TextMaxX(barCol.Max.X),
|
||||
draw.TextOverrunMode(draw.OverrunModeThreeDot),
|
||||
)
|
||||
}
|
||||
|
||||
// barWidth determines the width of a single bar based on options and the canvas.
|
||||
func (bc *BarChart) barWidth(cvs *canvas.Canvas) int {
|
||||
if len(bc.values) == 0 {
|
||||
return 0 // No width when we have no values.
|
||||
}
|
||||
|
||||
if bc.opts.barWidth >= 1 {
|
||||
// Prefer width set via the options if it is positive.
|
||||
return bc.opts.barWidth
|
||||
}
|
||||
|
||||
gaps := len(bc.values) - 1
|
||||
gapW := gaps * bc.opts.barGap
|
||||
rem := cvs.Area().Dx() - gapW
|
||||
return rem / len(bc.values)
|
||||
}
|
||||
|
||||
// barHeight determines the height of the i-th bar based on the value it is displaying.
|
||||
func (bc *BarChart) barHeight(cvs *canvas.Canvas, i, value int) int {
|
||||
available := cvs.Area().Dy()
|
||||
if len(bc.opts.labels) > 0 {
|
||||
// One line for the bar labels.
|
||||
available--
|
||||
}
|
||||
|
||||
ratio := float32(value) / float32(bc.max)
|
||||
return int(float32(available) * ratio)
|
||||
}
|
||||
|
||||
// barRect returns a rectangle that represents the i-th bar on the canvas that
|
||||
// displays the specified value.
|
||||
func (bc *BarChart) barRect(cvs *canvas.Canvas, i, value int) (image.Rectangle, error) {
|
||||
bw := bc.barWidth(cvs)
|
||||
minX := bw * i
|
||||
if i > 0 {
|
||||
minX += bc.opts.barGap * i
|
||||
}
|
||||
maxX := minX + bw
|
||||
|
||||
bh := bc.barHeight(cvs, i, value)
|
||||
maxY := cvs.Area().Max.Y
|
||||
if len(bc.opts.labels) > 0 {
|
||||
// One line for the bar labels.
|
||||
maxY--
|
||||
}
|
||||
minY := maxY - bh
|
||||
return image.Rect(minX, minY, maxX, maxY), nil
|
||||
}
|
||||
|
||||
// barColor safely determines the color for the i-th bar.
|
||||
// Colors are optional and don't have to be specified for all the bars.
|
||||
func (bc *BarChart) barColor(i int) cell.Color {
|
||||
if len(bc.opts.barColors) > i {
|
||||
return bc.opts.barColors[i]
|
||||
}
|
||||
return DefaultBarColor
|
||||
}
|
||||
|
||||
// valColor safely determines the color for the i-th value.
|
||||
// Colors are optional and don't have to be specified for all the values.
|
||||
func (bc *BarChart) valColor(i int) cell.Color {
|
||||
if len(bc.opts.valueColors) > i {
|
||||
return bc.opts.valueColors[i]
|
||||
}
|
||||
return DefaultValueColor
|
||||
}
|
||||
|
||||
// label safely determines the label and its color for the i-th bar.
|
||||
// Labels are optional and don't have to be specified for all the bars.
|
||||
func (bc *BarChart) label(i int) (string, cell.Color) {
|
||||
var label string
|
||||
if len(bc.opts.labels) > i {
|
||||
label = bc.opts.labels[i]
|
||||
}
|
||||
|
||||
if len(bc.opts.labelColors) > i {
|
||||
return label, bc.opts.labelColors[i]
|
||||
}
|
||||
return label, DefaultLabelColor
|
||||
}
|
||||
|
||||
// Values sets the values to be displayed by the BarChart.
|
||||
// Each value ends up in its own bar. The values must not be negative and must
|
||||
// be less or equal the maximum value. A bar displaying the maximum value is a
|
||||
// full bar, taking all available vertical space.
|
||||
// Provided options override values set when New() was called.
|
||||
func (bc *BarChart) Values(values []int, max int, opts ...Option) error {
|
||||
bc.mu.Lock()
|
||||
defer bc.mu.Unlock()
|
||||
|
||||
if err := validateValues(values, max); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.set(bc.opts)
|
||||
}
|
||||
bc.values = values
|
||||
bc.max = max
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keyboard input isn't supported on the BarChart widget.
|
||||
func (*BarChart) Keyboard(k *terminalapi.Keyboard) error {
|
||||
return errors.New("the BarChart widget doesn't support keyboard events")
|
||||
}
|
||||
|
||||
// Mouse input isn't supported on the BarChart widget.
|
||||
func (*BarChart) Mouse(m *terminalapi.Mouse) error {
|
||||
return errors.New("the BarChart widget doesn't support mouse events")
|
||||
}
|
||||
|
||||
// Options implements widgetapi.Widget.Options.
|
||||
func (bc *BarChart) Options() widgetapi.Options {
|
||||
bc.mu.Lock()
|
||||
defer bc.mu.Unlock()
|
||||
return widgetapi.Options{
|
||||
MinimumSize: bc.minSize(),
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
}
|
||||
}
|
||||
|
||||
// minSize determines the minimum required size of the canvas.
|
||||
func (bc *BarChart) minSize() image.Point {
|
||||
bars := len(bc.values)
|
||||
if bars == 0 {
|
||||
return image.Point{1, 1}
|
||||
}
|
||||
|
||||
minHeight := 1 // At least one character vertically to display the bar.
|
||||
if len(bc.opts.labels) > 0 {
|
||||
minHeight++ // One line for the labels.
|
||||
}
|
||||
|
||||
var minBarWidth int
|
||||
if bc.opts.barWidth < 1 {
|
||||
minBarWidth = 1 // At least one char for the bar itself.
|
||||
} else {
|
||||
minBarWidth = bc.opts.barWidth
|
||||
}
|
||||
minWidth := bars*minBarWidth + (bars-1)*bc.opts.barGap
|
||||
return image.Point{minWidth, minHeight}
|
||||
}
|
||||
|
||||
// validateValues validates the provided values and maximum.
|
||||
func validateValues(values []int, max int) error {
|
||||
if max < 1 {
|
||||
return fmt.Errorf("invalid maximum value %d, must be at least 1", max)
|
||||
}
|
||||
|
||||
for i, v := range values {
|
||||
if v < 0 || v > max {
|
||||
return fmt.Errorf("invalid values[%d]: %d, each value must be 0 <= value <= max", i, v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
676
widgets/barchart/barchart_test.go
Normal file
676
widgets/barchart/barchart_test.go
Normal file
@ -0,0 +1,676 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package barchart
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/mum4k/termdash/canvas"
|
||||
"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"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
)
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
bc *BarChart
|
||||
update func(*BarChart) error // update gets called before drawing of the widget.
|
||||
canvas image.Rectangle
|
||||
want func(size image.Point) *faketerm.Terminal
|
||||
wantUpdateErr bool // whether to expect an error on a call to the update function
|
||||
wantDrawErr bool
|
||||
}{
|
||||
{
|
||||
desc: "draws empty for no values",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return nil
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fails for zero max",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, 2, 5, 10}, 0)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails for negative max",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, 2, 5, 10}, -1)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails when negative value",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, -2, 5, 10}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails for value larger than max",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, 2, 5, 11}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 3, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
return faketerm.MustNew(size)
|
||||
},
|
||||
wantUpdateErr: true,
|
||||
},
|
||||
{
|
||||
desc: "displays bars",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, 2, 5, 10}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 7, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(4, 5, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(6, 0, 7, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays bars with labels",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
Labels([]string{
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
}),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2, 5, 10}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 7, 11),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(4, 5, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(6, 0, 7, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
|
||||
// Labels.
|
||||
testdraw.MustText(c, "1", image.Point{0, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "2", image.Point{2, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "3", image.Point{4, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "trims too long labels",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
Labels([]string{
|
||||
"1",
|
||||
"22",
|
||||
"3",
|
||||
}),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2, 5, 10}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 7, 11),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(4, 5, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(6, 0, 7, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
|
||||
// Labels.
|
||||
testdraw.MustText(c, "1", image.Point{0, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "…", image.Point{2, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "3", image.Point{4, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "displays bars with labels and values",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
Labels([]string{
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
}),
|
||||
ShowValues(),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2, 5, 10}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 7, 11),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(4, 5, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(6, 0, 7, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
// Labels.
|
||||
testdraw.MustText(c, "1", image.Point{0, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "2", image.Point{2, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
testdraw.MustText(c, "3", image.Point{4, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultLabelColor),
|
||||
))
|
||||
// Values.
|
||||
testdraw.MustText(c, "1", image.Point{0, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultValueColor),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
testdraw.MustText(c, "2", image.Point{2, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultValueColor),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
testdraw.MustText(c, "5", image.Point{4, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultValueColor),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
testdraw.MustText(c, "…", image.Point{6, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultValueColor),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bars take as much width as available",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 2, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(3, 8, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects set bar width",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
BarWidth(1),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "options can be set on a call to Values",
|
||||
bc: New(),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2}, 10, Char('o'), BarWidth(1))
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects set bar gap",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
BarGap(2),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(3, 8, 4, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects both width and gap",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
BarGap(2),
|
||||
BarWidth(2),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{5, 3}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 6, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 5, 2, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustRectangle(c, image.Rect(4, 7, 6, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects bar and label colors",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
BarColors([]cell.Color{
|
||||
cell.ColorBlue,
|
||||
cell.ColorYellow,
|
||||
}),
|
||||
LabelColors([]cell.Color{
|
||||
cell.ColorCyan,
|
||||
cell.ColorMagenta,
|
||||
}),
|
||||
Labels([]string{
|
||||
"1",
|
||||
"2",
|
||||
}),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{1, 2, 3}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 11),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(0, 9, 1, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(cell.ColorBlue)),
|
||||
)
|
||||
testdraw.MustText(c, "1", image.Point{0, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(cell.ColorCyan),
|
||||
))
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(cell.ColorYellow)),
|
||||
)
|
||||
testdraw.MustText(c, "2", image.Point{2, 10}, draw.TextCellOpts(
|
||||
cell.FgColor(cell.ColorMagenta),
|
||||
))
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(4, 7, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "respects value colors",
|
||||
bc: New(
|
||||
Char('o'),
|
||||
ValueColors([]cell.Color{
|
||||
cell.ColorBlue,
|
||||
cell.ColorBlack,
|
||||
}),
|
||||
ShowValues(),
|
||||
),
|
||||
update: func(bc *BarChart) error {
|
||||
return bc.Values([]int{0, 2, 3}, 10)
|
||||
},
|
||||
canvas: image.Rect(0, 0, 5, 10),
|
||||
want: func(size image.Point) *faketerm.Terminal {
|
||||
ft := faketerm.MustNew(size)
|
||||
c := testcanvas.MustNew(ft.Area())
|
||||
|
||||
testdraw.MustText(c, "0", image.Point{0, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(cell.ColorBlue),
|
||||
))
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(2, 8, 3, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustText(c, "2", image.Point{2, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(cell.ColorBlack),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
|
||||
testdraw.MustRectangle(c, image.Rect(4, 7, 5, 10),
|
||||
draw.RectChar('o'),
|
||||
draw.RectCellOpts(cell.BgColor(DefaultBarColor)),
|
||||
)
|
||||
testdraw.MustText(c, "3", image.Point{4, 9}, draw.TextCellOpts(
|
||||
cell.FgColor(DefaultValueColor),
|
||||
cell.BgColor(DefaultBarColor),
|
||||
))
|
||||
testcanvas.MustApply(c, ft)
|
||||
return ft
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c, err := canvas.New(tc.canvas)
|
||||
if err != nil {
|
||||
t.Fatalf("canvas.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = tc.update(tc.bc)
|
||||
if (err != nil) != tc.wantUpdateErr {
|
||||
t.Errorf("update => unexpected error: %v, wantUpdateErr: %v", err, tc.wantUpdateErr)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = tc.bc.Draw(c)
|
||||
if (err != nil) != tc.wantDrawErr {
|
||||
t.Errorf("Draw => unexpected error: %v, wantDrawErr: %v", err, tc.wantDrawErr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
got, err := faketerm.New(c.Size())
|
||||
if err != nil {
|
||||
t.Fatalf("faketerm.New => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := c.Apply(got); err != nil {
|
||||
t.Fatalf("Apply => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if diff := faketerm.Diff(tc.want(c.Size()), got); diff != "" {
|
||||
t.Errorf("Draw => %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
create func() (*BarChart, error)
|
||||
want widgetapi.Options
|
||||
}{
|
||||
{
|
||||
desc: "minimum size for no bars",
|
||||
create: func() (*BarChart, error) {
|
||||
return New(), nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{1, 1},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "minimum size for no bars, but have labels",
|
||||
create: func() (*BarChart, error) {
|
||||
return New(
|
||||
Labels([]string{"foo"}),
|
||||
), nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{1, 1},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "minimum size for one bar, default width, gap and no labels",
|
||||
create: func() (*BarChart, error) {
|
||||
bc := New()
|
||||
if err := bc.Values([]int{1}, 3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bc, nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{1, 1},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "minimum size for two bars, default width, gap and no labels",
|
||||
create: func() (*BarChart, error) {
|
||||
bc := New()
|
||||
if err := bc.Values([]int{1, 2}, 3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bc, nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{3, 1},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "minimum size for two bars, custom width, gap and no labels",
|
||||
create: func() (*BarChart, error) {
|
||||
bc := New(
|
||||
BarWidth(3),
|
||||
BarGap(2),
|
||||
)
|
||||
if err := bc.Values([]int{1, 2}, 3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bc, nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{8, 1},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "minimum size for two bars, custom width, gap and labels",
|
||||
create: func() (*BarChart, error) {
|
||||
bc := New(
|
||||
BarWidth(3),
|
||||
BarGap(2),
|
||||
)
|
||||
if err := bc.Values([]int{1, 2}, 3, Labels([]string{"foo", "bar"})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bc, nil
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{8, 2},
|
||||
WantKeyboard: false,
|
||||
WantMouse: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
bc, err := tc.create()
|
||||
if err != nil {
|
||||
t.Fatalf("create => unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got := bc.Options()
|
||||
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||
t.Errorf("Options => unexpected diff (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
112
widgets/barchart/barchartdemo/barchartdemo.go
Normal file
112
widgets/barchart/barchartdemo/barchartdemo.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Binary barchartdemo displays a couple of BarChart widgets.
|
||||
// Exist when 'q' is pressed.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"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"
|
||||
"github.com/mum4k/termdash/terminalapi"
|
||||
"github.com/mum4k/termdash/widgets/barchart"
|
||||
)
|
||||
|
||||
// playBarChart continuously changes the displayed values on the bar chart once every delay.
|
||||
// Exits when the context expires.
|
||||
func playBarChart(ctx context.Context, bc *barchart.BarChart, delay time.Duration) {
|
||||
const (
|
||||
bars = 6
|
||||
max = 100
|
||||
)
|
||||
|
||||
values := make([]int, 6)
|
||||
|
||||
ticker := time.NewTicker(delay)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
for i := range values {
|
||||
values[i] = int(rand.Int31n(max + 1))
|
||||
}
|
||||
|
||||
if err := bc.Values(values, max); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t, err := termbox.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
bc := barchart.New(
|
||||
barchart.BarColors([]cell.Color{
|
||||
cell.ColorBlue,
|
||||
cell.ColorRed,
|
||||
cell.ColorYellow,
|
||||
cell.ColorBlue,
|
||||
cell.ColorGreen,
|
||||
cell.ColorRed,
|
||||
}),
|
||||
barchart.ValueColors([]cell.Color{
|
||||
cell.ColorRed,
|
||||
cell.ColorYellow,
|
||||
cell.ColorBlue,
|
||||
cell.ColorGreen,
|
||||
cell.ColorRed,
|
||||
cell.ColorBlue,
|
||||
}),
|
||||
barchart.ShowValues(),
|
||||
barchart.Labels([]string{
|
||||
"CPU1",
|
||||
"",
|
||||
"CPU3",
|
||||
}),
|
||||
)
|
||||
go playBarChart(ctx, bc, 1*time.Second)
|
||||
|
||||
c := container.New(
|
||||
t,
|
||||
container.Border(draw.LineStyleLight),
|
||||
container.BorderTitle("PRESS Q TO QUIT"),
|
||||
container.PlaceWidget(bc),
|
||||
)
|
||||
|
||||
quitter := func(k *terminalapi.Keyboard) {
|
||||
if k.Key == 'q' || k.Key == 'Q' {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
147
widgets/barchart/options.go
Normal file
147
widgets/barchart/options.go
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package barchart
|
||||
|
||||
// options.go contains configurable options for BarChart.
|
||||
|
||||
import (
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/draw"
|
||||
)
|
||||
|
||||
// Option is used to provide options.
|
||||
type Option interface {
|
||||
// set sets the provided option.
|
||||
set(*options)
|
||||
}
|
||||
|
||||
// option implements Option.
|
||||
type option func(*options)
|
||||
|
||||
// set implements Option.set.
|
||||
func (o option) set(opts *options) {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// options holds the provided options.
|
||||
type options struct {
|
||||
barChar rune
|
||||
barWidth int
|
||||
barGap int
|
||||
showValues bool
|
||||
barColors []cell.Color
|
||||
labelColors []cell.Color
|
||||
valueColors []cell.Color
|
||||
labels []string
|
||||
}
|
||||
|
||||
// newOptions returns options with the default values set.
|
||||
func newOptions() *options {
|
||||
return &options{
|
||||
barChar: DefaultChar,
|
||||
barGap: DefaultBarGap,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultChar is the default value for the Char option.
|
||||
const DefaultChar = draw.DefaultRectChar
|
||||
|
||||
// Char sets the rune that is used when drawing the rectangle representing the
|
||||
// bars.
|
||||
func Char(ch rune) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.barChar = ch
|
||||
})
|
||||
}
|
||||
|
||||
// BarWidth sets the width of the bars. If not set, the bars use all the space
|
||||
// available to the widget.
|
||||
func BarWidth(width int) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.barWidth = width
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultBarGap is the default value for the BarGap option.
|
||||
const DefaultBarGap = 1
|
||||
|
||||
// BarGap sets the width of the space between the bars.
|
||||
// Defaults to DefaultBarGap.
|
||||
func BarGap(width int) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.barGap = width
|
||||
})
|
||||
}
|
||||
|
||||
// ShowValues tells the bar chart to display the actual values inside each of the bars.
|
||||
func ShowValues() Option {
|
||||
return option(func(opts *options) {
|
||||
opts.showValues = true
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultBarColor is the default color of a bar, unless specified otherwise
|
||||
// via the BarColors option.
|
||||
const DefaultBarColor = cell.ColorRed
|
||||
|
||||
// BarColors sets the colors of each of the bars.
|
||||
// Bars are created on a call to Values(), each value ends up in its own Bar.
|
||||
// The first supplied color applies to the bar displaying the first value.
|
||||
// Any bars that don't have a color specified use the DefaultBarColor.
|
||||
func BarColors(colors []cell.Color) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.barColors = colors
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultLabelColor is the default color of a bar label, unless specified
|
||||
// otherwise via the LabelColors option.
|
||||
const DefaultLabelColor = cell.ColorGreen
|
||||
|
||||
// LabelColors sets the colors of each of the labels under the bars.
|
||||
// Bars are created on a call to Values(), each value ends up in its own Bar.
|
||||
// The first supplied color applies to the label of the bar displaying the
|
||||
// first value. Any labels that don't have a color specified use the
|
||||
// DefaultLabelColor.
|
||||
func LabelColors(colors []cell.Color) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.labelColors = colors
|
||||
})
|
||||
}
|
||||
|
||||
// Labels sets the labels displayed under each bar,
|
||||
// Bars are created on a call to Values(), each value ends up in its own Bar.
|
||||
// The first supplied label applies to the bar displaying the first value.
|
||||
// If not specified, the corresponding bar (or all the bars) don't have a
|
||||
// label.
|
||||
func Labels(labels []string) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.labels = labels
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultValueColor is the default color of a bar value, unless specified
|
||||
// otherwise via the ValueColors option.
|
||||
const DefaultValueColor = cell.ColorYellow
|
||||
|
||||
// ValueColors sets the colors of each of the values in the bars. Bars are
|
||||
// created on a call to Values(), each value ends up in its own Bar. The first
|
||||
// supplied color applies to the bar displaying the first value. Any values
|
||||
// that don't have a color specified use the DefaultValueColor.
|
||||
func ValueColors(colors []cell.Color) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.valueColors = colors
|
||||
})
|
||||
}
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package text
|
||||
|
||||
import (
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package text
|
||||
|
||||
import (
|
||||
|
Loading…
x
Reference in New Issue
Block a user