-
-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Started working updating vsl.plot #181
Changes from all commits
965fe80
662755c
0e38019
9a5bf05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
module main | ||
|
||
import vsl.plot | ||
import vsl.util | ||
|
||
fn main() { | ||
y := [ | ||
0.0, | ||
1, | ||
3, | ||
1, | ||
0, | ||
-1, | ||
-3, | ||
-1, | ||
0, | ||
1, | ||
3, | ||
1, | ||
0, | ||
] | ||
x := util.arange(y.len) | ||
mut plt := plot.Plot.new() | ||
plt.scatter( | ||
x: x | ||
y: y | ||
mode: 'lines+markers' | ||
marker: plot.Marker{ | ||
size: []f64{len: x.len, init: 10.0} | ||
color: []string{len: x.len, init: '#FF0000'} | ||
} | ||
line: plot.Line{ | ||
color: '#FF0000' | ||
} | ||
) | ||
plt.layout( | ||
title: 'Scatter plot example' | ||
annotations: [plot.Annotation{ | ||
text: 'test annotation' | ||
}] | ||
) | ||
plt.show()! | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,118 +2,149 @@ module plot | |
|
||
import json | ||
import net | ||
import net.html | ||
import net.http | ||
import os | ||
import time | ||
|
||
type TracesWithTypeValue = Trace | string | ||
|
||
struct PlotlyHandler { | ||
plot Plot | ||
mut: | ||
server &http.Server [str: skip] = unsafe { nil } | ||
} | ||
|
||
fn (mut handler PlotlyHandler) handle(req http.Request) http.Response { | ||
mut r := http.Response{ | ||
body: handler.plot.plotly() | ||
header: req.header | ||
} | ||
r.set_status(.ok) | ||
r.set_version(req.version) | ||
go fn [mut handler] () { | ||
time.sleep(300 * time.millisecond) | ||
handler.server.close() | ||
}() | ||
return r | ||
// PlotConfig is a configuration for the Plotly plot. | ||
[params] | ||
pub struct PlotConfig { | ||
use_cdn bool | ||
} | ||
|
||
// show starts a web server and opens a browser window to display the plot. | ||
pub fn (plot Plot) show() ! { | ||
pub fn (p Plot) show(config PlotConfig) ! { | ||
$if test ? { | ||
println('Ignoring plot.show() because we are running in test mode') | ||
} $else { | ||
mut handler := PlotlyHandler{ | ||
plot: plot | ||
use_cdn: true | ||
plot: p | ||
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - use_cdn: true
+ use_cdn: config.use_cdn |
||
} | ||
listener := net.listen_tcp(net.AddrFamily.ip, ':0')! | ||
mut server := &http.Server{ | ||
accept_timeout: 1 * time.second | ||
listener: listener | ||
port: 0 | ||
handler: handler | ||
} | ||
handler.server = server | ||
t := spawn server.listen_and_serve() | ||
for server.status() != .running { | ||
time.sleep(10 * time.millisecond) | ||
} | ||
server.wait_till_running()! | ||
os.open_uri('http://${server.addr}')! | ||
t.wait() | ||
} | ||
} | ||
|
||
// TODO: This is a hack to allow the json encoder to work with sum types | ||
fn encode[T](obj T) string { | ||
strings_to_replace := [ | ||
',"[]f64"', | ||
'"[]f64"', | ||
',"[]string"', | ||
'"[]string"', | ||
] | ||
mut obj_json := json.encode(obj) | ||
for string_to_replace in strings_to_replace { | ||
obj_json = obj_json.replace(string_to_replace, '') | ||
} | ||
return obj_json | ||
// Plot is a plotly plot. | ||
type TracesWithTypeValue = Trace | string | ||
|
||
// PlotlyScriptConfig is a configuration for the Plotly plot script. | ||
[params] | ||
pub struct PlotlyScriptConfig { | ||
PlotConfig | ||
} | ||
Comment on lines
+42
to
46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
fn (plot Plot) plotly() string { | ||
traces_with_type := plot.traces.map({ | ||
// get_plotly_script returns the plot script as an html tag. | ||
pub fn (p Plot) get_plotly_script(element_id string, config PlotlyScriptConfig) &html.Tag { | ||
traces_with_type := p.traces.map({ | ||
'type': TracesWithTypeValue(it.trace_type()) | ||
'trace': TracesWithTypeValue(it) | ||
}) | ||
traces_with_type_json := encode(traces_with_type) | ||
layout_json := encode(plot.layout) | ||
layout_json := encode(p.layout) | ||
|
||
plot_script := &html.Tag{ | ||
name: 'script' | ||
attributes: { | ||
'type': 'module' | ||
} | ||
content: 'import "https://cdn.plot.ly/plotly-2.26.2.min.js"; | ||
|
||
function removeEmptyFieldsDeeply(obj) { | ||
if (Array.isArray(obj)) { | ||
return obj.map(removeEmptyFieldsDeeply); | ||
} | ||
if (typeof obj === "object") { | ||
const newObj = Object.fromEntries( | ||
Object.entries(obj) | ||
.map(([key, value]) => [key, removeEmptyFieldsDeeply(value)]) | ||
.filter(([_, value]) => !!value) | ||
); | ||
return Object.keys(newObj).length > 0 ? newObj : undefined; | ||
} | ||
return obj; | ||
} | ||
|
||
const layout = ${layout_json}; | ||
const traces_with_type_json = ${traces_with_type_json}; | ||
const data = [...traces_with_type_json] | ||
.map(({ type, trace: { CommonTrace, _type, ...trace } }) => ({ type, ...CommonTrace, ...trace })); | ||
|
||
const payload = { | ||
data: removeEmptyFieldsDeeply(data), | ||
layout: removeEmptyFieldsDeeply(layout), | ||
}; | ||
|
||
Plotly.newPlot("${element_id}", payload);' | ||
} | ||
|
||
return plot_script | ||
} | ||
|
||
fn (p Plot) get_html(element_id string, config PlotConfig) string { | ||
title := if p.layout.title == '' { 'VSL Plot' } else { p.layout.title } | ||
plot_script := p.get_plotly_script(element_id, use_cdn: config.use_cdn) | ||
|
||
return '<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>VSL Plot</title> | ||
<title>${title}</title> | ||
</head> | ||
<body> | ||
<div id="gd"></div> | ||
|
||
<script type="module"> | ||
import "https://cdn.plot.ly/plotly-2.26.2.min.js"; | ||
|
||
function removeEmptyFieldsDeeply(obj) { | ||
if (Array.isArray(obj)) { | ||
return obj.map(removeEmptyFieldsDeeply); | ||
} | ||
if (typeof obj === "object") { | ||
const newObj = Object.fromEntries( | ||
Object.entries(obj) | ||
.map(([key, value]) => [key, removeEmptyFieldsDeeply(value)]) | ||
.filter(([_, value]) => !!value) | ||
); | ||
return Object.keys(newObj).length > 0 ? newObj : undefined; | ||
} | ||
return obj; | ||
} | ||
|
||
const layout = ${layout_json}; | ||
const traces_with_type_json = ${traces_with_type_json}; | ||
const data = [...traces_with_type_json] | ||
.map(({ type, trace: { CommonTrace, _type, ...trace } }) => ({ type, ...CommonTrace, ...trace })); | ||
|
||
const payload = { | ||
data: removeEmptyFieldsDeeply(data), | ||
layout: removeEmptyFieldsDeeply(layout), | ||
}; | ||
|
||
Plotly.newPlot("gd", payload); | ||
</script> | ||
<div id="${element_id}"></div> | ||
|
||
${*plot_script} | ||
</body> | ||
</html>' | ||
} | ||
|
||
struct PlotlyHandler { | ||
PlotlyScriptConfig | ||
plot Plot | ||
mut: | ||
server &http.Server [str: skip] = unsafe { nil } | ||
} | ||
|
||
fn (mut handler PlotlyHandler) handle(req http.Request) http.Response { | ||
mut r := http.Response{ | ||
body: handler.plot.get_html('gd', use_cdn: handler.use_cdn) | ||
header: req.header | ||
} | ||
r.set_status(.ok) | ||
r.set_version(req.version) | ||
go fn [mut handler] () { | ||
time.sleep(300 * time.millisecond) | ||
handler.server.close() | ||
}() | ||
return r | ||
} | ||
|
||
// TODO: This is a hack to allow the json encoder to work with sum types | ||
fn encode[T](obj T) string { | ||
strings_to_replace := [ | ||
',"[]f64"', | ||
'"[]f64"', | ||
',"[][]f64"', | ||
'"[][]f64"', | ||
',"[]int"', | ||
'"[]int"', | ||
',"[]string"', | ||
'"[]string"', | ||
] | ||
mut obj_json := json.encode(obj) | ||
for string_to_replace in strings_to_replace { | ||
obj_json = obj_json.replace(string_to_replace, '') | ||
} | ||
return obj_json | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
PlotConfig
struct only contains a single fielduse_cdn
. If this is the only configuration option for the plot, it might be more straightforward to pass this as a boolean parameter to theshow
function andget_html
function, rather than wrapping it in a struct. This would simplify the function signatures and make the code easier to understand.