Files
MujicaUI/chart/chart_crosshair.go
T

85 lines
2.5 KiB
Go

package chart
import (
"math"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/internal"
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/theme"
)
// ChartCrosshairOptions configure a ChartCrosshair.
type ChartCrosshairOptions struct {
// X draws vertical lines; Y draws horizontal lines.
X, Y *ChartAxisPart
// Reach defaults to 24 DIPs for point snapping.
Reach float32
}
// ChartCrosshairPart draws and snaps the crosshair.
type ChartCrosshairPart struct {
opts ChartCrosshairOptions
k theme.Tokens
size float32
pad float32
}
// ChartCrosshair draws crossed lines and axis readouts.
func ChartCrosshair(c *ui.Context, opts ChartCrosshairOptions) ChartCrosshairPart {
if opts.X == nil && opts.Y == nil || opts.Reach < 0 || math.IsNaN(float64(opts.Reach)) || math.IsInf(float64(opts.Reach), 0) {
panic("mujica: ChartCrosshair needs an axis and Reach ≥ 0")
}
if opts.Reach == 0 {
opts.Reach = 24
}
return ChartCrosshairPart{opts: opts, k: core.Tokens(c), size: core.FontSize(c, theme.CaptionSize), pad: core.Density(c).Unit()}
}
// Snap returns the nearest point within Reach, or -1.
func (h ChartCrosshairPart) Snap(n int, at func(i int) (x, y float32), x, y float32) int {
return internal.Nearest(n, at, x, y, h.opts.Reach, false)
}
// Draw paints crossed lines and value labels.
func (h ChartCrosshairPart) Draw(p *ui.Painter, plot ui.Rect, x, y float32) {
k := h.k
if a := h.opts.X; a != nil {
xx := internal.Snap(p, x)
p.Line(xx, plot.Y, xx, plot.Y+plot.H, 1, k.TextMuted)
at := plot.Y + plot.H
if a.opts.Side == SideTop {
at = plot.Y
}
h.pill(p, a.readout(a.Invert(plot, x)), x, at, true, a.opts.Side == SideTop)
}
if a := h.opts.Y; a != nil {
yy := internal.Snap(p, y)
p.Line(plot.X, yy, plot.X+plot.W, yy, 1, k.TextMuted)
at := plot.X
if a.opts.Side == SideRight {
at = plot.X + plot.W
}
h.pill(p, a.readout(a.Invert(plot, y)), at, y, false, a.opts.Side == SideRight)
}
}
// pill paints a value tag outside the plot.
func (h ChartCrosshairPart) pill(p *ui.Painter, s string, x, y float32, across, far bool) {
w, ht := p.MeasureText(0, ui.Span{Text: s, Size: h.size})
box := ui.Rect{W: w + 2*h.pad, H: ht + h.pad}
switch {
case across && far:
box.X, box.Y = x-box.W/2, y-box.H-2
case across:
box.X, box.Y = x-box.W/2, y+2
case far:
box.X, box.Y = x+2, y-box.H/2
default:
box.X, box.Y = x-box.W-2, y-box.H/2
}
p.Fill(box, h.k.Text, theme.ControlRadius)
p.Text(box.X+h.pad, box.Y+h.pad/2, s, h.size, h.k.Surface)
}