Skip to content

Commit d9e8121

Browse files
authored
Merge pull request #168 from scicloj/plotly_tutorial
Created a plotly tutorial
2 parents dad374f + 751378e commit d9e8121

File tree

2 files changed

+133
-1
lines changed

2 files changed

+133
-1
lines changed

notebooks/chapters.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"automl"
1818
"interactions_ols"]}
1919
{:part "Data Visualization"
20-
:chapters ["tableplot_datavis_intro"
20+
:chapters ["plotly_tutorial"
21+
"tableplot_datavis_intro"
2122
"echarts"]}
2223
#_{:part "Linear Algebra"
2324
:chapters ["linear_algebra_intro"
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
;; # Creating plots with Plotly
2+
3+
;; author: Cvetomir Dimov
4+
5+
;; last change: 2025-04-14
6+
7+
;; [Plotly](https://plotly.com/) is a popular data visualization library that can be accessed from multiple programming languages such as [Javascript](https://plotly.com/javascript/) and [Python](https://plotly.com/python/). There is even a [library](https://plotly.com/ggplot2/) that translates R's ggplot specifications to Plotly. Relevant to Noj, it is [one of the backends](https://scicloj.github.io/tableplot/tableplot_book.plotly_walkthrough.html) that the [Tableplot](https://scicloj.github.io/tableplot/) library supports. With one of the subsequent tutorials, we will present how to work with Tableplot.
8+
9+
;; With this tutorial, we will present how to directly specify a Plotly plot from Clojure. Our task is greatly simplified by the fact that it is [one of the data visualization kinds](https://scicloj.github.io/kindly-noted/kinds.html#plotly) supported by [Kindly](https://scicloj.github.io/kindly-noted/). There are multiple example plots at [Plotly JS's website](https://plotly.com/javascript) that can be translated to Clojure in a few simple steps. We will focus on a complex plot, a [2D histogram contour plot with histogram subplots](https://plotly.com/javascript/2d-density-plots/), in order to demonstrate the full potential of this approach.
10+
11+
;; ## Setup
12+
(ns noj-book.plotly-tutorial
13+
(:require [scicloj.kindly.v4.kind :as kind]
14+
[fitdistr.core :as fd]
15+
[fitdistr.distributions :as fdd]
16+
[clojure.math :as math]))
17+
18+
;; ## Overview
19+
;; Our target plot consists of a 2D histogram contour plot, which visualize the bivariate distribution of two randomly generated variables. In addition, histogram subplots visualize the univariate distribution of each variable. In Plotly, each subplot is specified separately as a trace and then a layout specification tells Plotly how to arrange these subplots. Generating the plot consists of generating the random data, specifying all traces and layout, and then calling `kind/plotly`.
20+
21+
;; ## Random number generation
22+
23+
;; Our two variables, `x` and `y`, and power functions of `t`, which is uniformly distributed between -1 and 2.2.
24+
25+
(def t
26+
(->> (range 0 2001)
27+
(map #(/ % 2000))
28+
(map #(* % 2.2))
29+
(map #(- % 1))))
30+
31+
;; Both `x` and `y` are generated by adding random noise drawn from a normal distribution with a mean of 0 and SD of 0.3. We define it with the `distribution` function from the `fitdistr` package.
32+
(def xy-distr (fd/distribution :normal {:mu 0 :sd 0.3}))
33+
34+
;; `x` is `t` to the third power.
35+
(def x
36+
(map +
37+
(fd/->seq xy-distr (count t))
38+
(map #(math/pow % 3) t)))
39+
40+
;; `y` is `t` to the sixth power.
41+
(def y
42+
(map +
43+
(fd/->seq xy-distr (count t))
44+
(map #(math/pow % 6) t)))
45+
46+
;; ## Translating a Plotly specification to Clojure
47+
;; Each component from a Plotly JS plot is defined in JSON. To translate it to Clojure, it needs to be transformed to a Clojure map. This is as simple removing the colon after the key and changing the key to a keyword. For example, the first trace, which specifies a scatterplot of the two variables, is defined as follows:
48+
49+
(def trace1
50+
{:x x,
51+
:y y,
52+
:mode "markers",
53+
:name "points",
54+
:marker {
55+
:color "rgb(102,0,0)",
56+
:size 2,
57+
:opacity 0.4
58+
},
59+
:type "scatter"
60+
};
61+
)
62+
63+
;; The second trace adds a histogram 2d contour of the same data.
64+
65+
(def trace2
66+
{:x x,
67+
:y y,
68+
:name "density",
69+
:ncontours 20,
70+
:colorscale "Hot",
71+
:reversescale true,
72+
:showscale false,
73+
:type "histogram2dcontour"
74+
}
75+
)
76+
77+
;; The third and fourth traces specify the histograms of our two variables. Note that the `:yaxis` of `trace3` and the `:xaxis` of `trace4` are not the same as those of `trace1` and `trace2`. This is because we don't want these histograms to overlap with the 2d contour plot.
78+
79+
(def trace3
80+
{:x x,
81+
:name "x density",
82+
:marker {:color "rgb(102,0,0)"},
83+
:yaxis "y2",
84+
:type "histogram"
85+
}
86+
)
87+
88+
(def trace4
89+
{:y y,
90+
:name "y density",
91+
:marker {:color "rgb(102,0,0)"},
92+
:xaxis "x2",
93+
:type "histogram"
94+
};
95+
)
96+
97+
;; The layout JSON specification is transformed to a Clojure map just as trivially. It defines two x-axis regions and two y-axis regions, which take 85% and 15% of each axis, respectively.
98+
99+
(def layout
100+
{:showlegend false,
101+
:autosize false,
102+
:width 600,
103+
:height 550,
104+
:margin {:t 50},
105+
:hovermode "closest",
106+
:bargap 0,
107+
:xaxis {:domain [0, 0.85],
108+
:showgrid false,
109+
:zeroline false
110+
},
111+
:yaxis {:domain [0, 0.85],
112+
:showgrid false,
113+
:zeroline false
114+
},
115+
:xaxis2 {:domain [0.85, 1],
116+
:showgrid false,
117+
:zeroline false
118+
},
119+
:yaxis2 {:domain [0.85, 1],
120+
:showgrid false,
121+
:zeroline false
122+
}
123+
};
124+
)
125+
126+
;; ## Realizing the plot with kindly
127+
;; All we need to do now is tell Clojure that this is of kind `plotly`, where we specify in a map a vector of traces as our data and our layout.
128+
129+
(kind/plotly {:data [trace1, trace2, trace3, trace4]
130+
:layout layout})
131+

0 commit comments

Comments
 (0)