Skip to content

Commit

Permalink
enable offline plots
Browse files Browse the repository at this point in the history
  • Loading branch information
pafo committed May 12, 2024
1 parent 2a3e744 commit da906d6
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 85 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ The local paths are relative to the project root.
Be aware, that the template file [plotly.tmpl](generator%2Ftemplates%2Fplotly.tmpl) also has a go generate directive.
This directive has relative paths, based on the assumed final path within the project

#### embedding a downloaded plotly js source

In the example: [main.go](examples%2Fbar%2Fmain.go) you find an example to reuse a static plotly js file and pass that reference to the generated html, instead of using a hardcoded CDN reference which would require internet connection.
```go
abs, err := filepath.Abs("asset/plotly-2.29.1.min.js")
if err != nil {
return
}
grob.ToHtml(fig, "bar.html", grob.FigOptions{HeadContent: fmt.Sprintf(`<title>Offline Bars</title><script src="%s"></script>`, abs)})
grob.Show(fig, grob.FigOptions{HeadContent: fmt.Sprintf(`<title>Offline Bars</title><script src="%s"></script>`, abs)})
```

#### Missing Files?

Expand Down Expand Up @@ -186,6 +197,7 @@ http://plotly-json-editor.getforge.io/
- removed offline package, as each version package needs its own **plot.go**.
- Replaced all `offline` imports in the examples packages by just calling the `grob` package of the specified version
- all html's will now refer to the correct plotly version's cdn in the html's head
- allow defining your own head for the plot, which allows embedding the plotly.js source instead of referencing the CDN. This enables generation of offline plots

## Official Plotly Release Notes
For detailed changes please follow the release notes of the original JS repo: https://github.com/plotly/plotly.js/releases
8 changes: 8 additions & 0 deletions examples/asset/plotly-2.29.1.min.js

Large diffs are not rendered by default.

12 changes: 0 additions & 12 deletions examples/bar.html

This file was deleted.

12 changes: 12 additions & 0 deletions examples/bar/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import (
"fmt"
"path/filepath"

grob "github.com/MetalBlueberry/go-plotly/generated/v2.29.1/graph_objects"
)

Expand Down Expand Up @@ -28,6 +31,15 @@ func main() {
},
}

// by default, using the cdn reference, downloading the plotly js on demand
grob.ToHtml(fig, "bar.html")
grob.Show(fig)

// example for using static assets, which can be embedded into the golang application
abs, err := filepath.Abs("asset/plotly-2.29.1.min.js")
if err != nil {
return
}
grob.ToHtml(fig, "bar.html", grob.FigOptions{HeadContent: fmt.Sprintf(`<title>Offline Bars</title><script src="%s"></script>`, abs)})
grob.Show(fig, grob.FigOptions{HeadContent: fmt.Sprintf(`<title>Offline Bars</title><script src="%s"></script>`, abs)})
}
1 change: 1 addition & 0 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
)

require (
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
Expand Down
61 changes: 44 additions & 17 deletions generated/v2.19.0/graph_objects/plot_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,61 @@ package grob
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"text/template"

"github.com/pkg/browser"
"github.com/pkg/errors"
)

// FigOptions enables users to pass their custom cdn or local plotly file reference to allow building offline solutions
type FigOptions struct {
HeadContent string
}

type Options struct {
FigOptions
Addr string
}

