1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-27 13:48:49 +08:00
termdash/widgets/barchart/barchart.go
2018-06-18 21:36:29 +01:00

122 lines
3.0 KiB
Go

// Package barchart implements a widget that displays multiple bars displaying
// values and their relative ratios.
package barchart
import (
"fmt"
"image"
"sync"
"github.com/mum4k/termdash/canvas"
"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
// it displays 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()
return nil
}
// 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
}
// 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
}