Skip to content

Commit

Permalink
Merge pull request #125 from mum4k/custom-scale
Browse files Browse the repository at this point in the history
Allow users to provide custom Y axis scale for the LineChart.
  • Loading branch information
mum4k committed Feb 15, 2019
2 parents 6969da6 + 18ed0f1 commit d7c1cf5
Show file tree
Hide file tree
Showing 6 changed files with 414 additions and 17 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- A function that draws text in vertically.
- The LineChart widget can display X axis labels in vertical orientation.
- The LineChart widget allows the user to specify a custom scale for the Y
axis.

### Changed

- Termbox is now initialized in 256 color mode by default.
- Generalized mouse button FSM for use in widgets that need to track mouse
button clicks.
- The constructor of the LineChart widget now also returns an error so that it
can validate its options. This is a breaking change on the LineChart API.

### Fixed

- The LineChart widget now correctly determines the Y axis scale when multiple
series are provided.

## [0.6.1] - 12-Feb-2019

Expand Down
30 changes: 22 additions & 8 deletions termdashdemo/termdashdemo.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
),
}

heartLC, err := newHeartbeat(ctx)
if err != nil {
return nil, err
}
gaugeAndHeartbeat := []container.Option{
container.SplitHorizontal(
container.Top(
Expand All @@ -88,7 +92,7 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
container.Bottom(
container.Border(draw.LineStyleLight),
container.BorderTitle("A LineChart"),
container.PlaceWidget(newHeartbeat(ctx)),
container.PlaceWidget(heartLC),
),
container.SplitPercent(20),
),
Expand All @@ -107,6 +111,10 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
return nil, err
}

sineLC, err := newSines(ctx)
if err != nil {
return nil, err
}
rightSide := []container.Option{
container.SplitHorizontal(
container.Top(
Expand All @@ -127,7 +135,7 @@ func layout(ctx context.Context, t terminalapi.Terminal) (*container.Container,
container.Border(draw.LineStyleLight),
container.BorderTitle("Multiple series"),
container.BorderTitleAlignRight(),
container.PlaceWidget(newSines(ctx)),
container.PlaceWidget(sineLC),
),
container.SplitPercent(30),
),
Expand Down Expand Up @@ -317,18 +325,21 @@ func newDonut(ctx context.Context) (*donut.Donut, error) {
}

// newHeartbeat returns a line chart that displays a heartbeat-like progression.
func newHeartbeat(ctx context.Context) *linechart.LineChart {
func newHeartbeat(ctx context.Context) (*linechart.LineChart, error) {
var inputs []float64
for i := 0; i < 100; i++ {
v := math.Pow(math.Sin(float64(i)), 63) * math.Sin(float64(i)+1.5) * 8
inputs = append(inputs, v)
}

lc := linechart.New(
lc, err := linechart.New(
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
linechart.XLabelCellOpts(cell.FgColor(cell.ColorGreen)),
)
if err != nil {
return nil, err
}
step := 0
go periodic(ctx, redrawInterval/3, func() error {
step = (step + 1) % len(inputs)
Expand All @@ -339,7 +350,7 @@ func newHeartbeat(ctx context.Context) *linechart.LineChart {
}),
)
})
return lc
return lc, nil
}

// newBarChart returns a BarcChart that displays random values on multiple bars.
Expand Down Expand Up @@ -380,18 +391,21 @@ func newBarChart(ctx context.Context) *barchart.BarChart {
}

// newSines returns a line chart that displays multiple sine series.
func newSines(ctx context.Context) *linechart.LineChart {
func newSines(ctx context.Context) (*linechart.LineChart, error) {
var inputs []float64
for i := 0; i < 200; i++ {
v := math.Sin(float64(i) / 100 * math.Pi)
inputs = append(inputs, v)
}

lc := linechart.New(
lc, err := linechart.New(
linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)),
linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)),
linechart.XLabelCellOpts(cell.FgColor(cell.ColorGreen)),
)
if err != nil {
return nil, err
}
step1 := 0
go periodic(ctx, redrawInterval/3, func() error {
step1 = (step1 + 1) % len(inputs)
Expand All @@ -404,7 +418,7 @@ func newSines(ctx context.Context) *linechart.LineChart {
step2 := (step1 + 100) % len(inputs)
return lc.Series("second", rotateFloats(inputs, step2), linechart.SeriesCellOpts(cell.FgColor(cell.ColorWhite)))
})
return lc
return lc, nil
}

// rotateFloats returns a new slice with inputs rotated by step.
Expand Down
30 changes: 27 additions & 3 deletions widgets/linechart/linechart.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,16 @@ type LineChart struct {
}

// New returns a new line chart widget.
func New(opts ...Option) *LineChart {
func New(opts ...Option) (*LineChart, error) {
opt := newOptions(opts...)
if err := opt.validate(); err != nil {
return nil, err
}
return &LineChart{
series: map[string]*seriesValues{},
yAxis: axes.NewY(0, 0),
opts: opt,
}
}, nil
}

// SeriesOption is used to provide options to Series.
Expand Down Expand Up @@ -136,6 +139,27 @@ func SeriesXLabels(labels map[int]string) SeriesOption {
})
}

// yMinMax determines the min and max values for the Y axis.
func (lc *LineChart) yMinMax() (float64, float64) {
var (
minimums []float64
maximums []float64
)
for _, sv := range lc.series {
minimums = append(minimums, sv.min)
maximums = append(maximums, sv.max)
}

if lc.opts.yAxisCustomScale != nil {
minimums = append(minimums, lc.opts.yAxisCustomScale.min)
maximums = append(maximums, lc.opts.yAxisCustomScale.max)
}

min, _ := numbers.MinMax(minimums)
_, max := numbers.MinMax(maximums)
return min, max
}

// 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.
Expand Down Expand Up @@ -164,7 +188,7 @@ func (lc *LineChart) Series(label string, values []float64, opts ...SeriesOption
}

lc.series[label] = series
lc.yAxis = axes.NewY(series.min, series.max)
lc.yAxis = axes.NewY(lc.yMinMax())
return nil
}

Expand Down

0 comments on commit d7c1cf5

Please sign in to comment.