ana-ledger/src/view/chart.go

161 lines
2.9 KiB
Go

package view
import (
"fmt"
"io"
"slices"
"strings"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
type Chart interface {
AddX(interface{})
AddY(string, []int)
Render(io.Writer) error
Overlap(Chart)
}
func NewChart(name string) Chart {
switch name {
case "line":
return NewLine()
case "bar":
return NewBar()
case "stack":
return NewStack()
case "pie":
return NewPie()
default:
panic("bad chart name " + name)
}
}
type Line struct {
*charts.Line
}
func NewLine() Line {
return Line{Line: charts.NewLine()}
}
func (line Line) AddX(v interface{}) {
line.SetXAxis(v)
}
func (line Line) AddY(name string, v []int) {
y := make([]opts.LineData, len(v))
for i := range y {
y[i].Value = v[i]
}
line.AddSeries(name, y).
SetSeriesOptions(charts.WithBarChartOpts(opts.BarChart{
Stack: "stackB",
}))
}
func (line Line) Overlap(other Chart) {
overlapper, ok := other.(charts.Overlaper)
if !ok {
panic(fmt.Sprintf("cannot overlap %T", other))
}
line.Line.Overlap(overlapper)
}
type Bar struct {
*charts.Bar
}
func NewBar() Bar {
return Bar{Bar: charts.NewBar()}
}
func (bar Bar) AddX(v interface{}) {
bar.SetXAxis(v)
}
func (bar Bar) AddY(name string, v []int) {
y := make([]opts.BarData, len(v))
for i := range v {
y[i].Value = v[i]
}
bar.AddSeries(name, y)
}
func (bar Bar) Overlap(other Chart) {
overlapper, ok := other.(charts.Overlaper)
if !ok {
panic(fmt.Sprintf("cannot overlap %T", other))
}
bar.Bar.Overlap(overlapper)
}
type Stack struct {
Bar
}
func NewStack() Stack {
bar := NewBar()
bar.SetSeriesOptions(charts.WithBarChartOpts(opts.BarChart{Stack: "x"}))
return Stack{Bar: bar}
}
func (stack Stack) AddY(name string, v []int) {
y := make([]opts.BarData, len(v))
for i := range v {
y[i].Value = v[i]
}
stack.AddSeries(name, y).
SetSeriesOptions(charts.WithBarChartOpts(opts.BarChart{
Stack: "stackA",
}))
}
type Pie struct {
*charts.Pie
series []opts.PieData
}
func NewPie() *Pie {
return &Pie{Pie: charts.NewPie()}
}
func (pie *Pie) AddX(v interface{}) {
}
func (pie *Pie) Render(w io.Writer) error {
slices.SortFunc(pie.series, func(a, b opts.PieData) int {
return strings.Compare(a.Name, b.Name)
})
commonPrefixLen := -1
for i := 0; i < len(pie.series[0].Name) && i < len(pie.series[len(pie.series)-1].Name); i++ {
if pie.series[0].Name[i] != pie.series[len(pie.series)-1].Name[i] {
break
}
commonPrefixLen = i
}
for i := range pie.series {
pie.series[i].Name = pie.series[i].Name[commonPrefixLen+1:]
}
pie.AddSeries("", pie.series)
pie.SetGlobalOptions(charts.WithLegendOpts(opts.Legend{
Show: false,
}))
return pie.Pie.Render(w)
}
func (pie *Pie) AddY(name string, v []int) {
for _, v := range v {
pie.series = append(pie.series, opts.PieData{
Name: fmt.Sprintf("%s ($%d)", name, v),
Value: v,
})
}
}
func (pie *Pie) Overlap(other Chart) {
panic("nope")
}