Skip to content

Anchor charts #1498

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

Open
wants to merge 25 commits into
base: anchor
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a634b2
implement working pie charts
thao13039 Nov 27, 2019
660258d
clean up comments
thao13039 Nov 27, 2019
07e66b6
implement merge bounding box
thao13039 Dec 2, 2019
df0533a
add either import
thao13039 Dec 2, 2019
8ed8dd6
add ScatterPlotSeries type and default
thao13039 Dec 2, 2019
650afbb
add PlotChartWindowObject type and default
thao13039 Dec 2, 2019
6c5fcfd
add scatter-plot-series to DataSeries data definition
thao13039 Dec 2, 2019
0aada8f
remove constr comment from pie- + bar-chart-window
thao13039 Dec 2, 2019
7151326
add plot-chart-window to ChartWindow data def
thao13039 Dec 2, 2019
67f8f62
add scatter-plot-from-list function
thao13039 Dec 2, 2019
f87ef1d
scatter-plot not done, place to start histogram
thao13039 Dec 4, 2019
4d5e968
implement histogram
thao13039 Dec 4, 2019
44664d1
remove unnecessary comments
thao13039 Dec 4, 2019
a93d78d
add BoxChartSeries and BoxChartWindowObject type
thao13039 Dec 4, 2019
351336c
add box-plot TODOs
thao13039 Dec 4, 2019
20a7bdd
add LinePlotSeries and FunctionPlotSeries types
thao13039 Dec 4, 2019
1a42162
add PlottableFunction type
thao13039 Dec 4, 2019
aab7f9a
add unsafe-equal helper function
thao13039 Dec 4, 2019
15e264f
add line- and function-plot-series to DataSeries
thao13039 Dec 4, 2019
1c13fb6
add lots of TODOs...
thao13039 Dec 4, 2019
208c1e7
add function- and line-plot-from-list functions
thao13039 Dec 4, 2019
b2b1390
add missing O. to none
thao13039 Dec 4, 2019
fdca918
add minor fixes to get to working state
thao13039 Dec 4, 2019
6c3e636
implement list.each(...)
thao13039 Dec 4, 2019
2fae8e6
fix calls to each
thao13039 Dec 4, 2019
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
458 changes: 421 additions & 37 deletions src/runtime-arr/chart.arr
Original file line number Diff line number Diff line change
@@ -3,30 +3,32 @@ provide-types *

import global as G
import chart-lib as CL
import either as E
import image as IM
import list as L
import option as O

include from O: type Option end

################################################################################
# CONSTANTS
################################################################################


################################################################################
# TYPE SYNONYMS
################################################################################

type PlottableFunction = (Number -> Number)
type Posn = RawArray<Number>
type TableIntern = RawArray<RawArray<Any>>

################################################################################
# HELPERS
################################################################################

fun check-num(v :: Number) -> Nothing: nothing end
fun check-string(v :: String) -> Nothing: nothing end

posn = {(x :: Number, y :: Number): [G.raw-array: x, y]}

unsafe-equal = {(x :: Number, y :: Number): (x <= y) and (y <= x)}

fun map2(xs :: L.List<Any>, ys :: L.List<Any>):
L.map2({(x, y): [G.raw-array: x, y]}, xs, ys)
end
@@ -39,12 +41,11 @@ fun to-table2(xs :: L.List<Any>, ys :: L.List<Any>) -> TableIntern:
L.to-raw-array(L.map2({(x, y): [G.raw-array: x, y]}, xs, ys))
end

# TODO(tiffany): add in get-vs-from-img after VS is implemented

################################################################################
# METHODS
################################################################################
fun to-table3(xs :: L.List<Any>, ys :: L.List<Any>, zs :: L.List<Any>) -> TableIntern:
L.to-raw-array(L.map3({(x, y, z): [G.raw-array: x, y, z]}, xs, ys, zs))
end

# TODO(tiffany): add in get-vs-from-img after VS is implemented

################################################################################
# BOUNDING BOX
@@ -90,10 +91,44 @@ fun get-bounding-box(ps :: L.List<Posn>) -> BoundingBox:
end
end

