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.
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package chart
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
type BarChartOptions struct {
|
|
// Width and Height are dots; zeros mean 160 by 80.
|
|
Width, Height int
|
|
// Slot fills the bars; zero means Accent.
|
|
Slot pixel.Slot
|
|
}
|
|
|
|
// BarChart stands bars on micro-font axes; hover reads a value.
|
|
func BarChart(c *ui.Context, text string, labels []string, values []float64, o BarChartOptions) *ui.Element {
|
|
k := pixel.K(c)
|
|
if len(labels) != len(values) || len(values) == 0 {
|
|
panic(fmt.Sprintf("chart: BarChart has %d labels for %d values", len(labels), len(values)))
|
|
}
|
|
w, h := size("BarChart", o.Width, o.Height, 160, 80)
|
|
lo, hi := span(values)
|
|
p := fit(min(0, lo), max(0, hi), w, h, labels)
|
|
e := ui.Box(c).Size(k.D(w), k.D(h)).Role(ui.RoleImage).Label(text).Description(read(labels, values))
|
|
hover := -1
|
|
if x, y, over := e.PointerPosition(); over {
|
|
if dx := int(x/k.Dot()) - p.x; dx >= 0 && dx < p.w && int(y/k.Dot()) < p.y+p.h {
|
|
hover = dx * len(values) / p.w
|
|
}
|
|
}
|
|
ink := pick(o.Slot, 0)
|
|
return e.Draw(func(pt *ui.Painter, r ui.Rect) {
|
|
g := k.Canvas(pt, r)
|
|
p.grid(g, labels)
|
|
share := p.w / len(values)
|
|
bw, zero := max(1, share*2/3), p.at(0)
|
|
for i, v := range values {
|
|
x, top := p.slot(i, len(values))-bw/2, p.at(v)
|
|
g.Rect(x, min(top, zero), bw, max(1, abs(zero-top)), ink)
|
|
}
|
|
if hover >= 0 {
|
|
readout(g, p.slot(hover, len(values)), p.at(values[hover]), num(values[hover]), w)
|
|
}
|
|
})
|
|
}
|
|
|
|
// readout boxes s above x, y, kept inside w dots.
|
|
func readout(g pixel.Canvas, x, y int, s string, w int) {
|
|
bw := tinyW(s) + 4
|
|
bx, by := max(0, min(w-bw, x-bw/2)), max(0, y-11)
|
|
g.Frame(bx, by, bw, 9, pixel.Flat, pixel.Lit)
|
|
tiny(g, bx+2, by+2, s, pixel.Ink)
|
|
}
|
|
|
|
// read lists values for assistive technology: "A 3, B 5".
|
|
func read(labels []string, values []float64) string {
|
|
parts := make([]string, len(values))
|
|
for i, v := range values {
|
|
parts[i] = labels[i] + " " + num(v)
|
|
}
|
|
return strings.Join(parts, ", ")
|
|
}
|
|
|
|
func abs(n int) int {
|
|
if n < 0 {
|
|
return -n
|
|
}
|
|
return n
|
|
}
|