Skip to content

Add subplot macro for basic subplots #34

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 11 commits into from
Dec 20, 2018
103 changes: 103 additions & 0 deletions examples/fig18_subplots.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import plotly, sequtils, macros, algorithm
import json
import math
import chroma
import strformat

# given some data
const
n = 5
var
y = new_seq[float64](n)
x = new_seq[float64](n)
x2 = newSeq[int](n)
y2 = newSeq[int](n)
x3 = newSeq[int](n)
y3 = newSeq[int](n)
sizes = new_seq[float64](n)
for i in 0 .. y.high:
x[i] = i.float
y[i] = sin(i.float)
x2[i] = i
y2[i] = i * 5
x3[i] = i
y3[i] = -(i * 5)
sizes[i] = float64(10 + (i mod 10))

# and with it defined plots of possibly different datatypes (note `float` and `int`)
let d = Trace[float64](mode: PlotMode.LinesMarkers, `type`: PlotType.ScatterGL,
xs: x, ys: y)
let d2 = Trace[int](mode: PlotMode.LinesMarkers, `type`: PlotType.ScatterGL,
xs: x2, ys: y2)
let d3 = Trace[float](mode: PlotMode.LinesMarkers, `type`: PlotType.ScatterGL,
xs: x2.mapIt(it.float), ys: y2.mapIt(it.float))
let d4 = Trace[int](mode: PlotMode.LinesMarkers, `type`: PlotType.ScatterGL,
xs: x3, ys: y3)

let layout = Layout(title: "saw the sin, colors of sin value!", width: 1000, height: 400,
xaxis: Axis(title: "my x-axis"),
yaxis: Axis(title: "y-axis too"), autosize: false)

let baseLayout = Layout(title: "A bunch of subplots!", width: 800, height: 800,
xaxis: Axis(title: "linear x"),
yaxis: Axis(title: "y also linear"), autosize: false)

let plt1 = Plot[float64](layout: layout, traces: @[d, d3])
let plt2 = Plot[int](layout: baseLayout, traces: @[d2, d4])
let plt3 = scatterPlot(x3, y3).title("Another plot!").width(1000)

# we wish to create a subplot including all three plots. The `subplots` macro
# returns a special `PlotJson` object, which stores the same information as
# a `Plot[T]` object, but already converted to `JsonNodes`. This is done for easier
# handling of different data types. But fear not, this object is given straight to
# `show` or `saveImage` unless you wish to manually add something to the `JsonNodes`.
let pltCombined = subplots:
# first we need to define a base layout for our plot, which defines size
# of canvas and other applicable properties
baseLayout: baseLayout
# now we define all plots in `plot` blocks
plot:
# the first identifier points to a `Plot[T]` object
plt1
# it follows the description of the `Domain`, i.e. the location and
# size of the subplot. This can be done explicitly as follows:
# Note that the order of the fields is not important, but you need to
# define all 4!
left: 0.0
bottom: 0.0
width: 0.45
height: 1.0
plot:
plt2
# alternatively a nameless tuple conforming to the order
(0.6, 0.5, 0.4, 0.5)
plot:
plt3
# or instead of defining via `:`, you can use `=`
left = 0.7
bottom = 0.0
# and also replace `widht` and `height` by the right and top edge of the plot
# NOTE: you *cannot* mix e.g. right with height!
right = 1.0
top = 0.3
pltCombined.show()

# if you do not wish to define domains for each plot, you also simply define
# grid as we do here
let pltC2 = subplots:
baseLayout: baseLayout
# this requires the `grid` block
grid:
# it may contain a `rows` and `column` field, although both are optional
# If only one is set, the other will be set to 1. If neither is set,
# nor any domains on the plots, a grid will be calculated automatically,
# favoring more columns than rows.
rows: 3
columns: 1
plot:
plt1
plot:
plt2
plot:
plt3
pltC2.show()
44 changes: 26 additions & 18 deletions src/plotly.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import sequtils

# we now import the plotly modules and export them so that
# the user sees them as a single module
import plotly/api
import plotly / [api, plotly_types, errorbar, plotly_sugar, plotly_subplots]
export api
import plotly/plotly_types
export plotly_types
import plotly/errorbar
export errorbar
import plotly/plotly_sugar
export plotly_sugar
export plotly_subplots

when defined(webview):
import webview
Expand Down Expand Up @@ -76,16 +74,20 @@ when not defined(js):

