|
21 | 21 | )) |
22 | 22 |
|
23 | 23 |
|
24 | | -;;; # Data (repeated from first part) TODO hide |
| 24 | +;; # The World's Smallest Violin (plot generating code), Part 2 |
| 25 | + |
| 26 | + |
| 27 | +;;; [Link to Part 1]() |
| 28 | + |
| 29 | + |
| 30 | +^:kindly/hide-code ^:kind/hidden |
25 | 31 | (def penguin-data-url |
26 | 32 | "https://raw.githubusercontent.com/ttimbers/palmerpenguins/refs/heads/file-variants/inst/extdata/penguins.tsv") |
27 | 33 |
|
| 34 | +^:kindly/hide-code ^:kind/hidden |
28 | 35 | (def penguin-data |
29 | 36 | (tc/dataset penguin-data-url {:key-fn keyword})) |
30 | 37 |
|
|
36 | 43 | ;;; Once we know how to make a visualization, it makes sense to abstract it into a procedure, so this knowledge in a function. |
37 | 44 |
|
38 | 45 |
|
39 | | -;;; For inst |
| 46 | +;;; So lets do that for dot plot. We'll make a function that takes three required objects: `data`, `value-field`, and `group-field`, along with some options. |
40 | 47 |
|
41 | 48 | (defn dot-plot |
42 | | - [data value-field group-field] |
| 49 | + [data value-field group-field |
| 50 | + & {:keys [jitter? color-field]}] |
43 | 51 | {:mark {:type "point" :tooltip {:content :data}} |
44 | 52 | :data data |
| 53 | + :transform [{:calculate "random()" :as "jitter"}] |
45 | 54 | :encoding |
46 | 55 | {:x {:field value-field |
47 | 56 | :type :quantitative |
|
50 | 59 | :type :nominal |
51 | 60 | :header {:labelAngle 0 :labelAlign "left"} |
52 | 61 | :spacing 0} |
53 | | - :color {:field group-field |
| 62 | + :y {:field (if jitter? "jitter" nil) |
| 63 | + :type :quantitative |
| 64 | + :axis false} |
| 65 | + :color {:field (or color-field group-field) |
54 | 66 | :type :nominal |
55 | | - :legend false} |
56 | | - } |
| 67 | + :legend (if color-field true false)}} |
57 | 68 | :height 50 |
58 | 69 | :width 800 |
59 | 70 | }) |
60 | 71 |
|
61 | | -;;; Which can be used like this: |
62 | | - |
| 72 | +;;; Which can be used like this (here we'll look some differen attributes) |
63 | 73 |
|
64 | 74 | ^:kind/vega-lite |
65 | 75 | (dot-plot {:values (tc/rows penguin-data :as-maps)} |
66 | | - "flipper_length_mm" "year" |
| 76 | + "body_mass_g" "sex" |
| 77 | + :jitter? true |
| 78 | + :color-field "species island" |
67 | 79 | ) |
68 | 80 |
|
69 | 81 |
|
70 | | -;;; On any data set |
| 82 | +;;; And we can easily reuse the function on a different data set (this one is about movies) |
| 83 | + |
71 | 84 | ^:kind/vega-lite |
72 | 85 | (dot-plot {:url "https://vega.github.io/editor/data/movies.json"} |
73 | | - "US Gross" "Major Genre") |
| 86 | + "US Gross" "Major Genre" |
| 87 | + :jitter? true) |
74 | 88 |
|
75 | 89 |
|
76 | 90 |
|
77 | | -;;; Add jitter |
78 | | - |
79 | | -(defn dot-plot-2 |
80 | | - [data value-field group-field jitter?] |
81 | | - {:mark {:type "point" :tooltip {:content :data}} |
82 | | - :data data |
83 | | - :transform (if jitter? [{:calculate "random()" :as "jitter"}] []) |
84 | | - :encoding |
85 | | - {:x {:field value-field |
86 | | - :type :quantitative |
87 | | - :scale {:zero false}} |
88 | | - :y (when jitter? |
89 | | - {:field "jitter" |
90 | | - :type :quantitative |
91 | | - :axis false}) |
92 | | - :row {:field group-field |
93 | | - :type :nominal |
94 | | - :header {:labelAngle 0 :labelAlign "left"} |
95 | | - :spacing 0} |
96 | | - :color {:field group-field |
97 | | - :type :nominal |
98 | | - :legend false} |
99 | | - } |
100 | | - :height 50 |
101 | | - :width 800 |
102 | | - }) |
103 | | - |
104 | | - |
105 | | -;;; On any data set |
106 | | -^:kind/vega-lite |
107 | | -(dot-plot-2 {:url "https://vega.github.io/editor/data/movies.json"} |
108 | | - "US Gross" "Major Genre" true) |
109 | | - |
110 | | - |
111 | 91 | ;;; # Generalize |
112 | 92 |
|
113 | | -;;; This section introduces a new, and somewhat funky way of using and generalizing Vega specs. |
114 | | - |
115 | | -;;; Take our dot-plot abstraction above. We could parameterize it further, say :type which could be :dotplot or :boxplot. But instead, we're going to hack it by introducing a function that can merge arbitrarily nested structures. This means we can alter any aspect of the spec, at the cost of having to have some knowledge of its structure. Eg we could change the height or spacing or fonts. |
| 93 | +;;; This section introduces a new, and somewhat funky way of generalizing Vega specs. |
116 | 94 |
|
| 95 | +;;; Take our dot-plot abstraction above. We could parameterize it further, eg by adding optional arguments for height or scale or any of the many things Vega allows you to tweak. |
117 | 96 |
|
118 | | -;; TODO maybe more confuscing than it is worth here. For a later section? |
| 97 | +;; But instead, we're going to introduce a much more general (if somewhat unclean) way of modifying a base Vega spec – through structural merge. This makes use of a function `mu/merge-recursive` from the (Multitool utility library)[https://github.com/hyperphor/multitool/blob/9e10c6b9cfe7f1deb496e842fc12505748a09d69/src/cljc/hyperphor/multitool/core.cljc#L1012]. This function m merges arbitrarily nested structures. This means we can alter any aspect of the spec, at the cost of having to have some knowledge of its structure. |
119 | 98 |
|
| 99 | +(defn dot-plot-g |
| 100 | + [data value-field group-field overrides] |
| 101 | + (mu/merge-recursive |
| 102 | + (dot-plot data value-field group-field false) |
| 103 | + overrides)) |
120 | 104 |
|
121 | | -;; mu/merge-recursive is a function from the Multitool utility library [link]. |
122 | 105 |
|
123 | | -(defn box-plot |
124 | | - [data value-field group-field] |
125 | | - (-> (dot-plot-2 data value-field group-field false) |
126 | | - (mu/merge-recursive |
127 | | - {:mark {:type :boxplot |
128 | | - :extent :min-max}}))) |
| 106 | +^:kind/vega-lite |
| 107 | +(dot-plot-g {:values (tc/rows penguin-data :as-maps)} |
| 108 | + "body_mass_g" "sex" |
| 109 | + {:mark {:filled true}} |
| 110 | + ) |
129 | 111 |
|
130 | 112 |
|
131 | 113 |
|
|
0 commit comments