160 lines
3.1 KiB
Go
160 lines
3.1 KiB
Go
package charts
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"ruben/inventory2/server/ui/svg"
|
|
)
|
|
|
|
type (
|
|
// TODO: rename to BarChart
|
|
Bar struct {
|
|
values []BarValue
|
|
classes barChartClasses
|
|
}
|
|
|
|
BarValue struct {
|
|
Label string
|
|
Value float64
|
|
}
|
|
|
|
barChartClasses struct {
|
|
svg []string
|
|
bar []string
|
|
}
|
|
)
|
|
|
|
func NewBar(vs ...BarValue) *Bar {
|
|
return &Bar{
|
|
values: vs,
|
|
}
|
|
}
|
|
|
|
func (c *Bar) WithSVGClass(classes ...string) *Bar {
|
|
c.classes.svg = append(c.classes.svg, classes...)
|
|
return c
|
|
}
|
|
|
|
func (c *Bar) WithBarClass(classes ...string) *Bar {
|
|
c.classes.bar = append(c.classes.bar, classes...)
|
|
return c
|
|
}
|
|
|
|
func (c *Bar) SVG() svg.MarshalerReader {
|
|
const (
|
|
minX = 0
|
|
minY = 0
|
|
maxX = 100
|
|
maxY = 100
|
|
rangeX = maxX - minX
|
|
rangeY = maxY - minY
|
|
)
|
|
|
|
s := svg.NewSVG().
|
|
Attr(
|
|
svg.Style{
|
|
"width": "100%",
|
|
"height": "auto",
|
|
"padding": "1em",
|
|
"border-width": "2px",
|
|
},
|
|
svg.ViewBox{
|
|
X: minX,
|
|
Y: minY,
|
|
Width: maxX - minX,
|
|
Height: maxY - minY,
|
|
},
|
|
svg.Class(
|
|
strings.Join(append(
|
|
[]string{"rounded-lg", "border-border"},
|
|
c.classes.bar...,
|
|
), " "),
|
|
),
|
|
)
|
|
|
|
if len(c.classes.svg) != 0 {
|
|
s = s.Attr(svg.Class(strings.Join(c.classes.svg, " ")))
|
|
}
|
|
|
|
if len(c.values) > 0 {
|
|
minV := slices.MinFunc(c.values, func(a, b BarValue) int {
|
|
if a.Value < b.Value {
|
|
return -1
|
|
}
|
|
if a.Value > b.Value {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
maxV := slices.MaxFunc(c.values, func(a, b BarValue) int {
|
|
if a.Value < b.Value {
|
|
return -1
|
|
}
|
|
if a.Value > b.Value {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
rangeV := maxV.Value - minV.Value
|
|
|
|
spacePerBar := float64(rangeX) / float64(len(c.values))
|
|
barPadding := 0.025 * spacePerBar
|
|
barWidth := spacePerBar - (2 * barPadding)
|
|
fontSize := barWidth / 2
|
|
|
|
for i, v := range c.values {
|
|
scaledV := (v.Value - minV.Value) * rangeY / rangeV
|
|
barX := float64(i)*spacePerBar + barPadding
|
|
s = s.Child(
|
|
new(svg.Rect).
|
|
Attr(
|
|
svg.NewLength(barX).AsX(),
|
|
svg.NewLength(maxY-scaledV).AsY(),
|
|
svg.NewLength(barWidth).AsWidth(),
|
|
svg.NewLength(scaledV).AsHeight(),
|
|
svg.Class(
|
|
strings.Join(append(
|
|
[]string{"fill-accent"},
|
|
c.classes.bar...,
|
|
), " "),
|
|
),
|
|
),
|
|
svg.NewText(fmt.Sprint(v.Value)).
|
|
Attr(
|
|
svg.NewLength(barX+(barWidth/2)).AsX(),
|
|
svg.NewLength(maxY-scaledV+fontSize).AsY(),
|
|
svg.Fill("var(--accent-foreground)"),
|
|
svg.TextAnchorMiddle,
|
|
// svg.NewLength(fontSize).AsFontSize(),
|
|
svg.Style{
|
|
"font-size": fmt.Sprintf("%vpx", fontSize),
|
|
},
|
|
),
|
|
)
|
|
|
|
if v.Label != "" {
|
|
x := barX + (barWidth / 2)
|
|
y := maxY - fontSize
|
|
s = s.Child(
|
|
svg.NewText(v.Label).
|
|
Attr(
|
|
svg.NewLength(x).AsX(),
|
|
svg.NewLength(y).AsY(),
|
|
svg.Fill("var(--accent-foreground)"),
|
|
//svg.NewLength(fontSize).AsFontSize(),
|
|
svg.Style{
|
|
"font-size": fmt.Sprintf("%vpx", fontSize),
|
|
},
|
|
svg.DominantBaselineCentral,
|
|
svg.TransformRotate(-90).About(x, y),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return s
|
|
}
|