proc fillHtmlTemplate(html_template,
data_string: string,
p: Plot,
p: SomePlot,
filename = ""): string =
## fills the HTML template with the correct strings and, if compiled with
## ``--threads:on``, inject the save image HTML code and fills that
var
slayout = "{}"
title = ""
if p.layout != nil:
slayout = $(%p.layout)
title = p.layout.title
when type(p) is Plot:
slayout = $(%p.layout)
title = p.layout.title
else:
slayout = $p.layout
title = p.layout{"title"}.getStr

# read the HTML template and insert data, layout and title strings
# imageInject is will be filled iff the user compiles with ``--threads:on``
Expand All @@ -95,26 +97,32 @@ when not defined(js):
if filename.len > 0:
# prepare save image code
let filetype = parseImageType(filename)
let swidth = $p.layout.width
let sheight = $p.layout.height
when type(p) is Plot:
let swidth = $p.layout.width
let sheight = $p.layout.height
else:
let swidth = $p.layout{"width"}
let sheight = $p.layout{"height"}
imageInject = fillImageInjectTemplate(filetype, swidth, sheight)

# now fill all values into the html template
result = html_template % ["data", data_string, "layout", slayout,
"title", title, "saveImage", imageInject]

proc save*(p: Plot, path = "", html_template = defaultTmplString, filename = ""): string =
proc save*(p: SomePlot, path = "", html_template = defaultTmplString, filename = ""): string =
result = path
if result == "":
when defined(Windows):
result = getEnv("TEMP") / "x.html"
else:
result = "/tmp/x.html"

let
when type(p) is Plot:
# convert traces to data suitable for plotly and fill Html template
data_string = parseTraces(p.traces)
html = html_template.fillHtmlTemplate(data_string, p, filename)
let data_string = parseTraces(p.traces)
else:
let data_string = $p.traces
let html = html_template.fillHtmlTemplate(data_string, p, filename)

var
f: File
Expand All @@ -126,14 +134,14 @@ when not defined(js):
when not hasThreadSupport:
# some violation of DRY for the sake of better error messages at
# compile time
proc show*(p: Plot,
proc show*(p: SomePlot,
filename: string,
path = "",
html_template = defaultTmplString) =
{.fatal: "`filename` argument to save plot only supported if compiled " &
"with --threads:on!".}

proc show*(p: Plot, path = "", html_template = defaultTmplString) =
proc show*(p: SomePlot, path = "", html_template = defaultTmplString) =
## creates the temporary Html file using `save`, and opens the user's
## default browser
let tmpfile = p.save(path, html_template)
Expand All @@ -143,12 +151,12 @@ when not defined(js):
## remove file after thread is finished
removeFile(tmpfile)

proc saveImage*(p: Plot, filename: string) =
proc saveImage*(p: SomePlot, filename: string) =
{.fatal: "`saveImage` only supported if compiled with --threads:on!".}

else:
# if compiled with --threads:on
proc show*(p: Plot, filename = "", path = "", html_template = defaultTmplString) =
proc show*(p: SomePlot, filename = "", path = "", html_template = defaultTmplString) =
## creates the temporary Html file using `save`, and opens the user's
## default browser
# if we are handed a filename, the user wants to save the file to disk.
Expand All @@ -165,7 +173,7 @@ when not defined(js):
thr.joinThread
removeFile(tmpfile)

proc saveImage*(p: Plot, filename: string) =
proc saveImage*(p: SomePlot, filename: string) =
## saves the image under the given filename
## supported filetypes:
## - jpg, png, svg, webp
Expand Down
9 changes: 9 additions & 0 deletions src/plotly/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import plotly_types
import color
import errorbar

proc toPlotJson*[T](plt: Plot[T]): PlotJson =
## converts a given `Plot[T]` object to a `PlotJson` object
result = new PlotJson
result.traces = % plt.traces
result.layout = % plt.layout

func parseHistogramFields[T](fields: var OrderedTable[string, JsonNode], t: Trace[T]) =
## parse the fields of the histogram type. Usese a separate proc
## for clarity.
Expand Down Expand Up @@ -229,6 +235,9 @@ func `%`*(t: Trace): JsonNode =
if t.ys.len > 0:
fields["y"] = % t.ys

if t.xaxis != "":
fields["xaxis"] = % t.xaxis

if t.yaxis != "":
fields["yaxis"] = % t.yaxis

Expand Down
Loading