basic scatter/line plots
This commit is contained in:
parent
46fa140d01
commit
60b929db2b
0
lib/wasm.js → lib/wasm_tinygo.js
vendored
0
lib/wasm.js → lib/wasm_tinygo.js
vendored
50
notebooks/deno_tinygo_wasm.ipynb
vendored
50
notebooks/deno_tinygo_wasm.ipynb
vendored
File diff suppressed because one or more lines are too long
|
@ -12,6 +12,7 @@ import (
|
||||||
func InitPlotExports(this js.Value, args []js.Value) interface{} {
|
func InitPlotExports(this js.Value, args []js.Value) interface{} {
|
||||||
exports := args[0]
|
exports := args[0]
|
||||||
exports.Set("Hist", js.FuncOf(src.HistPlot))
|
exports.Set("Hist", js.FuncOf(src.HistPlot))
|
||||||
|
exports.Set("Plot", js.FuncOf(src.Plot))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
plot/mod.wasm
BIN
plot/mod.wasm
Binary file not shown.
208
plot/src/Plot.go
Normal file
208
plot/src/Plot.go
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
//go:build js && wasm
|
||||||
|
// +build js,wasm
|
||||||
|
|
||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"github.com/gonum/stat"
|
||||||
|
"gonum.org/v1/plot"
|
||||||
|
"gonum.org/v1/plot/plotter"
|
||||||
|
"gonum.org/v1/plot/vg"
|
||||||
|
"gonum.org/v1/plot/vg/draw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Plot(this js.Value, args []js.Value) interface{} {
|
||||||
|
plt := plot.New()
|
||||||
|
opts := args[0]
|
||||||
|
var (
|
||||||
|
title = opts.Get("title").String()
|
||||||
|
XLabel = opts.Get("XLabel").String()
|
||||||
|
YLabel = opts.Get("YLabel").String()
|
||||||
|
width = 7.5
|
||||||
|
height = 4.25
|
||||||
|
)
|
||||||
|
if !opts.Get("width").IsUndefined() {
|
||||||
|
width = opts.Get("width").Float()
|
||||||
|
}
|
||||||
|
if !opts.Get("height").IsUndefined() {
|
||||||
|
height = opts.Get("height").Float()
|
||||||
|
}
|
||||||
|
if opts.Get("title").IsUndefined() {
|
||||||
|
title = "Plot"
|
||||||
|
}
|
||||||
|
if opts.Get("XLabel").IsUndefined() {
|
||||||
|
XLabel = "X"
|
||||||
|
}
|
||||||
|
if opts.Get("YLabel").IsUndefined() {
|
||||||
|
YLabel = "Y"
|
||||||
|
}
|
||||||
|
|
||||||
|
plt.Title.Text = title
|
||||||
|
plt.X.Label.Text = XLabel
|
||||||
|
plt.Y.Label.Text = YLabel
|
||||||
|
plt.Add(plotter.NewGrid())
|
||||||
|
|
||||||
|
lines := make([]plot.Plotter, 0)
|
||||||
|
for i, arg := range args {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
PlotterLinesFromJSObject(arg, plt, &lines)
|
||||||
|
}
|
||||||
|
plt.Add(lines...)
|
||||||
|
plt.Legend.Top = true
|
||||||
|
|
||||||
|
writer, err := plt.WriterTo(vg.Length(width)*vg.Inch, vg.Length(height)*vg.Inch, "png")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b64string := WriterToBase64String(writer)
|
||||||
|
return b64string
|
||||||
|
}
|
||||||
|
|
||||||
|
func PlotterLinesFromJSObject(object js.Value, plt *plot.Plot, plotters *[]plot.Plotter) {
|
||||||
|
var (
|
||||||
|
typ = object.Get("type").String()
|
||||||
|
data = object.Get("data")
|
||||||
|
legend = ""
|
||||||
|
glyphColor = object.Get("glyphColor")
|
||||||
|
glyphRadius = object.Get("glypRadius")
|
||||||
|
glyphShape = object.Get("glyphShape")
|
||||||
|
lineWidth = object.Get("lineWidth")
|
||||||
|
lineColor = object.Get("lineColor")
|
||||||
|
lineDashes = object.Get("lineDashes")
|
||||||
|
)
|
||||||
|
|
||||||
|
shapeGlyph := func(g *draw.GlyphStyle) {
|
||||||
|
if !glyphShape.IsUndefined() {
|
||||||
|
shape := glyphShape.String()
|
||||||
|
switch shape {
|
||||||
|
case "cross":
|
||||||
|
g.Shape = draw.CrossGlyph{}
|
||||||
|
case "plus":
|
||||||
|
g.Shape = draw.PlusGlyph{}
|
||||||
|
case "ring":
|
||||||
|
g.Shape = draw.RingGlyph{}
|
||||||
|
case "square":
|
||||||
|
g.Shape = draw.SquareGlyph{}
|
||||||
|
case "triangle":
|
||||||
|
g.Shape = draw.TriangleGlyph{}
|
||||||
|
case "pyramid":
|
||||||
|
g.Shape = draw.PyramidGlyph{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
radiusGlyph := func(r *vg.Length) {
|
||||||
|
if !glyphRadius.IsUndefined() {
|
||||||
|
*r = vg.Points(glyphRadius.Float())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dashLine := func(l *[]vg.Length) {
|
||||||
|
if !lineDashes.IsUndefined() {
|
||||||
|
w := lineDashes.Index(0).Float()
|
||||||
|
h := lineDashes.Index(1).Float()
|
||||||
|
*l = []vg.Length{vg.Points(w), vg.Points(h)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyColor := func(p *color.Color, hex js.Value) {
|
||||||
|
if !hex.IsUndefined() {
|
||||||
|
*p = HexToRGBA(hex.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !object.Get("legend").IsUndefined() {
|
||||||
|
legend = object.Get("legend").String()
|
||||||
|
}
|
||||||
|
if data.IsUndefined() {
|
||||||
|
panic("data is undefined")
|
||||||
|
}
|
||||||
|
|
||||||
|
XYs := XYFromJSObject(data)
|
||||||
|
if XYs == nil {
|
||||||
|
XYs = XYFromJSValues(data.Index(0), data.Index(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
case "scatter":
|
||||||
|
s, err := plotter.NewScatter(XYs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
applyColor(&s.GlyphStyle.Color, glyphColor)
|
||||||
|
shapeGlyph(&s.GlyphStyle)
|
||||||
|
radiusGlyph(&s.GlyphStyle.Radius)
|
||||||
|
*plotters = append(*plotters, s)
|
||||||
|
if legend != "" {
|
||||||
|
plt.Legend.Add(legend, s)
|
||||||
|
}
|
||||||
|
case "line":
|
||||||
|
l, err := plotter.NewLine(XYs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
applyColor(&l.LineStyle.Color, lineColor)
|
||||||
|
dashLine(&l.LineStyle.Dashes)
|
||||||
|
if !lineWidth.IsUndefined() {
|
||||||
|
l.LineStyle.Width = vg.Points(lineWidth.Float())
|
||||||
|
}
|
||||||
|
*plotters = append(*plotters, l)
|
||||||
|
if legend != "" {
|
||||||
|
plt.Legend.Add(legend, l)
|
||||||
|
}
|
||||||
|
case "linePoints":
|
||||||
|
l, lp, err := plotter.NewLinePoints(XYs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if !lineWidth.IsUndefined() {
|
||||||
|
l.LineStyle.Width = vg.Points(lineWidth.Float())
|
||||||
|
}
|
||||||
|
applyColor(&l.LineStyle.Color, lineColor)
|
||||||
|
dashLine(&l.LineStyle.Dashes)
|
||||||
|
shapeGlyph(&lp.GlyphStyle)
|
||||||
|
applyColor(&lp.GlyphStyle.Color, glyphColor)
|
||||||
|
radiusGlyph(&lp.GlyphStyle.Radius)
|
||||||
|
*plotters = append(*plotters, l, lp)
|
||||||
|
if legend != "" {
|
||||||
|
plt.Legend.Add(legend, l, lp)
|
||||||
|
}
|
||||||
|
case "fitLinear":
|
||||||
|
X := make([]float64, len(XYs))
|
||||||
|
Y := make([]float64, len(XYs))
|
||||||
|
min := XYs[0].Y
|
||||||
|
max := XYs[0].Y
|
||||||
|
for i, xy := range XYs {
|
||||||
|
X[i] = xy.X
|
||||||
|
Y[i] = xy.Y
|
||||||
|
if xy.Y < min {
|
||||||
|
min = xy.Y
|
||||||
|
}
|
||||||
|
if xy.Y > max {
|
||||||
|
max = xy.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a, b := stat.LinearRegression(X, Y, nil, false)
|
||||||
|
l, err := plotter.NewLine(plotter.XYs{
|
||||||
|
{X: X[0], Y: a + b*X[0]},
|
||||||
|
{X: X[len(X)-1], Y: a + b*X[len(X)-1]},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
applyColor(&l.LineStyle.Color, lineColor)
|
||||||
|
dashLine(&l.LineStyle.Dashes)
|
||||||
|
if !lineWidth.IsUndefined() {
|
||||||
|
l.LineStyle.Width = vg.Points(lineWidth.Float())
|
||||||
|
}
|
||||||
|
*plotters = append(*plotters, l)
|
||||||
|
if legend != "" {
|
||||||
|
plt.Legend.Add(legend, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
"io"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"gonum.org/v1/plot/plotter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriterToBase64String(writer io.WriterTo) string {
|
func WriterToBase64String(writer io.WriterTo) string {
|
||||||
|
@ -36,3 +39,23 @@ func HexToRGBA(hex string) color.RGBA {
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func XYFromJSObject(object js.Value) plotter.XYs {
|
||||||
|
var (
|
||||||
|
x = object.Get("x")
|
||||||
|
y = object.Get("y")
|
||||||
|
)
|
||||||
|
if x.IsUndefined() || y.IsUndefined() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return XYFromJSValues(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func XYFromJSValues(x, y js.Value) plotter.XYs {
|
||||||
|
xy := make(plotter.XYs, x.Length())
|
||||||
|
for i := range xy {
|
||||||
|
xy[i].X = x.Index(i).Float()
|
||||||
|
xy[i].Y = y.Index(i).Float()
|
||||||
|
}
|
||||||
|
return xy
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import "../lib/wasm.js";
|
import "../lib/wasm_tinygo.js";
|
||||||
import type { Stat } from "./types.ts";
|
import type { Stat } from "./types.ts";
|
||||||
|
|
||||||
// @ts-expect-error: no types
|
// @ts-expect-error: no types
|
||||||
|
|
Loading…
Reference in a new issue