237 lines
3.7 KiB
Go
237 lines
3.7 KiB
Go
package charts
|
|
|
|
import (
|
|
"ruben/inventory2/server/ui/svg"
|
|
"slices"
|
|
)
|
|
|
|
type (
|
|
LineChart struct {
|
|
width float64
|
|
height float64
|
|
foreground string
|
|
background string
|
|
values []LineValue
|
|
max *float64
|
|
min *float64
|
|
}
|
|
LineValue struct {
|
|
Label string
|
|
Value float64
|
|
}
|
|
)
|
|
|
|
func NewLineChart(width, height float64, vs ...LineValue) *LineChart {
|
|
return &LineChart{
|
|
width: width,
|
|
height: height,
|
|
values: vs,
|
|
}
|
|
}
|
|
|
|
func (c *LineChart) Max(n float64) *LineChart {
|
|
c.max = &n
|
|
return c
|
|
}
|
|
|
|
func (c *LineChart) Min(n float64) *LineChart {
|
|
c.min = &n
|
|
return c
|
|
}
|
|
|
|
func (c *LineChart) Foreground(fg string) *LineChart {
|
|
c.foreground = fg
|
|
return c
|
|
}
|
|
|
|
func (c *LineChart) Background(bg string) *LineChart {
|
|
c.background = bg
|
|
return c
|
|
}
|
|
|
|
func (c *LineChart) SVG() svg.MarshalerReader {
|
|
var (
|
|
minVal float64
|
|
maxVal float64
|
|
)
|
|
|
|
fg := "black"
|
|
if c.foreground != "" {
|
|
fg = c.foreground
|
|
}
|
|
bg := c.background
|
|
|
|
if c.min != nil {
|
|
minVal = *c.min
|
|
} else if len(c.values) > 0 {
|
|
minVal = slices.MinFunc(c.values, compareLineValues).Value
|
|
}
|
|
|
|
if c.max != nil {
|
|
maxVal = *c.max
|
|
} else if len(c.values) > 0 {
|
|
maxVal = slices.MaxFunc(c.values, compareLineValues).Value
|
|
}
|
|
|
|
svgBg := bg
|
|
if svgBg == "" {
|
|
svgBg = "auto"
|
|
}
|
|
|
|
s := svg.NewSVG().
|
|
Attr(
|
|
svg.NewLength(c.width).
|
|
AsWidth(),
|
|
svg.NewLength(c.height).
|
|
AsHeight(),
|
|
svg.Style{
|
|
// "width": "100%",
|
|
// "height": "100%",
|
|
"background": svgBg,
|
|
},
|
|
/*
|
|
svg.ViewBox{
|
|
X: 0,
|
|
Y: minVal,
|
|
Width: float64(len(c.values) - 1),
|
|
Height: maxVal - minVal,
|
|
},
|
|
*/
|
|
svg.ViewBox{
|
|
X: 0,
|
|
Y: 0,
|
|
Width: c.width,
|
|
Height: c.height,
|
|
},
|
|
svg.PreserveAspectRatio{},
|
|
)
|
|
|
|
g := new(svg.G).
|
|
Attr(
|
|
// getChartOrientationTransform(c.height),
|
|
svg.Transform{
|
|
// scale down to provide padding
|
|
svg.TransformTranslate{
|
|
X: 0.05 * c.width,
|
|
Y: 0.05 * c.height,
|
|
},
|
|
svg.TransformScale{
|
|
X: 0.9,
|
|
Y: 0.9,
|
|
},
|
|
|
|
// flip
|
|
svg.TransformScale{
|
|
X: 1,
|
|
Y: -1,
|
|
},
|
|
svg.TransformTranslate{
|
|
Y: -c.height,
|
|
},
|
|
},
|
|
)
|
|
|
|
// lines
|
|
|
|
pts := make(svg.Points, len(c.values))
|
|
for i, p := range c.values {
|
|
pts[i] = svg.Point{
|
|
X: (c.width / float64(len(c.values)-1)) * float64(i),
|
|
Y: ((p.Value - minVal) * c.height) / (maxVal - minVal),
|
|
}
|
|
}
|
|
|
|
g = g.Child(
|
|
new(svg.Polyline).Attr(
|
|
pts,
|
|
svg.NewLength(2).
|
|
Unit(svg.Px).
|
|
AsStrokeWidth(),
|
|
svg.VectorEffectNonScalingStroke,
|
|
svg.Stroke(fg),
|
|
svg.Fill("none"),
|
|
),
|
|
)
|
|
|
|
// points
|
|
|
|
if len(c.values) > 0 {
|
|
r := 1.0 / float64(len(c.values))
|
|
rx := r
|
|
ry := r
|
|
|
|
for i, p := range c.values {
|
|
// dot on the line graph
|
|
|
|
x := pts[i].X
|
|
y := pts[i].Y
|
|
|
|
ell := new(svg.Ellipse).
|
|
Attr(
|
|
svg.NewLength(x).
|
|
AsCX(),
|
|
svg.NewLength(y).
|
|
AsCY(),
|
|
svg.Percentage(rx).
|
|
AsRX(),
|
|
svg.Percentage(ry).
|
|
AsRY(),
|
|
svg.Stroke(fg),
|
|
svg.NewLength(2).
|
|
Unit(svg.Px).
|
|
AsStrokeWidth(),
|
|
svg.VectorEffectNonScalingStroke,
|
|
)
|
|
|
|
if bg != "" {
|
|
ell = ell.Attr(
|
|
svg.Fill(bg),
|
|
)
|
|
} else {
|
|
ell = ell.Attr(
|
|
svg.Fill(fg),
|
|
)
|
|
}
|
|
|
|
g = g.Child(
|
|
ell,
|
|
|
|
// label
|
|
upsideDownCenteredText(p.Label, x, y).
|
|
Attr(
|
|
svg.NewLength(c.height*0.05).
|
|
Unit(svg.Px).
|
|
AsFontSize(),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
return s.Child(g)
|
|
}
|
|
|
|
func compareLineValues(a, b LineValue) int {
|
|
if a.Value < b.Value {
|
|
return -1
|
|
}
|
|
if a.Value > b.Value {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func upsideDownCenteredText(txt string, x, y float64) *svg.Text {
|
|
return svg.NewText(txt).
|
|
Attr(
|
|
svg.TextAnchorMiddle,
|
|
svg.DominantBaselineMiddle,
|
|
|
|
svg.NewLength(x).
|
|
AsX(),
|
|
svg.NewLength(y).
|
|
AsY(),
|
|
|
|
upsideDownTransform(x, y),
|
|
)
|
|
}
|