fun merge-bounding-box(bs :: L.List<BoundingBox>) -> BoundingBox:
for L.fold(prev from default-bounding-box, e from bs):
ask:
| e.is-valid and prev.is-valid then:
default-bounding-box.{
x-min: G.num-min(e.x-min, prev.x-min),
x-max: G.num-max(e.x-max, prev.x-max),
y-min: G.num-min(e.y-min, prev.y-min),
y-max: G.num-max(e.y-max, prev.y-max),
is-valid: true,
}
| e.is-valid then: e
| prev.is-valid then: prev
| otherwise: default-bounding-box
end
end
end

################################################################################
# DEFAULT VALUES
################################################################################

type BoxChartSeries = {
tab :: TableIntern,
height :: Number,
horizontal :: Boolean
}

default-box-plot-series = {
horizontal: false
}

type PieChartSeries = {
tab :: TableIntern,
}

default-pie-chart-series = {}

type BarChartSeries = {
tab :: TableIntern,
legends :: RawArray<String>,
@@ -102,6 +137,54 @@ type BarChartSeries = {

default-bar-chart-series = {}

type HistogramSeries = {
tab :: TableIntern,
bin-width :: Option<Number>,
max-num-bins :: Option<Number>,
min-num-bins :: Option<Number>,
}

default-histogram-series = {
bin-width: O.none,
max-num-bins: O.none,
min-num-bins: O.none,
}

type LinePlotSeries = {
ps :: L.List<Posn>,
color :: Option<IM.Color>,
legend :: String,
}

default-line-plot-series = {
color: O.none,
legend: '',
}

type ScatterPlotSeries = {
ps :: L.List<Posn>,
color :: Option<IM.Color>,
legend :: String,
point-size :: Number,
}

default-scatter-plot-series = {
color: O.none,
legend: '',
point-size: 7,
}

type FunctionPlotSeries = {
f :: PlottableFunction,
color :: Option<IM.Color>,
legend :: String,
}

default-function-plot-series = {
color: O.none,
legend: '',
}

###########

type ChartWindowObject = {
@@ -118,6 +201,29 @@ default-chart-window-object :: ChartWindowObject = {
render: method(self): G.raise('unimplemented') end,
}

type BoxChartWindowObject = {
title :: String,
width :: Number,
height :: Number,
x-axis :: String,
y-axis :: String,
render :: ( -> IM.Image),
}

default-box-plot-chart-window-object :: BoxChartWindowObject = default-chart-window-object.{
x-axis: '',
y-axis: '',
}

type PieChartWindowObject = {
title :: String,
width :: Number,
height :: Number,
render :: ( -> IM.Image),
}

default-pie-chart-window-object :: PieChartWindowObject = default-chart-window-object

type BarChartWindowObject = {
title :: String,
width :: Number,
@@ -136,14 +242,110 @@ default-bar-chart-window-object :: BarChartWindowObject = default-chart-window-o
y-max: O.none,
}

type HistogramChartWindowObject = {
title :: String,
width :: Number,
height :: Number,
render :: ( -> IM.Image),
x-axis :: String,
y-axis :: String,
x-min :: Option<Number>,
x-max :: Option<Number>,
y-max :: Option<Number>,
}

default-histogram-chart-window-object :: HistogramChartWindowObject =
default-chart-window-object.{
x-axis: '',
y-axis: '',
x-min: O.none,
x-max: O.none,
y-max: O.none,
}

type PlotChartWindowObject = {
title :: String,
width :: Number,
height :: Number,
render :: ( -> IM.Image),
x-axis :: String,
y-axis :: String,
x-min :: Option<Number>,
x-max :: Option<Number>,
x-max :: Option<Number>,
y-max :: Option<Number>,
num-samples :: Number,
}

default-plot-chart-window-object :: PlotChartWindowObject = default-chart-window-object.{
x-axis: '',
y-axis: '',
x-min: O.none,
x-max: O.none,
y-min: O.none,
y-max: O.none,
num-samples: 1000,
}

################################################################################
# DATA DEFINITIONS
################################################################################

data DataSeries:
| line-plot-series(obj :: LinePlotSeries) with:
is-single: false,
color: method(self, color :: IM.Color):
line-plot-series(self.obj.{color: O.some(color)})
end,
legend: method(self, legend :: String):
line-plot-series(self.obj.{legend: legend})
end,
| scatter-plot-series(obj :: ScatterPlotSeries) with:
is-single: false,
color: method(self, color :: IM.Color):
scatter-plot-series(self.obj.{color: O.some(color)})
end,
legend: method(self, legend :: String):
scatter-plot-series(self.obj.{legend: legend})
end,
point-size: method(self, point-size :: Number):
scatter-plot-series(self.obj.{point-size: point-size})
end,
| function-plot-series(obj :: FunctionPlotSeries) with:
is-single: false,
color: method(self, color :: IM.Color):
function-plot-series(self.obj.{color: O.some(color)})
end,
legend: method(self, legend :: String):
function-plot-series(self.obj.{legend: legend})
end,
| pie-chart-series(obj :: PieChartSeries) with:
is-single: true,
| bar-chart-series(obj :: BarChartSeries) with:
is-single: true,
constr: {(): bar-chart-series},
# TODO(tiffany): add box-plot-series in by adding a |
# box-plot-series(obj :: BoxChartSeries) with:
# is-single: true,
# horizontal: method(self, h):
# box-plot-series(self.obj.{horizontal: h})
# end
| histogram-series(obj :: HistogramSeries) with:
is-single: true,
method bin-width(self, bin-width :: Number):
histogram-series(self.obj.{bin-width: O.some(bin-width)})
end,
method max-num-bins(self, max-num-bins :: Number):
histogram-series(self.obj.{max-num-bins: O.some(max-num-bins)})
end,
method min-num-bins(self, min-num-bins :: Number):
histogram-series(self.obj.{min-num-bins: O.some(min-num-bins)})
end,
method num-bins(self, num-bins :: Number):
histogram-series(self.obj.{
min-num-bins: O.some(num-bins),
max-num-bins: O.some(num-bins)
})
end,
# TODO(tiffany): add _output and test get-vs-from-img after VS is implemented
end

@@ -156,37 +358,138 @@ fun check-chart-window(p :: ChartWindowObject) -> Nothing:
end

data ChartWindow:
| pie-chart-window(obj :: PieChartWindowObject) with:
title: method(self, title :: String): pie-chart-window(self.obj.{title: title}) end,
width: method(self, width :: Number): pie-chart-window(self.obj.{width: width}) end,
height: method(self, height :: Number): pie-chart-window(self.obj.{height: height}) end,
display: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: true}.render()
end,
get-image: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: false}.render()
end,
# TODO(tiffany): add box-plot-chart-window in with a |
# box-plot-chart-window(obj :: BoxChartWindowObject) with:
# x-axis: method(self, x-axis :: String): box-plot-chart-window(self.obj.{x-axis: x-axis}) end,
# y-axis: method(self, y-axis :: String): box-plot-chart-window(self.obj.{y-axis: y-axis}) end,
# title: method(self, title :: String): box-plot-chart-window(self.obj.{title: title}) end,
# width: method(self, width :: Number): box-plot-chart-window(self.obj.{width: width}) end,
# height: method(self, height :: Number): box-plot-chart-window(self.obj.{height: height}) end,
# display: method(self):
# _ = check-chart-window(self.obj)
# self.obj.{interact: true}.render()
# end,
# get-image: method(self):
# _ = check-chart-window(self.obj)
# self.obj.{interact: false}.render()
# end,
| bar-chart-window(obj :: BarChartWindowObject) with:
constr: {(): bar-chart-window},
x-axis: method(self, x-axis :: String): self.constr()(self.obj.{x-axis: x-axis}) end,
y-axis: method(self, y-axis :: String): self.constr()(self.obj.{y-axis: y-axis}) end,
y-min: method(self, y-min :: Number): self.constr()(self.obj.{y-min: O.some(y-min)}) end,
y-max: method(self, y-max :: Number): self.constr()(self.obj.{y-max: O.some(y-max)}) end,
sharing:
method display(self):
_ = check-chart-window(self.obj)
self.obj.{interact: true}.render()
end,
method get-image(self):
_ = check-chart-window(self.obj)
self.obj.{interact: false}.render()
end,
method title(self, title :: String):
self.constr()(self.obj.{title: title})
end,
method width(self, width :: Number):
self.constr()(self.obj.{width: width})
end,
method height(self, height :: Number):
self.constr()(self.obj.{height: height})
end,
x-axis: method(self, x-axis :: String): bar-chart-window(self.obj.{x-axis: x-axis}) end,
y-axis: method(self, y-axis :: String): bar-chart-window(self.obj.{y-axis: y-axis}) end,
y-min: method(self, y-min :: Number): bar-chart-window(self.obj.{y-min: O.some(y-min)}) end,
y-max: method(self, y-max :: Number): bar-chart-window(self.obj.{y-max: O.some(y-max)}) end,
title: method(self, title :: String): bar-chart-window(self.obj.{title: title}) end,
width: method(self, width :: Number): bar-chart-window(self.obj.{width: width}) end,
height: method(self, height :: Number): bar-chart-window(self.obj.{height: height}) end,
display: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: true}.render()
end,
get-image: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: false}.render()
end,
| histogram-chart-window(obj :: HistogramChartWindowObject) with:
x-axis: method(self, x-axis :: String): histogram-chart-window(self.obj.{x-axis: x-axis}) end,
y-axis: method(self, y-axis :: String): histogram-chart-window(self.obj.{y-axis: y-axis}) end,
x-min: method(self, x-min :: Number): histogram-chart-window(self.obj.{x-min: O.some(x-min)}) end,
x-max: method(self, x-max :: Number): histogram-chart-window(self.obj.{x-max: O.some(x-max)}) end,
y-max: method(self, y-max :: Number): histogram-chart-window(self.obj.{y-max: O.some(y-max)}) end,
| plot-chart-window(obj :: PlotChartWindowObject) with:
x-axis: method(self, x-axis :: String): plot-chart-window(self.obj.{x-axis: x-axis}) end,
y-axis: method(self, y-axis :: String): plot-chart-window(self.obj.{y-axis: y-axis}) end,
x-min: method(self, x-min :: Number): plot-chart-window(self.obj.{x-min: O.some(x-min)}) end,
x-max: method(self, x-max :: Number): plot-chart-window(self.obj.{x-max: O.some(x-max)}) end,
y-min: method(self, y-min :: Number): plot-chart-window(self.obj.{y-min: O.some(y-min)}) end,
y-max: method(self, y-max :: Number): plot-chart-window(self.obj.{y-max: O.some(y-max)}) end,
num-samples: method(self, num-samples :: Number) block:
when (num-samples <= 0) or (num-samples > 100000) or G.not(G.num-is-integer(num-samples)):
G.raise('num-samples: value must be an ineger between 1 and 100000')
end
plot-chart-window(self.obj.{num-samples: num-samples})
end,
title: method(self, title :: String): plot-chart-window(self.obj.{title: title}) end,
width: method(self, width :: Number): plot-chart-window(self.obj.{width: width}) end,
height: method(self, height :: Number): plot-chart-window(self.obj.{height: height}) end,
display: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: true}.render()
end,
get-image: method(self):
_ = check-chart-window(self.obj)
self.obj.{interact: false}.render()
end,
# TODO(tiffany): add _output and test get-vs-from-img after VS is implemented
end

################################################################################
# FUNCTIONS
################################################################################

fun function-plot-from-list(f :: PlottableFunction) -> DataSeries:
function-plot-series(default-function-plot-series.{
f: f,
})
end

fun line-plot-from-list(xs :: L.List<Number>, ys :: L.List<Number>) -> DataSeries block:
when L.length(xs) <> L.length(ys):
G.raise('line-plot: xs and ys should have the same length')
end
L.each(check-num, xs)
L.each(check-num, ys)
line-plot-series(default-line-plot-series.{
ps: L.map2({(x, y): [G.raw-array: x, y]}, xs, ys)
})
end

fun scatter-plot-from-list(xs :: L.List<Number>, ys :: L.List<Number>) -> DataSeries block:
when L.length(xs) <> L.length(ys):
G.raise('scatter-plot: xs and ys should have the same length')
end
L.each(check-num, xs)
L.each(check-num, ys)
scatter-plot-series(default-scatter-plot-series.{
ps: L.map2({(x, y): [G.raw-array: x, y]}, xs, ys)
})
end

# TODO(tiffany): implement labeled-scatter-plot-from-list

# TODO(tiffany): implement exploding-pie-chart-from-list

fun pie-chart-from-list(labels :: L.List<String>, values :: L.List<Number>) -> DataSeries block:
doc: ```
Consume labels, a list of string, and values, a list of numbers
and construct a pie chart
```
label-length = L.length(labels)
value-length = L.length(values)
when label-length <> value-length:
G.raise('pie-chart: labels and values should have the same length')
end
when label-length == 0:
G.raise('pie-chart: need at least one data')
end
L.each(check-num, values)
L.each(check-string, labels)
pie-chart-series(default-pie-chart-series.{
tab: to-table2(labels, values)
})
end

fun bar-chart-from-list(labels :: L.List<String>, values :: L.List<Number>) -> DataSeries block:
doc: ```
Consume labels, a list of string, and values, a list of numbers
@@ -197,23 +500,50 @@ fun bar-chart-from-list(labels :: L.List<String>, values :: L.List<Number>) -> D
when label-length <> value-length:
G.raise('bar-chart: labels and values should have the same length')
end
# TODO(tiffany): uncomment after implementing each
#values.each(check-num)
#labels.each(check-string)
L.each(check-num, values)
L.each(check-string, labels)
bar-chart-series(default-bar-chart-series.{
tab: to-table2(labels, values),
legends: [G.raw-array: ''],
has-legend: false,
})
end

# TODO(tiffany): implement grouped-bar-chart-from-list

# TODO(tiffany): implement box-plot-from-list

# TODO(tiffany): implement labeled-box-plot-from-list

# TODO(tiffany): implement freq-bar-chart-from-list

fun histogram-from-list(values :: L.List<Number>) -> DataSeries block:
doc: ```
Consume a list of numbers and construct a histogram
```
L.each(check-num, values)
histogram-series(default-histogram-series.{
tab: to-table2(L.map({(_): ''}, values), values),
})
end

# TODO(tiffany): implement labeled-histogram-from-list

################################################################################
# PLOTS
################################################################################

fun render-chart(s :: DataSeries) -> ChartWindow:
doc: 'Render it!'
cases (DataSeries) s:
# TODO(tiffany): fix scatter-plot-series
| line-plot-series(_) => plot-chart-window(default-plot-chart-window-object)
| function-plot-series(_) => plot-chart-window(default-plot-chart-window-object)
| scatter-plot-series(_) => plot-chart-window(default-plot-chart-window-object)
| pie-chart-series(obj) =>
pie-chart-window(default-pie-chart-window-object.{
render: method(self): CL.pie-chart(obj.tab) end
})
| bar-chart-series(obj) =>
bar-chart-window(default-bar-chart-window-object.{
render: method(self):
@@ -233,8 +563,62 @@ fun render-chart(s :: DataSeries) -> ChartWindow:
CL.bar-chart(obj.tab)
end
})
# TODO(tiffany): implement CL.box-plot
# box-plot-series(obj) =>
# box-plot-chart-window(default-box-plot-chart-window-object.{
# render: method(self):
# CL.box-plot(self, obj)
# end
# })
| histogram-series(obj) =>
histogram-chart-window(default-histogram-chart-window-object.{
render: method(self):
shadow self = self.{y-min: O.none}
_ = cases (Option) self.x-min:
| some(x-min) =>
cases (Option) self.x-max:
| some(x-max) =>
if x-min >= x-max:
G.raise("render: x-min must be strictly less than x-max")
else:
G.nothing
end
| else => G.nothing
end
| else => G.nothing
end
_ = cases (Option) self.y-min:
| some(y-min) =>
cases (Option) self.y-max:
| some(y-max) =>
if y-min >= y-max:
G.raise("render: y-min must be strictly less than y-max")
else:
G.nothing
end
| else => G.nothing
end
| else => G.nothing
end
CL.histogram(obj.tab)
end
})
end
#where:
# render-now = {(x): render-chart(x).get-image()}
end


# TODO(tiffany): implement inbound-x

# TODO(tiffany): implement inbound-y

# TODO(tiffany): implement inbound-xy

# TODO(tiffany): implement dist

# TODO(tiffany): implement nearest

# TODO(tiffany): implement find-pt-on-edge

# TODO(tiffany): implement line-plot-edge-cut
27 changes: 26 additions & 1 deletion src/runtime/chart-lib.arr.js
Original file line number Diff line number Diff line change
@@ -20,7 +20,6 @@ function checkColor(val) {
/* @stopify flat */
function barChart(tableFromRawArray) {
headers = ["",""];

return {
"$brand": "chart",
"chartType": "BarChart",
@@ -39,6 +38,16 @@ function barChartFromTable(table) {
};
}

/* @stopify flat */
function pieChart(tableFromRawArray) {
return {
"$brand": "chart",
"chartType": "PieChart",
"_headers": ["",""],
"_rows": tableFromRawArray
};
}

/* @stopify flat */
function pieChartFromTable(table) {
return {
@@ -49,6 +58,16 @@ function pieChartFromTable(table) {
};
}

/* @stopify flat */
function histogram(tableFromRawArray) {
return {
"$brand": "chart",
"chartType": "Histogram",
"_headers": ["",""],
"_rows": tableFromRawArray
};
}

/* @stopify flat */
function histogramFromTable(table) {
return {
@@ -89,9 +108,15 @@ return module.exports = {
"bar-chart-from-table": /* @stopify flat */ function (table) {
return barChartFromTable(table);
},
"pie-chart": /* @stopify flat */ function (tableFromRawArray) {
return pieChart(tableFromRawArray);
},
"pie-chart-from-table": /* @stopify flat */ function (table) {
return pieChartFromTable(table);
},
"histogram": /* @stopify flat */ function (tableFromRawArray) {
return histogram(tableFromRawArray);
},
"histogram-from-table": /* @stopify flat */ function (table) {
return histogramFromTable(table);
},
2 changes: 2 additions & 0 deletions src/runtime/chart-lib.arr.json
Original file line number Diff line number Diff line change
@@ -19,7 +19,9 @@
"check-color": ["arrow", ["Any"], "Any"],
"bar-chart": ["arrow", ["rOfR"], "Chart"],
"bar-chart-from-table": ["arrow", ["Table"], "Chart"],
"pie-chart": ["arrow", ["rOfR"], "Chart"],
"pie-chart-from-table": ["arrow", ["Table"], "Chart"],
"histogram": ["arrow", ["rOfR"], "Chart"],
"histogram-from-table": ["arrow", ["Table"], "Chart"],
"scatter-chart-from-table": ["arrow", ["Table"], "Chart"],
"line-chart-from-table": ["arrow", ["Table"], "Chart"]
5 changes: 5 additions & 0 deletions src/runtime/list.arr.js
Original file line number Diff line number Diff line change
@@ -20,6 +20,11 @@ module.exports = {
}
return arr;
},
'each': function( fun, lst ) {
for (let i = 0; i < lst.length; i++) {
lst[i] = fun(lst[i]);
}
},
'map': function( fun, list ) {
return list.map( fun );
},
1 change: 1 addition & 0 deletions src/runtime/list.arr.json
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
"length": ["forall", ["a"], ["arrow", ["lOfA"], "Number"]],
"contains": ["forall", ["a"], ["arrow", ["lOfA", "tva"], "Boolean"]],
"to-raw-array": ["forall", ["a"], ["arrow", ["lOfA"], "rOfA"]],
"each": ["forall", ["a"], ["arrow", [["arrow", ["tva"], "Nothing"], "lOfA"], "Nothing"]],
"map": ["forall", ["a", "b"], ["arrow", [["arrow", ["tva"], "tvb"], "lOfA"], "lOfB"]],
"map2": ["forall", ["a", "b", "c"], ["arrow", [["arrow", ["tva", "tvb"], "tvc"], "lOfA", "lOfB"], "lOfC"]],
"map3": ["forall", ["a", "b", "c", "d"], ["arrow", [["arrow", ["tva", "tvb", "tvc"], "tvd"], "lOfA", "lOfB", "lOfC"], "lOfD"]],