// ToHtml saves the figure as standalone HTML. It still requires internet to load plotly.js from CDN.
func ToHtml(fig *Fig, path string) {
buf := figToBuffer(fig)
func ToHtml(fig *Fig, path string, options ...FigOptions) {
buf := figToBuffer(fig, options...)
os.WriteFile(path, buf.Bytes(), os.ModePerm)
}

// Show displays the figure in your browser.
// Use serve if you want a persistent view
func Show(fig *Fig) {
buf := figToBuffer(fig)
func Show(fig *Fig, options ...FigOptions) {
buf := figToBuffer(fig, options...)
browser.OpenReader(buf)
}

func figToBuffer(fig *Fig) *bytes.Buffer {
const cdnUrl = `<script src="https://cdn.plot.ly/plotly-2.19.0.min.js"></script>`

// computeFigOptions allows to default to the common cdn reference
func computeFigOptions(options ...FigOptions) string {
if len(options) > 1 {
panic(errors.New(fmt.Sprintf("only use 1 option at a time. Passed: %d. options: %+v", len(options), options)))
}
if len(options) == 0 {
return cdnUrl
}
return options[0].HeadContent
}

// figToBuffer with optional parameter options, only the first argument is evaluated
func figToBuffer(fig *Fig, options ...FigOptions) *bytes.Buffer {
var headContent string = computeFigOptions(options...)

figBytes, err := json.Marshal(fig)
if err != nil {
panic(err)
}
tmpl, err := template.New("plotly").Parse(baseHtml)
tmpl, err := template.New("plotly").Parse(getBaseHtml(headContent))
if err != nil {
panic(err)
}
Expand All @@ -44,10 +68,10 @@ func figToBuffer(fig *Fig) *bytes.Buffer {

// Serve creates a local web server that displays the image using plotly.js
// Is a good alternative to Show to avoid creating tmp files.
// plotlyversion refers to the version of plotly to be used, such that the CDN can be correctly referenced in the head
func Serve(fig *Fig, opt ...Options) {
opts := computeOptions(Options{
Addr: "localhost:8080",
FigOptions: FigOptions{HeadContent: cdnUrl},
Addr: "localhost:8080",
}, opt...)

mux := &http.ServeMux{}
Expand All @@ -56,7 +80,7 @@ func Serve(fig *Fig, opt ...Options) {
Addr: opts.Addr,
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
buf := figToBuffer(fig)
buf := figToBuffer(fig, opts.FigOptions)
buf.WriteTo(w)
})

Expand All @@ -72,20 +96,23 @@ func computeOptions(def Options, opt ...Options) Options {
opts := opt[0]
if opts.Addr != "" {
def.Addr = opts.Addr
def.HeadContent = opts.HeadContent
}
}
return def
}

var baseHtml = `
<head>
<script src="https://cdn.plot.ly/plotly-2.19.0.min.js"></script>
</head>
</body>
<div id="plot"></div>
func getBaseHtml(head string) string {
return fmt.Sprintf(`
<head>
%s
</head>
<body>
<div id="plot"></div>
<script>
data = JSON.parse('{{ . }}')
Plotly.newPlot('plot', data);
</script>
<body>
`
</body>
`, head)
}
61 changes: 44 additions & 17 deletions generated/v2.29.1/graph_objects/plot_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,61 @@ package grob
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"text/template"

"github.com/pkg/browser"
"github.com/pkg/errors"
)

// FigOptions enables users to pass their custom cdn or local plotly file reference to allow building offline solutions
type FigOptions struct {
HeadContent string
}

type Options struct {
FigOptions
Addr string
}

// ToHtml saves the figure as standalone HTML. It still requires internet to load plotly.js from CDN.
func ToHtml(fig *Fig, path string) {
buf := figToBuffer(fig)
func ToHtml(fig *Fig, path string, options ...FigOptions) {
buf := figToBuffer(fig, options...)
os.WriteFile(path, buf.Bytes(), os.ModePerm)
}

// Show displays the figure in your browser.
// Use serve if you want a persistent view
func Show(fig *Fig) {
buf := figToBuffer(fig)
func Show(fig *Fig, options ...FigOptions) {
buf := figToBuffer(fig, options...)
browser.OpenReader(buf)
}

func figToBuffer(fig *Fig) *bytes.Buffer {
const cdnUrl = `<script src="https://cdn.plot.ly/plotly-2.29.1.min.js"></script>`

// computeFigOptions allows to default to the common cdn reference
func computeFigOptions(options ...FigOptions) string {
if len(options) > 1 {
panic(errors.New(fmt.Sprintf("only use 1 option at a time. Passed: %d. options: %+v", len(options), options)))
}
if len(options) == 0 {
return cdnUrl
}
return options[0].HeadContent
}

// figToBuffer with optional parameter options, only the first argument is evaluated
func figToBuffer(fig *Fig, options ...FigOptions) *bytes.Buffer {
var headContent string = computeFigOptions(options...)

figBytes, err := json.Marshal(fig)
if err != nil {
panic(err)
}
tmpl, err := template.New("plotly").Parse(baseHtml)
tmpl, err := template.New("plotly").Parse(getBaseHtml(headContent))
if err != nil {
panic(err)
}
Expand All @@ -44,10 +68,10 @@ func figToBuffer(fig *Fig) *bytes.Buffer {

// Serve creates a local web server that displays the image using plotly.js
// Is a good alternative to Show to avoid creating tmp files.
// plotlyversion refers to the version of plotly to be used, such that the CDN can be correctly referenced in the head
func Serve(fig *Fig, opt ...Options) {
opts := computeOptions(Options{
Addr: "localhost:8080",
FigOptions: FigOptions{HeadContent: cdnUrl},
Addr: "localhost:8080",
}, opt...)

mux := &http.ServeMux{}
Expand All @@ -56,7 +80,7 @@ func Serve(fig *Fig, opt ...Options) {
Addr: opts.Addr,
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
buf := figToBuffer(fig)
buf := figToBuffer(fig, opts.FigOptions)
buf.WriteTo(w)
})

Expand All @@ -72,20 +96,23 @@ func computeOptions(def Options, opt ...Options) Options {
opts := opt[0]
if opts.Addr != "" {
def.Addr = opts.Addr
def.HeadContent = opts.HeadContent
}
}
return def
}

var baseHtml = `
<head>
<script src="https://cdn.plot.ly/plotly-2.29.1.min.js"></script>
</head>
</body>
<div id="plot"></div>
func getBaseHtml(head string) string {
return fmt.Sprintf(`
<head>
%s
</head>
<body>
<div id="plot"></div>
<script>
data = JSON.parse('{{ . }}')
Plotly.newPlot('plot', data);
</script>
<body>
`
</body>
`, head)
}
Loading

0 comments on commit da906d6

Please sign in to comment.