mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-28 13:48:51 +08:00
Merge branch 'devel' into text-input
This commit is contained in:
commit
afe70553e5
@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
executed.
|
executed.
|
||||||
- The SegmentDisplay widget now has a method that returns the observed character
|
- The SegmentDisplay widget now has a method that returns the observed character
|
||||||
capacity the last time Draw was called.
|
capacity the last time Draw was called.
|
||||||
|
- The grid.Builder API now allows users to specify options for intermediate
|
||||||
|
containers, i.e. containers that don't have widgets, but represent rows and
|
||||||
|
columns.
|
||||||
|
- Line chart widget now allows `math.NaN` values to represent "no value" (values
|
||||||
|
that will not be rendered) in the values slice.
|
||||||
|
|
||||||
#### Breaking API changes
|
#### Breaking API changes
|
||||||
|
|
||||||
@ -119,7 +124,6 @@ identifiers shouldn't be used externally.
|
|||||||
- The Text widget now has a Write option that atomically replaces the entire
|
- The Text widget now has a Write option that atomically replaces the entire
|
||||||
text content.
|
text content.
|
||||||
|
|
||||||
|
|
||||||
#### Improvements to the infrastructure
|
#### Improvements to the infrastructure
|
||||||
|
|
||||||
- A function that draws text vertically.
|
- A function that draws text vertically.
|
||||||
@ -248,7 +252,7 @@ identifiers shouldn't be used externally.
|
|||||||
- The Gauge widget.
|
- The Gauge widget.
|
||||||
- The Text widget.
|
- The Text widget.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/mum4k/termdash/compare/v0.8.0...devel
|
[unreleased]: https://github.com/mum4k/termdash/compare/v0.8.0...devel
|
||||||
[0.8.0]: https://github.com/mum4k/termdash/compare/v0.7.2...v0.8.0
|
[0.8.0]: https://github.com/mum4k/termdash/compare/v0.7.2...v0.8.0
|
||||||
[0.7.2]: https://github.com/mum4k/termdash/compare/v0.7.1...v0.7.2
|
[0.7.2]: https://github.com/mum4k/termdash/compare/v0.7.1...v0.7.2
|
||||||
[0.7.1]: https://github.com/mum4k/termdash/compare/v0.7.0...v0.7.1
|
[0.7.1]: https://github.com/mum4k/termdash/compare/v0.7.0...v0.7.1
|
||||||
|
@ -109,18 +109,19 @@ func build(elems []Element, parentHeightPerc, parentWidthPerc int) []container.O
|
|||||||
|
|
||||||
switch e := elem.(type) {
|
switch e := elem.(type) {
|
||||||
case *row:
|
case *row:
|
||||||
|
|
||||||
if len(elems) > 0 {
|
if len(elems) > 0 {
|
||||||
perc := innerPerc(e.heightPerc, parentHeightPerc)
|
perc := innerPerc(e.heightPerc, parentHeightPerc)
|
||||||
childHeightPerc := parentHeightPerc - e.heightPerc
|
childHeightPerc := parentHeightPerc - e.heightPerc
|
||||||
return []container.Option{
|
return []container.Option{
|
||||||
container.SplitHorizontal(
|
container.SplitHorizontal(
|
||||||
container.Top(build(e.subElem, 100, parentWidthPerc)...),
|
container.Top(append(e.cOpts, build(e.subElem, 100, parentWidthPerc)...)...),
|
||||||
container.Bottom(build(elems, childHeightPerc, parentWidthPerc)...),
|
container.Bottom(build(elems, childHeightPerc, parentWidthPerc)...),
|
||||||
container.SplitPercent(perc),
|
container.SplitPercent(perc),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return build(e.subElem, 100, parentWidthPerc)
|
return append(e.cOpts, build(e.subElem, 100, parentWidthPerc)...)
|
||||||
|
|
||||||
case *col:
|
case *col:
|
||||||
if len(elems) > 0 {
|
if len(elems) > 0 {
|
||||||
@ -128,13 +129,13 @@ func build(elems []Element, parentHeightPerc, parentWidthPerc int) []container.O
|
|||||||
childWidthPerc := parentWidthPerc - e.widthPerc
|
childWidthPerc := parentWidthPerc - e.widthPerc
|
||||||
return []container.Option{
|
return []container.Option{
|
||||||
container.SplitVertical(
|
container.SplitVertical(
|
||||||
container.Left(build(e.subElem, parentHeightPerc, 100)...),
|
container.Left(append(e.cOpts, build(e.subElem, parentHeightPerc, 100)...)...),
|
||||||
container.Right(build(elems, parentHeightPerc, childWidthPerc)...),
|
container.Right(build(elems, parentHeightPerc, childWidthPerc)...),
|
||||||
container.SplitPercent(perc),
|
container.SplitPercent(perc),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return build(e.subElem, parentHeightPerc, 100)
|
return append(e.cOpts, build(e.subElem, parentHeightPerc, 100)...)
|
||||||
|
|
||||||
case *widget:
|
case *widget:
|
||||||
opts := e.cOpts
|
opts := e.cOpts
|
||||||
@ -186,6 +187,9 @@ type row struct {
|
|||||||
|
|
||||||
// subElem are the sub Rows or Columns or a single widget.
|
// subElem are the sub Rows or Columns or a single widget.
|
||||||
subElem []Element
|
subElem []Element
|
||||||
|
|
||||||
|
// cOpts are the options for the row's container.
|
||||||
|
cOpts []container.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
// isElement implements Element.isElement.
|
// isElement implements Element.isElement.
|
||||||
@ -204,6 +208,9 @@ type col struct {
|
|||||||
|
|
||||||
// subElem are the sub Rows or Columns or a single widget.
|
// subElem are the sub Rows or Columns or a single widget.
|
||||||
subElem []Element
|
subElem []Element
|
||||||
|
|
||||||
|
// cOpts are the options for the column's container.
|
||||||
|
cOpts []container.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
// isElement implements Element.isElement.
|
// isElement implements Element.isElement.
|
||||||
@ -244,6 +251,16 @@ func RowHeightPerc(heightPerc int, subElements ...Element) Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RowHeightPercWithOpts is like RowHeightPerc, but also allows to apply
|
||||||
|
// additional options to the container that represents the row.
|
||||||
|
func RowHeightPercWithOpts(heightPerc int, cOpts []container.Option, subElements ...Element) Element {
|
||||||
|
return &row{
|
||||||
|
heightPerc: heightPerc,
|
||||||
|
subElem: subElements,
|
||||||
|
cOpts: cOpts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ColWidthPerc creates a column of the specified width.
|
// ColWidthPerc creates a column of the specified width.
|
||||||
// The width is supplied as width percentage of the parent element.
|
// The width is supplied as width percentage of the parent element.
|
||||||
// The sum of all widths at the same level cannot be larger than 100%. If it
|
// The sum of all widths at the same level cannot be larger than 100%. If it
|
||||||
@ -257,6 +274,16 @@ func ColWidthPerc(widthPerc int, subElements ...Element) Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ColWidthPercWithOpts is like ColWidthPerc, but also allows to apply
|
||||||
|
// additional options to the container that represents the column.
|
||||||
|
func ColWidthPercWithOpts(widthPerc int, cOpts []container.Option, subElements ...Element) Element {
|
||||||
|
return &col{
|
||||||
|
widthPerc: widthPerc,
|
||||||
|
subElem: subElements,
|
||||||
|
cOpts: cOpts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Widget adds a widget into the Row or Column.
|
// Widget adds a widget into the Row or Column.
|
||||||
// The options will be applied to the container that directly holds this
|
// The options will be applied to the container that directly holds this
|
||||||
// widget.
|
// widget.
|
||||||
|
@ -389,6 +389,45 @@ func TestBuilder(t *testing.T) {
|
|||||||
return ft
|
return ft
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "two equal rows with options",
|
||||||
|
termSize: image.Point{10, 10},
|
||||||
|
builder: func() *Builder {
|
||||||
|
b := New()
|
||||||
|
b.Add(RowHeightPercWithOpts(
|
||||||
|
50,
|
||||||
|
[]container.Option{
|
||||||
|
container.Border(linestyle.Double),
|
||||||
|
},
|
||||||
|
Widget(mirror()),
|
||||||
|
))
|
||||||
|
b.Add(RowHeightPercWithOpts(
|
||||||
|
50,
|
||||||
|
[]container.Option{
|
||||||
|
container.Border(linestyle.Double),
|
||||||
|
},
|
||||||
|
Widget(mirror()),
|
||||||
|
))
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
|
||||||
|
top, bot := mustHSplit(ft.Area(), 50)
|
||||||
|
topCvs := testcanvas.MustNew(top)
|
||||||
|
botCvs := testcanvas.MustNew(bot)
|
||||||
|
testdraw.MustBorder(topCvs, topCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||||
|
testdraw.MustBorder(botCvs, botCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||||
|
testcanvas.MustApply(topCvs, ft)
|
||||||
|
testcanvas.MustApply(botCvs, ft)
|
||||||
|
|
||||||
|
topWidget := testcanvas.MustNew(area.ExcludeBorder(top))
|
||||||
|
botWidget := testcanvas.MustNew(area.ExcludeBorder(bot))
|
||||||
|
fakewidget.MustDraw(ft, topWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||||
|
fakewidget.MustDraw(ft, botWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "two unequal rows",
|
desc: "two unequal rows",
|
||||||
termSize: image.Point{10, 10},
|
termSize: image.Point{10, 10},
|
||||||
@ -406,6 +445,45 @@ func TestBuilder(t *testing.T) {
|
|||||||
return ft
|
return ft
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "two equal columns with options",
|
||||||
|
termSize: image.Point{20, 10},
|
||||||
|
builder: func() *Builder {
|
||||||
|
b := New()
|
||||||
|
b.Add(ColWidthPercWithOpts(
|
||||||
|
50,
|
||||||
|
[]container.Option{
|
||||||
|
container.Border(linestyle.Double),
|
||||||
|
},
|
||||||
|
Widget(mirror()),
|
||||||
|
))
|
||||||
|
b.Add(ColWidthPercWithOpts(
|
||||||
|
50,
|
||||||
|
[]container.Option{
|
||||||
|
container.Border(linestyle.Double),
|
||||||
|
},
|
||||||
|
Widget(mirror()),
|
||||||
|
))
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
want: func(size image.Point) *faketerm.Terminal {
|
||||||
|
ft := faketerm.MustNew(size)
|
||||||
|
|
||||||
|
left, right := mustVSplit(ft.Area(), 50)
|
||||||
|
leftCvs := testcanvas.MustNew(left)
|
||||||
|
rightCvs := testcanvas.MustNew(right)
|
||||||
|
testdraw.MustBorder(leftCvs, leftCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||||
|
testdraw.MustBorder(rightCvs, rightCvs.Area(), draw.BorderLineStyle(linestyle.Double))
|
||||||
|
testcanvas.MustApply(leftCvs, ft)
|
||||||
|
testcanvas.MustApply(rightCvs, ft)
|
||||||
|
|
||||||
|
leftWidget := testcanvas.MustNew(area.ExcludeBorder(left))
|
||||||
|
rightWidget := testcanvas.MustNew(area.ExcludeBorder(right))
|
||||||
|
fakewidget.MustDraw(ft, leftWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||||
|
fakewidget.MustDraw(ft, rightWidget, &widgetapi.Meta{}, widgetapi.Options{})
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "two equal columns",
|
desc: "two equal columns",
|
||||||
termSize: image.Point{20, 10},
|
termSize: image.Point{20, 10},
|
||||||
|
@ -109,14 +109,21 @@ func Round(x float64) float64 {
|
|||||||
|
|
||||||
// MinMax returns the smallest and the largest value among the provided values.
|
// MinMax returns the smallest and the largest value among the provided values.
|
||||||
// Returns (0, 0) if there are no values.
|
// Returns (0, 0) if there are no values.
|
||||||
|
// Ignores NaN values. Allowing NaN values could lead to a corner case where all
|
||||||
|
// values can be NaN, in this case the function will return NaN as min and max.
|
||||||
func MinMax(values []float64) (min, max float64) {
|
func MinMax(values []float64) (min, max float64) {
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
min = math.MaxFloat64
|
min = math.MaxFloat64
|
||||||
max = -1 * math.MaxFloat64
|
max = -1 * math.MaxFloat64
|
||||||
|
allNaN := true
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
|
if math.IsNaN(v) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allNaN = false
|
||||||
|
|
||||||
if v < min {
|
if v < min {
|
||||||
min = v
|
min = v
|
||||||
}
|
}
|
||||||
@ -124,6 +131,11 @@ func MinMax(values []float64) (min, max float64) {
|
|||||||
max = v
|
max = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allNaN {
|
||||||
|
return math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
return min, max
|
return min, max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,13 +215,28 @@ func TestMinMax(t *testing.T) {
|
|||||||
wantMin: -11.3,
|
wantMin: -11.3,
|
||||||
wantMax: 22.5,
|
wantMax: 22.5,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "min and max among negative, positive, zero and NaN values",
|
||||||
|
values: []float64{1.1, 0, 1.3, math.NaN(), -11.3, 22.5},
|
||||||
|
wantMin: -11.3,
|
||||||
|
wantMax: 22.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all NaN values",
|
||||||
|
values: []float64{math.NaN(), math.NaN(), math.NaN(), math.NaN()},
|
||||||
|
wantMin: math.NaN(),
|
||||||
|
wantMax: math.NaN(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
gotMin, gotMax := MinMax(tc.values)
|
gotMin, gotMax := MinMax(tc.values)
|
||||||
if gotMin != tc.wantMin || gotMax != tc.wantMax {
|
if diff := pretty.Compare(tc.wantMin, gotMin); diff != "" {
|
||||||
t.Errorf("MinMax => (%v, %v), want (%v, %v)", gotMin, gotMax, tc.wantMin, tc.wantMax)
|
t.Errorf("MinMax => unexpected min, diff (-want, +got):\n %s", diff)
|
||||||
|
}
|
||||||
|
if diff := pretty.Compare(tc.wantMax, gotMax); diff != "" {
|
||||||
|
t.Errorf("MinMax => unexpected max, diff (-want, +got):\n %s", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,7 @@ func gridLayout(w *widgets, lt layoutType) ([]container.Option, error) {
|
|||||||
container.BorderTitle("A rolling text"),
|
container.BorderTitle("A rolling text"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
grid.ColWidthPerc(50,
|
||||||
grid.RowHeightPerc(50,
|
grid.RowHeightPerc(50,
|
||||||
grid.Widget(w.spGreen,
|
grid.Widget(w.spGreen,
|
||||||
container.Border(linestyle.Light),
|
container.Border(linestyle.Light),
|
||||||
@ -179,6 +180,7 @@ func gridLayout(w *widgets, lt layoutType) ([]container.Option, error) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
grid.RowHeightPerc(7,
|
grid.RowHeightPerc(7,
|
||||||
grid.Widget(w.gauge,
|
grid.Widget(w.gauge,
|
||||||
container.Border(linestyle.Light),
|
container.Border(linestyle.Light),
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ func newSeriesValues(values []float64) *seriesValues {
|
|||||||
v := make([]float64, len(values))
|
v := make([]float64, len(values))
|
||||||
copy(v, values)
|
copy(v, values)
|
||||||
|
|
||||||
min, max := numbers.MinMax(v)
|
min, max := minMax(v)
|
||||||
return &seriesValues{
|
return &seriesValues{
|
||||||
values: v,
|
values: v,
|
||||||
min: min,
|
min: min,
|
||||||
@ -175,8 +176,9 @@ func (lc *LineChart) yMinMax() (float64, float64) {
|
|||||||
maximums = append(maximums, lc.opts.yAxisCustomScale.max)
|
maximums = append(maximums, lc.opts.yAxisCustomScale.max)
|
||||||
}
|
}
|
||||||
|
|
||||||
min, _ := numbers.MinMax(minimums)
|
min, _ := minMax(minimums)
|
||||||
_, max := numbers.MinMax(maximums)
|
_, max := minMax(maximums)
|
||||||
|
|
||||||
return min, max
|
return min, max
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +198,8 @@ func (lc *LineChart) ValueCapacity() int {
|
|||||||
|
|
||||||
// Series sets the values that should be displayed as the line chart with the
|
// Series sets the values that should be displayed as the line chart with the
|
||||||
// provided label.
|
// provided label.
|
||||||
|
// The values that should not be displayed on the line chart should be represented
|
||||||
|
// as math.NaN values on the values slice.
|
||||||
// Subsequent calls with the same label replace any previously provided values.
|
// Subsequent calls with the same label replace any previously provided values.
|
||||||
func (lc *LineChart) Series(label string, values []float64, opts ...SeriesOption) error {
|
func (lc *LineChart) Series(label string, values []float64, opts ...SeriesOption) error {
|
||||||
if label == "" {
|
if label == "" {
|
||||||
@ -364,6 +368,7 @@ func (lc *LineChart) graphAr(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDe
|
|||||||
// drawSeries draws the graph representing the stored series.
|
// drawSeries draws the graph representing the stored series.
|
||||||
// Returns XDetails that might be adjusted to not start at zero value if some
|
// Returns XDetails that might be adjusted to not start at zero value if some
|
||||||
// of the series didn't fit the graphs and XAxisUnscaled was provided.
|
// of the series didn't fit the graphs and XAxisUnscaled was provided.
|
||||||
|
// If the series has NaN values they will be ignored and not draw on the graph.
|
||||||
func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDetails) (*axes.XDetails, error) {
|
func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.YDetails) (*axes.XDetails, error) {
|
||||||
graphAr := lc.graphAr(cvs, xd, yd)
|
graphAr := lc.graphAr(cvs, xd, yd)
|
||||||
bc, err := braille.New(graphAr)
|
bc, err := braille.New(graphAr)
|
||||||
@ -406,7 +411,14 @@ func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.
|
|||||||
|
|
||||||
var prev float64
|
var prev float64
|
||||||
for i := 1; i < len(sv.values); i++ {
|
for i := 1; i < len(sv.values); i++ {
|
||||||
|
v := sv.values[i]
|
||||||
prev = sv.values[i-1]
|
prev = sv.values[i-1]
|
||||||
|
|
||||||
|
// Skip the values that are missing.
|
||||||
|
if math.IsNaN(v) || math.IsNaN(prev) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if i < int(xdZoomed.Scale.Min.Value)+1 || i > int(xdZoomed.Scale.Max.Value) {
|
if i < int(xdZoomed.Scale.Min.Value)+1 || i > int(xdZoomed.Scale.Max.Value) {
|
||||||
// Don't draw lines for values that aren't supposed to be visible.
|
// Don't draw lines for values that aren't supposed to be visible.
|
||||||
// These are either values outside of the current zoom or
|
// These are either values outside of the current zoom or
|
||||||
@ -429,7 +441,7 @@ func (lc *LineChart) drawSeries(cvs *canvas.Canvas, xd *axes.XDetails, yd *axes.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i-1, yd.Scale, prev, err)
|
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i-1, yd.Scale, prev, err)
|
||||||
}
|
}
|
||||||
v := sv.values[i]
|
|
||||||
endY, err := yd.Scale.ValueToPixel(v)
|
endY, err := yd.Scale.ValueToPixel(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i, yd.Scale, v, err)
|
return nil, fmt.Errorf("failure for series %v[%d] on scale %v, yd.Scale.ValueToPixel(%v) => %v", name, i, yd.Scale, v, err)
|
||||||
@ -519,3 +531,17 @@ func (lc *LineChart) maxXValue() int {
|
|||||||
}
|
}
|
||||||
return maxLen - 1
|
return maxLen - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// minMax is a wrapper around numbers.MinMax that controls
|
||||||
|
// the output if the values are NaN and sets defaults if it's
|
||||||
|
// the case.
|
||||||
|
func minMax(values []float64) (x, y float64) {
|
||||||
|
min, max := numbers.MinMax(values)
|
||||||
|
if math.IsNaN(min) {
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
if math.IsNaN(max) {
|
||||||
|
max = 0
|
||||||
|
}
|
||||||
|
return min, max
|
||||||
|
}
|
||||||
|
@ -1523,6 +1523,119 @@ func TestLineChartDraws(t *testing.T) {
|
|||||||
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0})
|
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{26, 0})
|
||||||
testbraille.MustCopyTo(bc, c)
|
testbraille.MustCopyTo(bc, c)
|
||||||
|
|
||||||
|
testcanvas.MustApply(c, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all NaN values",
|
||||||
|
canvas: image.Rect(0, 0, 20, 10),
|
||||||
|
writes: func(lc *LineChart) error {
|
||||||
|
return lc.Series("first", []float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()})
|
||||||
|
},
|
||||||
|
wantCapacity: 36,
|
||||||
|
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, 8}},
|
||||||
|
{Start: image.Point{1, 8}, End: image.Point{19, 8}},
|
||||||
|
}
|
||||||
|
testdraw.MustHVLines(c, lines)
|
||||||
|
|
||||||
|
// Value labels.
|
||||||
|
testdraw.MustText(c, "0", image.Point{0, 7})
|
||||||
|
testdraw.MustText(c, "0", image.Point{2, 9})
|
||||||
|
testdraw.MustText(c, "1", image.Point{6, 9})
|
||||||
|
testdraw.MustText(c, "2", image.Point{10, 9})
|
||||||
|
testdraw.MustText(c, "3", image.Point{14, 9})
|
||||||
|
testdraw.MustText(c, "4", image.Point{18, 9})
|
||||||
|
|
||||||
|
testcanvas.MustApply(c, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "first and last NaN values",
|
||||||
|
canvas: image.Rect(0, 0, 28, 10),
|
||||||
|
writes: func(lc *LineChart) error {
|
||||||
|
return lc.Series("first", []float64{math.NaN(), math.NaN(), 100, 150, math.NaN()})
|
||||||
|
},
|
||||||
|
wantCapacity: 44,
|
||||||
|
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{27, 8}},
|
||||||
|
}
|
||||||
|
testdraw.MustHVLines(c, lines)
|
||||||
|
|
||||||
|
// Value labels.
|
||||||
|
testdraw.MustText(c, "0", image.Point{4, 7})
|
||||||
|
testdraw.MustText(c, "77.44", image.Point{0, 3})
|
||||||
|
testdraw.MustText(c, "0", image.Point{6, 9})
|
||||||
|
testdraw.MustText(c, "1", image.Point{11, 9})
|
||||||
|
testdraw.MustText(c, "2", image.Point{16, 9})
|
||||||
|
testdraw.MustText(c, "3", image.Point{22, 9})
|
||||||
|
testdraw.MustText(c, "4", image.Point{27, 9})
|
||||||
|
|
||||||
|
graphAr := image.Rect(6, 0, 25, 8)
|
||||||
|
bc := testbraille.MustNew(graphAr)
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{21, 10}, image.Point{32, 0})
|
||||||
|
testbraille.MustCopyTo(bc, c)
|
||||||
|
|
||||||
|
testcanvas.MustApply(c, ft)
|
||||||
|
return ft
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "more values than capacity, X rescales with NaN values ignored",
|
||||||
|
canvas: image.Rect(0, 0, 11, 10),
|
||||||
|
writes: func(lc *LineChart) error {
|
||||||
|
return lc.Series("first", []float64{0, 1, 2, 3, 4, 5, 6, 7, math.NaN(), math.NaN(), math.NaN(), math.NaN(), 12, 13, 14, 15, 16, 17, 18, 19})
|
||||||
|
},
|
||||||
|
wantCapacity: 12,
|
||||||
|
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{4, 0}, End: image.Point{4, 8}},
|
||||||
|
{Start: image.Point{4, 8}, End: image.Point{10, 8}},
|
||||||
|
}
|
||||||
|
testdraw.MustHVLines(c, lines)
|
||||||
|
|
||||||
|
// Value labels.
|
||||||
|
testdraw.MustText(c, "0", image.Point{3, 7})
|
||||||
|
testdraw.MustText(c, "9.92", image.Point{0, 3})
|
||||||
|
testdraw.MustText(c, "0", image.Point{5, 9})
|
||||||
|
testdraw.MustText(c, "14", image.Point{9, 9})
|
||||||
|
|
||||||
|
// Braille line.
|
||||||
|
graphAr := image.Rect(5, 0, 11, 8)
|
||||||
|
bc := testbraille.MustNew(graphAr)
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{0, 31}, image.Point{1, 29})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{1, 29}, image.Point{1, 28})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{1, 28}, image.Point{2, 26})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{2, 26}, image.Point{2, 25})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{2, 25}, image.Point{3, 23})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{3, 23}, image.Point{3, 21})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{3, 21}, image.Point{4, 20})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{7, 12}, image.Point{8, 10})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{8, 10}, image.Point{8, 8})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{8, 8}, image.Point{9, 7})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{9, 7}, image.Point{9, 5})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{9, 5}, image.Point{10, 4})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{10, 4}, image.Point{10, 2})
|
||||||
|
testdraw.MustBrailleLine(bc, image.Point{10, 2}, image.Point{11, 0})
|
||||||
|
testbraille.MustCopyTo(bc, c)
|
||||||
|
|
||||||
testcanvas.MustApply(c, ft)
|
testcanvas.MustApply(c, ft)
|
||||||
return ft
|
return ft
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user