This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user