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.
201 lines
8.5 KiB
Go
201 lines
8.5 KiB
Go
package gallery
|
||
|
||
import (
|
||
"fmt"
|
||
"math/rand/v2"
|
||
"slices"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/chart"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
)
|
||
|
||
var chartEntries = []Entry{
|
||
{216, "BarChart", "柱状图", "chart", "Gold earned per day. Point at a bar to read it.", demoBarChart},
|
||
{217, "StackedBar", "堆叠柱图", "chart", "Gold under gems, stacked day by day.", demoStackedBar},
|
||
{218, "HorizontalBar", "条形图", "chart", "Slimes slain, by hero. Brom is not sharing.", demoHorizontalBar},
|
||
{219, "LineChart", "折线图", "chart", "HP and MP down a dungeon, joined dot by dot.", demoLineChart},
|
||
{220, "AreaChart", "面积图", "chart", "Gold over a week; the ground under the line dithers.", demoAreaChart},
|
||
{221, "Sparkline", "迷你图", "chart", "A chart one text line tall. It fits in a sentence.", demoSparkline},
|
||
{222, "PieChart", "饼图", "chart", "Where the loot went, sector by sector, dot by dot.", demoPieChart},
|
||
{223, "DonutChart", "环形图", "chart", "A pie with a hole; the total waits inside.", demoDonutChart},
|
||
{224, "RadarChart", "雷达图", "chart", "Six stats on a hexagon web. LEVEL UP and watch it grow.", demoRadarChart},
|
||
{225, "Histogram", "直方图", "chart", "Damage rolls in bins. ROLL throws ten more.", demoHistogram},
|
||
{226, "ScatterPlot", "散点图", "chart", "Every hit by distance and damage, one 2×2 dot each.", demoScatterPlot},
|
||
{227, "BubbleChart", "气泡图", "chart", "Towns by trade and danger, sized by how many live there.", demoBubbleChart},
|
||
{228, "Heatmap", "热力图", "chart", "Raids by day and hour. Hot cells dither red.", demoHeatmap},
|
||
{229, "CalendarHeatmap", "日历热力图", "chart", "A year of play, a square a day. A commit graph for slimes.", demoCalendarHeatmap},
|
||
{230, "Gauge", "仪表", "chart", "Push the throttle; the needle follows.", demoGauge},
|
||
}
|
||
|
||
type chartDemo struct {
|
||
stats []float64
|
||
rolls []float64
|
||
dice *rand.Rand
|
||
throttle float64
|
||
hidden []bool
|
||
since time.Time
|
||
play bool
|
||
matches []int
|
||
}
|
||
|
||
func chartState(c *ui.Context) *chartDemo {
|
||
return ui.Local(c.Root(), demoKey("chart"), func() chartDemo {
|
||
s := chartDemo{stats: []float64{4, 3, 2, 4, 3, 2}, dice: rand.New(rand.NewPCG(1, 2)), throttle: 40,
|
||
hidden: []bool{false, false, false}, play: true, matches: []int{1, 1, -1, 1, 0, -1, 1, 1}}
|
||
for range 30 {
|
||
s.rolls = append(s.rolls, roll(s.dice))
|
||
}
|
||
return s
|
||
})
|
||
}
|
||
|
||
// roll throws two dice.
|
||
func roll(r *rand.Rand) float64 { return float64(r.IntN(6) + r.IntN(6) + 2) }
|
||
|
||
var week = []string{"MON", "TUE", "WED", "THU", "FRI"}
|
||
|
||
// heroes are three HP series with fixed colors.
|
||
var heroes = []chart.Series{
|
||
{Name: "Aria", Values: []float64{30, 26, 28, 18, 24}, Slot: pixel.Good},
|
||
{Name: "Brom", Values: []float64{40, 34, 22, 26, 12}, Slot: pixel.Warn},
|
||
{Name: "Cid", Values: []float64{18, 20, 16, 22, 20}, Slot: pixel.Info},
|
||
}
|
||
|
||
func demoBarChart(c *ui.Context, log func(string)) {
|
||
chart.BarChart(c, "Gold per day", week, []float64{120, 340, 90, 260, 410}, chart.BarChartOptions{Width: 200, Height: 90})
|
||
}
|
||
|
||
func demoStackedBar(c *ui.Context, log func(string)) {
|
||
chart.StackedBar(c, "Loot per day", week, []chart.Series{
|
||
{Name: "Gold", Values: []float64{12, 30, 8, 22, 18}, Slot: pixel.Warn},
|
||
{Name: "Gems", Values: []float64{4, 6, 12, 2, 9}, Slot: pixel.Magic},
|
||
}, chart.StackedBarOptions{Width: 200, Height: 90})
|
||
}
|
||
|
||
func demoHorizontalBar(c *ui.Context, log func(string)) {
|
||
chart.HorizontalBar(c, "Slimes slain", []string{"Aria", "Brom", "Cid", "Dax"}, []float64{48, 112, 37, 64}, chart.HorizontalBarOptions{Width: 200})
|
||
}
|
||
|
||
func demoLineChart(c *ui.Context, log func(string)) {
|
||
floors := []string{"B1", "B2", "B3", "B4", "B5", "B6"}
|
||
chart.LineChart(c, "Dungeon run", floors, []chart.Series{
|
||
{Name: "HP", Values: []float64{50, 44, 47, 30, 36, 12}, Slot: pixel.Good},
|
||
{Name: "MP", Values: []float64{20, 16, 8, 12, 4, 6}, Slot: pixel.Info},
|
||
}, chart.LineChartOptions{Width: 200, Height: 90})
|
||
}
|
||
|
||
func demoAreaChart(c *ui.Context, log func(string)) {
|
||
chart.AreaChart(c, "Gold", week, []float64{120, 180, 150, 260, 310}, chart.AreaChartOptions{Width: 200, Height: 90, Slot: pixel.Warn})
|
||
}
|
||
|
||
func demoSparkline(c *ui.Context, log func(string)) {
|
||
k := pixel.K(c)
|
||
ui.Column(c).Gap(k.D(2)).Children(func() {
|
||
for _, r := range []struct {
|
||
name, now string
|
||
values []float64
|
||
slot pixel.Slot
|
||
}{
|
||
{"Ping", "42 ms", []float64{40, 44, 39, 61, 45, 42, 38, 42}, pixel.Info},
|
||
{"FPS", "60", []float64{60, 60, 58, 41, 59, 60, 60, 60}, pixel.Good},
|
||
{"Gold", "1.2K", []float64{300, 420, 380, 650, 700, 980, 1100, 1200}, pixel.Warn},
|
||
} {
|
||
ui.Row(c).Gap(k.D(6)).AlignItems(ui.Center).Children(func() {
|
||
ui.Box(c).Width(k.D(36)).Children(func() { pixel.Text(c, r.name, pixel.TextOptions{Slot: pixel.Faint}) })
|
||
chart.Sparkline(c, r.name, r.values, chart.SparklineOptions{Slot: r.slot})
|
||
pixel.Text(c, r.now, pixel.TextOptions{})
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
func demoPieChart(c *ui.Context, log func(string)) {
|
||
chart.PieChart(c, "Loot", []chart.Slice{{Name: "Gold", Value: 52, Slot: pixel.Warn}, {Name: "Gems", Value: 23, Slot: pixel.Magic},
|
||
{Name: "Potions", Value: 15, Slot: pixel.Bad}, {Name: "Junk", Value: 10, Slot: pixel.Faint}}, chart.PieChartOptions{Size: 80})
|
||
}
|
||
|
||
func demoDonutChart(c *ui.Context, log func(string)) {
|
||
chart.DonutChart(c, "Party gold", []chart.Slice{{Name: "Aria", Value: 120}, {Name: "Brom", Value: 340}, {Name: "Cid", Value: 90}}, chart.DonutChartOptions{Size: 80})
|
||
}
|
||
|
||
func demoRadarChart(c *ui.Context, log func(string)) {
|
||
k := pixel.K(c)
|
||
s := chartState(c)
|
||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||
chart.RadarChart(c, "Hero", []string{"STR", "DEX", "INT", "VIT", "AGI", "LUK"}, s.stats, chart.RadarChartOptions{Max: 10, Size: 96, Slot: pixel.Accent})
|
||
maxed := slices.Min(s.stats) >= 10
|
||
if input.Button(c, "LEVEL UP", input.ButtonOptions{Disabled: maxed}).Clicked {
|
||
for i := range s.stats {
|
||
s.stats[i] = min(10, s.stats[i]+float64(1+i%2))
|
||
}
|
||
log(fmt.Sprintf("STR %g, DEX %g", s.stats[0], s.stats[1]))
|
||
}
|
||
})
|
||
}
|
||
|
||
func demoHistogram(c *ui.Context, log func(string)) {
|
||
k := pixel.K(c)
|
||
s := chartState(c)
|
||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||
chart.Histogram(c, "2d6 rolls", s.rolls, chart.HistogramOptions{Bins: 11, Width: 200, Height: 90})
|
||
if input.Button(c, "ROLL", input.ButtonOptions{}).Clicked {
|
||
for range 10 {
|
||
s.rolls = append(s.rolls, roll(s.dice))
|
||
}
|
||
log(fmt.Sprintf("%d rolls", len(s.rolls)))
|
||
}
|
||
})
|
||
}
|
||
|
||
func demoScatterPlot(c *ui.Context, log func(string)) {
|
||
hits := []chart.Point{{X: 1, Y: 12}, {X: 2, Y: 10}, {X: 2, Y: 14}, {X: 3, Y: 9}, {X: 4, Y: 8}, {X: 5, Y: 8}, {X: 5, Y: 3},
|
||
{X: 6, Y: 6}, {X: 7, Y: 5}, {X: 8, Y: 4}, {X: 8, Y: 9}, {X: 9, Y: 2}}
|
||
chart.ScatterPlot(c, "Hits", hits, chart.ScatterPlotOptions{Width: 200, Height: 90, Slot: pixel.Bad})
|
||
}
|
||
|
||
func demoBubbleChart(c *ui.Context, log func(string)) {
|
||
towns := []chart.Point{{X: 2, Y: 8, Size: 120}, {X: 5, Y: 3, Size: 900}, {X: 7, Y: 6, Size: 300}, {X: 9, Y: 9, Size: 40}}
|
||
chart.BubbleChart(c, "Towns", towns, chart.BubbleChartOptions{Width: 200, Height: 90, Slot: pixel.Info})
|
||
}
|
||
|
||
func demoHeatmap(c *ui.Context, log func(string)) {
|
||
chart.Heatmap(c, "Raids", []string{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}, []string{"AM", "NOON", "PM", "NIGHT"}, [][]float64{
|
||
{1, 2, 3, 6}, {0, 2, 4, 7}, {1, 1, 3, 5}, {0, 3, 5, 8}, {2, 4, 7, 12}, {5, 8, 10, 14}, {6, 7, 9, 4},
|
||
}, chart.HeatmapOptions{Cell: 12})
|
||
}
|
||
|
||
func demoCalendarHeatmap(c *ui.Context, log func(string)) {
|
||
k := pixel.K(c)
|
||
now := k.Now()
|
||
// Tomorrow's square lights up at midnight.
|
||
k.At(c, time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()))
|
||
// Days come as UTC dates; today is the local date.
|
||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
||
chart.CalendarHeatmap(c, "Play days", now.Year(), func(d time.Time) int {
|
||
if today.Before(d) {
|
||
return 0
|
||
}
|
||
// Weekends run hotter; the rest is a made-up habit.
|
||
n := (d.YearDay()*d.YearDay()*7 + d.YearDay()*3) % 10
|
||
if d.Weekday() == time.Saturday || d.Weekday() == time.Sunday {
|
||
n += 4
|
||
}
|
||
return min(4, n/3)
|
||
}, chart.CalendarHeatmapOptions{})
|
||
}
|
||
|
||
func demoGauge(c *ui.Context, log func(string)) {
|
||
k := pixel.K(c)
|
||
s := chartState(c)
|
||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||
chart.Gauge(c, "Speed", s.throttle, chart.GaugeOptions{Size: 80})
|
||
if input.Slider(c, "Throttle", &s.throttle, input.SliderOptions{Max: 100}).Changed {
|
||
log(fmt.Sprintf("speed %.0f", s.throttle))
|
||
}
|
||
})
|
||
}
|