Skip to content
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

Merged
merged 4 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions examples/plot_heatmap_golden_ratio/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,38 @@ module main
import math
import vsl.plot
// import vsl.util
// phi = (1 + np.sqrt(5) )/2. # golden ratio
// xe = [0, 1, 1+(1/(phi**4)), 1+(1/(phi**3)), phi]
// ye = [0, 1/(phi**3), 1/phi**3+1/phi**4, 1/(phi**2), 1]

phi := (1 + math.sqrt(5)) / 2.0
phi_pow_2 := math.pow(phi, 2.0)
phi_pow_3 := math.pow(phi, 3.0)
phi_pow_4 := math.pow(phi, 4.0)
xe := [0.0, 1.0, 1 + (1 / phi_pow_4), 1 + (1 / phi_pow_3), phi]
ye := [0.0, 1 / phi_pow_3, (1 / phi_pow_3) + (1 / phi_pow_4), 1 / phi_pow_2, 1]
z := [[13.0, 3, 3, 5], [13.0, 2, 1, 5], [13.0, 10, 11, 12], [13.0, 8, 8, 8]]
fn main() {
phi := (1 + math.sqrt(5)) / 2.0
phi_pow_2 := math.pow(phi, 2.0)
phi_pow_3 := math.pow(phi, 3.0)
phi_pow_4 := math.pow(phi, 4.0)
xe := [0.0, 1.0, 1 + (1 / phi_pow_4), 1 + (1 / phi_pow_3), phi]
ye := [0.0, 1 / phi_pow_3, (1 / phi_pow_3) + (1 / phi_pow_4), 1 / phi_pow_2, 1]
z := [[13.0, 3, 3, 5], [13.0, 2, 1, 5], [13.0, 10, 11, 12],
[13.0, 8, 8, 8]]

// TODO: Draw Spiral
// a := 1.120529
// b := 0.306349
// TODO: Draw Spiral
// a := 1.120529
// b := 0.306349

// theta := util.lin_space(-math.pi/13, 4*math.pi, 1000)
// r := a*math.exp(-b*theta)
// x := r*math.cos(theta)
// y := r*math.sin(theta)
// theta := util.lin_space(-math.pi/13, 4*math.pi, 1000)
// r := a*math.exp(-b*theta)
// x := r*math.cos(theta)
// y := r*math.sin(theta)

mut plt := plot.Plot.new()
mut plt := plot.Plot.new()

plt.heatmap(
x: xe
y: ye
z: z
)
plt.layout(
title: 'Heatmap with Unequal Block Sizes'
width: 750
height: 750
)
plt.heatmap(
x: xe
y: ye
z: z
)
plt.layout(
title: 'Heatmap with Unequal Block Sizes'
width: 750
height: 750
)

plt.show()!
plt.show()!
}
2 changes: 1 addition & 1 deletion examples/plot_scatter/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ y := [
1,
0,
]
x := util.arange(y.len).map(f64(it))
x := util.arange(y.len)

mut plt := plot.Plot.new()
plt.scatter(
Expand Down
2 changes: 1 addition & 1 deletion examples/plot_scatter3d_1/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn main() {
1,
0,
]
x := util.arange(y.len).map(f64(it))
x := util.arange(y.len)
z := util.arange(y.len).map(util.arange(y.len).map(f64(it * it)))

mut plt := plot.Plot.new()
Expand Down
2 changes: 1 addition & 1 deletion examples/plot_scatter_colorscale/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ y := [
1,
0,
]
x := util.arange(y.len).map(f64(it))
x := util.arange(y.len)

mut plt := plot.Plot.new()
plt.scatter(
Expand Down
43 changes: 43 additions & 0 deletions examples/plot_scatter_with_annotations/main.v
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()!
}
8 changes: 4 additions & 4 deletions plot/annotation.v
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ module plot
// Annotation handles all the information needed to annotate plots
pub struct Annotation {
pub mut:
x f64
y f64
text string [omitempty]
showarrow bool [omitempty]
x f64 [omitempty]
y f64 [omitempty]
text string [required]
showarrow bool
arrowhead int [omitempty]
arrowcolor string [omitempty]
align string [omitempty]
Expand Down
8 changes: 4 additions & 4 deletions plot/layout.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ module plot
pub struct Layout {
pub mut:
title string
title_x f64 = 0.5
autosize bool = true
width int = 550
height int = 550
title_x f64
autosize bool
width int = 550
height int = 550
xaxis Axis
yaxis Axis
annotations []Annotation
Expand Down
181 changes: 106 additions & 75 deletions plot/show.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +13 to 14
Copy link

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 field use_cdn. If this is the only configuration option for the plot, it might be more straightforward to pass this as a boolean parameter to the show function and get_html function, rather than wrapping it in a struct. This would simplify the function signatures and make the code easier to understand.


// 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use_cdn field of the PlotlyHandler struct is always set to true, ignoring the value in the PlotConfig parameter. This should be changed to use the value from the PlotConfig parameter.

- 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PlotlyScriptConfig struct is defined but it only inherits from PlotConfig and doesn't add any new fields. If there are no additional configuration options for the plot script, it might be unnecessary to define a separate struct for it. Consider using PlotConfig directly instead.


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
}
Loading
Loading