Add chart components 216-245; step-aware ticks, font-aware labels
Bars, lines, areas, pies, donuts, radar, histogram, scatter, bubbles, heatmaps, gauge, bullet, funnel, waterfall, treemap, candles, box plots, steps, rings, legend, axis, crosshair, polar area, pyramid, spectrum bars and win-loss strips, each drawn dot by dot. Ticks follow 1-2-5 steps and print as precisely as their step needs; a label row uses micro letters or the pixel font as a whole. Descriptions carry the data in numbers. Bad data panics.
This commit is contained in:
43 files changed
+3336
-1
No files matched your search
@@ -0,0 +1,152 @@
|
||||
package chart_test
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/chart"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
var hp = []chart.Series{{Name: "HP", Values: []float64{10, 30, 20, 40}, Slot: pixel.Good}, {Name: "MP", Values: []float64{5, 5, 15, 10}, Slot: pixel.Info}}
|
||||
|
||||
func TestLineChartJoinsAndMarksPoints(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), func(c *ui.Context) {
|
||||
chart.LineChart(c, "Stats", days, hp, chart.LineChartOptions{})
|
||||
})
|
||||
img := tt.Image()
|
||||
good, info := count(t, img, 0, 0, 160, 80, pixel.Good), count(t, img, 0, 0, 160, 80, pixel.Info)
|
||||
if good < 100 || info < 100 {
|
||||
t.Fatalf("line dots: HP %d, MP %d", good, info)
|
||||
}
|
||||
// One-dot lines leave whole 3×3 blocks only at the markers.
|
||||
if hb, mb := blocks(t, img, pixel.Good), blocks(t, img, pixel.Info); hb != 4 || mb != 4 {
|
||||
t.Fatalf("markers: HP %d, MP %d", hb, mb)
|
||||
}
|
||||
keeps(t, img)
|
||||
panics(t, "a series too short", func() {
|
||||
view(100, 100, newClock(), func(c *ui.Context) {
|
||||
chart.LineChart(c, "x", days, []chart.Series{{Name: "a", Values: []float64{1}}}, chart.LineChartOptions{})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAreaChartDithersBelowTheLine(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), func(c *ui.Context) {
|
||||
chart.AreaChart(c, "Gold", days, []float64{20, 20, 20, 20}, chart.AreaChartOptions{Slot: pixel.Warn})
|
||||
})
|
||||
img := tt.Image()
|
||||
// Under a flat line, the area mixes Warn and ground.
|
||||
lit, ground := count(t, img, 60, 50, 40, 10, pixel.Warn), count(t, img, 60, 50, 40, 10, pixel.Back)
|
||||
if lit == 0 || ground == 0 {
|
||||
t.Fatalf("area under the line: %d Warn, %d ground", lit, ground)
|
||||
}
|
||||
keeps(t, img)
|
||||
}
|
||||
|
||||
func TestAreaChartFillsBelowZeroToo(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), func(c *ui.Context) {
|
||||
chart.AreaChart(c, "Debt", days, []float64{-20, -20, -20, -20}, chart.AreaChartOptions{Slot: pixel.Bad})
|
||||
})
|
||||
img := tt.Image()
|
||||
// Zero tops the plot; the area hangs below it.
|
||||
lit, ground := count(t, img, 60, 10, 40, 10, pixel.Bad), count(t, img, 60, 10, 40, 10, pixel.Back)
|
||||
if lit == 0 || ground == 0 {
|
||||
t.Fatalf("area above the line: %d Bad, %d ground", lit, ground)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepChartHoldsThenSteps(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), func(c *ui.Context) {
|
||||
chart.StepChart(c, "Level", days, []float64{1, 1, 3, 3}, chart.StepChartOptions{Slot: pixel.Magic})
|
||||
})
|
||||
img := tt.Image()
|
||||
// A flat run of a whole share holds one row.
|
||||
rows := map[int]bool{}
|
||||
for x := 20; x < 40; x++ {
|
||||
for y := range 80 {
|
||||
if at(t, img, x, y) == pixel.Magic {
|
||||
rows[y] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rows) != 1 {
|
||||
t.Fatalf("the first hold spans rows %v", rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSparklineMarksItsEnd(t *testing.T) {
|
||||
tt := view(200, 60, newClock(), func(c *ui.Context) {
|
||||
chart.Sparkline(c, "Ping", []float64{3, 9, 4, 12}, chart.SparklineOptions{Slot: pixel.Info})
|
||||
})
|
||||
img := tt.Image()
|
||||
if count(t, img, 0, 0, 48, 16, pixel.Info) < 30 || count(t, img, 44, 0, 4, 16, pixel.Accent) == 0 {
|
||||
t.Fatal("no line, or no mark at its end")
|
||||
}
|
||||
panics(t, "one value", func() {
|
||||
view(100, 60, newClock(), func(c *ui.Context) { chart.Sparkline(c, "x", []float64{1}, chart.SparklineOptions{}) })
|
||||
})
|
||||
}
|
||||
|
||||
func TestCrosshairCrossesOnHover(t *testing.T) {
|
||||
tt := view(400, 200, newClock(), func(c *ui.Context) {
|
||||
chart.Crosshair(c, "Stats", days, hp, chart.CrosshairOptions{})
|
||||
})
|
||||
if n := count(t, tt.Image(), 0, 0, 160, 80, pixel.Lit); n != 0 {
|
||||
t.Fatal("a readout shows before hover")
|
||||
}
|
||||
tt.Move(2*90, 2*30)
|
||||
img := tt.Image()
|
||||
if count(t, img, 0, 0, 160, 80, pixel.Lit) == 0 || count(t, img, 0, 30, 160, 1, pixel.Ink) < 20 {
|
||||
t.Fatal("hover drew no crosshair or readout")
|
||||
}
|
||||
keeps(t, img)
|
||||
}
|
||||
|
||||
func TestSpectrumPeaksFallAndRest(t *testing.T) {
|
||||
clk := newClock()
|
||||
levels := []float64{1, 0.5}
|
||||
tt := view(200, 100, clk, func(c *ui.Context) {
|
||||
chart.SpectrumBars(c, "EQ", levels, chart.SpectrumBarsOptions{})
|
||||
})
|
||||
img := tt.Image()
|
||||
if lit := count(t, img, 0, 0, 5, 30, pixel.Shade); lit != 0 {
|
||||
t.Fatalf("a full bar shows %d dark dots", lit)
|
||||
}
|
||||
// Five idle seconds do not count toward the fall.
|
||||
clk.t = clk.t.Add(5 * time.Second)
|
||||
levels[0] = 0
|
||||
tt.Frame()
|
||||
if top, _ := column(t, tt.Image(), 2, 30, pixel.Ink); top != 0 {
|
||||
t.Fatalf("at the drop the peak sits at row %d", top)
|
||||
}
|
||||
clk.t = clk.t.Add(300 * time.Millisecond)
|
||||
tt.Frame()
|
||||
// After 0.3 s the peak has fallen three blocks.
|
||||
top, _ := column(t, tt.Image(), 2, 30, pixel.Ink)
|
||||
if top != 9 {
|
||||
t.Fatalf("the peak sits at row %d", top)
|
||||
}
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
tt.Frame()
|
||||
if top, _ := column(t, tt.Image(), 2, 30, pixel.Ink); top != -1 {
|
||||
t.Fatalf("reduced, the peak still floats at %d", top)
|
||||
}
|
||||
}
|
||||
|
||||
// blocks counts 3×3 squares of s on a 160×80 chart.
|
||||
func blocks(t *testing.T, img *image.RGBA, s pixel.Slot) int {
|
||||
t.Helper()
|
||||
n := 0
|
||||
for y := range 78 {
|
||||
for x := range 158 {
|
||||
if count(t, img, x, y, 3, 3, s) == 9 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
Reference in new issue
Block a user