Charts I: components 101-120

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 14:34:31 -04:00
1 parent 6f0c02d339
commit efee19c7d4
116 files changed
+7825 -3

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
package mujica
import (
"fmt"
"math"
"github.com/egoist/mygo/ui"
"mujicaui/internal"
"mujicaui/theme"
)
// GaugeTone colors a Gauge segment; its label still names it.
type GaugeTone int
const (
GaugeNeutral GaugeTone = iota
GaugeGood
GaugeWarning
GaugeDanger
)
// GaugeSegment is a named dial range up to To.
type GaugeSegment struct {
To float64
Label string
Tone GaugeTone
}
// GaugeOptions configure a Gauge; Frame.Height 0 means 200 DIPs.
type GaugeOptions struct {
Frame ChartFrame
Min, Max float64
// Segments split Min..Max ascending; the last ends at Max.
Segments []GaugeSegment
Unit string
// Full draws a whole circle instead of a half.
Full bool
Format func(v float64) string
}
// Gauge points a needle at value on named segments.
func Gauge(c *ui.Context, value float64, opts GaugeOptions) ChartResult {
if !(opts.Max > opts.Min) || math.IsNaN(value) || value < opts.Min || value > opts.Max {
panic("mujica: Gauge needs Min < Max and a value between them")
}
prev := opts.Min
for i, s := range opts.Segments {
if s.To <= prev || s.Label == "" || i == len(opts.Segments)-1 && s.To != opts.Max {
panic("mujica: Gauge segments need labels and ascending ends finishing at Max")
}
prev = s.To
}
if opts.Frame.Height == 0 {
opts.Frame.Height = 200
}
ch := newChart(c, opts.Frame)
k := Tokens(c)
size := FontSize(c, theme.CaptionSize)
tone := func(t GaugeTone) ui.Color {
return map[GaugeTone]ui.Color{GaugeNeutral: internal.Palette(k)[5], GaugeGood: k.Success, GaugeWarning: k.Warning, GaugeDanger: k.Danger}[t]
}
val := func(v float64) string { return chartValue(c, v, opts.Format) }
seg := -1
for i, s := range opts.Segments {
if value <= s.To && seg < 0 {
seg = i
}
}
a0, a1 := -math.Pi/2, math.Pi/2
if opts.Full {
a0, a1 = -math.Pi, math.Pi
}
angle := func(v float64) float64 { return a0 + (a1-a0)*(v-opts.Min)/(opts.Max-opts.Min) }
geom := func(r ui.Rect) (cx, cy, rad float32) {
if opts.Full {
rad = min(r.W, r.H)/2 - 12
return r.X + r.W/2, r.Y + r.H/2, rad
}
rad = min(r.W/2-12, r.H-36)
return r.X + r.W/2, r.Y + 8 + rad, rad
}
reading := val(value) + " " + opts.Unit
if seg >= 0 {
reading += " · " + opts.Segments[seg].Label
}
spec := chartSpec{n: 1 + len(opts.Segments)}
spec.readout = func(i int) string {
if i == 0 {
return reading
}
s, from := opts.Segments[i-1], opts.Min
if i > 1 {
from = opts.Segments[i-2].To
}
return fmt.Sprintf("%s: %s–%s", s.Label, val(from), val(s.To))
}
spec.pick = func(r ui.Rect, x, y float32) int {
cx, cy, rad := geom(r)
d := float32(math.Hypot(float64(x-cx), float64(y-cy)))
if d < rad*0.45 {
return 0
}
if d > rad+6 {
return -1
}
a := math.Atan2(float64(x-cx), float64(cy-y))
from := a0
for i, s := range opts.Segments {
if a >= from && a <= angle(s.To) {
return i + 1
}
from = angle(s.To)
}
return -1
}
spec.draw = func(p *ui.Painter, r ui.Rect, active int) {
cx, cy, rad := geom(r)
from := a0
if len(opts.Segments) == 0 {
p.FillPath(internal.Sector(cx, cy, rad*0.78, rad, a0, a1), k.SurfaceHover)
}
for i, s := range opts.Segments {
to := angle(s.To)
grow := float32(0)
if active == i+1 {
grow = 4
}
p.FillPath(internal.Sector(cx, cy, rad*0.78-grow, rad+grow, from, to), tone(s.Tone))
p.StrokePath(internal.Sector(cx, cy, rad*0.78-grow, rad+grow, from, to), 1.5, k.Surface)
lx, ly := internal.Polar(cx, cy, rad+10, to)
if i < len(opts.Segments)-1 {
chartText(p, val(s.To), lx, ly, size, k.TextMuted, 0.5, 1)
}
from = to
}
nx, ny := internal.Polar(cx, cy, rad*0.9, angle(value))
var needle ui.Path
needle.MoveTo(cx, cy).LineTo(internal.Snap(p, nx), internal.Snap(p, ny))
w := float32(3)
if active == 0 {
w = 4.5
}
p.StrokePath(&needle, w, k.Text)
p.FillPath(internal.Diamond(cx, cy, 12), k.Text)
ty := cy + 34
chartText(p, val(value)+" "+opts.Unit, cx, ty, FontSize(c, theme.H3Size), k.Text, 0.5, 1)
if seg >= 0 {
chartText(p, opts.Segments[seg].Label, cx, ty+2, size, k.TextMuted, 0.5, 0)
}
}
res, _ := ch.build(spec)
return res
}