Skip to content

Commit

Permalink
Started working updating vsl.plot (#181)
Browse files Browse the repository at this point in the history
* Started working updating vsl.plot

* Fixed some issues and updated examples
  • Loading branch information
ulises-jeremias authored Oct 22, 2023
1 parent 0bedacc commit 1ee1419
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 118 deletions.
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
}

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

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

0 comments on commit 1ee1419

Please sign in to comment.