├── examples ├── bar │ ├── Bar.png │ ├── bar.html │ └── main.go ├── wasm │ ├── main.wasm │ ├── run.sh │ ├── index.html │ └── main.go ├── static_image │ ├── out.png │ ├── README.md │ └── main.go ├── numpy_bdata │ ├── Dockerfile │ ├── Makefile │ ├── compute.py │ └── main.go ├── transforms │ ├── bar.html │ └── main.go ├── responsive │ ├── bar.html │ └── main.go ├── bar_custom │ ├── bar_custom.html │ └── main.go ├── go.mod ├── shapes │ ├── bar.html │ └── main.go ├── subplots_share_axes │ ├── subplots_share_axes.html │ └── main.go ├── unmarshal │ └── main.go ├── scatter │ ├── main.go │ └── scatter.html ├── scatter3d │ ├── main.go │ └── scatter3d.html ├── animation_slider │ └── main_test.go ├── colorscale │ └── main.go ├── waterfall_bar_chart │ ├── waterfall.html │ └── main.go ├── animation │ └── animation.go ├── stargazers │ └── main.go ├── range_slider │ └── main.go └── go.sum ├── .gitignore ├── generator ├── templates │ ├── config_base.tmpl │ ├── layout_base.tmpl │ ├── animation_base.tmpl │ ├── enum.tmpl │ ├── trace.tmpl │ ├── flaglist.tmpl │ ├── dummy_types.go │ ├── trace_base.tmpl │ ├── unmarshal.tmpl │ ├── unmarshal_test.go │ ├── frames.go │ └── plotly.go ├── generator_suite_test.go ├── parser_test.go ├── cmd │ ├── downloader │ │ └── main.go │ └── generator │ │ └── main.go ├── mocks │ └── creator.go ├── renderer_test.go └── schema.go ├── pkg ├── types │ ├── trace.go │ ├── fig.go │ ├── arrayok.go │ ├── basic.go │ ├── color.go │ ├── data_array_test.go │ └── data_array.go └── offline │ └── plot.go ├── go.mod ├── generated ├── v2.19.0 │ └── graph_objects │ │ ├── unmarshal_gen_test.go │ │ ├── frames_gen.go │ │ ├── plotly_gen.go │ │ ├── animation_gen.go │ │ └── unmarshal_gen.go ├── v2.29.1 │ └── graph_objects │ │ ├── unmarshal_gen_test.go │ │ ├── frames_gen.go │ │ ├── plotly_gen.go │ │ ├── animation_gen.go │ │ └── unmarshal_gen.go ├── v2.31.1 │ └── graph_objects │ │ ├── unmarshal_gen_test.go │ │ ├── frames_gen.go │ │ ├── plotly_gen.go │ │ ├── animation_gen.go │ │ └── unmarshal_gen.go └── v2.34.0 │ └── graph_objects │ ├── unmarshal_gen_test.go │ ├── frames_gen.go │ ├── plotly_gen.go │ ├── animation_gen.go │ └── unmarshal_gen.go ├── .github └── workflows │ └── test.yaml ├── schemas.yaml ├── LICENSE ├── go.sum └── README.md /examples/bar/Bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetalBlueberry/go-plotly/HEAD/examples/bar/Bar.png -------------------------------------------------------------------------------- /examples/wasm/main.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetalBlueberry/go-plotly/HEAD/examples/wasm/main.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | examples/Bar/Bar 2 | examples/bar/bar 3 | examples/bar_custom/go_custom 4 | .vscode/launch.json 5 | -------------------------------------------------------------------------------- /examples/static_image/out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetalBlueberry/go-plotly/HEAD/examples/static_image/out.png -------------------------------------------------------------------------------- /generator/templates/config_base.tmpl: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | {{ . }} 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | -------------------------------------------------------------------------------- /generator/templates/layout_base.tmpl: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | {{ . }} 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | -------------------------------------------------------------------------------- /examples/wasm/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GOOS=js GOARCH=wasm go build -o main.wasm 3 | goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))' -------------------------------------------------------------------------------- /generator/templates/animation_base.tmpl: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | {{ . }} 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | -------------------------------------------------------------------------------- /examples/numpy_bdata/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | 3 | WORKDIR /app 4 | 5 | RUN pip install numpy 6 | 7 | COPY compute.py . 8 | 9 | CMD ["python", "compute.py"] 10 | -------------------------------------------------------------------------------- /examples/numpy_bdata/Makefile: -------------------------------------------------------------------------------- 1 | IMAGE_NAME = plotly-data-generator 2 | 3 | all: build run 4 | 5 | build: 6 | @docker build -t $(IMAGE_NAME) . 7 | 8 | run: 9 | @docker run --rm $(IMAGE_NAME) 10 | 11 | 12 | 13 | clean: 14 | @docker rmi $(IMAGE_NAME) 15 | -------------------------------------------------------------------------------- /generator/templates/enum.tmpl: -------------------------------------------------------------------------------- 1 | {{- $root := . -}} 2 | // {{.Name }} {{.Description}} 3 | // {{ .JSONPath }} 4 | type {{.Name }} {{.Type}} 5 | 6 | {{ .ConstOrVar }} ( 7 | {{ range .Values -}} 8 | {{.Name}} {{$root.Name}} = {{.Value}} 9 | {{ end }} 10 | ) 11 | -------------------------------------------------------------------------------- /examples/static_image/README.md: -------------------------------------------------------------------------------- 1 | # Static Image 2 | 3 | To save Plotly images as static images, you should use the [orca](https://github.com/plotly/orca) project. This example uses a docker image with orca to render a plot to PNG. You can refer to orca documentation to extend this further. 4 | -------------------------------------------------------------------------------- /generator/generator_suite_test.go: -------------------------------------------------------------------------------- 1 | package generator_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestGenerator(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Generator Suite") 13 | } 14 | -------------------------------------------------------------------------------- /generator/templates/trace.tmpl: -------------------------------------------------------------------------------- 1 | // {{.Name }} {{.Description}} 2 | type {{.Name }} struct { 3 | {{ range .Fields }} 4 | // {{.Name }} {{ range .Description }} 5 | // {{.}} {{ end }} 6 | // {{ .JSONPath }} 7 | {{.Name }} {{.Type}} `json:"{{.JSONName}},omitempty"` 8 | {{ end }} 9 | } 10 | -------------------------------------------------------------------------------- /pkg/types/trace.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // TraceType is the type for the TraceType field on every trace 4 | type TraceType string 5 | 6 | // Trace Every trace implements this interface 7 | // It is useful for autocompletion, it is a better idea to use 8 | // type assertions/switches to identify trace types 9 | type Trace interface { 10 | GetType() TraceType 11 | } 12 | -------------------------------------------------------------------------------- /examples/bar/bar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /examples/transforms/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /generator/templates/flaglist.tmpl: -------------------------------------------------------------------------------- 1 | {{- $root := . -}} 2 | // {{.Name }} {{.Description}} 3 | // {{ .JSONPath }} 4 | type {{.Name }} {{.Type}} 5 | 6 | {{ .ConstOrVar }} ( 7 | // Flags 8 | {{ range .Flags -}} 9 | {{.Name}} {{$root.Name}} = {{.Value}} 10 | {{ end }} 11 | // Extra 12 | {{ range .Extra -}} 13 | {{.Name}} {{$root.Name}} = {{.Value}} 14 | {{ end }} 15 | ) 16 | -------------------------------------------------------------------------------- /examples/responsive/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /generator/parser_test.go: -------------------------------------------------------------------------------- 1 | package generator_test 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | 7 | "github.com/MetalBlueberry/go-plotly/generator" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("Parser", func() { 13 | It("Should parse traces", func() { 14 | reader := bytes.NewReader(schema) 15 | 16 | root, err := generator.LoadSchema(reader) 17 | Expect(err).To(BeNil()) 18 | 19 | log.Println(root) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /examples/wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | -------------------------------------------------------------------------------- /examples/bar_custom/bar_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metalblueberry/plotly/examples 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/MetalBlueberry/go-plotly v0.4.0 7 | github.com/go-gota/gota v0.12.0 8 | github.com/lucasb-eyer/go-colorful v1.2.0 9 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c 10 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 11 | ) 12 | 13 | require ( 14 | golang.org/x/net v0.28.0 // indirect 15 | golang.org/x/sys v0.23.0 // indirect 16 | gonum.org/v1/gonum v0.15.0 // indirect 17 | ) 18 | 19 | replace github.com/MetalBlueberry/go-plotly => ./../ 20 | -------------------------------------------------------------------------------- /examples/shapes/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /generator/templates/dummy_types.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Types defined here are just meant to allow compilation for the template package 10 | // This simplifies the process of writing the static templates 11 | 12 | type Layout struct{} 13 | type Config struct{} 14 | type Animation struct{} 15 | 16 | func UnmarshalTrace(json.RawMessage) (types.Trace, error) { return &Bar{}, nil } 17 | 18 | type Bar struct { 19 | Type types.TraceType `json:"type"` 20 | } 21 | 22 | func (b *Bar) GetType() types.TraceType { return "bar" } 23 | -------------------------------------------------------------------------------- /examples/subplots_share_axes/subplots_share_axes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /pkg/types/fig.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // Fig Represents a plotly figure 4 | // use the Info method to get details about the plotly version 5 | type Fig interface { 6 | Info() Version 7 | } 8 | 9 | type Version struct { 10 | Name string `yaml:"Name"` // name of the version 11 | Tag string `yaml:"Tag"` // git tag of the plotly version 12 | URL string `yaml:"URL"` // url under which the plotly schema json file can be downloaded directly 13 | Path string `yaml:"Path"` // path under which the schema file will be saved locally for future use 14 | Generated string `yaml:"Generated"` // path for the generated package 15 | Cdn string `yaml:"CDN"` // url for the cdn which should be included in the head of the generated html 16 | } 17 | -------------------------------------------------------------------------------- /generator/templates/trace_base.tmpl: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | {{ .DoNotEdit }} 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | "encoding/json" 8 | ) 9 | 10 | var TraceType{{ .TraceTypeName }} types.TraceType = "{{ .TraceName }}" 11 | 12 | func (t *{{ .TraceTypeName }}) GetType() types.TraceType { 13 | return TraceType{{ .TraceTypeName }} 14 | } 15 | 16 | func (t *{{ .TraceTypeName }}) MarshalJSON() ([]byte, error) { 17 | // Define the custom JSON structure including the "type" field 18 | type Alias {{ .TraceTypeName }} 19 | return json.Marshal(&struct { 20 | Type types.TraceType `json:"type"` 21 | *Alias 22 | }{ 23 | Type: t.GetType(), // Add your desired default value here 24 | Alias: (*Alias)(t), // Embed the original struct fields 25 | }) 26 | } 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/MetalBlueberry/go-plotly 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/golang/mock v1.6.0 7 | github.com/huandu/xstrings v1.4.0 8 | github.com/onsi/ginkgo/v2 v2.17.2 9 | github.com/onsi/gomega v1.33.0 10 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c 11 | gopkg.in/yaml.v3 v3.0.1 12 | ) 13 | 14 | require ( 15 | github.com/go-logr/logr v1.4.1 // indirect 16 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 17 | github.com/google/go-cmp v0.6.0 // indirect 18 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect 19 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 20 | golang.org/x/net v0.28.0 // indirect 21 | golang.org/x/sys v0.23.0 // indirect 22 | golang.org/x/text v0.17.0 // indirect 23 | golang.org/x/tools v0.24.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /examples/numpy_bdata/compute.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import json 3 | import base64 4 | 5 | # Generate Z data using NumPy 6 | # Let's create a similar 3x5 matrix with some values 7 | z = np.array([ 8 | [1, np.nan, 30, 50, 1], 9 | [20, 1, 60, 80, 30], 10 | [30, 60, 1, -10, 20] 11 | ]) 12 | 13 | 14 | 15 | dtype = z.dtype 16 | bdata = z.tobytes() # Binary data representation 17 | shape = z.shape 18 | 19 | # Encode the binary data as a base64 string 20 | z_bdata_base64 = base64.b64encode(bdata).decode('utf-8') 21 | 22 | # Create the dictionary that represents the JSON object 23 | z_data = { 24 | 'dtype': str(dtype), 25 | 'shape': shape, 26 | 'bdata': z_bdata_base64 27 | } 28 | 29 | # Convert the dictionary to a JSON object 30 | json_data = json.dumps(z_data, indent=2) 31 | 32 | # Output the JSON object 33 | print(json_data) -------------------------------------------------------------------------------- /examples/transforms/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | fig = dict({ 12 | "data": [{"type": "bar", 13 | "x": [1, 2, 3], 14 | "y": [1, 3, 2]}], 15 | "layout": {"title": {"text": "A Figure Specified By Python Dictionary"}} 16 | }) 17 | */ 18 | fig := &grob.Fig{ 19 | Data: []types.Trace{ 20 | &grob.Bar{ 21 | X: types.DataArray([]float64{1, 2, 3}), 22 | Y: types.DataArray([]float64{1, 2, 3}), 23 | }, 24 | }, 25 | Layout: &grob.Layout{ 26 | Title: &grob.LayoutTitle{ 27 | Text: types.S("A Figure Specified By Go Struct"), 28 | }, 29 | }, 30 | } 31 | 32 | offline.ToHtml(fig, "bar.html") 33 | offline.Show(fig) 34 | } 35 | -------------------------------------------------------------------------------- /examples/bar/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | fig = dict({ 12 | "data": [{"type": "bar", 13 | "x": [1, 2, 3], 14 | "y": [1, 3, 2]}], 15 | "layout": {"title": {"text": "A Figure Specified By Python Dictionary"}} 16 | }) 17 | */ 18 | fig := &grob.Fig{ 19 | Data: []types.Trace{ 20 | &grob.Bar{ 21 | X: types.DataArray([]float64{1, 2, 3}), 22 | Y: types.DataArray([]float64{1, 2, 3}), 23 | }, 24 | }, 25 | Layout: &grob.Layout{ 26 | Title: &grob.LayoutTitle{ 27 | Text: "A Figure Specified By Go Struct", 28 | }, 29 | }, 30 | } 31 | 32 | offline.ToHtml(fig, "bar.html") 33 | offline.Show(fig) 34 | offline.Serve(fig) 35 | } 36 | -------------------------------------------------------------------------------- /generator/templates/unmarshal.tmpl: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // Code generated by go-plotly/generator. DO NOT EDIT 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | "github.com/MetalBlueberry/go-plotly/pkg/types" 9 | ) 10 | 11 | type unmarshalType struct { 12 | Type types.TraceType `json:"type,omitempty"` 13 | } 14 | 15 | // UnmarshalTrace decodes an array of bytes into a Trace interface. 16 | func UnmarshalTrace(data []byte) (types.Trace,error) { 17 | traceType := unmarshalType{} 18 | err := json.Unmarshal(data, &traceType) 19 | if err != nil { 20 | return nil, err 21 | } 22 | switch traceType.Type { 23 | {{- range $name := .Types }} 24 | case TraceType{{ $name }}: 25 | trace := &{{ $name }}{} 26 | err = json.Unmarshal(data,trace) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return trace, nil 31 | {{- end }} 32 | default: 33 | return nil, errors.New("Trace Type is not registered") 34 | } 35 | } -------------------------------------------------------------------------------- /examples/responsive/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | fig = dict({ 12 | "data": [{"type": "bar", 13 | "x": [1, 2, 3], 14 | "y": [1, 3, 2]}], 15 | "layout": {"title": {"text": "A Figure Specified By Python Dictionary"}} 16 | }) 17 | */ 18 | fig := &grob.Fig{ 19 | Data: []types.Trace{ 20 | &grob.Bar{ 21 | X: types.DataArray([]float64{1, 2, 3}), 22 | Y: types.DataArray([]float64{1, 2, 3}), 23 | }, 24 | }, 25 | Layout: &grob.Layout{ 26 | Title: &grob.LayoutTitle{ 27 | Text: types.S("A Figure Specified By Go Struct"), 28 | }, 29 | }, 30 | Config: &grob.Config{ 31 | Responsive: types.True, 32 | }, 33 | } 34 | 35 | offline.ToHtml(fig, "bar.html") 36 | offline.Show(fig) 37 | } 38 | -------------------------------------------------------------------------------- /generator/templates/unmarshal_test.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshalTrace(t *testing.T) { 10 | data := `{"type":"bar"}` 11 | trace, err := UnmarshalTrace([]byte(data)) 12 | if err != nil { 13 | t.Logf("Error during Unmarshal, %s", err) 14 | t.FailNow() 15 | } 16 | if traceType := trace.GetType(); traceType != "bar" { 17 | t.Logf("Error recovering type, Expected \"bar\", got %s", traceType) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestMarshalTrace(t *testing.T) { 23 | bar := &Bar{} 24 | v, err := json.Marshal(bar) 25 | if err != nil { 26 | t.Logf("Error during Marshal, %s", err) 27 | t.FailNow() 28 | } 29 | if !strings.Contains(string(v), "type") { 30 | t.Logf("Output doesn't contain the type field, Expected to contain \"type\", got: %s", v) 31 | t.FailNow() 32 | } 33 | if strings.Contains(string(v), "null") { 34 | t.Logf("Output contains null fields, Expected to not contain any, got: %s", v) 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generated/v2.19.0/graph_objects/unmarshal_gen_test.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshalTrace(t *testing.T) { 10 | data := `{"type":"bar"}` 11 | trace, err := UnmarshalTrace([]byte(data)) 12 | if err != nil { 13 | t.Logf("Error during Unmarshal, %s", err) 14 | t.FailNow() 15 | } 16 | if traceType := trace.GetType(); traceType != "bar" { 17 | t.Logf("Error recovering type, Expected \"bar\", got %s", traceType) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestMarshalTrace(t *testing.T) { 23 | bar := &Bar{} 24 | v, err := json.Marshal(bar) 25 | if err != nil { 26 | t.Logf("Error during Marshal, %s", err) 27 | t.FailNow() 28 | } 29 | if !strings.Contains(string(v), "type") { 30 | t.Logf("Output doesn't contain the type field, Expected to contain \"type\", got: %s", v) 31 | t.FailNow() 32 | } 33 | if strings.Contains(string(v), "null") { 34 | t.Logf("Output contains null fields, Expected to not contain any, got: %s", v) 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generated/v2.29.1/graph_objects/unmarshal_gen_test.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshalTrace(t *testing.T) { 10 | data := `{"type":"bar"}` 11 | trace, err := UnmarshalTrace([]byte(data)) 12 | if err != nil { 13 | t.Logf("Error during Unmarshal, %s", err) 14 | t.FailNow() 15 | } 16 | if traceType := trace.GetType(); traceType != "bar" { 17 | t.Logf("Error recovering type, Expected \"bar\", got %s", traceType) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestMarshalTrace(t *testing.T) { 23 | bar := &Bar{} 24 | v, err := json.Marshal(bar) 25 | if err != nil { 26 | t.Logf("Error during Marshal, %s", err) 27 | t.FailNow() 28 | } 29 | if !strings.Contains(string(v), "type") { 30 | t.Logf("Output doesn't contain the type field, Expected to contain \"type\", got: %s", v) 31 | t.FailNow() 32 | } 33 | if strings.Contains(string(v), "null") { 34 | t.Logf("Output contains null fields, Expected to not contain any, got: %s", v) 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generated/v2.31.1/graph_objects/unmarshal_gen_test.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshalTrace(t *testing.T) { 10 | data := `{"type":"bar"}` 11 | trace, err := UnmarshalTrace([]byte(data)) 12 | if err != nil { 13 | t.Logf("Error during Unmarshal, %s", err) 14 | t.FailNow() 15 | } 16 | if traceType := trace.GetType(); traceType != "bar" { 17 | t.Logf("Error recovering type, Expected \"bar\", got %s", traceType) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestMarshalTrace(t *testing.T) { 23 | bar := &Bar{} 24 | v, err := json.Marshal(bar) 25 | if err != nil { 26 | t.Logf("Error during Marshal, %s", err) 27 | t.FailNow() 28 | } 29 | if !strings.Contains(string(v), "type") { 30 | t.Logf("Output doesn't contain the type field, Expected to contain \"type\", got: %s", v) 31 | t.FailNow() 32 | } 33 | if strings.Contains(string(v), "null") { 34 | t.Logf("Output contains null fields, Expected to not contain any, got: %s", v) 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generated/v2.34.0/graph_objects/unmarshal_gen_test.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshalTrace(t *testing.T) { 10 | data := `{"type":"bar"}` 11 | trace, err := UnmarshalTrace([]byte(data)) 12 | if err != nil { 13 | t.Logf("Error during Unmarshal, %s", err) 14 | t.FailNow() 15 | } 16 | if traceType := trace.GetType(); traceType != "bar" { 17 | t.Logf("Error recovering type, Expected \"bar\", got %s", traceType) 18 | t.FailNow() 19 | } 20 | } 21 | 22 | func TestMarshalTrace(t *testing.T) { 23 | bar := &Bar{} 24 | v, err := json.Marshal(bar) 25 | if err != nil { 26 | t.Logf("Error during Marshal, %s", err) 27 | t.FailNow() 28 | } 29 | if !strings.Contains(string(v), "type") { 30 | t.Logf("Output doesn't contain the type field, Expected to contain \"type\", got: %s", v) 31 | t.FailNow() 32 | } 33 | if strings.Contains(string(v), "null") { 34 | t.Logf("Output contains null fields, Expected to not contain any, got: %s", v) 35 | t.FailNow() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: "1.22" 20 | 21 | - name: Check Generated Schema 22 | run: | 23 | go generate ./... 24 | git diff --exit-code 25 | 26 | - name: Test 27 | run: go test -v ./... 28 | 29 | - name: Build all packages 30 | run: go build -v ./... 31 | 32 | - name: Build all examples 33 | run: cd examples && go build -v ./... 34 | 35 | - name: Build readme examples 36 | run: |- 37 | mkdir -p readme 38 | sed -n '/^```go/,/^```/p' README.md | sed -e '/^```/d' > "readme/main.go" 39 | go build -o readme/readme "readme/main.go" 40 | rm -rd readme 41 | -------------------------------------------------------------------------------- /schemas.yaml: -------------------------------------------------------------------------------- 1 | versions: 2 | - Name: Plotly 2.34.0 3 | Tag: v2.34.0 4 | URL: https://raw.githubusercontent.com/plotly/plotly.js/v2.34.0/test/plot-schema.json 5 | Path: schemas/v2.34.0/plot-schema.json 6 | Generated: generated/v2.34.0 7 | CDN: https://cdn.plot.ly/plotly-2.34.0.min.js 8 | - Name: Plotly 2.31.1 9 | Tag: v2.31.1 10 | URL: https://raw.githubusercontent.com/plotly/plotly.js/v2.31.1/test/plot-schema.json 11 | Path: schemas/v2.31.1/plot-schema.json 12 | Generated: generated/v2.31.1 13 | CDN: https://cdn.plot.ly/plotly-2.31.1.min.js 14 | - Name: Plotly 2.29.1 15 | Tag: v2.29.1 16 | URL: https://raw.githubusercontent.com/plotly/plotly.js/v2.29.1/test/plot-schema.json 17 | Path: schemas/v2.29.1/plot-schema.json 18 | Generated: generated/v2.29.1 19 | CDN: https://cdn.plot.ly/plotly-2.29.1.min.js 20 | - Name: Plotly 2.19.0 21 | Tag: v2.19.0 22 | URL: https://raw.githubusercontent.com/plotly/plotly.js/v2.19.0/test/plot-schema.json 23 | Path: schemas/v2.19.0/plot-schema.json 24 | Generated: generated/v2.19.0 25 | CDN: https://cdn.plot.ly/plotly-2.19.0.min.js 26 | -------------------------------------------------------------------------------- /generator/cmd/downloader/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/MetalBlueberry/go-plotly/generator" 9 | ) 10 | 11 | // schemaPath location of the schemas file, holding all version to be created 12 | const configPath = "schemas.yaml" 13 | 14 | func main() { 15 | var schemapath string 16 | flag.StringVar(&schemapath, "config", configPath, "yaml file defining versions to be generated") 17 | flag.Parse() 18 | 19 | // Define a flag for the version 20 | versions := generator.ReadSchemas(schemapath) 21 | if versions == nil { 22 | fmt.Printf("could not find versions\n") 23 | return 24 | } 25 | var err error 26 | for _, version := range versions { 27 | 28 | // check if version already downloaded: 29 | _, err = os.Stat(version.Path) 30 | if err == nil { 31 | fmt.Printf("already downloaded version: %s\n", version.Tag) 32 | continue 33 | } 34 | 35 | // download schema 36 | _, err = generator.DownloadSchema(version.Tag, version.URL, version.Path) 37 | if err != nil { 38 | fmt.Printf("failed to download version: %s\n", version.Tag) 39 | return 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Víctor Pérez (MetalBlueberry) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/unmarshal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | 7 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 8 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 9 | "github.com/MetalBlueberry/go-plotly/pkg/types" 10 | ) 11 | 12 | func main() { 13 | t := linspace(0, 10, 100) 14 | y := sin(t) 15 | 16 | fig := &grob.Fig{ 17 | Data: []types.Trace{ 18 | &grob.Scatter{ 19 | X: types.DataArray(t), 20 | Y: types.DataArray(y), 21 | Mode: grob.ScatterModeMarkers, 22 | }, 23 | }, 24 | } 25 | 26 | bytes, err := json.Marshal(fig) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | // Demonstrates that a json representation can be loaded 32 | recoveredFig := &grob.Fig{} 33 | json.Unmarshal(bytes, recoveredFig) 34 | offline.Show(recoveredFig) 35 | } 36 | 37 | func linspace(start, stop float64, points int) []float64 { 38 | step := (stop - start) / float64(points) 39 | out := make([]float64, points) 40 | 41 | for i := 0; i < points; i++ { 42 | out[i] = start + step*float64(i) 43 | } 44 | return out 45 | } 46 | 47 | func sin(x []float64) []float64 { 48 | y := make([]float64, len(x)) 49 | for i := 1; i < len(x); i++ { 50 | y[i] = math.Sin(x[i]) 51 | } 52 | return y 53 | } 54 | -------------------------------------------------------------------------------- /generator/templates/frames.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // {{ . }} 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Frame 10 | type Frame struct { 11 | 12 | // Baseframe 13 | // The name of the frame into which this frame's properties are merged before applying. This is used to unify properties and avoid needing to specify the same values for the same properties in multiple frames. 14 | Baseframe types.StringType `json:"baseframe,omitempty"` 15 | 16 | // Data 17 | // A list of traces this frame modifies. The format is identical to the normal trace definition. 18 | Data []types.Trace `json:"data,omitempty"` 19 | 20 | // Group 21 | // An identifier that specifies the group to which the frame belongs, used by animate to select a subset of frames. 22 | Group types.StringType `json:"group,omitempty"` 23 | 24 | // Layout 25 | // Layout properties which this frame modifies. The format is identical to the normal layout definition. 26 | Layout *Layout `json:"layout,omitempty"` 27 | 28 | // Name 29 | // A label by which to identify the frame 30 | Name types.StringType `json:"name,omitempty"` 31 | 32 | // Traces 33 | // A list of trace indices that identify the respective traces in the data attribute 34 | Traces []int `json:"traces,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /examples/scatter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 7 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 8 | "github.com/MetalBlueberry/go-plotly/pkg/types" 9 | ) 10 | 11 | func main() { 12 | /* 13 | import plotly.graph_objects as go 14 | import numpy as np 15 | 16 | N = 1000 17 | t = np.linspace(0, 10, 100) 18 | y = np.sin(t) 19 | 20 | fig = go.Figure(data=go.Scatter(x=t, y=y, mode='markers')) 21 | 22 | fig.show() 23 | */ 24 | t := linspace(0, 10, 100) 25 | y := sin(t) 26 | 27 | fig := &grob.Fig{ 28 | Data: []types.Trace{ 29 | &grob.Scatter{ 30 | X: types.DataArray(t), 31 | Y: types.DataArray(y), 32 | Mode: grob.ScatterModeMarkers, 33 | }, 34 | }, 35 | } 36 | 37 | offline.ToHtml(fig, "scatter.html") 38 | offline.Show(fig) 39 | } 40 | 41 | func linspace(start, stop float64, points int) []float64 { 42 | step := (stop - start) / float64(points) 43 | out := make([]float64, points) 44 | 45 | for i := 0; i < points; i++ { 46 | out[i] = start + step*float64(i) 47 | } 48 | return out 49 | } 50 | 51 | func sin(x []float64) []float64 { 52 | y := make([]float64, len(x)) 53 | for i := 1; i < len(x); i++ { 54 | y[i] = math.Sin(x[i]) 55 | } 56 | return y 57 | } 58 | -------------------------------------------------------------------------------- /generated/v2.19.0/graph_objects/frames_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // // Code generated by go-plotly/generator. DO NOT EDIT. 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Frame 10 | type Frame struct { 11 | 12 | // Baseframe 13 | // The name of the frame into which this frame's properties are merged before applying. This is used to unify properties and avoid needing to specify the same values for the same properties in multiple frames. 14 | Baseframe types.StringType `json:"baseframe,omitempty"` 15 | 16 | // Data 17 | // A list of traces this frame modifies. The format is identical to the normal trace definition. 18 | Data []types.Trace `json:"data,omitempty"` 19 | 20 | // Group 21 | // An identifier that specifies the group to which the frame belongs, used by animate to select a subset of frames. 22 | Group types.StringType `json:"group,omitempty"` 23 | 24 | // Layout 25 | // Layout properties which this frame modifies. The format is identical to the normal layout definition. 26 | Layout *Layout `json:"layout,omitempty"` 27 | 28 | // Name 29 | // A label by which to identify the frame 30 | Name types.StringType `json:"name,omitempty"` 31 | 32 | // Traces 33 | // A list of trace indices that identify the respective traces in the data attribute 34 | Traces []int `json:"traces,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /generated/v2.29.1/graph_objects/frames_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // // Code generated by go-plotly/generator. DO NOT EDIT. 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Frame 10 | type Frame struct { 11 | 12 | // Baseframe 13 | // The name of the frame into which this frame's properties are merged before applying. This is used to unify properties and avoid needing to specify the same values for the same properties in multiple frames. 14 | Baseframe types.StringType `json:"baseframe,omitempty"` 15 | 16 | // Data 17 | // A list of traces this frame modifies. The format is identical to the normal trace definition. 18 | Data []types.Trace `json:"data,omitempty"` 19 | 20 | // Group 21 | // An identifier that specifies the group to which the frame belongs, used by animate to select a subset of frames. 22 | Group types.StringType `json:"group,omitempty"` 23 | 24 | // Layout 25 | // Layout properties which this frame modifies. The format is identical to the normal layout definition. 26 | Layout *Layout `json:"layout,omitempty"` 27 | 28 | // Name 29 | // A label by which to identify the frame 30 | Name types.StringType `json:"name,omitempty"` 31 | 32 | // Traces 33 | // A list of trace indices that identify the respective traces in the data attribute 34 | Traces []int `json:"traces,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /generated/v2.31.1/graph_objects/frames_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // // Code generated by go-plotly/generator. DO NOT EDIT. 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Frame 10 | type Frame struct { 11 | 12 | // Baseframe 13 | // The name of the frame into which this frame's properties are merged before applying. This is used to unify properties and avoid needing to specify the same values for the same properties in multiple frames. 14 | Baseframe types.StringType `json:"baseframe,omitempty"` 15 | 16 | // Data 17 | // A list of traces this frame modifies. The format is identical to the normal trace definition. 18 | Data []types.Trace `json:"data,omitempty"` 19 | 20 | // Group 21 | // An identifier that specifies the group to which the frame belongs, used by animate to select a subset of frames. 22 | Group types.StringType `json:"group,omitempty"` 23 | 24 | // Layout 25 | // Layout properties which this frame modifies. The format is identical to the normal layout definition. 26 | Layout *Layout `json:"layout,omitempty"` 27 | 28 | // Name 29 | // A label by which to identify the frame 30 | Name types.StringType `json:"name,omitempty"` 31 | 32 | // Traces 33 | // A list of trace indices that identify the respective traces in the data attribute 34 | Traces []int `json:"traces,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /generated/v2.34.0/graph_objects/frames_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | // // Code generated by go-plotly/generator. DO NOT EDIT. 4 | 5 | import ( 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Frame 10 | type Frame struct { 11 | 12 | // Baseframe 13 | // The name of the frame into which this frame's properties are merged before applying. This is used to unify properties and avoid needing to specify the same values for the same properties in multiple frames. 14 | Baseframe types.StringType `json:"baseframe,omitempty"` 15 | 16 | // Data 17 | // A list of traces this frame modifies. The format is identical to the normal trace definition. 18 | Data []types.Trace `json:"data,omitempty"` 19 | 20 | // Group 21 | // An identifier that specifies the group to which the frame belongs, used by animate to select a subset of frames. 22 | Group types.StringType `json:"group,omitempty"` 23 | 24 | // Layout 25 | // Layout properties which this frame modifies. The format is identical to the normal layout definition. 26 | Layout *Layout `json:"layout,omitempty"` 27 | 28 | // Name 29 | // A label by which to identify the frame 30 | Name types.StringType `json:"name,omitempty"` 31 | 32 | // Traces 33 | // A list of trace indices that identify the respective traces in the data attribute 34 | Traces []int `json:"traces,omitempty"` 35 | } 36 | -------------------------------------------------------------------------------- /examples/wasm/main.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "syscall/js" 8 | 9 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 10 | "github.com/MetalBlueberry/go-plotly/pkg/types" 11 | ) 12 | 13 | // to run this, wasm need to be set: GOOS=js GOARCH=wasm go build -o main.wasm 14 | 15 | func plot(this js.Value, inputs []js.Value) interface{} { 16 | fig := &grob.Fig{ 17 | Data: []types.Trace{ 18 | &grob.Choropleth{ 19 | Autocolorscale: types.True, 20 | Locationmode: grob.ChoroplethLocationmodeUSAStates, 21 | }, 22 | }, 23 | Layout: &grob.Layout{ 24 | Title: &grob.LayoutTitle{ 25 | Text: "Demo", 26 | }, 27 | Geo: &grob.LayoutGeo{ 28 | Scope: grob.LayoutGeoScopeUsa, 29 | }, 30 | }, 31 | } 32 | b, err := json.Marshal(fig) 33 | if err != nil { 34 | return nil 35 | } 36 | 37 | plot := js.Global().Get("JSON").Call("parse", string(b)) 38 | plotly := js.Global().Get("Plotly") 39 | plotly.Call("plot", "plot", plot) 40 | return nil 41 | } 42 | 43 | var c chan bool 44 | 45 | func init() { 46 | c = make(chan bool) 47 | } 48 | 49 | func stop(this js.Value, inputs []js.Value) interface{} { 50 | c <- true 51 | return nil 52 | } 53 | 54 | func main() { 55 | js.Global().Set("plot", js.FuncOf(plot)) 56 | js.Global().Set("stop", js.FuncOf(stop)) 57 | <-c 58 | println("We are out of here") 59 | } 60 | -------------------------------------------------------------------------------- /pkg/types/arrayok.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func ArrayOKValue[T any](value T) *ArrayOK[*T] { 8 | v := &value 9 | return &ArrayOK[*T]{Value: v} 10 | } 11 | 12 | func ArrayOKArray[T any](array ...T) *ArrayOK[*T] { 13 | out := make([]*T, len(array)) 14 | for i, v := range array { 15 | value := v 16 | out[i] = &value 17 | } 18 | return &ArrayOK[*T]{ 19 | Array: out, 20 | } 21 | } 22 | func ArrayOKAppend[T any](array *ArrayOK[*T], values ...T) { 23 | for _, el := range values { 24 | array.Array = append(array.Array, &el) 25 | } 26 | } 27 | 28 | // ArrayOK is a type that allows you to define a single value or an array of values, But not both. 29 | // If Array is defined, Value will be ignored. 30 | type ArrayOK[T any] struct { 31 | Value T 32 | Array []T 33 | } 34 | 35 | func (arrayOK *ArrayOK[T]) MarshalJSON() ([]byte, error) { 36 | if arrayOK.Array != nil { 37 | return json.Marshal(arrayOK.Array) 38 | } 39 | return json.Marshal(arrayOK.Value) 40 | } 41 | 42 | func (arrayOK *ArrayOK[T]) UnmarshalJSON(data []byte) error { 43 | arrayOK.Array = nil 44 | 45 | var array []T 46 | err := json.Unmarshal(data, &array) 47 | if err == nil { 48 | arrayOK.Array = array 49 | return nil 50 | } 51 | 52 | var value T 53 | err = json.Unmarshal(data, &value) 54 | if err != nil { 55 | return err 56 | } 57 | arrayOK.Value = value 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /examples/numpy_bdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | 7 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 8 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 9 | "github.com/MetalBlueberry/go-plotly/pkg/types" 10 | ) 11 | 12 | func main() { 13 | // https://plotly.com/javascript/heatmaps/ 14 | // var data = [ 15 | // { 16 | // z: [[1, null, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, -10, 20]], 17 | // x: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], 18 | // y: ['Morning', 'Afternoon', 'Evening'], 19 | // type: 'heatmap', 20 | // hoverongaps: false 21 | // } 22 | // ]; 23 | zJSON, err := exec.Command("make").Output() 24 | if err != nil { 25 | panic(err) 26 | } 27 | type bdata struct { 28 | DType types.DType 29 | Shape []int 30 | BData []byte 31 | } 32 | 33 | z := &bdata{} 34 | err = json.Unmarshal(zJSON, z) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | fig := &grob.Fig{ 40 | Data: []types.Trace{ 41 | &grob.Heatmap{ 42 | X: types.DataArray([]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}), 43 | Y: types.DataArray([]string{"Morning", "Afternoon", "Evening"}), 44 | Z: types.BDataArray(z.DType, z.BData, z.Shape), 45 | Hoverongaps: types.False, 46 | }, 47 | }, 48 | } 49 | 50 | offline.Show(fig) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /generator/mocks/creator.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/MetalBlueberry/go-plotly/generator (interfaces: Creator) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | gomock "github.com/golang/mock/gomock" 9 | io "io" 10 | reflect "reflect" 11 | ) 12 | 13 | // MockCreator is a mock of Creator interface 14 | type MockCreator struct { 15 | ctrl *gomock.Controller 16 | recorder *MockCreatorMockRecorder 17 | } 18 | 19 | // MockCreatorMockRecorder is the mock recorder for MockCreator 20 | type MockCreatorMockRecorder struct { 21 | mock *MockCreator 22 | } 23 | 24 | // NewMockCreator creates a new mock instance 25 | func NewMockCreator(ctrl *gomock.Controller) *MockCreator { 26 | mock := &MockCreator{ctrl: ctrl} 27 | mock.recorder = &MockCreatorMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use 32 | func (m *MockCreator) EXPECT() *MockCreatorMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Create mocks base method 37 | func (m *MockCreator) Create(arg0 string) (io.WriteCloser, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Create", arg0) 40 | ret0, _ := ret[0].(io.WriteCloser) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // Create indicates an expected call of Create 46 | func (mr *MockCreatorMockRecorder) Create(arg0 interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCreator)(nil).Create), arg0) 49 | } 50 | -------------------------------------------------------------------------------- /examples/shapes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.19.0/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | fig = dict({ 12 | "data": [{"type": "bar", 13 | "x": [1, 2, 3], 14 | "y": [1, 3, 2]}], 15 | "layout": {"title": {"text": "A Figure Specified By Python Dictionary"}} 16 | }) 17 | */ 18 | fig := &grob.Fig{ 19 | Layout: &grob.Layout{ 20 | Title: &grob.LayoutTitle{ 21 | Text: types.S("A Figure Specified By Go Struct"), 22 | }, 23 | Shapes: []grob.LayoutShape{ 24 | { 25 | Type: "line", 26 | X0: 1, 27 | Y0: 0, 28 | X1: 1, 29 | Y1: 2, 30 | Line: &grob.LayoutShapeLine{ 31 | Color: "RoyalBlue", 32 | Width: types.N(3), 33 | }, 34 | }, 35 | { 36 | Type: "line", 37 | X0: 2, 38 | Y0: 2, 39 | X1: 5, 40 | Y1: 2, 41 | Line: &grob.LayoutShapeLine{ 42 | Color: "LightSeaGreen", 43 | Width: types.N(4), 44 | Dash: types.S(string(grob.Scatter3dLineDashDashdot)), 45 | }, 46 | }, 47 | { 48 | Type: "line", 49 | X0: 4, 50 | Y0: 0, 51 | X1: 6, 52 | Y1: 2, 53 | Line: &grob.LayoutShapeLine{ 54 | Color: "MediumPurple", 55 | Width: types.N(4), 56 | Dash: types.S(string(grob.Scatter3dLineDashDot)), 57 | }, 58 | }, 59 | }, 60 | }, 61 | } 62 | 63 | offline.ToHtml(fig, "bar.html") 64 | offline.Show(fig) 65 | } 66 | -------------------------------------------------------------------------------- /examples/scatter3d/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 7 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 8 | "github.com/MetalBlueberry/go-plotly/pkg/types" 9 | ) 10 | 11 | func main() { 12 | /* 13 | https://plotly.com/python/3d-scatter-plots/ 14 | import plotly.graph_objects as go 15 | import numpy as np 16 | 17 | # Helix equation 18 | t = np.linspace(0, 10, 50) 19 | x, y, z = np.cos(t), np.sin(t), t 20 | 21 | fig = go.Figure(data=[go.Scatter3d(x=x, y=y, z=z, 22 | mode='markers')]) 23 | fig.show() 24 | */ 25 | t := linspace(0, 10, 50) 26 | x := cos(t) 27 | y := sin(t) 28 | z := t 29 | 30 | fig := &grob.Fig{ 31 | Data: []types.Trace{ 32 | &grob.Scatter3d{ 33 | X: types.DataArray(x), 34 | Y: types.DataArray(y), 35 | Z: types.DataArray(z), 36 | Mode: grob.Scatter3dModeMarkers, 37 | }, 38 | }, 39 | } 40 | 41 | offline.ToHtml(fig, "scatter3d.html") 42 | offline.Show(fig) 43 | } 44 | 45 | func linspace(start, stop float64, points int) []float64 { 46 | step := (stop - start) / float64(points) 47 | out := make([]float64, points) 48 | 49 | for i := 0; i < points; i++ { 50 | out[i] = start + step*float64(i) 51 | } 52 | return out 53 | } 54 | 55 | func sin(x []float64) []float64 { 56 | y := make([]float64, len(x)) 57 | for i := 0; i < len(x); i++ { 58 | y[i] = math.Sin(x[i]) 59 | } 60 | return y 61 | } 62 | 63 | func cos(x []float64) []float64 { 64 | z := make([]float64, len(x)) 65 | for i := 0; i < len(x); i++ { 66 | z[i] = math.Cos(x[i]) 67 | } 68 | return z 69 | 70 | } 71 | -------------------------------------------------------------------------------- /examples/animation_slider/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSplit(t *testing.T) { 9 | // Test case 1: Basic functionality 10 | reference := []string{"a", "b", "a", "b"} 11 | slices := [][]interface{}{ 12 | {1, 2, 3, 4}, 13 | {"s1", "s2", "s3", "s4"}, 14 | } 15 | expected := map[string][][]interface{}{ 16 | "a": { 17 | {1, 3}, 18 | {"s1", "s3"}, 19 | }, 20 | "b": { 21 | {2, 4}, 22 | {"s2", "s4"}, 23 | }, 24 | } 25 | 26 | result, _ := split(reference, slices) 27 | 28 | if !reflect.DeepEqual(result, expected) { 29 | t.Errorf("Test case 1 failed. Expected %v, got %v", expected, result) 30 | } 31 | 32 | // Test case 2: Single element in reference 33 | reference = []string{"a"} 34 | slices = [][]interface{}{ 35 | {5}, 36 | {"single"}, 37 | } 38 | expected = map[string][][]interface{}{ 39 | "a": { 40 | {5}, 41 | {"single"}, 42 | }, 43 | } 44 | 45 | result, _ = split(reference, slices) 46 | 47 | if !reflect.DeepEqual(result, expected) { 48 | t.Errorf("Test case 2 failed. Expected %v, got %v", expected, result) 49 | } 50 | 51 | // Test case 3: Empty slices 52 | reference = []string{} 53 | slices = [][]interface{}{} 54 | expected = map[string][][]interface{}{} 55 | 56 | result, _ = split(reference, slices) 57 | 58 | if !reflect.DeepEqual(result, expected) { 59 | t.Errorf("Test case 3 failed. Expected %v, got %v", expected, result) 60 | } 61 | 62 | // Test case 4: Multiple same keys 63 | reference = []string{"x", "x", "y", "y"} 64 | slices = [][]interface{}{ 65 | {10, 20, 30, 40}, 66 | {"a", "b", "c", "d"}, 67 | } 68 | expected = map[string][][]interface{}{ 69 | "x": { 70 | {10, 20}, 71 | {"a", "b"}, 72 | }, 73 | "y": { 74 | {30, 40}, 75 | {"c", "d"}, 76 | }, 77 | } 78 | 79 | result, _ = split(reference, slices) 80 | 81 | if !reflect.DeepEqual(result, expected) { 82 | t.Errorf("Test case 4 failed. Expected %v, got %v", expected, result) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/static_image/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | 11 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 12 | "github.com/MetalBlueberry/go-plotly/pkg/types" 13 | ) 14 | 15 | func main() { 16 | /* 17 | fig = dict({ 18 | "data": [{"type": "bar", 19 | "x": [1, 2, 3], 20 | "y": [1, 3, 2]}], 21 | "layout": {"title": {"text": "A Figure Specified By Python Dictionary"}} 22 | }) 23 | */ 24 | fig := &grob.Fig{ 25 | Data: []types.Trace{ 26 | &grob.Bar{ 27 | X: types.DataArray([]float64{1, 2, 3}), 28 | Y: types.DataArray([]float64{1, 2, 3}), 29 | }, 30 | }, 31 | Layout: &grob.Layout{ 32 | Title: &grob.LayoutTitle{ 33 | Text: types.S("A Figure Specified By Go Struct"), 34 | }, 35 | }, 36 | } 37 | 38 | fmt.Fprint(os.Stderr, "saving to PNG...\n") 39 | err := savePNG(fig, "out.png") 40 | if err != nil { 41 | panic(err) 42 | } 43 | fmt.Fprint(os.Stderr, "Done!\n") 44 | } 45 | 46 | func savePNG(fig *grob.Fig, path string) error { 47 | args := "run --rm -i quay.io/plotly/orca graph --format png" 48 | cmd := exec.Command("docker", strings.Split(args, " ")...) 49 | 50 | in, err := cmd.StdinPipe() 51 | if err != nil { 52 | return fmt.Errorf("Failed to open StdIn, %w", err) 53 | } 54 | go func() { 55 | err := json.NewEncoder(in).Encode(fig) 56 | if err != nil { 57 | panic(err) 58 | } 59 | err = in.Close() 60 | }() 61 | 62 | out, err := cmd.StdoutPipe() 63 | if err != nil { 64 | return fmt.Errorf("Failed to open StdOut, %w", err) 65 | } 66 | 67 | f, err := os.Create(path) 68 | if err != nil { 69 | return fmt.Errorf("Cannot create output file") 70 | } 71 | go func() { 72 | defer f.Close() 73 | _, err = io.Copy(f, out) 74 | if err != nil { 75 | panic(err) 76 | } 77 | }() 78 | 79 | cmd.Stderr = os.Stderr 80 | 81 | err = cmd.Run() 82 | if err != nil { 83 | return fmt.Errorf("Failed to run command, %w", err) 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /examples/subplots_share_axes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | https://plotly.com/javascript/subplots/#subplots-with-shared-axes 12 | 13 | var trace1 = { 14 | x: [1, 2, 3], 15 | y: [2, 3, 4], 16 | type: 'scatter' 17 | }; 18 | 19 | var trace2 = { 20 | x: [20, 30, 40], 21 | y: [5, 5, 5], 22 | xaxis: 'x2', 23 | yaxis: 'y', 24 | type: 'scatter' 25 | }; 26 | 27 | var trace3 = { 28 | x: [2, 3, 4], 29 | y: [600, 700, 800], 30 | xaxis: 'x', 31 | yaxis: 'y3', 32 | type: 'scatter' 33 | }; 34 | 35 | var trace4 = { 36 | x: [4000, 5000, 6000], 37 | y: [7000, 8000, 9000], 38 | xaxis: 'x4', 39 | yaxis: 'y4', 40 | type: 'scatter' 41 | }; 42 | 43 | var data = [trace1, trace2, trace3, trace4]; 44 | 45 | var layout = { 46 | grid: { 47 | rows: 2, 48 | columns: 2, 49 | subplots:[['xy','x2y'], ['xy3','x4y4']], 50 | roworder:'bottom to top' 51 | } 52 | }; 53 | 54 | Plotly.newPlot('myDiv', data, layout); 55 | */ 56 | fig := &grob.Fig{ 57 | Data: []types.Trace{ 58 | &grob.Scatter{ 59 | X: types.DataArray([]float64{1, 2, 3}), 60 | Y: types.DataArray([]float64{2, 3, 4}), 61 | }, 62 | &grob.Scatter{ 63 | X: types.DataArray([]float64{20, 30, 40}), 64 | Y: types.DataArray([]float64{5, 5, 5}), 65 | Xaxis: types.S("x2"), 66 | Yaxis: types.S("y"), 67 | }, 68 | &grob.Scatter{ 69 | X: types.DataArray([]float64{2, 3, 4}), 70 | Y: types.DataArray([]float64{600, 700, 800}), 71 | Xaxis: types.S("x"), 72 | Yaxis: types.S("y3"), 73 | }, 74 | &grob.Scatter{ 75 | X: types.DataArray([]float64{4000, 5000, 6000}), 76 | Y: types.DataArray([]float64{7000, 8000, 9000}), 77 | Xaxis: types.S("x4"), 78 | Yaxis: types.S("y4"), 79 | }, 80 | }, 81 | Layout: &grob.Layout{ 82 | Grid: &grob.LayoutGrid{ 83 | Rows: types.I(2), 84 | Columns: types.I(2), 85 | Subplots: [][]string{ 86 | {"xy", "x2y"}, 87 | {"xy3", "x4y4"}, 88 | }, 89 | Roworder: grob.LayoutGridRoworderBottomToTop, 90 | }, 91 | }, 92 | } 93 | 94 | offline.ToHtml(fig, "subplots_share_axes.html") 95 | offline.Show(fig) 96 | } 97 | -------------------------------------------------------------------------------- /examples/bar_custom/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "strconv" 6 | 7 | "github.com/lucasb-eyer/go-colorful" 8 | 9 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 10 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 11 | "github.com/MetalBlueberry/go-plotly/pkg/types" 12 | ) 13 | 14 | func main() { 15 | /* 16 | https://plotly.com/javascript/bar-charts/ 17 | var xValue = ['Product A', 'Product B', 'Product C']; 18 | 19 | var yValue = [20, 14, 23]; 20 | 21 | var trace1 = { 22 | x: xValue, 23 | y: yValue, 24 | type: 'bar', 25 | text: yValue.map(String), 26 | textposition: 'auto', 27 | hoverinfo: 'none', 28 | marker: { 29 | color: 'rgb(158,202,225)', 30 | opacity: 0.6, 31 | line: { 32 | color: 'rgb(8,48,107)', 33 | width: 1.5 34 | } 35 | } 36 | }; 37 | 38 | var data = [trace1]; 39 | 40 | var layout = { 41 | title: 'January 2013 Sales Report', 42 | barmode: 'stack' 43 | }; 44 | 45 | */ 46 | xValue := []string{"Product A", "Product B", "Product C"} 47 | yValue := []int{20, 14, 23} 48 | 49 | markerColor, ok := colorful.MakeColor(color.NRGBA{158, 202, 225, 1}) 50 | if !ok { 51 | panic("fail to build a color") 52 | } 53 | 54 | trace1 := &grob.Bar{ 55 | X: types.DataArray(xValue), 56 | Y: types.DataArray(yValue), 57 | Text: types.ArrayOKArray(toString(yValue)...), 58 | Textposition: types.ArrayOKValue(grob.BarTextpositionAuto), 59 | Hoverinfo: types.ArrayOKValue(grob.BarHoverinfoNone), 60 | Marker: &grob.BarMarker{ 61 | Color: types.ArrayOKValue(types.UseColor(types.Color( 62 | markerColor.Hex(), // Use colorfull 63 | ))), 64 | Opacity: types.ArrayOKValue(types.N(0.6)), 65 | Line: &grob.BarMarkerLine{ 66 | Color: types.ArrayOKValue(types.UseColor("rgb(8,48,107)")), // Or just write the string 67 | Width: types.ArrayOKValue(types.N(1.5)), 68 | }, 69 | }, 70 | } 71 | 72 | layout := &grob.Layout{ 73 | Title: &grob.LayoutTitle{ 74 | Text: types.S("A Figure Specified By Go Struct"), 75 | }, 76 | } 77 | 78 | fig := &grob.Fig{ 79 | Data: []types.Trace{trace1}, 80 | Layout: layout, 81 | } 82 | 83 | offline.ToHtml(fig, "bar_custom.html") 84 | offline.Show(fig) 85 | } 86 | 87 | func toString(in []int) []types.StringType { 88 | out := make([]types.StringType, len(in)) 89 | for i := range in { 90 | out[i] = types.S(strconv.Itoa(in[i])) 91 | } 92 | return out 93 | } 94 | -------------------------------------------------------------------------------- /examples/colorscale/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 7 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 8 | "github.com/MetalBlueberry/go-plotly/pkg/types" 9 | ) 10 | 11 | func main() { 12 | t := linspace(0, 10, 50) 13 | x := cos(t) 14 | y := sin(t) 15 | z := t 16 | fig := &grob.Fig{ 17 | Data: []types.Trace{ 18 | &grob.Scatter3d{ 19 | X: types.DataArray(x), 20 | Y: types.DataArray(y), 21 | Z: types.DataArray(z), 22 | Mode: grob.Scatter3dModeMarkers, 23 | Marker: &grob.Scatter3dMarker{ 24 | Autocolorscale: types.False, 25 | Cauto: types.False, 26 | Cmin: types.N(0), 27 | Cmid: types.N(5), 28 | Cmax: types.N(10), 29 | Color: types.ArrayOKArray(types.UseColorScaleValues(z)...), 30 | Colorscale: &types.ColorScale{ 31 | Values: []types.ColorScaleReference{ 32 | {NormalizedValue: 0.0, Color: "#6e40aa"}, 33 | {NormalizedValue: 0.1, Color: `#963db3`}, 34 | {NormalizedValue: 0.2, Color: "#bf3caf"}, 35 | {NormalizedValue: 0.3, Color: "#e4419d"}, 36 | {NormalizedValue: 0.4, Color: "#fe4b83"}, 37 | {NormalizedValue: 0.5, Color: "#ff5e63"}, 38 | {NormalizedValue: 0.6, Color: "#ff7847"}, 39 | {NormalizedValue: 0.7, Color: "#fb9633"}, 40 | {NormalizedValue: 0.8, Color: "#e2b72f"}, 41 | {NormalizedValue: 0.9, Color: "#c6d63c"}, 42 | {NormalizedValue: 1.0, Color: "#aff05b"}, 43 | }, 44 | }, 45 | Showscale: types.True, 46 | Size: types.ArrayOKValue(types.N(4.0)), 47 | }, 48 | }, 49 | }, 50 | Layout: &grob.Layout{ 51 | Height: types.N(700), 52 | Width: types.N(1200), 53 | Title: &grob.LayoutTitle{ 54 | Text: types.S("3D Spiral"), 55 | }, 56 | }, 57 | } 58 | offline.Show(fig) 59 | } 60 | 61 | func linspace(start, stop float64, points int) []float64 { 62 | step := (stop - start) / float64(points) 63 | out := make([]float64, points) 64 | 65 | for i := 0; i < points; i++ { 66 | out[i] = start + step*float64(i) 67 | } 68 | return out 69 | } 70 | 71 | func sin(x []float64) []float64 { 72 | y := make([]float64, len(x)) 73 | for i := 0; i < len(x); i++ { 74 | y[i] = math.Sin(x[i]) 75 | } 76 | return y 77 | } 78 | 79 | func cos(x []float64) []float64 { 80 | z := make([]float64, len(x)) 81 | for i := 0; i < len(x); i++ { 82 | z[i] = math.Cos(x[i]) 83 | } 84 | return z 85 | 86 | } 87 | -------------------------------------------------------------------------------- /examples/scatter3d/scatter3d.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /examples/waterfall_bar_chart/waterfall.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /examples/animation/animation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | 6 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.19.0/graph_objects" 7 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 8 | "github.com/MetalBlueberry/go-plotly/pkg/types" 9 | ) 10 | 11 | // Plotly.newPlot('myDiv', [{ 12 | // x: [1, 2, 3], 13 | // y: [0, 0.5, 1], 14 | // line: {simplify: false}, 15 | // }]); 16 | 17 | // function randomize() { 18 | // Plotly.animate('myDiv', { 19 | // data: [{y: [Math.random(), Math.random(), Math.random()]}], 20 | // traces: [0], 21 | // layout: {} 22 | // }, { 23 | // transition: { 24 | // duration: 500, 25 | // easing: 'cubic-in-out' 26 | // }, 27 | // frame: { 28 | // duration: 500 29 | // } 30 | // }) 31 | // } 32 | func main() { 33 | 34 | frames := []grob.Frame{} 35 | // Because this example runs entirely in go, build a few random frames 36 | for i := 0; i < 20; i++ { 37 | frames = append(frames, grob.Frame{ 38 | Data: []types.Trace{ 39 | &grob.Scatter{ 40 | Y: types.DataArray([]float64{rand.Float64(), rand.Float64(), rand.Float64()}), 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | fig := &grob.Fig{ 47 | Data: []types.Trace{ 48 | &grob.Scatter{ 49 | X: types.DataArray([]float64{1, 2, 3}), 50 | Y: types.DataArray([]float64{0, 0.5, 1}), 51 | Line: &grob.ScatterLine{ 52 | Simplify: types.False, 53 | }, 54 | }, 55 | }, 56 | Layout: &grob.Layout{ 57 | Yaxis: &grob.LayoutYaxis{ 58 | Range: []int{0, 1}, 59 | }, 60 | Updatemenus: []grob.LayoutUpdatemenu{ 61 | { 62 | Type: grob.LayoutUpdatemenuTypeButtons, 63 | Showactive: types.False, 64 | Buttons: []grob.LayoutUpdatemenuButton{ 65 | { 66 | Label: types.S("Play"), 67 | Method: grob.LayoutUpdatemenuButtonMethodAnimate, 68 | Args: []*ButtonArgs{ 69 | nil, 70 | { 71 | Mode: "immediate", 72 | FromCurrent: false, 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | Frames: frames, 81 | Animation: &grob.Animation{ 82 | Transition: &grob.AnimationTransition{ 83 | Duration: types.N(500), 84 | Easing: grob.AnimationTransitionEasingCubicInOut, 85 | }, 86 | Frame: &grob.AnimationFrame{ 87 | Duration: types.N(500), 88 | Redraw: types.True, 89 | }, 90 | }, 91 | } 92 | 93 | offline.Show(fig) 94 | } 95 | 96 | type ButtonArgs struct { 97 | Frame map[string]interface{} `json:"frame,omitempty"` 98 | Transition map[string]interface{} `json:"transition,omitempty"` 99 | FromCurrent bool `json:"fromcurrent,omitempty"` 100 | Mode string `json:"mode,omitempty"` 101 | } 102 | -------------------------------------------------------------------------------- /generator/templates/plotly.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Fig is the base type for figures. 10 | type Fig struct { 11 | // Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference. 12 | // https://plotly.com/javascript/reference 13 | Data []types.Trace `json:"data,omitempty"` 14 | 15 | // Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference. 16 | // https://plotly.com/javascript/reference/layout 17 | Layout *Layout `json:"layout,omitempty"` 18 | 19 | // Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown. 20 | // https://plotly.com/javascript/configuration-options 21 | Config *Config `json:"config,omitempty"` 22 | 23 | // Animation is not yet implemented, feel free to insert custom a struct 24 | Animation *Animation `json:"animation,omitempty"` 25 | 26 | // Frames are the animation frames 27 | Frames []Frame `json:"frames,omitempty"` 28 | } 29 | 30 | // AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil. 31 | func (fig *Fig) AddTraces(traces ...types.Trace) { 32 | if fig.Data == nil { 33 | fig.Data = make([]types.Trace, 0) 34 | } 35 | fig.Data = append(fig.Data, traces...) 36 | } 37 | 38 | func (fig *Fig) Info() types.Version { 39 | return types.Version{ 40 | // {{ printf "\nName: \"%s\"," .Name }} 41 | // {{ printf "\nTag: \"%s\"," .Tag }} 42 | // {{ printf "\nURL: \"%s\"," .URL }} 43 | // {{ printf "\nPath: \"%s\"," .Path }} 44 | // {{ printf "\nGenerated: \"%s\"," .Generated }} 45 | // {{ printf "\nCdn: \"%s\"," .Cdn }} 46 | } 47 | } 48 | 49 | // UnmarshalJSON is a custom unmarshal function to properly handle special cases. 50 | func (fig *Fig) UnmarshalJSON(data []byte) error { 51 | var err error 52 | tmp := unmarshalFig{} 53 | err = json.Unmarshal(data, &tmp) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | fig.Layout = tmp.Layout 59 | fig.Config = tmp.Config 60 | 61 | for i := range tmp.Data { 62 | trace, err := UnmarshalTrace(tmp.Data[i]) 63 | if err != nil { 64 | return err 65 | } 66 | fig.AddTraces(trace) 67 | } 68 | return nil 69 | } 70 | 71 | type unmarshalFig struct { 72 | Data []json.RawMessage `json:"data,omitempty"` 73 | Layout *Layout `json:"layout,omitempty"` 74 | Config *Config `json:"config,omitempty"` 75 | } 76 | -------------------------------------------------------------------------------- /generated/v2.19.0/graph_objects/plotly_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Fig is the base type for figures. 10 | type Fig struct { 11 | // Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference. 12 | // https://plotly.com/javascript/reference 13 | Data []types.Trace `json:"data,omitempty"` 14 | 15 | // Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference. 16 | // https://plotly.com/javascript/reference/layout 17 | Layout *Layout `json:"layout,omitempty"` 18 | 19 | // Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown. 20 | // https://plotly.com/javascript/configuration-options 21 | Config *Config `json:"config,omitempty"` 22 | 23 | // Animation is not yet implemented, feel free to insert custom a struct 24 | Animation *Animation `json:"animation,omitempty"` 25 | 26 | // Frames are the animation frames 27 | Frames []Frame `json:"frames,omitempty"` 28 | } 29 | 30 | // AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil. 31 | func (fig *Fig) AddTraces(traces ...types.Trace) { 32 | if fig.Data == nil { 33 | fig.Data = make([]types.Trace, 0) 34 | } 35 | fig.Data = append(fig.Data, traces...) 36 | } 37 | 38 | func (fig *Fig) Info() types.Version { 39 | return types.Version{ 40 | // 41 | Name: "Plotly 2.19.0", 42 | // 43 | Tag: "v2.19.0", 44 | // 45 | URL: "https://raw.githubusercontent.com/plotly/plotly.js/v2.19.0/test/plot-schema.json", 46 | // 47 | Path: "schemas/v2.19.0/plot-schema.json", 48 | // 49 | Generated: "generated/v2.19.0", 50 | // 51 | Cdn: "https://cdn.plot.ly/plotly-2.19.0.min.js", 52 | } 53 | } 54 | 55 | // UnmarshalJSON is a custom unmarshal function to properly handle special cases. 56 | func (fig *Fig) UnmarshalJSON(data []byte) error { 57 | var err error 58 | tmp := unmarshalFig{} 59 | err = json.Unmarshal(data, &tmp) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | fig.Layout = tmp.Layout 65 | fig.Config = tmp.Config 66 | 67 | for i := range tmp.Data { 68 | trace, err := UnmarshalTrace(tmp.Data[i]) 69 | if err != nil { 70 | return err 71 | } 72 | fig.AddTraces(trace) 73 | } 74 | return nil 75 | } 76 | 77 | type unmarshalFig struct { 78 | Data []json.RawMessage `json:"data,omitempty"` 79 | Layout *Layout `json:"layout,omitempty"` 80 | Config *Config `json:"config,omitempty"` 81 | } 82 | -------------------------------------------------------------------------------- /generated/v2.29.1/graph_objects/plotly_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Fig is the base type for figures. 10 | type Fig struct { 11 | // Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference. 12 | // https://plotly.com/javascript/reference 13 | Data []types.Trace `json:"data,omitempty"` 14 | 15 | // Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference. 16 | // https://plotly.com/javascript/reference/layout 17 | Layout *Layout `json:"layout,omitempty"` 18 | 19 | // Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown. 20 | // https://plotly.com/javascript/configuration-options 21 | Config *Config `json:"config,omitempty"` 22 | 23 | // Animation is not yet implemented, feel free to insert custom a struct 24 | Animation *Animation `json:"animation,omitempty"` 25 | 26 | // Frames are the animation frames 27 | Frames []Frame `json:"frames,omitempty"` 28 | } 29 | 30 | // AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil. 31 | func (fig *Fig) AddTraces(traces ...types.Trace) { 32 | if fig.Data == nil { 33 | fig.Data = make([]types.Trace, 0) 34 | } 35 | fig.Data = append(fig.Data, traces...) 36 | } 37 | 38 | func (fig *Fig) Info() types.Version { 39 | return types.Version{ 40 | // 41 | Name: "Plotly 2.29.1", 42 | // 43 | Tag: "v2.29.1", 44 | // 45 | URL: "https://raw.githubusercontent.com/plotly/plotly.js/v2.29.1/test/plot-schema.json", 46 | // 47 | Path: "schemas/v2.29.1/plot-schema.json", 48 | // 49 | Generated: "generated/v2.29.1", 50 | // 51 | Cdn: "https://cdn.plot.ly/plotly-2.29.1.min.js", 52 | } 53 | } 54 | 55 | // UnmarshalJSON is a custom unmarshal function to properly handle special cases. 56 | func (fig *Fig) UnmarshalJSON(data []byte) error { 57 | var err error 58 | tmp := unmarshalFig{} 59 | err = json.Unmarshal(data, &tmp) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | fig.Layout = tmp.Layout 65 | fig.Config = tmp.Config 66 | 67 | for i := range tmp.Data { 68 | trace, err := UnmarshalTrace(tmp.Data[i]) 69 | if err != nil { 70 | return err 71 | } 72 | fig.AddTraces(trace) 73 | } 74 | return nil 75 | } 76 | 77 | type unmarshalFig struct { 78 | Data []json.RawMessage `json:"data,omitempty"` 79 | Layout *Layout `json:"layout,omitempty"` 80 | Config *Config `json:"config,omitempty"` 81 | } 82 | -------------------------------------------------------------------------------- /generated/v2.31.1/graph_objects/plotly_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Fig is the base type for figures. 10 | type Fig struct { 11 | // Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference. 12 | // https://plotly.com/javascript/reference 13 | Data []types.Trace `json:"data,omitempty"` 14 | 15 | // Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference. 16 | // https://plotly.com/javascript/reference/layout 17 | Layout *Layout `json:"layout,omitempty"` 18 | 19 | // Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown. 20 | // https://plotly.com/javascript/configuration-options 21 | Config *Config `json:"config,omitempty"` 22 | 23 | // Animation is not yet implemented, feel free to insert custom a struct 24 | Animation *Animation `json:"animation,omitempty"` 25 | 26 | // Frames are the animation frames 27 | Frames []Frame `json:"frames,omitempty"` 28 | } 29 | 30 | // AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil. 31 | func (fig *Fig) AddTraces(traces ...types.Trace) { 32 | if fig.Data == nil { 33 | fig.Data = make([]types.Trace, 0) 34 | } 35 | fig.Data = append(fig.Data, traces...) 36 | } 37 | 38 | func (fig *Fig) Info() types.Version { 39 | return types.Version{ 40 | // 41 | Name: "Plotly 2.31.1", 42 | // 43 | Tag: "v2.31.1", 44 | // 45 | URL: "https://raw.githubusercontent.com/plotly/plotly.js/v2.31.1/test/plot-schema.json", 46 | // 47 | Path: "schemas/v2.31.1/plot-schema.json", 48 | // 49 | Generated: "generated/v2.31.1", 50 | // 51 | Cdn: "https://cdn.plot.ly/plotly-2.31.1.min.js", 52 | } 53 | } 54 | 55 | // UnmarshalJSON is a custom unmarshal function to properly handle special cases. 56 | func (fig *Fig) UnmarshalJSON(data []byte) error { 57 | var err error 58 | tmp := unmarshalFig{} 59 | err = json.Unmarshal(data, &tmp) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | fig.Layout = tmp.Layout 65 | fig.Config = tmp.Config 66 | 67 | for i := range tmp.Data { 68 | trace, err := UnmarshalTrace(tmp.Data[i]) 69 | if err != nil { 70 | return err 71 | } 72 | fig.AddTraces(trace) 73 | } 74 | return nil 75 | } 76 | 77 | type unmarshalFig struct { 78 | Data []json.RawMessage `json:"data,omitempty"` 79 | Layout *Layout `json:"layout,omitempty"` 80 | Config *Config `json:"config,omitempty"` 81 | } 82 | -------------------------------------------------------------------------------- /generated/v2.34.0/graph_objects/plotly_gen.go: -------------------------------------------------------------------------------- 1 | package grob 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | // Fig is the base type for figures. 10 | type Fig struct { 11 | // Data The data to be plotted is described in an array usually called data, whose elements are trace objects of various types (e.g. scatter, bar etc) as documented in the Full Reference. 12 | // https://plotly.com/javascript/reference 13 | Data []types.Trace `json:"data,omitempty"` 14 | 15 | // Layout The layout of the plot – non-data-related visual attributes such as the title, annotations etc – is described in an object usually called layout, as documented in/ the Full Reference. 16 | // https://plotly.com/javascript/reference/layout 17 | Layout *Layout `json:"layout,omitempty"` 18 | 19 | // Config High-level configuration options for the plot, such as the scroll/zoom/hover behaviour, is described in an object usually called config, as documented here. The difference between config and layout is that layout relates to the content of the plot, whereas config relates to the context in which the plot is being shown. 20 | // https://plotly.com/javascript/configuration-options 21 | Config *Config `json:"config,omitempty"` 22 | 23 | // Animation is not yet implemented, feel free to insert custom a struct 24 | Animation *Animation `json:"animation,omitempty"` 25 | 26 | // Frames are the animation frames 27 | Frames []Frame `json:"frames,omitempty"` 28 | } 29 | 30 | // AddTraces Is a shorthand to add figures to a given figure. It handles the case where the Traces value is nil. 31 | func (fig *Fig) AddTraces(traces ...types.Trace) { 32 | if fig.Data == nil { 33 | fig.Data = make([]types.Trace, 0) 34 | } 35 | fig.Data = append(fig.Data, traces...) 36 | } 37 | 38 | func (fig *Fig) Info() types.Version { 39 | return types.Version{ 40 | // 41 | Name: "Plotly 2.34.0", 42 | // 43 | Tag: "v2.34.0", 44 | // 45 | URL: "https://raw.githubusercontent.com/plotly/plotly.js/v2.34.0/test/plot-schema.json", 46 | // 47 | Path: "schemas/v2.34.0/plot-schema.json", 48 | // 49 | Generated: "generated/v2.34.0", 50 | // 51 | Cdn: "https://cdn.plot.ly/plotly-2.34.0.min.js", 52 | } 53 | } 54 | 55 | // UnmarshalJSON is a custom unmarshal function to properly handle special cases. 56 | func (fig *Fig) UnmarshalJSON(data []byte) error { 57 | var err error 58 | tmp := unmarshalFig{} 59 | err = json.Unmarshal(data, &tmp) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | fig.Layout = tmp.Layout 65 | fig.Config = tmp.Config 66 | 67 | for i := range tmp.Data { 68 | trace, err := UnmarshalTrace(tmp.Data[i]) 69 | if err != nil { 70 | return err 71 | } 72 | fig.AddTraces(trace) 73 | } 74 | return nil 75 | } 76 | 77 | type unmarshalFig struct { 78 | Data []json.RawMessage `json:"data,omitempty"` 79 | Layout *Layout `json:"layout,omitempty"` 80 | Config *Config `json:"config,omitempty"` 81 | } 82 | -------------------------------------------------------------------------------- /examples/scatter/scatter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /pkg/types/basic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // BoolType represents a *bool value. Needed to tell the different between false and nil. 8 | type BoolType *bool 9 | 10 | func B(v bool) BoolType { 11 | return BoolType(&v) 12 | } 13 | 14 | // BA converts a list of bool into BooleanType 15 | func BA(v []bool) []BoolType { 16 | result := make([]BoolType, len(v)) 17 | for i := range v { 18 | result[i] = B(v[i]) 19 | } 20 | return result 21 | } 22 | 23 | var ( 24 | trueValue bool = true 25 | falseValue bool = false 26 | 27 | // True is a *bool with true value 28 | True BoolType = &trueValue 29 | // False is a *bool with false value 30 | False BoolType = &falseValue 31 | ) 32 | 33 | // StringType as defined by plotly schema 34 | // This is not *string because I do not see any case where an empty string is a desired input for plotly and having this as string makes it much easier to work with the package. 35 | type StringType string 36 | 37 | // S converts a string to a StringType 38 | // It is not needed, but if you use this method, it will make it easier to migrate to different implementations of String type in case we actually need *string instead of string 39 | func S(v string) StringType { 40 | return StringType(v) 41 | } 42 | 43 | func SA(v []string) []StringType { 44 | result := make([]StringType, len(v)) 45 | for i := range v { 46 | result[i] = S(v[i]) 47 | } 48 | return result 49 | } 50 | 51 | // NumberType as defined by plotly schema 52 | type NumberType *float64 53 | 54 | func N(n float64) NumberType { 55 | return NumberType(&n) 56 | } 57 | 58 | // NA converts a list of float64 to NumberType 59 | func NA(n []float64) []NumberType { 60 | result := make([]NumberType, len(n)) 61 | for i := range n { 62 | result[i] = N(n[i]) 63 | } 64 | return result 65 | } 66 | 67 | // NS Given a string, parses it as a float64 number 68 | // Panics if the string is not a float number 69 | func NS(n string) NumberType { 70 | v, err := strconv.ParseFloat(n, 64) 71 | if err != nil { 72 | panic(err) 73 | } 74 | return NumberType(&v) 75 | } 76 | func NSA(n []string) []NumberType { 77 | result := make([]NumberType, len(n)) 78 | for i := range n { 79 | result[i] = NS(n[i]) 80 | } 81 | return result 82 | } 83 | 84 | // IntegerType as defined by plotly schema 85 | type IntegerType *int 86 | 87 | func I(n int) IntegerType { 88 | return IntegerType(&n) 89 | } 90 | 91 | // IA converts a list of int to IntegerType 92 | func IA(n []int) []IntegerType { 93 | result := make([]IntegerType, len(n)) 94 | for i := range n { 95 | result[i] = I(n[i]) 96 | } 97 | return result 98 | } 99 | 100 | // IS Given a string, parses it as an integer number 101 | // Panics if the string is not an integer number 102 | func IS(n string) IntegerType { 103 | v, err := strconv.Atoi(n) 104 | if err != nil { 105 | panic(err) 106 | } 107 | return IntegerType(&v) 108 | } 109 | func ISA(n []string) []IntegerType { 110 | result := make([]IntegerType, len(n)) 111 | for i := range n { 112 | result[i] = IS(n[i]) 113 | } 114 | return result 115 | } 116 | -------------------------------------------------------------------------------- /generator/renderer_test.go: -------------------------------------------------------------------------------- 1 | package generator_test 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | 7 | _ "embed" 8 | 9 | "github.com/golang/mock/gomock" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | 13 | // . "github.com/onsi/gomega/format" 14 | 15 | "github.com/MetalBlueberry/go-plotly/generator" 16 | "github.com/MetalBlueberry/go-plotly/generator/mocks" 17 | ) 18 | 19 | // This test schema is used to make sure the generator works as expected 20 | // It may be worth extending this to test with all schemas. 21 | // 22 | //go:embed test_schema.json 23 | var schema []byte 24 | 25 | var _ = Describe("Renderer", func() { 26 | 27 | Describe("A single trace", func() { 28 | var ( 29 | ctrl *gomock.Controller 30 | mockCreator *mocks.MockCreator 31 | ) 32 | BeforeEach(func() { 33 | ctrl = gomock.NewController(GinkgoT()) 34 | mockCreator = mocks.NewMockCreator(ctrl) 35 | }) 36 | AfterEach(func() { 37 | ctrl.Finish() 38 | }) 39 | 40 | It("Should create package", func() { 41 | buf := NopWriterCloser{&bytes.Buffer{}} 42 | 43 | mockCreator.EXPECT().Create(gomock.Eq("scatter_gen.go")).Return(buf, nil).Times(1) 44 | 45 | root, err := generator.LoadSchema(bytes.NewReader(schema)) 46 | Expect(err).To(BeNil()) 47 | 48 | r, err := generator.NewRenderer(mockCreator, root) 49 | Expect(err).To(BeNil()) 50 | 51 | err = r.CreateTrace(".", "scatter") 52 | Expect(err).To(BeNil()) 53 | 54 | formatted, err := format.Source(buf.Bytes()) 55 | Expect(err).To(BeNil()) 56 | 57 | Expect(string(formatted)).To(ContainSubstring(`package grob`)) 58 | // Type is defined 59 | Expect(string(formatted)).To(ContainSubstring(`type Scatter struct`)) 60 | // Implements interface GetType() 61 | Expect(string(formatted)).To(ContainSubstring(`func (t *Scatter) GetType() types.TraceType`)) 62 | 63 | }) 64 | }) 65 | Describe("When writing", func() { 66 | var r *generator.Renderer 67 | 68 | BeforeEach(func() { 69 | root, err := generator.LoadSchema(bytes.NewReader(schema)) 70 | Expect(err).To(BeNil()) 71 | 72 | r, err = generator.NewRenderer(nil, root) 73 | Expect(err).To(BeNil()) 74 | }) 75 | 76 | Describe("The config", func() { 77 | It("Should be consistent", func() { 78 | 79 | original := &bytes.Buffer{} 80 | err := r.WriteConfig(original) 81 | Expect(err).To(BeNil()) 82 | 83 | for i := 0; i < 10; i++ { 84 | attempt := &bytes.Buffer{} 85 | err = r.WriteConfig(attempt) 86 | Expect(err).To(BeNil()) 87 | Expect(attempt).To(Equal(original)) 88 | } 89 | 90 | }) 91 | }) 92 | Describe("The Layout", func() { 93 | // TruncatedDiff = false 94 | // MaxLength = 0 95 | It("Should be consistent", func() { 96 | 97 | original := &bytes.Buffer{} 98 | err := r.WriteLayout(original) 99 | Expect(err).To(BeNil()) 100 | 101 | for i := 0; i < 10; i++ { 102 | attempt := &bytes.Buffer{} 103 | err = r.WriteLayout(attempt) 104 | Expect(err).To(BeNil()) 105 | Expect(attempt.String()).To(Equal(original.String())) 106 | } 107 | 108 | }) 109 | }) 110 | }) 111 | }) 112 | 113 | type NopWriterCloser struct { 114 | *bytes.Buffer 115 | } 116 | 117 | func (_ NopWriterCloser) Close() error { 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /pkg/types/color.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // Color A string describing color. Supported formats: - hex (e.g. '#d3d3d3') - rgb (e.g. 'rgb(255, 0, 0)') - rgba (e.g. 'rgb(255, 0, 0, 0.5)') - hsl (e.g. 'hsl(0, 100%, 50%)') - hsv (e.g. 'hsv(0, 100%, 100%)') - named colors (full list: http://www.w3.org/TR/css3-color/#svg-color)", 9 | type Color string 10 | 11 | func C(v string) Color { 12 | return Color(v) 13 | } 14 | 15 | func CN(v []string) []Color { 16 | result := make([]Color, len(v)) 17 | for i := range v { 18 | result[i] = C(v[i]) 19 | } 20 | return result 21 | } 22 | 23 | func UseColorScaleValues(in []float64) []ColorWithColorScale { 24 | out := make([]ColorWithColorScale, len(in), len(in)) 25 | for i := 0; i < len(in); i++ { 26 | out[i] = ColorWithColorScale{ 27 | Value: in[i], 28 | } 29 | } 30 | return out 31 | } 32 | 33 | func UseColors(in []Color) []ColorWithColorScale { 34 | out := make([]ColorWithColorScale, len(in), len(in)) 35 | for i := 0; i < len(in); i++ { 36 | out[i] = ColorWithColorScale{ 37 | Color: &in[i], 38 | } 39 | } 40 | return out 41 | } 42 | 43 | func UseColor(in Color) ColorWithColorScale { 44 | return ColorWithColorScale{ 45 | Color: &in, 46 | } 47 | } 48 | 49 | type ColorWithColorScale struct { 50 | Color *Color 51 | Value float64 52 | } 53 | 54 | func (c *ColorWithColorScale) MarshalJSON() ([]byte, error) { 55 | if c.Color != nil { 56 | return json.Marshal(c.Color) 57 | } 58 | return json.Marshal(c.Value) 59 | } 60 | 61 | func (c *ColorWithColorScale) UnmarshalJSON(data []byte) error { 62 | c.Color = nil 63 | 64 | var color Color 65 | err := json.Unmarshal(data, &color) 66 | if err == nil { 67 | c.Color = &color 68 | return nil 69 | } 70 | 71 | var value float64 72 | err = json.Unmarshal(data, &value) 73 | if err != nil { 74 | return err 75 | } 76 | c.Value = value 77 | return nil 78 | } 79 | 80 | // ColorList A list of colors. Must be an {array} containing valid colors. 81 | type ColorList []Color 82 | 83 | // ColorScale A Plotly colorscale either picked by a name: (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, RdBu, Reds, Blues, Picnic, Rainbow, Portland, Jet, Hot, Blackbody, Earth, Electric, Viridis, Cividis ) customized as an {array} of 2-element {arrays} where the first element is the normalized color level value (starting at *0* and ending at *1*), and the second item is a valid color string. 84 | type ColorScale struct { 85 | Name string 86 | Values []ColorScaleReference 87 | } 88 | 89 | type ColorScaleReference struct { 90 | NormalizedValue float64 91 | Color Color 92 | } 93 | 94 | func (cs *ColorScaleReference) MarshalJSON() ([]byte, error) { 95 | data := []interface{}{cs.NormalizedValue, cs.Color} 96 | return json.Marshal(data) 97 | } 98 | 99 | func (cs *ColorScale) MarshalJSON() ([]byte, error) { 100 | if cs.Values != nil { 101 | return json.Marshal(cs.Values) 102 | } 103 | return json.Marshal(cs.Name) 104 | } 105 | 106 | func (cs *ColorScale) UnmarshalJSON(data []byte) error { 107 | var values []ColorScaleReference 108 | 109 | err := json.Unmarshal(data, &values) 110 | if err == nil { 111 | cs.Values = values 112 | return nil 113 | } 114 | var name string 115 | err = json.Unmarshal(data, &name) 116 | if err == nil { 117 | cs.Name = name 118 | return nil 119 | } 120 | return fmt.Errorf("Unable to Unmarshal ColorScale, %w", err) 121 | } 122 | -------------------------------------------------------------------------------- /examples/stargazers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "text/template" 11 | "time" 12 | 13 | "github.com/pkg/browser" 14 | 15 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 16 | "github.com/MetalBlueberry/go-plotly/pkg/types" 17 | ) 18 | 19 | type User struct { 20 | Login string `json:"login,omitempty"` 21 | AvatarURL string `json:"avatar_url,omitempty"` 22 | } 23 | 24 | type GitHubStargazers struct { 25 | StarredAt time.Time `json:"starred_at,omitempty"` 26 | User User `json:"user,omitempty"` 27 | } 28 | 29 | func main() { 30 | 31 | request, err := http.NewRequest("GET", "https://api.github.com/repos/MetalBlueberry/go-plotly/stargazers", nil) 32 | if err != nil { 33 | log.Fatalf("Failed to build request, %s", err) 34 | } 35 | request.Header.Set("Accept", "application/vnd.github.star+json") 36 | 37 | q := request.URL.Query() 38 | q.Set("per_page", "100") 39 | request.URL.RawQuery = q.Encode() 40 | 41 | response, err := http.DefaultClient.Do(request) 42 | if err != nil { 43 | log.Fatalf("Failed to make request to github, %s", err) 44 | } 45 | 46 | defer response.Body.Close() 47 | 48 | buf := &bytes.Buffer{} 49 | 50 | starredAt := []GitHubStargazers{} 51 | err = json.NewDecoder(io.TeeReader(response.Body, buf)).Decode(&starredAt) 52 | if err != nil { 53 | log.Fatalf("Failed to decode response, %s\n%s", err, buf.String()) 54 | } 55 | 56 | x := []string{} 57 | y := []int{} 58 | text := []types.StringType{} 59 | link := []interface{}{} 60 | 61 | for i := 0; i < len(starredAt); i++ { 62 | x = append(x, starredAt[i].StarredAt.Format(time.RFC3339)) 63 | y = append(y, i+1) 64 | text = append(text, types.S(starredAt[i].User.Login)) 65 | link = append(link, starredAt[i].User.AvatarURL) 66 | 67 | } 68 | 69 | fig := &grob.Fig{ 70 | Data: []types.Trace{ 71 | &grob.Scatter{ 72 | X: types.DataArray(x), 73 | Y: types.DataArray(y), 74 | Text: types.ArrayOKArray(text...), 75 | Meta: types.ArrayOKArray(link...), 76 | Mode: grob.ScatterModeLines + "+" + grob.ScatterModeMarkers, 77 | Name: types.S("Stars"), 78 | Line: &grob.ScatterLine{ 79 | Color: "#f0ed46", 80 | }, 81 | }, 82 | }, 83 | 84 | Layout: &grob.Layout{ 85 | Title: &grob.LayoutTitle{ 86 | Text: types.S("Metalblueberry/go-plotly Stargazers"), 87 | }, 88 | Legend: &grob.LayoutLegend{}, 89 | }, 90 | } 91 | 92 | db, _ := json.MarshalIndent(fig, "", " ") 93 | fmt.Println(string(db)) 94 | 95 | buf = figToBuffer(fig) 96 | browser.OpenReader(buf) 97 | } 98 | 99 | var page = ` 100 | 101 | 102 | 103 | 120 | 121 | ` 122 | 123 | func figToBuffer(fig *grob.Fig) *bytes.Buffer { 124 | figBytes, err := json.Marshal(fig) 125 | if err != nil { 126 | panic(err) 127 | } 128 | tmpl, err := template.New("plotly").Parse(page) 129 | if err != nil { 130 | panic(err) 131 | } 132 | buf := &bytes.Buffer{} 133 | tmpl.Execute(buf, string(figBytes)) 134 | return buf 135 | } 136 | -------------------------------------------------------------------------------- /pkg/types/data_array_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/MetalBlueberry/go-plotly/pkg/types" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var testSomeEncodedDataString = "SomeEncodedData" 15 | var testSomeEncodedDataBytes = []byte("SomeEncodedData") 16 | var testSomeEncodedData = base64.StdEncoding.EncodeToString([]byte(testSomeEncodedDataString)) 17 | 18 | func TestDataArrayType_MarshalJSON(t *testing.T) { 19 | 20 | tests := []struct { 21 | name string 22 | data *types.DataArrayType 23 | expected string 24 | }{ 25 | { 26 | name: "Marshal with values", 27 | data: types.DataArray([]int{1, 2, 3}), 28 | expected: `[1,2,3]`, 29 | }, 30 | { 31 | name: "Marshal with bdata", 32 | data: types.BDataArray(types.DTypeFloat32, testSomeEncodedDataBytes, []int{2, 2}), 33 | expected: fmt.Sprintf(`{"dtype":"float32","bdata":"%s","shape":"2,2"}`, testSomeEncodedData), 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | b, err := tt.data.MarshalJSON() 40 | if err != nil { 41 | t.Fatalf("MarshalJSON() error = %v", err) 42 | } 43 | if string(b) != tt.expected { 44 | t.Errorf("MarshalJSON() = %v, want %v", string(b), tt.expected) 45 | } 46 | }) 47 | } 48 | } 49 | 50 | func TestDataArrayType_UnmarshalJSON(t *testing.T) { 51 | tests := []struct { 52 | name string 53 | data string 54 | expected *types.DataArrayType 55 | }{ 56 | { 57 | name: "Unmarshal with values", 58 | data: `[1,2,3]`, 59 | expected: types.DataArray([]float64{1, 2, 3}), 60 | }, 61 | { 62 | name: "Unmarshal with bdata", 63 | data: fmt.Sprintf(`{"dtype":"float32","bdata":"%s","shape":"2,2"}`, testSomeEncodedData), 64 | expected: types.BDataArray(types.DTypeFloat32, testSomeEncodedDataBytes, []int{2, 2}), 65 | }, 66 | } 67 | 68 | for _, tt := range tests { 69 | t.Run(tt.name, func(t *testing.T) { 70 | g := NewWithT(t) 71 | got := &types.DataArrayType{} 72 | if err := json.Unmarshal([]byte(tt.data), got); err != nil { 73 | t.Fatalf("UnmarshalJSON() error = %v", err) 74 | } 75 | g.Expect(tt.expected).To(Equal(got)) 76 | }) 77 | } 78 | } 79 | 80 | func TestDataArrayShape_MarshalJSON(t *testing.T) { 81 | tests := []struct { 82 | name string 83 | data types.DataArrayShape 84 | expected string 85 | }{ 86 | { 87 | name: "Marshal shape", 88 | data: types.DataArrayShape{2, 3, 4}, 89 | expected: `"2,3,4"`, 90 | }, 91 | } 92 | 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | b, err := tt.data.MarshalJSON() 96 | if err != nil { 97 | t.Fatalf("MarshalJSON() error = %v", err) 98 | } 99 | if string(b) != tt.expected { 100 | t.Errorf("MarshalJSON() = %v, want %v", string(b), tt.expected) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | func TestDataArrayShape_UnmarshalJSON(t *testing.T) { 107 | tests := []struct { 108 | name string 109 | data string 110 | expected types.DataArrayShape 111 | }{ 112 | { 113 | name: "Unmarshal shape", 114 | data: `"2,3,4"`, 115 | expected: types.DataArrayShape{2, 3, 4}, 116 | }, 117 | } 118 | 119 | for _, tt := range tests { 120 | t.Run(tt.name, func(t *testing.T) { 121 | var got types.DataArrayShape 122 | if err := json.Unmarshal([]byte(tt.data), &got); err != nil { 123 | t.Fatalf("UnmarshalJSON() error = %v", err) 124 | } 125 | if !reflect.DeepEqual(got, tt.expected) { 126 | t.Errorf("UnmarshalJSON() = %+v, want %+v", got, tt.expected) 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /examples/range_slider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/go-gota/gota/dataframe" 8 | 9 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 10 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 11 | "github.com/MetalBlueberry/go-plotly/pkg/types" 12 | ) 13 | 14 | func main() { 15 | /* 16 | https://plotly.com/python/range-slider/ 17 | import plotly.graph_objects as go 18 | 19 | import pandas as pd 20 | 21 | # Load data 22 | df = pd.read_csv( 23 | "https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv") 24 | df.columns = [col.replace("AAPL.", "") for col in df.columns] 25 | 26 | # Create figure 27 | fig = go.Figure() 28 | 29 | fig.add_trace( 30 | go.Scatter(x=list(df.Date), y=list(df.High))) 31 | 32 | # Set title 33 | fig.update_layout( 34 | title_text="Time series with range slider and selectors" 35 | ) 36 | 37 | # Add range slider 38 | fig.update_layout( 39 | xaxis=dict( 40 | rangeselector=dict( 41 | buttons=list([ 42 | dict(count=1, 43 | label="1m", 44 | step="month", 45 | stepmode="backward"), 46 | dict(count=6, 47 | label="6m", 48 | step="month", 49 | stepmode="backward"), 50 | dict(count=1, 51 | label="YTD", 52 | step="year", 53 | stepmode="todate"), 54 | dict(count=1, 55 | label="1y", 56 | step="year", 57 | stepmode="backward"), 58 | dict(step="all") 59 | ]) 60 | ), 61 | rangeslider=dict( 62 | visible=True 63 | ), 64 | type="date" 65 | ) 66 | ) 67 | 68 | fig.show() 69 | */ 70 | 71 | request, err := http.Get("https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv") 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | df := dataframe.ReadCSV(request.Body) 77 | for _, col := range df.Names() { 78 | df = df.Rename(strings.TrimPrefix(col, "AAPL."), col) 79 | } 80 | 81 | fig := &grob.Fig{} 82 | fig.Data = append(fig.Data, &grob.Scatter{ 83 | X: types.DataArray(df.Col("Date").Records()), 84 | Y: types.DataArray(df.Col("High").Float()), 85 | }) 86 | 87 | fig.Layout = &grob.Layout{ 88 | Title: &grob.LayoutTitle{ 89 | Text: types.S("Time series with range slider and selectors"), 90 | }, 91 | Xaxis: &grob.LayoutXaxis{ 92 | Rangeselector: &grob.LayoutXaxisRangeselector{ 93 | Buttons: []grob.LayoutXaxisRangeselectorButton{ 94 | { 95 | Count: types.N(1), 96 | Label: types.S("1m"), 97 | Step: "month", 98 | Stepmode: "backward", 99 | }, 100 | { 101 | Count: types.N(6), 102 | Label: types.S("6m"), 103 | Step: "month", 104 | Stepmode: "backward", 105 | }, 106 | { 107 | Count: types.N(1), 108 | Label: types.S("YTD"), 109 | Step: "year", 110 | Stepmode: "todate", 111 | }, 112 | { 113 | Count: types.N(1), 114 | Label: types.S("1y"), 115 | Step: "year", 116 | Stepmode: "backward", 117 | }, 118 | { 119 | Step: "all", 120 | }, 121 | }, 122 | }, 123 | Rangeslider: &grob.LayoutXaxisRangeslider{ 124 | Visible: types.True, 125 | }, 126 | Type: grob.LayoutXaxisTypeDate, 127 | }, 128 | } 129 | 130 | offline.ToHtml(fig, "range_slider.html") 131 | offline.Show(fig) 132 | } 133 | -------------------------------------------------------------------------------- /generator/cmd/generator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/MetalBlueberry/go-plotly/generator" 12 | ) 13 | 14 | type Creator struct{} 15 | 16 | func (c Creator) Create(name string) (io.WriteCloser, error) { 17 | abs, err := filepath.Abs(name) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return os.Create(abs) 23 | } 24 | 25 | // The generate command always executes with the working directory set to the path with the file with the directive 26 | //go:generate go run main.go --config=../../../schemas.yaml -clean 27 | 28 | const configPath = "schemas.yaml" 29 | 30 | func main() { 31 | var schemapath string 32 | var clean bool 33 | 34 | flag.StringVar(&schemapath, "config", configPath, "yaml file defining versions to be generated") 35 | flag.BoolVar(&clean, "clean", false, "clean the output directory first. Mandatory on CI") 36 | 37 | flag.Parse() 38 | 39 | // Define a flag for the version 40 | schemas := generator.ReadSchemas(schemapath) 41 | if schemas == nil { 42 | fmt.Printf("could not find versions\n") 43 | return 44 | } 45 | 46 | root := filepath.Dir(schemapath) 47 | 48 | for _, schema := range schemas { 49 | generatePackage(root, schema, clean) 50 | } 51 | 52 | } 53 | 54 | func rootDirectories(root, schema, output string) (string, string) { 55 | schema = filepath.Join(root, schema) 56 | output = filepath.Join(root, output) 57 | 58 | return schema, output 59 | } 60 | 61 | // create the packages and write them into the specified folders 62 | 63 | func generatePackage(projectRoot string, version generator.Version, clean bool) { 64 | // look for the correct schema and output paths 65 | schema, relativeVersionOutput := rootDirectories(projectRoot, version.Path, version.Generated) 66 | log.Println("schema:", schema, "versionoutput", relativeVersionOutput) 67 | 68 | file, err := os.Open(schema) 69 | if err != nil { 70 | log.Fatalf("unable to open schema, %s", err) 71 | } 72 | 73 | graphObjectsOuput := filepath.Join(relativeVersionOutput, "graph_objects") 74 | 75 | schemaRoot, err := generator.LoadSchema(file) 76 | if err != nil { 77 | log.Fatalf("unable to load schema, %s", err) 78 | } 79 | 80 | r, err := generator.NewRenderer(Creator{}, schemaRoot) 81 | if err != nil { 82 | log.Fatalf("unable to create a new renderer, %s", err) 83 | } 84 | 85 | if clean { 86 | for _, path := range []string{graphObjectsOuput} { 87 | err = os.RemoveAll(path) 88 | if err != nil { 89 | log.Fatalf("Failed to clean output directory, %s", err) 90 | } 91 | 92 | if err = os.MkdirAll(path, 0755); err != nil { 93 | log.Fatalf("Failed to create output dir %s, %s", path, err) 94 | } 95 | } 96 | } 97 | 98 | err = r.CreatePlotly(graphObjectsOuput, version) 99 | if err != nil { 100 | log.Fatalf("unable to write plotly, %s", err) 101 | } 102 | 103 | err = r.CreateTraces(graphObjectsOuput) 104 | if err != nil { 105 | log.Fatalf("unable to write traces, %s", err) 106 | } 107 | 108 | err = r.CreateLayout(graphObjectsOuput) 109 | if err != nil { 110 | log.Fatalf("unable to write layout, %s", err) 111 | } 112 | 113 | err = r.CreateConfig(graphObjectsOuput) 114 | if err != nil { 115 | log.Fatalf("unable to write config, %s", err) 116 | } 117 | 118 | err = r.CreateAnimation(graphObjectsOuput) 119 | if err != nil { 120 | log.Fatalf("unable to write layout, %s", err) 121 | } 122 | 123 | err = r.CreateFrames(graphObjectsOuput) 124 | if err != nil { 125 | log.Fatalf("unable to write layout, %s", err) 126 | } 127 | 128 | err = r.CreateUnmarshal(graphObjectsOuput) 129 | if err != nil { 130 | log.Fatalf("unable to write unmarshal, %s", err) 131 | } 132 | 133 | err = r.CreateUnmarshalTests(graphObjectsOuput) 134 | if err != nil { 135 | log.Fatalf("unable to write unmarshal, %s", err) 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /pkg/offline/plot.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "log" 8 | "net/http" 9 | "os" 10 | "text/template" 11 | 12 | "github.com/MetalBlueberry/go-plotly/pkg/types" 13 | "github.com/pkg/browser" 14 | ) 15 | 16 | type Options struct { 17 | Addr string 18 | } 19 | 20 | // ToHtml saves the figure as standalone HTML. It still requires internet to load plotly.js from CDN. 21 | func ToHtml(fig types.Fig, path string) { 22 | buf := figToBuffer(fig) 23 | os.WriteFile(path, buf.Bytes(), os.ModePerm) 24 | } 25 | 26 | // Show displays the figure in your browser. 27 | // Use serve if you want a persistent view 28 | func Show(fig types.Fig) { 29 | buf := figToBuffer(fig) 30 | browser.OpenReader(buf) 31 | } 32 | 33 | func figToBuffer(fig types.Fig) *bytes.Buffer { 34 | figBytes, err := json.Marshal(fig) 35 | if err != nil { 36 | panic(err) 37 | } 38 | tmpl, err := template.New("plotly").Parse(singleFileHTML) 39 | if err != nil { 40 | panic(err) 41 | } 42 | buf := &bytes.Buffer{} 43 | data := struct { 44 | Version types.Version 45 | B64Content string 46 | }{ 47 | Version: fig.Info(), 48 | // Encode to avoid problems with special characters 49 | B64Content: base64.StdEncoding.EncodeToString(figBytes), 50 | } 51 | 52 | err = tmpl.Execute(buf, data) 53 | if err != nil { 54 | panic(err) 55 | } 56 | return buf 57 | } 58 | 59 | // Serve creates a local web server that displays the image using plotly.js 60 | // Is a good alternative to Show to avoid creating tmp files. 61 | func Serve(fig types.Fig, opt ...Options) { 62 | opts := computeOptions(Options{ 63 | Addr: "localhost:8080", 64 | }, opt...) 65 | 66 | mux := &http.ServeMux{} 67 | srv := &http.Server{ 68 | Handler: mux, 69 | Addr: opts.Addr, 70 | } 71 | 72 | mux.HandleFunc("/content", func(w http.ResponseWriter, r *http.Request) { 73 | err := json.NewEncoder(w).Encode(fig) 74 | if err != nil { 75 | log.Printf("Error rendering template, %s", err) 76 | w.WriteHeader(http.StatusInternalServerError) 77 | } 78 | }) 79 | 80 | mux.HandleFunc("/", webPage(fig, "/content")) 81 | 82 | log.Printf("Starting server at %s", srv.Addr) 83 | if err := srv.ListenAndServe(); err != nil { 84 | log.Print(err) 85 | } 86 | log.Print("Stop server") 87 | } 88 | 89 | func computeOptions(def Options, opt ...Options) Options { 90 | if len(opt) == 1 { 91 | opts := opt[0] 92 | if opts.Addr != "" { 93 | def.Addr = opts.Addr 94 | } 95 | } 96 | return def 97 | } 98 | 99 | var singleFileHTML = ` 100 | 101 | 102 | 103 | 104 | 105 | 109 | 110 | ` 111 | 112 | type serverHTMLdata struct { 113 | Version types.Version 114 | ContentURL string 115 | } 116 | 117 | func webPage(fig types.Fig, contentURL string) func(w http.ResponseWriter, r *http.Request) { 118 | return func(w http.ResponseWriter, r *http.Request) { 119 | tmpl := template.Must(template.New("server").Parse(serverHTML)) 120 | data := serverHTMLdata{ 121 | Version: fig.Info(), 122 | ContentURL: contentURL, 123 | } 124 | err := tmpl.Execute(w, data) 125 | if err != nil { 126 | log.Printf("Error rendering template, %s", err) 127 | w.WriteHeader(http.StatusInternalServerError) 128 | return 129 | } 130 | } 131 | } 132 | 133 | var serverHTML = ` 134 | 135 | 136 | 137 | 138 | 139 | 149 | 150 | ` 151 | -------------------------------------------------------------------------------- /generator/schema.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | 12 | "gopkg.in/yaml.v3" 13 | ) 14 | 15 | type Schemas struct { 16 | Versions []Version `yaml:"versions"` 17 | } 18 | 19 | type Version struct { 20 | Name string `yaml:"Name"` // name of the version 21 | Tag string `yaml:"Tag"` // git tag of the plotly version 22 | URL string `yaml:"URL"` // url under which the plotly schema json file can be downloaded directly 23 | Path string `yaml:"Path"` // path under which the schema file will be saved locally for future use 24 | Generated string `yaml:"Generated"` // path for the generated package 25 | Cdn string `yaml:"CDN"` // url for the cdn which should be included in the head of the generated html 26 | } 27 | 28 | // cleanJson makes sure that whitespaces and new line characters are removed from the downloaded json 29 | func cleanJson(stream io.ReadCloser) (bytes.Buffer, error) { 30 | var err error 31 | var buf bytes.Buffer 32 | 33 | // Read and decode JSON content 34 | var jsonData interface{} 35 | err = json.NewDecoder(stream).Decode(&jsonData) 36 | if err != nil { 37 | return buf, fmt.Errorf("could not decode json content of response") 38 | } 39 | 40 | // Re-encode JSON content with minimized whitespace and no new lines 41 | encoder := json.NewEncoder(&buf) 42 | encoder.SetIndent("", "") 43 | err = encoder.Encode(jsonData) 44 | if err != nil { 45 | return buf, fmt.Errorf("could not remove whitespaces and new lines from json by encoding content") 46 | } 47 | // Remove newline character from the end of the encoded JSON 48 | buf.Truncate(buf.Len() - 1) 49 | return buf, nil 50 | } 51 | 52 | // DownloadSchema gets the schema file for a given version, and either saves the raw content, 53 | // or prepares the file for the generator if prep is set to true 54 | func DownloadSchema(version string, schemaURL string, outputpath string) (string, error) { 55 | // Make GET request to download schema 56 | response, err := http.Get(schemaURL) 57 | if err != nil { 58 | return "", err 59 | } 60 | if response.StatusCode != http.StatusOK { 61 | return "", fmt.Errorf("could not schema file for version: %s\n[Status Code: %d (%s)]\nused url: %s\nlist the available tags first and recheck", version, response.StatusCode, response.Status, schemaURL) 62 | } 63 | defer response.Body.Close() 64 | 65 | // clean the response 66 | buf, err := cleanJson(response.Body) 67 | if err != nil { 68 | return "", fmt.Errorf("%s: from: %s", err, schemaURL) 69 | } 70 | 71 | // Create file to save the schema 72 | err = os.MkdirAll(filepath.Dir(outputpath), 0755) 73 | if err != nil { 74 | return "", fmt.Errorf("could not create directory for plotly schema: %s", filepath.Dir(outputpath)) 75 | } 76 | 77 | _, err = os.Stat(outputpath) 78 | if err == nil { 79 | fmt.Printf("WARN: overwriting: %s\n", outputpath) 80 | } 81 | file, err := os.Create(outputpath) 82 | if err != nil { 83 | return "", fmt.Errorf("could not create file for saving: %s", outputpath) 84 | } 85 | defer file.Close() 86 | 87 | // Copy response body to file 88 | fmt.Printf("Downloading %s -> %s\n", schemaURL, outputpath) 89 | 90 | _, err = file.Write([]byte(`{"sha1":"11b662302a42aa0698df091a9974ac8f6e1a2292","modified":true,"schema":`)) 91 | if err != nil { 92 | return "", fmt.Errorf("could not write json schema prepared for code generator: %s", outputpath) 93 | } 94 | 95 | // Write JSON content to file 96 | _, err = io.Copy(file, &buf) 97 | if err != nil { 98 | return "", err 99 | } 100 | 101 | _, err = file.Write([]byte(`}`)) 102 | if err != nil { 103 | return "", err 104 | } 105 | 106 | fmt.Printf("Schema downloaded and saved as: %s\n", outputpath) 107 | return outputpath, nil 108 | } 109 | 110 | func ReadSchemas(schemapath string) []Version { 111 | var err error 112 | f, err := os.Open(schemapath) 113 | if err != nil { 114 | fmt.Printf("error opening schema yaml file: %s\n", err) 115 | return nil 116 | } 117 | 118 | var schemas Schemas 119 | decoder := yaml.NewDecoder(f) 120 | err = decoder.Decode(&schemas) 121 | if err != nil { 122 | fmt.Printf("error decoding schema yaml file: %s\n", err) 123 | return nil 124 | } 125 | 126 | return schemas.Versions 127 | } 128 | -------------------------------------------------------------------------------- /examples/waterfall_bar_chart/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | grob "github.com/MetalBlueberry/go-plotly/generated/v2.31.1/graph_objects" 5 | "github.com/MetalBlueberry/go-plotly/pkg/offline" 6 | "github.com/MetalBlueberry/go-plotly/pkg/types" 7 | ) 8 | 9 | func main() { 10 | /* 11 | https://plotly.com/javascript/bar-charts/#waterfall-bar-chart 12 | 13 | // Base 14 | 15 | var xData = ['Product