wip
parent
75447320dc
commit
9dc35a8a80
|
|
@ -0,0 +1,151 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/go-echarts/go-echarts/v2/opts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Data Data
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Points [][]float64
|
||||||
|
Labels []string
|
||||||
|
Weight struct {
|
||||||
|
Floor int
|
||||||
|
Range int
|
||||||
|
}
|
||||||
|
Title string
|
||||||
|
Subtitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig() (Config, error) {
|
||||||
|
var result Config
|
||||||
|
result.Data.Points = [][]float64{
|
||||||
|
[]float64{1, 1},
|
||||||
|
[]float64{2, 2},
|
||||||
|
[]float64{2, 2},
|
||||||
|
}
|
||||||
|
result.Data.Weight.Floor = 12
|
||||||
|
result.Data.Weight.Range = 20
|
||||||
|
|
||||||
|
if js := os.Getenv("POINTS_JSON"); js == "" {
|
||||||
|
} else if err := json.Unmarshal([]byte(js), &result.Data.Points); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Data.Points) > 0 {
|
||||||
|
for i := range result.Data.Points {
|
||||||
|
if want := len(result.Data.Points[0]); want != len(result.Data.Points[i]) {
|
||||||
|
return result, fmt.Errorf("point [%d] expected %d dimensions but found %d", i, want, len(result.Data.Points[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Data.Labels = make([]string, len(result.Data.Points[0]))
|
||||||
|
for i := range result.Data.Labels {
|
||||||
|
result.Data.Labels[i] = fmt.Sprintf("%c", byte(int('A')+i))
|
||||||
|
}
|
||||||
|
|
||||||
|
if js := os.Getenv("LABELS_JSON"); js == "" {
|
||||||
|
} else if err := json.Unmarshal([]byte(js), &result.Data.Labels); err != nil {
|
||||||
|
return result, err
|
||||||
|
} else if len(result.Data.Labels) < len(result.Data.Points[0]) {
|
||||||
|
return result, fmt.Errorf("expected at least %d labels but got %d", len(result.Data.Points[0]), len(result.Data.Labels))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if js := os.Getenv("WEIGHT_RANGE"); js == "" {
|
||||||
|
} else if err := json.Unmarshal([]byte(js), &result.Data.Weight.Range); err != nil {
|
||||||
|
return result, err
|
||||||
|
} else if result.Data.Weight.Range < 0 {
|
||||||
|
return result, fmt.Errorf("found negative weight range %d", result.Data.Weight.Range)
|
||||||
|
}
|
||||||
|
|
||||||
|
if js := os.Getenv("WEIGHT_FLOOR"); js == "" {
|
||||||
|
} else if err := json.Unmarshal([]byte(js), &result.Data.Weight.Floor); err != nil {
|
||||||
|
return result, err
|
||||||
|
} else if result.Data.Weight.Floor < 0 {
|
||||||
|
return result, fmt.Errorf("found negative weight floor %d", result.Data.Weight.Floor)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data.Title = os.Getenv("TITLE")
|
||||||
|
result.Data.Subtitle = os.Getenv("SUBTITLE")
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Data) AsScatterData() (string, []float64, string, []opts.ScatterData, error) {
|
||||||
|
if !(2 <= len(d.Labels) && len(d.Labels) <= 3) {
|
||||||
|
return "", nil, "", nil, fmt.Errorf("cannot map %d dimensions to [2,3]", len(d.Labels))
|
||||||
|
}
|
||||||
|
d = d.weighXYasZ()
|
||||||
|
|
||||||
|
xs := make([]float64, 0)
|
||||||
|
for i := range d.Points {
|
||||||
|
xs = append(xs, d.Points[i][0])
|
||||||
|
}
|
||||||
|
slices.Sort(xs)
|
||||||
|
xs = slices.Compact(xs)
|
||||||
|
|
||||||
|
zs := make([]float64, 0)
|
||||||
|
for i := range d.Points {
|
||||||
|
zs = append(zs, d.Points[i][2])
|
||||||
|
}
|
||||||
|
slices.Sort(zs)
|
||||||
|
zmin, zmax := zs[0], zs[len(zs)-1]
|
||||||
|
zrange := zmax - zmin
|
||||||
|
if zrange == 0 {
|
||||||
|
zrange = 1
|
||||||
|
}
|
||||||
|
log.Printf("d=%+v zmin=%v zmax=%v zrange=%v", d.Points, zmin, zmax, zrange)
|
||||||
|
|
||||||
|
ys := make([]opts.ScatterData, 0)
|
||||||
|
for i := range xs {
|
||||||
|
for j := range d.Points {
|
||||||
|
x := d.Points[j][0]
|
||||||
|
if x == xs[i] {
|
||||||
|
ys = append(ys, opts.ScatterData{
|
||||||
|
Value: d.Points[j][1],
|
||||||
|
SymbolSize: d.Weight.Floor + int((float64(d.Weight.Range)*(d.Points[j][2]-zmin))/zrange),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d.Labels[0], xs, d.Labels[1], ys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Data) weighXYasZ() Data {
|
||||||
|
xyz := map[float64]map[float64]float64{}
|
||||||
|
for i := range d.Points {
|
||||||
|
x := d.Points[i][0]
|
||||||
|
if _, ok := xyz[x]; !ok {
|
||||||
|
xyz[x] = map[float64]float64{}
|
||||||
|
}
|
||||||
|
y := d.Points[i][1]
|
||||||
|
z := 1.0
|
||||||
|
if len(d.Labels) > 2 {
|
||||||
|
z = d.Points[i][2]
|
||||||
|
}
|
||||||
|
if _, ok := xyz[x][y]; !ok {
|
||||||
|
xyz[x][y] = 0.0
|
||||||
|
}
|
||||||
|
xyz[x][y] += z
|
||||||
|
}
|
||||||
|
result := Data{
|
||||||
|
Labels: d.Labels[:],
|
||||||
|
Points: [][]float64{},
|
||||||
|
Weight: d.Weight,
|
||||||
|
}
|
||||||
|
for x := range xyz {
|
||||||
|
for y, z := range xyz[x] {
|
||||||
|
result.Points = append(result.Points, []float64{x, y, z})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,3 +1,5 @@
|
||||||
module charts
|
module charts
|
||||||
|
|
||||||
go 1.21.1
|
go 1.21.1
|
||||||
|
|
||||||
|
require github.com/go-echarts/go-echarts/v2 v2.3.3 // indirect
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg=
|
||||||
|
github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
|
||||||
46
main.go
46
main.go
|
|
@ -2,8 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"log"
|
||||||
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/go-echarts/go-echarts/v2/charts"
|
||||||
|
"github.com/go-echarts/go-echarts/v2/opts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -15,5 +19,43 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx context.Context) error {
|
func run(ctx context.Context) error {
|
||||||
return io.EOF
|
config, err := newConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("%+v", config)
|
||||||
|
|
||||||
|
x, xs, y, ys, err := config.Data.AsScatterData()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("%s: %v", x, xs)
|
||||||
|
log.Printf("%s: %v", y, ys)
|
||||||
|
|
||||||
|
scatter := charts.NewScatter()
|
||||||
|
scatter.SetGlobalOptions(charts.WithTitleOpts(opts.Title{
|
||||||
|
Title: config.Data.Title,
|
||||||
|
Subtitle: config.Data.Subtitle,
|
||||||
|
}))
|
||||||
|
scatter.SetGlobalOptions(charts.WithLegendOpts(opts.Legend{Show: false}))
|
||||||
|
scatter.SetGlobalOptions(charts.WithXAxisOpts(opts.XAxis{
|
||||||
|
Name: x,
|
||||||
|
NameLocation: "middle",
|
||||||
|
NameGap: config.Data.Weight.Floor + config.Data.Weight.Range,
|
||||||
|
}))
|
||||||
|
scatter.SetGlobalOptions(charts.WithYAxisOpts(opts.YAxis{
|
||||||
|
Name: y,
|
||||||
|
NameLocation: "middle",
|
||||||
|
NameGap: config.Data.Weight.Floor + config.Data.Weight.Range,
|
||||||
|
}))
|
||||||
|
scatter.SetXAxis(xs).
|
||||||
|
AddSeries(y, ys)
|
||||||
|
f, _ := os.Create("/tmp/f")
|
||||||
|
defer f.Close()
|
||||||
|
if err := scatter.Render(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("firefox /tmp/f")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue