2 Examples
This chapter lists various usage examples of Clay. Most of the behaviours demonstrated here are defined by the Kindly specification.
(ns clay-book.examples
(:require
[clojure.math :as math]
[scicloj.kindly.v4.kind :as kind]
[tablecloth.api :as tc]
[scicloj.metamorph.ml.toydata :as toydata]
[scicloj.tableplot.v1.hanami :as hanami]))
2.1 Plain values
By default, when there is no kind information provided by Kindly, values are simply pretty-printed.
(+ 4 5)
9
(str "abcd" "efgh")
"abcdefgh"
2.3 Hiccup
Hiccup, a popular Clojure way to represent HTML, can be specified by kind:
(kind/hiccup
[:ul
[:li [:p "hi"]]
[:li [:big [:big [:p {:style ; https://www.htmlcsscolor.com/hex/7F5F3F
{:color "#7F5F3F"}}
"hello"]]]]])
hi
hello
As we can see, this kind is displayed by converting Hiccup to HTML.
2.4 Reagent
With kind/reagent
, one may render Reagent components.
(kind/reagent
['(fn [numbers]
[:p {:style {:background "#d4ebe9"}}
(pr-str (map inc numbers))])
(vec (range 40))])
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40)
From the reagent tutorial:
(kind/reagent
['(fn []
(let [*click-count (reagent.core/atom 0)]
(fn []
[:div
"The atom " [:code "*click-count"] " has value: "
@*click-count ". "
[:input {:type "button" :value "Click me!"
:on-click #(swap! *click-count inc)}]])))])
*click-count
has value: 0. (kind/reagent
['(fn [{:keys [spec0
transition
time-for-transition]}]
(let [*spec (reagent.core/atom spec0)]
(fn []
^{:key @*spec}
[:div
[:div {:style {:height "400px"}
:ref (fn [el]
(when el
(let [chart (.init js/echarts el)]
(.setOption chart (clj->js @*spec)))))}]
(js/setInterval #(swap! *spec transition) time-for-transition)
;; Include this to force component update:
[:p {:style {:display :none}}
(hash @*spec)]])))
{:spec0 {:title {:text "Echarts Example"}
:tooltip {}
:legend {:data ["sales"]}
:xAxis {:data ["Shirts", "Cardigans", "Chiffons",
"Pants", "Heels", "Socks"]}
:yAxis {}
:series [{:name "sales"
:type "bar"
:data [5 20 36
10 10 20]}]}
:transition '(fn [spec]
(update-in spec
[:series 0 :data]
(partial map #(+ %
(rand-int 10)
-5))))
:time-for-transition 1000}]
{:html/deps [:echarts]})
2.5 Scittle
With kind/scittle
, one may specify Clojurescript code to run through Scittle.
(kind/scittle
'(.log js/console "hello"))
(kind/scittle
'(defn f [x]
(+ x 9)))
(kind/reagent
['(fn []
[:p (f 11)])])
20
Code defined with kind/scittle
is by default evaluated asynchronously, which fits very well with Reagent. However, the Scittle API allows to turn off this default and force synchronous evaluation with the following JavaScript commands:
scittle.core.disable_auto_eval();
scittle.core.eval_script_tags();
Also the Small Clojure Interpreter wich powers Scittle is exposed to JavaScript. Because within this notebook the default evaluation is still active, you can check in the browser console that the following string 123
appears before the above hello
.
(kind/html
"<script>
scittle.core.eval_string('(.log js/console 123)')
</script>")
2.6 HTML
Raw html can be represented as a kind too:
(kind/html "<div style='height:40px; width:40px; background:purple'></div> ")
(kind/html
"
<svg height=100 width=100>
<circle cx=50 cy=50 r=40 stroke='purple' stroke-width=3 fill='floralwhite' />
</svg> ")
(kind/html
["<svg height=100 width=100>"
"<circle cx=50 cy=50 r=40 stroke='purple' stroke-width=3 fill='floralwhite' />"
"</svg>"])
(kind/html
(list
"<svg height=100 width=100>"
"<circle cx=50 cy=50 r=40 stroke='purple' stroke-width=3 fill='floralwhite' />"
"</svg>"))
2.7 Markdown
Markdown text (a string or a vector of strings) can be handled using a kind too.
(kind/md
"This is [markdown](https://www.markdownguide.org/).")
This is markdown.
(kind/md
["
* This is [markdown](https://www.markdownguide.org/).
* *Isn't it??*"
"
* Here is **some more** markdown."])
- This is markdown.
- Isn’t it??
- Here is some more markdown.
(kind/md
(list
"
* This is [markdown](https://www.markdownguide.org/).
* *Isn't it??*"
"
* Here is **some more** markdown."))
- This is markdown.
- Isn’t it??
- Here is some more markdown.
LaTeX formulae are supported as well.
(kind/md
"Let $x=9$. Then $$x+11=20$$")
Let
2.8 TeX
(kind/tex "x^2=\\alpha")
2.9 Code
Clojure code can be annotated with kind/code
.
(kind/code "(update {:x 9} :x inc)")
(update {:x 9} :x inc)
(kind/code
["(update {:x 9} :x inc)"
"(update {:x 9} :x dec)"])
(update {:x 9} :x inc)
(update {:x 9} :x dec)
(kind/code
(list
"(update {:x 9} :x inc)"
"(update {:x 9} :x dec)"))
(update {:x 9} :x inc)
(update {:x 9} :x dec)
2.10 Images
Java BufferedImage objects are displayed as images.
(import javax.imageio.ImageIO
java.net.URL)
java.net.URL
(defonce clay-image
(-> "https://upload.wikimedia.org/wikipedia/commons/2/2c/Clay-ss-2005.jpg"
(URL.)
(ImageIO/read)))
clay-image

Urls to images can be annotated as images as well.
(kind/image
{:src "https://upload.wikimedia.org/wikipedia/commons/2/2c/Clay-ss-2005.jpg"})

Other image representations are currently not supported.
(kind/image
"AN IMAGE")
unsupported image format
2.11 Plain data structures
Plain data structures (lists and sequnces, vectors, sets, maps) are pretty printed if there isn’t any value inside which needs to be displayed in special kind of way.
(def people-as-maps
(->> (range 29)
(mapv (fn [i]
{:preferred-language (["clojure" "clojurescript" "babashka"]
(rand-int 3))
:age (rand-int 100)}))))
(def people-as-vectors
(->> people-as-maps
(mapv (juxt :preferred-language :age))))
(take 5 people-as-maps)
({:preferred-language "clojure", :age 65}
{:preferred-language "babashka", :age 27}
{:preferred-language "babashka", :age 0}
{:preferred-language "babashka", :age 61}
{:preferred-language "babashka", :age 36})
(take 5 people-as-vectors)
(["clojure" 65]
["babashka" 27]
["babashka" 0]
["babashka" 61]
["babashka" 36])
(->> people-as-vectors
(take 5)
set)
#{["babashka" 61]
["clojure" 65]
["babashka" 36]
["babashka" 0]
["babashka" 27]}
When something inside needs to be displayed in a special kind of way, the data structures are printed in a way that makes that clear.
(def nested-structure-1
{:vector-of-numbers [2 9 -1]
:vector-of-different-things ["hi"
(kind/hiccup
[:big [:big "hello"]])]
:map-of-different-things {:markdown (kind/md ["*hi*, **hi**"])
:number 9999}
:hiccup (kind/hiccup
[:big [:big "bye"]])
:dataset (tc/dataset {:x (range 3)
:y [:A :B :C]})})
nested-structure-1
{
:vector-of-numbers [2 9 -1]
|
[
] |
|
{
} |
|
bye
|
|
_unnamed [3 2]:
|
}
2.12 Pretty printing
The :kind/pprint
kind makes sure to simply pretty-print values:
(kind/pprint nested-structure-1)
{:vector-of-numbers [2 9 -1],
:vector-of-different-things ["hi" [:big [:big "hello"]]],
:map-of-different-things {:markdown ["*hi*, **hi**"], :number 9999},
:hiccup [:big [:big "bye"]],
:dataset _unnamed [3 2]:
| :x | :y |
|---:|----|
| 0 | :A |
| 1 | :B |
| 2 | :C |
}
2.13 Datasets
tech.ml.dataset datasets currently use the default printing of the library,
Let us create such a dataset using Tablecloth.
(require '[tablecloth.api :as tc])
(-> {:x (range 6)
:y [:A :B :C :A :B :C]}
tc/dataset)
_unnamed [6 2]:
:x | :y |
---|---|
0 | :A |
1 | :B |
2 | :C |
3 | :A |
4 | :B |
5 | :C |
(-> {:x [1 [2 3] 4]
:y [:A :B :C]}
tc/dataset)
_unnamed [3 2]:
:x | :y |
---|---|
1 | :A |
[2 3] | :B |
4 | :C |
(-> [{:x 1 :y 2 :z 3}
{:y 4 :z 5}]
tc/dataset)
_unnamed [2 3]:
:x | :y | :z |
---|---|---|
1 | 2 | 3 |
4 | 5 |
(-> people-as-maps
tc/dataset)
_unnamed [29 2]:
:preferred-language | :age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
clojurescript | 37 |
clojure | 40 |
clojure | 48 |
clojurescript | 82 |
babashka | 45 |
… | … |
clojure | 28 |
babashka | 84 |
babashka | 60 |
clojurescript | 14 |
babashka | 65 |
clojurescript | 61 |
clojure | 58 |
clojurescript | 38 |
babashka | 67 |
clojurescript | 9 |
clojurescript | 90 |
Some kind options of kind/dataset
control the way a dataset is printed.
(-> {:x (range 30)}
tc/dataset
(kind/dataset {:dataset/print-range 6}))
_unnamed [30 1]:
:x |
---|
0 |
1 |
2 |
… |
26 |
27 |
28 |
29 |
2.14 Tables
The :kind/table
kind can be handy for an interactive table view. :kind/table
understands many structures which can be rendered as a table.
A map containing either :row-vectors
(sequence of sequences) or row-maps
(sequence of maps) keys with optional :column-names
.
(kind/table
{:column-names [:preferred-language :age]
:row-vectors people-as-vectors})
preferred-language | age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
clojurescript | 37 |
clojure | 40 |
clojure | 48 |
clojurescript | 82 |
babashka | 45 |
clojure | 55 |
babashka | 10 |
clojure | 26 |
babashka | 82 |
clojurescript | 59 |
clojure | 15 |
clojure | 98 |
clojurescript | 50 |
clojure | 28 |
babashka | 84 |
babashka | 60 |
clojurescript | 14 |
babashka | 65 |
clojurescript | 61 |
clojure | 58 |
clojurescript | 38 |
babashka | 67 |
clojurescript | 9 |
clojurescript | 90 |
Lack of column names produces table without a header.
(kind/table
{:row-vectors (take 5 people-as-vectors)})
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
Column names are inferred from a sequence of maps
(kind/table
{:row-maps (take 5 people-as-maps)})
preferred-language | age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
We can limit displayed columns for sequence of maps case.
(kind/table
{:column-names [:preferred-language]
:row-maps (take 5 people-as-maps)})
preferred-language |
---|
clojure |
babashka |
babashka |
babashka |
babashka |
Sequence of sequences and sequence of maps also work
(kind/table (take 5 people-as-vectors))
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
(kind/table (take 5 people-as-maps))
preferred-language | age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
Additionally map of sequences is supported (unless it contains :row-vectors
or :row-maps
key, see such case above).
(kind/table {:x (range 6)
:y [:A :B :C :A :B :C]})
x | y |
---|---|
0 | A |
1 | B |
2 | C |
3 | A |
4 | B |
5 | C |
A dataset can be also treated as a table input.
(def people-as-dataset
(tc/dataset people-as-maps))
(-> people-as-dataset
kind/table)
preferred-language | age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
clojurescript | 37 |
clojure | 40 |
clojure | 48 |
clojurescript | 82 |
babashka | 45 |
clojure | 55 |
babashka | 10 |
clojure | 26 |
babashka | 82 |
clojurescript | 59 |
clojure | 15 |
clojure | 98 |
clojurescript | 50 |
clojure | 28 |
babashka | 84 |
babashka | 60 |
clojurescript | 14 |
babashka | 65 |
clojurescript | 61 |
clojure | 58 |
clojurescript | 38 |
babashka | 67 |
clojurescript | 9 |
clojurescript | 90 |
Additional options may hint at way the table should be rendered.
(-> people-as-dataset
(kind/table {:element/max-height "300px"}))
preferred-language | age |
---|---|
clojure | 65 |
babashka | 27 |
babashka | 0 |
babashka | 61 |
babashka | 36 |
clojurescript | 37 |
clojure | 40 |
clojure | 48 |
clojurescript | 82 |
babashka | 45 |
clojure | 55 |
babashka | 10 |
clojure | 26 |
babashka | 82 |
clojurescript | 59 |
clojure | 15 |
clojure | 98 |
clojurescript | 50 |
clojure | 28 |
babashka | 84 |
babashka | 60 |
clojurescript | 14 |
babashka | 65 |
clojurescript | 61 |
clojure | 58 |
clojurescript | 38 |
babashka | 67 |
clojurescript | 9 |
clojurescript | 90 |
It is possible to use datatables to reneder kind/table
, and in this case the user may specify datatables options (see the full list).
(-> people-as-maps
tc/dataset
(kind/table {:use-datatables true}))
(-> people-as-dataset
(kind/table {:use-datatables true
:datatables {:scrollY 300}}))
2.15 Vega and Vega-Lite
(defn vega-lite-point-plot [data]
(-> {:data {:values data},
:mark "point"
:encoding
{:size {:field "w" :type "quantitative"}
:x {:field "x", :type "quantitative"},
:y {:field "y", :type "quantitative"},
:fill {:field "z", :type "nominal"}}}
kind/vega-lite))
(defn random-data [n]
(->> (repeatedly n #(- (rand) 0.5))
(reductions +)
(map-indexed (fn [x y]
{:w (rand-int 9)
:z (rand-int 9)
:x x
:y y}))))
(defn random-vega-lite-plot [n]
(-> n
random-data
vega-lite-point-plot))
(random-vega-lite-plot 9)
When the vega/vega-lite data is given in CSV format, Clay will serve it in a separate CSV file alongside the generated HTML.
(-> {:data {:values "x,y
1,1
2,4
3,9
-1,1
-2,4
-3,9"
:format {:type :csv}},
:mark "point"
:encoding
{:x {:field "x", :type "quantitative"}
:y {:field "y", :type "quantitative"}}}
kind/vega-lite)
2.16 Cytoscape
See the Cytoscape docs.
(def cytoscape-example
{:elements {:nodes [{:data {:id "a" :parent "b"} :position {:x 215 :y 85}}
{:data {:id "b"}}
{:data {:id "c" :parent "b"} :position {:x 300 :y 85}}
{:data {:id "d"} :position {:x 215 :y 175}}
{:data {:id "e"}}
{:data {:id "f" :parent "e"} :position {:x 300 :y 175}}]
:edges [{:data {:id "ad" :source "a" :target "d"}}
{:data {:id "eb" :source "e" :target "b"}}]}
:style [{:selector "node"
:css {:content "data(id)"
:text-valign "center"
:text-halign "center"}}
{:selector "parent"
:css {:text-valign "top"
:text-halign "center"}}
{:selector "edge"
:css {:curve-style "bezier"
:target-arrow-shape "triangle"}}]
:layout {:name "preset"
:padding 5}})
(kind/cytoscape
cytoscape-example)
(-> cytoscape-example
(kind/cytoscape {:style
{:width "100px"
:height "100px"}}))
2.17 ECharts
This example is taken from Apache ECharts’ Getting Started.
(def echarts-example
{:title {:text "Echarts Example"}
:tooltip {}
:legend {:data ["sales"]}
:xAxis {:data ["Shirts", "Cardigans", "Chiffons",
"Pants", "Heels", "Socks"]}
:yAxis {}
:series [{:name "sales"
:type "bar"
:data [5 20 36
10 10 20]}]})
(kind/echarts
echarts-example)
(-> echarts-example
(kind/echarts {:style
{:width "500px"
:height "200px"}}))
2.18 Plotly
See the plotly.js docs.
(def plotly-example
{:data [{:x [0 1 3 2]
:y [0 6 4 5]
:z [0 8 9 7]
:type :scatter3d
:mode :lines+markers
:opacity 0.5
:line {:width 5}
:marker {:size 4
:colorscale :Viridis}}]
:layout {:title "Plotly example"}})
(kind/plotly
plotly-example)
(-> plotly-example
(kind/plotly {:style
{:width "300px"
:height "300px"}}))
2.19 Highcharts
(kind/highcharts
{:title {:text "Line chart"}
:subtitle {:text "By Job Category"}
:yAxis {:title {:text "Number of Employees"}}
:series [{:name "Installation & Developers"
:data [43934, 48656, 65165, 81827, 112143, 142383,
171533, 165174, 155157, 161454, 154610]}
{:name "Manufacturing",
:data [24916, 37941, 29742, 29851, 32490, 30282,
38121, 36885, 33726, 34243, 31050]}
{:name "Sales & Distribution",
:data [11744, 30000, 16005, 19771, 20185, 24377,
32147, 30912, 29243, 29213, 25663]}
{:name "Operations & Maintenance",
:data [nil, nil, nil, nil, nil, nil, nil,
nil, 11164, 11218, 10077]}
{:name "Other",
:data [21908, 5548, 8105, 11248, 8989, 11816, 18274,
17300, 13053, 11906, 10073]}]
:xAxis {:accessibility {:rangeDescription "Range: 2010 to 2020"}}
:legend {:layout "vertical",
:align "right",
:verticalAlign "middle"}
:plotOptions {:series {:label {:connectorAllowed false},
:pointStart 2010}}
:responsive {:rules [{:condition {:maxWidth 500},
:chartOptions {:legend {:layout "horizontal",
:align "center",
:verticalAlign "bottom"}}}]}})
2.20 Observable
Observable visualizations are supported when rendering through Quarto.
The following is adapted from the Penguins example in Quarto’s documentation.
Note that you can save your Clojure data as a csv file and refer to it from within your Observable code. See Referring to files for more information. In this case, we are using the local file, "notebooks/datasets/palmer-penguins.csv"
, which is transparently copied by Clay alongside the target HTML.
(kind/observable
"
//| panel: input
viewof bill_length_min = Inputs.range(
[32, 50],
{value: 35, step: 1, label: 'Bill length (min):'}
)
viewof islands = Inputs.checkbox(
['Torgersen', 'Biscoe', 'Dream'],
{ value: ['Torgersen', 'Biscoe'],
label: 'Islands:'
}
)
Plot.rectY(filtered,
Plot.binX(
{y: 'count'},
{x: 'body_mass_g', fill: 'species', thresholds: 20}
))
.plot({
facet: {
data: filtered,
x: 'sex',
y: 'species',
marginRight: 80
},
marks: [
Plot.frame(),
]
}
)
Inputs.table(filtered)
penguins = FileAttachment('notebooks/datasets/palmer-penguins.csv').csv({ typed: true })
filtered = penguins.filter(function(penguin) {
return bill_length_min < penguin.bill_length_mm &&
islands.includes(penguin.island);
})
")
More examples from Quarto’s Observable documentation:
(kind/observable
"athletes = FileAttachment('notebooks/datasets/athletes.csv').csv({typed: true})")
(kind/observable
"athletes")
(kind/observable
"Inputs.table(athletes)")
(kind/observable
"
Plot.plot({
grid: true,
facet: {
data: athletes,
y: 'sex'
},
marks: [
Plot.rectY(
athletes,
Plot.binX({y: 'count'}, {x: 'weight', fill: 'sex'})
),
Plot.ruleY([0])
]
})
")
(kind/observable
"population = FileAttachment('notebooks/datasets/population.json').json()")
(kind/observable
"population")
(kind/observable
" import { chart } with { population as data } from '@d3/zoomable-sunburst'
chart")
import {chart as chart} with {population as data} from "@d3/zoomable-sunburst"
2.21 Leaflet
This example was adapted from the Leaflet website. Note we are defining a tile layer using leaflet-providers.
(kind/reagent
['(fn []
[:div {:style {:height "200px"}
:ref (fn [el]
(let [m (-> js/L
(.map el)
(.setView (clj->js [51.505 -0.09])
13))]
(-> js/L
.-tileLayer
(.provider "OpenStreetMap.Mapnik")
(.addTo m))
(-> js/L
(.marker (clj->js [51.5 -0.09]))
(.addTo m)
(.bindPopup "A pretty CSS popup.<br> Easily customizable.")
(.openPopup))))}])]
;; Note we need to mention the dependency:
{:html/deps [:leaflet]})
2.22 D3
The following example is adapted from hiccup-d3. The code is a bit different, e.g. (.scaleOrdinal js/d3 (.-schemeCategory10 js/d3))
instead of (d3/scaleOrdinal d3/schemeCategory10)
. We still need to figure out how to make hiccup-d3’s examples work as they are.
(let [letter-frequencies [{:letter "A", :frequency 0.08167}
{:letter "B", :frequency 0.01492}
{:letter "C", :frequency 0.02782}
{:letter "D", :frequency 0.04253}
{:letter "E", :frequency 0.12702}
{:letter "F", :frequency 0.02288}
{:letter "G", :frequency 0.02015}
{:letter "H", :frequency 0.06094}
{:letter "I", :frequency 0.06966}
{:letter "J", :frequency 0.00153}
{:letter "K", :frequency 0.00772}
{:letter "L", :frequency 0.04025}
{:letter "M", :frequency 0.02406}
{:letter "N", :frequency 0.06749}
{:letter "O", :frequency 0.07507}
{:letter "P", :frequency 0.01929}
{:letter "Q", :frequency 0.00095}
{:letter "R", :frequency 0.05987}
{:letter "S", :frequency 0.06327}
{:letter "T", :frequency 0.09056}
{:letter "U", :frequency 0.02758}
{:letter "V", :frequency 0.00978}
{:letter "W", :frequency 0.0236}
{:letter "X", :frequency 0.0015}
{:letter "Y", :frequency 0.01974}
{:letter "Z", :frequency 0.00074}]]
(kind/reagent
['(fn [data]
(let [size 400
x (-> js/d3
.scaleLinear
(.range (into-array [0 size]))
(.domain (into-array [0 (apply max (map :frequency data))])))
y (-> js/d3
.scaleBand
(.domain (into-array (map :letter data)))
(.range (into-array [0 size])))
color (.scaleOrdinal js/d3 (.-schemeCategory10 js/d3))]
[:svg
{:viewBox (str "0 0 " size " " size)}
(map
(fn
[{:keys [letter frequency]}]
[:g
{:key letter, :transform (str "translate(" 0 "," (y letter) ")")}
[:rect
{:x (x 0),
:height (.bandwidth y),
:fill (color letter),
:width (x frequency)}]])
data)]))
letter-frequencies]
{:html/deps [:d3]}))
2.23 ggplotly
(WIP)
Clay supports rendering plots through the JS client side of ggplotly - an R package offering a Plotly fronted for ggplot2’s grammar of graphics implementation. This package is part of the htmlwidgets ecosystem, and we represent that in the kind’s name.
The following is a work-in-progress attempt to generate JSON specs of the kind consumed by ggplotly’s client side.
The following spec function was generaged by mimicking R’s ggplotly(ggplot(mtcars, aes(wt, mpg)) + geom_point())
. Therefore, some parts are hard-coded and require generalization. Other parts are missing (e.g., specifying colours).
(defn ->ggplotly-spec [{:keys [layers labels]}]
(kind/htmlwidgets-ggplotly
(let [;; assuming a single layer for now:
{:keys [data xmin xmax ymin ymax]} (first layers)
;; an auxiliary function to compute tick values:
->tickvals (fn [l r]
(let [jump (-> (- r l)
(/ 6)
math/floor
int
(max 1))]
(-> l
math/ceil
(range r jump))))]
{:x
{:config
{:doubleClick "reset",
:modeBarButtonsToAdd ["hoverclosest" "hovercompare"],
:showSendToCloud false},
:layout
{:plot_bgcolor "rgba(235,235,235,1)",
:paper_bgcolor "rgba(255,255,255,1)",
:legend
{:bgcolor "rgba(255,255,255,1)",
:bordercolor "transparent",
:borderwidth 1.88976377952756,
:font {:color "rgba(0,0,0,1)", :family "", :size 11.689497716895}},
:xaxis (let [tickvals (->tickvals xmin xmax)
ticktext (mapv str tickvals)
range-len (- xmax xmin)
range-expansion (* 0.1 range-len)
expanded-range [(- xmin range-expansion)
(+ xmax range-expansion)]]
{:linewidth 0,
:nticks nil,
:linecolor nil,
:ticklen 3.65296803652968,
:tickcolor "rgba(51,51,51,1)",
:tickmode "array",
:gridcolor "rgba(255,255,255,1)",
:automargin true,
:type "linear",
:tickvals tickvals
:zeroline false,
:title
{:text (:x labels),
:font {:color "rgba(0,0,0,1)", :family "", :size 14.6118721461187}},
:tickfont {:color "rgba(77,77,77,1)", :family "", :size 11.689497716895},
:autorange false,
:showticklabels true,
:showline false,
:showgrid true,
:ticktext ticktext
:ticks "outside",
:gridwidth 0.66417600664176,
:anchor "y",
:domain [0 1],
:hoverformat ".2f",
:tickangle 0,
:tickwidth 0.66417600664176,
:categoryarray ticktext,
:categoryorder "array",
:range expanded-range},)
:font {:color "rgba(0,0,0,1)", :family "", :size 14.6118721461187},
:showlegend false,
:barmode "relative",
:yaxis (let [tickvals (->tickvals ymin ymax)
ticktext (mapv str tickvals)
range-len (- ymax ymin)
range-expansion (* 0.1 range-len)
expanded-range [(- ymin range-expansion)
(+ ymax range-expansion)]]
{:linewidth 0,
:nticks nil,
:linecolor nil,
:ticklen 3.65296803652968,
:tickcolor "rgba(51,51,51,1)",
:tickmode "array",
:gridcolor "rgba(255,255,255,1)",
:automargin true,
:type "linear",
:tickvals tickvals,
:zeroline false,
:title
{:text (:y labels),
:font {:color "rgba(0,0,0,1)", :family "", :size 14.6118721461187}},
:tickfont {:color "rgba(77,77,77,1)", :family "", :size 11.689497716895},
:autorange false,
:showticklabels true,
:showline false,
:showgrid true,
:ticktext ticktext,
:ticks "outside",
:gridwidth 0.66417600664176,
:anchor "x",
:domain [0 1],
:hoverformat ".2f",
:tickangle 0,
:tickwidth 0.66417600664176,
:categoryarray ticktext,
:categoryorder "array",
:range expanded-range},)
:hovermode "closest",
:margin
{:t 25.7412480974125,
:r 7.30593607305936,
:b 39.6955859969559,
:l 37.2602739726027},
:shapes
[{:yref "paper",
:fillcolor nil,
:xref "paper",
:y1 1,
:type "rect",
:line {:color nil, :width 0, :linetype []},
:y0 0,
:x1 1,
:x0 0}]},
:highlight
{:on "plotly_click",
:persistent false,
:dynamic false,
:selectize false,
:opacityDim 0.2,
:selected {:opacity 1},
:debounce 0},
:base_url "https://plot.ly",
:cur_data "1f2fea5b54d146",
:source "A",
:shinyEvents
["plotly_hover"
"plotly_click"
"plotly_selected"
"plotly_relayout"
"plotly_brushed"
"plotly_brushing"
"plotly_clickannotation"
"plotly_doubleclick"
"plotly_deselect"
"plotly_afterplot"
"plotly_sunburstclick"],
:attrs {:1f2fea5b54d146 {:x {}, :y {}, :type "scatter"}},
:data
[{:y (:y data)
:hoveron "points",
:frame nil,
:hoverinfo "text",
:marker
{:autocolorscale false,
:color "rgba(0,0,0,1)",
:opacity 1,
:size 5.66929133858268,
:symbol "circle",
:line {:width 1.88976377952756, :color "rgba(0,0,0,1)"}},
:mode "markers"
:type "scatter",
:xaxis "x",
:showlegend false,
:yaxis "y",
:x (:x data)
:text (-> data
(tc/select-columns [:x :y])
(tc/rows :as-maps)
(->> (mapv pr-str)))}]},
:evals [],
:jsHooks []})))
(require '[tech.v3.datatype.functional :as fun])
A random walk example:
(let [n 100
xs (range n)
ys (reductions + (repeatedly n #(- (rand) 0.5)))
xmin (fun/reduce-min xs)
xmax (fun/reduce-max xs)
ymin (fun/reduce-min ys)
ymax (fun/reduce-max ys)
data (tc/dataset {:x xs
:y ys})]
(->ggplotly-spec
{:layers [{:data data
:xmin xmin :xmax xmax
:ymin ymin :ymax ymax}]
:labels {:x "wt"
:y "mpg"}}))
2.24 3DMol.js
Embedding a 3Dmol Viewer (original example):
(kind/reagent
['(fn [{:keys [data-pdb]}]
[:div {:style {:height "400px"
:width "400px"
:position :relative}
:class "viewer_3Dmoljs"
:data-pdb data-pdb
:data-backgroundcolor "0xffffff"
:data-style "stick"
:data-ui true}])
{:data-pdb "2POR"}]
;; Note we need to mention the dependency:
{:html/deps [:three-d-mol]})
Using 3Dmol within your code (inspired by these examples):
(defonce pdb-2POR
(slurp "https://files.rcsb.org/download/2POR.pdb"))
(kind/reagent
['(fn [{:keys [pdb-data]}]
[:div
{:style {:width "100%"
:height "500px"
:position "relative"}
:ref (fn [el]
(let [config (clj->js
{:backgroundColor "0xffffff"})
viewer (.createViewer js/$3Dmol el)]
(.setViewStyle viewer (clj->js
{:style "outline"}))
(.addModelsAsFrames viewer pdb-data "pdb")
(.addSphere viewer (clj->js
{:center {:x 0
:y 0
:z 0}
:radius 5
:color "green"
:alpha 0.2}))
(.zoomTo viewer)
(.render viewer)
(.zoom viewer 0.8 2000)))}])
{:pdb-data pdb-2POR}]
;; Note we need to mention the dependency:
{:html/deps [:three-d-mol]})
2.25 Video
Videos can be specified as urls (possibly to local files):
(kind/video {:src "https://www.sample-videos.com/video321/mp4/240/big_buck_bunny_240p_30mb.mp4"})
Videos can also be specified as youtube videos: See, e.g., HTML Youtube Videos on w3schools.
(kind/video {:youtube-id "DAQnvAgBma8"})
(kind/video {:youtube-id "DAQnvAgBma8"
:allowfullscreen false})
(kind/video {:youtube-id "DAQnvAgBma8"
:iframe-width 480
:iframe-height 270})
(kind/video {:youtube-id "DAQnvAgBma8"
:embed-options {:mute 1
:controls 0}})
2.26 Embedded Portal
We may embed Portal’s data-navigating viewers using kind/portal
.
(kind/portal {:x (range 3)})
Note that kind/portal
applies the kind-portal adapter to nested kinds.
(kind/portal
[(kind/hiccup [:img {:height 50 :width 50
:src "https://clojure.org/images/clojure-logo-120b.png"}])
(kind/hiccup [:img {:height 50 :width 50
:src "https://raw.githubusercontent.com/djblue/portal/fbc54632adc06c6e94a3d059c858419f0063d1cf/resources/splash.svg"}])])
(kind/portal
[(kind/hiccup [:big [:big "a plot"]])
(random-vega-lite-plot 9)])
2.27 Nesting kinds in Hiccup
Kinds are treated recursively inside Hiccup:
(kind/hiccup
[:div {:style {:background "#f5f3ff"
:border "solid"}}
[:hr]
[:pre [:code "kind/md"]]
(kind/md "*some text* **some more text**")
[:hr]
[:pre [:code "kind/code"]]
(kind/code "{:x (1 2 [3 4])}")
[:hr]
[:pre [:code "kind/dataset"]]
(tc/dataset {:x (range 33)
:y (map inc (range 33))})
[:hr]
[:pre [:code "kind/table"]]
(kind/table
(tc/dataset {:x (range 33)
:y (map inc (range 33))}))
[:hr]
[:pre [:code "kind/vega-lite"]]
(random-vega-lite-plot 9)
[:hr]
[:pre [:code "kind/vega-lite"]]
(-> {:data {:values "x,y
1,1
2,4
3,9
-1,1
-2,4
-3,9"
:format {:type :csv}},
:mark "point"
:encoding
{:x {:field "x", :type "quantitative"}
:y {:field "y", :type "quantitative"}}}
kind/vega-lite)
[:hr]
[:pre [:code "kind/reagent"]]
(kind/reagent
['(fn [numbers]
[:p {:style {:background "#d4ebe9"}}
(pr-str (map inc numbers))])
(vec (range 40))])])
kind/md
some text some more text
kind/code
{:x (1 2 [3 4])}
kind/dataset
_unnamed [33 2]:
:x | :y |
---|---|
0 | 1 |
1 | 2 |
2 | 3 |
3 | 4 |
4 | 5 |
5 | 6 |
6 | 7 |
7 | 8 |
8 | 9 |
9 | 10 |
... | ... |
22 | 23 |
23 | 24 |
24 | 25 |
25 | 26 |
26 | 27 |
27 | 28 |
28 | 29 |
29 | 30 |
30 | 31 |
31 | 32 |
32 | 33 |
kind/table
x | y |
---|---|
0 | 1 |
1 | 2 |
2 | 3 |
3 | 4 |
4 | 5 |
5 | 6 |
6 | 7 |
7 | 8 |
8 | 9 |
9 | 10 |
10 | 11 |
11 | 12 |
12 | 13 |
13 | 14 |
14 | 15 |
15 | 16 |
16 | 17 |
17 | 18 |
18 | 19 |
19 | 20 |
20 | 21 |
21 | 22 |
22 | 23 |
23 | 24 |
24 | 25 |
25 | 26 |
26 | 27 |
27 | 28 |
28 | 29 |
29 | 30 |
30 | 31 |
31 | 32 |
32 | 33 |
kind/vega-lite
kind/vega-lite
kind/reagent
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40)
Scittle and Reagent kinds are recognized automatically inside Hiccup:
(kind/hiccup
[:div
;; recognized as `kind/scittle`
'(defn g [x]
(+ x 9))
;; recognized as `kind/reagent`
['(fn []
[:p (g 11)])]])
20
2.28 Nesting kinds in Tables
Kinds are treated recursively inside Tables:
(kind/table
{:column-names [(kind/hiccup
[:div {:style {:background-color "#ccaabb"}} [:big ":x"]])
(kind/hiccup
[:div {:style {:background-color "#aabbcc"}} [:big ":y"]])]
:row-vectors [[(kind/md "*some text* **some more text**")
(kind/code "{:x (1 2 [3 4])}")]
[(tc/dataset {:x (range 3)
:y (map inc (range 3))})
(random-vega-lite-plot 9)]
[(kind/hiccup [:div.clay-limit-image-width
clay-image])
(kind/md "$x^2$")]]})
:x
|
:y
|
---|---|
some text some more text |
|
_unnamed [3 2]: :x:y 01 12 23 |
|
![]() |
(kind/table
{:column-names ["size" "square"]
:row-vectors (for [i (range 20)]
(let [size (* i 10)
px (str size "px")]
[size
(kind/hiccup
[:div {:style {:height px
:width px
:background-color "purple"}}])]))}
{:use-datatables true})
2.29 More nesting examples
{:plot (random-vega-lite-plot 9)
:dataset (tc/dataset {:x (range 3)
:y (repeatedly 3 rand)})
:arithmetic (kind/fn [+ 1 2])}
{
|
|
_unnamed [3 2]:
|
|
|
}
[(random-vega-lite-plot 9)
(tc/dataset {:x (range 3)
:y (repeatedly 3 rand)})
(kind/fragment [(+ 1 2)
(+ 3 4)])
(-> (toydata/iris-ds)
(hanami/plot hanami/rule-chart
{:=x :sepal-width
:=x2 :sepal-length
:=y :petal-width
:=y2 :petal-length
:=color :species
:=color-type :nominal
:=mark-size 3
:=mark-opacity 0.2}))]
[
_unnamed [3 2]:
:x | :y |
---|---|
0 | 0.54236461 |
1 | 0.49465045 |
2 | 0.90296640 |
3
7
]
2.30 emmy-viewers
The support for Emmy-viewers is documented at the 📖 Emmy-viewers chapter📖 of this book (temporarily removed).
2.31 Blockly
Here is an example of how one may embed Blockly in a page.
(kind/hiccup
[:div
[:script {:src "https://kloimhardt.github.io/twotiles/twotiles_core.js"}]
[:script "var parse = scittle.core.eval_string(twotiles.parse_clj);"]
[:script {:src "https://unpkg.com/blockly/blockly_compressed.js"}]
[:script "Blockly.defineBlocksWithJsonArray(twotiles.blocks);"]])
(def code '(->> (+ x 4) (for [x [1 2 3]])))
(kind/hiccup
[:div
[:script (str "var xml1 = parse('" code "')")]
[:div {:id "blocklyDiv1", :style {:height "100px"}}]
[:script "var workspace1 = Blockly.inject('blocklyDiv1',
{'toolbox': twotiles.toolbox, 'sounds': false})"]
[:script "const xmlDom1 = Blockly.utils.xml.textToDom(xml1)"]
[:script "Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom1,workspace1)"]])
source: notebooks/clay_book/examples.clj