Skip to content

Commit cf8be06

Browse files
authored
Merge pull request #154 from ClojureCivitas/tableplot-parameter-flow
Tableplot parameter flow
2 parents 60dae94 + d657eb9 commit cf8be06

File tree

2 files changed

+295
-0
lines changed

2 files changed

+295
-0
lines changed

deps.edn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
:deps
44
{org.clojure/clojure {:mvn/version "1.12.3"}
55
org.scicloj/noj {:mvn/version "2-beta18"}
6+
;; Tableplot can be removed after updating Noj:
7+
org.scicloj/tableplot {:mvn/version "1-beta14"}
68
markdown-clj/markdown-clj {:mvn/version "1.12.4"}
79
org.clojure/core.async {:mvn/version "1.9.808-alpha1"}
810
io.github.clojure/core.async.flow-monitor {:git/tag "v0.1.2"
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
^{:kindly/hide-code true
2+
:clay {:title "Tableplot Tutorial: Customizing Plots with Parameter Substitution"
3+
:quarto {:author [:daslu]
4+
:description "Learn how to customize Tableplot visualizations using substitution parameters."
5+
:category :clojure
6+
:type :post
7+
:draft true
8+
:date "2025-11-10"
9+
:tags [:data-visualization :tableplot :tutorial]}}}
10+
(ns data-visualization.tableplot-parameter-flow
11+
(:require [scicloj.kindly.v4.kind :as kind]
12+
[tablecloth.api :as tc]
13+
[scicloj.tableplot.v1.plotly :as plotly]))
14+
15+
^:kindly/hide-code
16+
(kind/hiccup
17+
[:style
18+
".printedClojure {
19+
max-height:600px;
20+
overflow-y: auto;
21+
}"])
22+
23+
;; ## Introduction
24+
;;
25+
;; [Tableplot](https://scicloj.github.io/tableplot/) is a declarative plotting library
26+
;; that makes it easy to create interactive visualizations from tabular data. One of its
27+
;; most powerful features is **parameter substitution** - the ability to customize plot
28+
;; appearance and behavior by passing parameters that override defaults.
29+
;;
30+
;; This tutorial is a brief intro to this feature.
31+
32+
;; ::: {.callout-tip collapse="true"}
33+
;; ### Background: The Layered Grammar of Graphics
34+
;;
35+
;; Tableplot is inspired by the [**layered grammar of graphics**](https://vita.had.co.nz/papers/layered-grammar.html),
36+
;; a framework for understanding and building statistical visualizations. Originally developed by
37+
;; Leland Wilkinson and later refined by Hadley Wickham in ggplot2, the grammar views plots as
38+
;; compositions of independent components: data, aesthetic mappings, geometric objects, scales,
39+
;; coordinates, and facets.
40+
;;
41+
;; The challenge in implementing such a grammar is achieving multiple goals simultaneously:
42+
;;
43+
;; - **Succinct**: Simple things should be simple - sensible defaults for common cases
44+
;; - **Declarative**: Describe *what* you want, not *how* to draw it
45+
;; - **Flexible**: Support customization without sacrificing simplicity
46+
;; - **Observable**: Make the details visible and understandable when needed
47+
;; - **Extensible**: Allow users to work with internals without breaking abstractions
48+
;;
49+
;; Tableplot addresses these challenges by mostly adopting [Hanami](https://github.com/jsa-aerial/hanami)'s
50+
;; solution as a starting point. Hanami introduced a template-based approach with substitution keys,
51+
;; allowing hierarchical defaults: you can rely on conventions for quick plots, or override specific
52+
;; details when needed. The templates approach makes the transformation process observable,
53+
;; as we'll see in this tutorial.
54+
;;
55+
;; **Further reading:**
56+
57+
;; - [ggplot2: Elegant Graphics for Data Analysis](https://ggplot2-book.org/) - The definitive guide showing how to balance simplicity and flexibility
58+
;; - [Demystifying stat_ layers in ggplot2](https://yjunechoe.github.io/posts/2020-09-26-demystifying-stat-layers-ggplot2/) - June Choe's exploration of how the grammar elegantly handles data transformations, with special focus on making the internals observable and extensible
59+
;; - [Analyzing Data with Clojure (Kevin Lynagh, 2012)](https://www.youtube.com/watch?v=xyGggdg31mc) - An early Clojure attempt to handle the challenge of building a grammar of graphics
60+
61+
;; :::
62+
63+
;; ## A toy challenge: Customizing Grid Colors
64+
;;
65+
;; Let's start with a basic dataset and plot.
66+
67+
(def sample-data
68+
(tc/dataset {:x [1 2 3 4 5]
69+
:y [2 4 3 5 7]}))
70+
71+
;; In this tutorial, we'll use Tableplot's [Plotly.js](https://plotly.com/javascript/) API,
72+
;; which generates interactive Plotly.js visualizations. Tableplot also supports other backends
73+
;; like Vega-Lite and an experimental transpilation API.
74+
75+
;; We can make a basic plot with two layers.
76+
;; This is really easy with our data, because the
77+
;; `:x` and `:y` columns are used by default for the plot's axes.
78+
79+
(-> sample-data
80+
(plotly/layer-point {:=mark-size 20})
81+
plotly/layer-line)
82+
83+
;; ::: {.callout-tip collapse="true"}
84+
;; ### A brief look inside
85+
86+
;; By default, when used in [Kindly](https://scicloj.github.io/kindly-noted/)-compatible
87+
;; tools like [Clay](https://scicloj.github.io/clay/) and in Clojure Civitas posts,
88+
;; Tableplot's plots are configured to be displayed visually.
89+
90+
;; But we can also change the `kind` annotation, so that we can see them as plain
91+
;; Clojure data structures.
92+
93+
(-> sample-data
94+
(plotly/layer-point {:=mark-size 20})
95+
plotly/layer-line
96+
kind/pprint)
97+
98+
;; You see, what API functions such as `plotly/layer-line` generate are
99+
;; certain maps called
100+
;; [templates](https://github.com/jsa-aerial/hanami?tab=readme-ov-file#templates-substitution-keys-and-transformations),
101+
;; a brilliant concept from the [Hanami](https://github.com/jsa-aerial/hanami) library.
102+
103+
;; This is not the resulting Plotly.js specification yet.
104+
;; It is a potential for it, specifying lots of partial intermediate
105+
;; values, called substitution keys.
106+
;; By Tableplot's convention, substitution keys are keywords beginning with `=`,
107+
;; such as `:=layout` or `:=mark-color`.
108+
;;
109+
;; **Why templates?** They separate *what you want* (data mappings, colors, sizes)
110+
;; from *how to render it* (the actual Plotly.js specification). This gives you
111+
;; flexibility: you can override specific details or let defaults handle everything.
112+
;;
113+
;; Substitution keys can have default values, which can also be functions computing
114+
;; them from the values defined by other keys. On the user side,
115+
;; we may override any of these, as we'll see below.
116+
117+
;; What if we actually want to see not the template, but the resulting
118+
;; Plotly.js specification? This is what `plotly/plot` is for.
119+
120+
(-> sample-data
121+
(plotly/layer-point {:=mark-size 20})
122+
plotly/layer-line
123+
plotly/plot
124+
kind/pprint)
125+
126+
;; :::
127+
128+
;; ### Goal
129+
130+
;; Assume that we now wish to colour the grid lines: vertical by green,
131+
;; horizontal by red. After all, what would be a
132+
;; better way to teach Tufte's [data-ink ratio](https://infovis-wiki.net/wiki/Data-Ink_Ratio) principle than doing exactly
133+
;; what it asks us to avoid, by adding some chartjunk?
134+
135+
;; Here are three approaches, each with different tradeoffs.
136+
137+
;; ## Approach 1: Using the relevant substitution keys
138+
139+
;; Sometimes, what we need can be precisely specified in Tableplot.
140+
;; You may find the following in Tableplot's
141+
;; [Plotly API reference](https://scicloj.github.io/tableplot/tableplot_book.plotly_reference.html#yaxis-gridcolor):
142+
143+
;; - [`:=xaxis-gridcolor`](https://scicloj.github.io/tableplot/tableplot_book.plotly_reference.html#xaxis-gridcolor) - The color for the x axis grid lines
144+
;; - [`:=yaxis-gridcolor`](https://scicloj.github.io/tableplot/tableplot_book.plotly_reference.html#yaxis-gridcolor) - The color for the y axis grid lines
145+
146+
;; To use them, you can add a `base` before your plot layers,
147+
;; and configure it with these keys. We use `plotly/base` because its
148+
;; parameters flow to all subsequent layers, which is useful when
149+
;; composing multiple layers with shared settings.
150+
151+
(-> sample-data
152+
(plotly/base {:=xaxis-gridcolor "green"
153+
:=yaxis-gridcolor "red"})
154+
(plotly/layer-point {:=mark-size 20})
155+
plotly/layer-line)
156+
157+
;; ::: {.callout-tip collapse="true"}
158+
;; ### A brief look inside
159+
160+
;; Let us see what actually has changed in the
161+
;; resulting specification:
162+
163+
(-> sample-data
164+
(plotly/base {:=xaxis-gridcolor "green"
165+
:=yaxis-gridcolor "red"})
166+
(plotly/layer-point {:=mark-size 20})
167+
plotly/layer-line
168+
plotly/plot
169+
kind/pprint)
170+
171+
;; :::
172+
173+
;; ## Approach 2: Overriding a broader-scope key
174+
175+
;; What if the specific keys you need don't exist in Tableplot yet?
176+
;;
177+
;; Plotly.js itself will always be richer and more flexible than Tableplot's
178+
;; parameter system.
179+
180+
;; Imagine that the above `:=xaxis-gridcolor` & `:=yaxis-gridcolor` would
181+
;; not be supported.
182+
183+
;; If you read about
184+
;; [Styling and Coloring Axes and the Zero-Line](https://plotly.com/javascript/axes/#styling-and-coloring-axes-and-the-zero-line)
185+
;; in the Plotly.js docs, you will
186+
;; see that, under the `layout` part of the specification,
187+
;; you can specify `gridcolor` for each of the axes.
188+
189+
;; In Tableplot, we can specify the whole layout using `:=layout`, and thus
190+
;; have anything we need in there.
191+
192+
;; - [`:=layout`](https://scicloj.github.io/tableplot/tableplot_book.plotly_reference.html#layout) - The layout part of the resulting Plotly.js specification
193+
194+
(-> sample-data
195+
(plotly/base {:=layout {:xaxis {:gridcolor "green"}
196+
:yaxis {:gridcolor "red"}}})
197+
(plotly/layer-point {:=mark-size 20})
198+
plotly/layer-line)
199+
200+
;; Notice that a few other details of the aesthetics have changed,
201+
;; like the plot's background color.
202+
203+
;; That is what happens when we override the whole `:=layout`.
204+
;; It is a powerful option, that you may or may not like, depending on
205+
;; your use case.
206+
207+
;; ::: {.callout-tip collapse="true"}
208+
;; ### A brief look inside
209+
210+
;; Let us see what happens:
211+
212+
(-> sample-data
213+
(plotly/base {:=layout {:xaxis {:gridcolor "green"}
214+
:yaxis {:gridcolor "red"}}})
215+
(plotly/layer-point {:=mark-size 20})
216+
plotly/layer-line
217+
plotly/plot
218+
kind/pprint)
219+
220+
;; As expected this time, the layout is small and simple,
221+
;; just what you specified.
222+
223+
;; By the way, if you read further in that link to the docs, you will see
224+
;; that `:=layout` depends on `:=xaxis-gridcolor` and `:=yaxis-gridcolor`,
225+
;; among other things. When we specified those narrow-scope keys
226+
;; in our previous example, we actually went through affecting the
227+
;; broad-scope key, `:=layout`.
228+
229+
;; :::
230+
231+
;; ## Approach 3: Direct Manipulation After `plotly/plot`
232+
233+
;; The previous approaches work within Tableplot's API. But what if you need
234+
;; more surgical control — to use Plotly.js concepts while preserving most defaults?
235+
236+
;; Of course we can do that!
237+
238+
;; Of course, the answer has been in front of us the whole time:
239+
;; [It's just data](https://www.youtube.com/watch?v=jlPaby7suOc&t=1000s).
240+
241+
;; We do not need to use Tableplot's API for everything.
242+
;;
243+
;; After we call `plotly/plot`, we can process the actual Plotly.js
244+
;; specification, as data.
245+
246+
(-> sample-data
247+
(plotly/layer-point {:=mark-size 20})
248+
plotly/layer-line
249+
plotly/plot
250+
(assoc-in [:layout :xaxis :gridcolor] "green")
251+
(assoc-in [:layout :yaxis :gridcolor] "red"))
252+
253+
;; ::: {.callout-tip collapse="true"}
254+
;; ### A brief look inside
255+
256+
;; You already know what to expect here:
257+
258+
(-> sample-data
259+
(plotly/layer-point {:=mark-size 20})
260+
plotly/layer-line
261+
plotly/plot
262+
(assoc-in [:layout :xaxis :gridcolor] "green")
263+
(assoc-in [:layout :yaxis :gridcolor] "red")
264+
kind/pprint)
265+
266+
;; :::
267+
268+
;; ## Summary
269+
;;
270+
;; Tableplot's parameter substitution system gives you three levels of control:
271+
;;
272+
;; 1. **Specific substitution keys** (`:=xaxis-gridcolor`, `:=yaxis-gridcolor`)
273+
;; - ✅ Most convenient and discoverable
274+
;; - ✅ Preserves all other defaults
275+
;; - ❌ Limited to what Tableplot explicitly supports
276+
;;
277+
;; 2. **Broad-scope keys** (`:=layout`)
278+
;; - ✅ Full Plotly.js flexibility
279+
;; - ✅ Declarative, within Tableplot's API
280+
;; - ❌ Overrides ALL defaults for that scope
281+
;;
282+
;; 3. **Direct data manipulation** (`assoc-in` after `plotly/plot`)
283+
;; - ✅ Complete control
284+
;; - ✅ Surgical precision - only change what you want
285+
;; - ❌ More verbose
286+
;; - ❌ Leaves Tableplot's template system
287+
;;
288+
;; The key insight: **it's all just data**. Templates with substitution keys give you
289+
;; flexibility without magic. You can always drop down to plain Clojure data manipulation
290+
;; when needed.
291+
;;
292+
;; For more examples and the complete API reference, see the
293+
;; [Tableplot documentation](https://scicloj.github.io/tableplot/).

0 commit comments

Comments
 (0)