Elements are now pass-scoped value handles: tests that read bounds after a frame bind a ui.Handle. pixel tells frames apart by the build pass's root, since the Tester clock no longer moves on its own. Fix the regressions the upgrade brought: - Clipboard reads in input handlers go through c.Services(). - MyGo notes an overlay's opener after building it, so AutoFocus inside lost it; pixel.AutoFocus focuses through a Handle, and archtest bans the raw call. Dialogs hand the focus back again. - Elements around a press stay hovered: Tooltip tells a press-given focus by focus arriving under the pointer. - The web host moves the Tester clock with real time, at most 500 ms a call, and gives components the real clock. The gallery's source index also accepts CRLF checkouts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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
|
|
}
|