Skip to content

Commit 087cfd4

Browse files
Merge pull request #36 from ClojureCivitas/kindly-collections
adds visualize_colls
2 parents 89a5a52 + 4b8ecd5 commit 087cfd4

File tree

2 files changed

+225
-4
lines changed

2 files changed

+225
-4
lines changed

src/graph/layout/elk_svg.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
[scicloj.kindly.v4.kind :as kind]))
99

1010
(def default-styles
11-
{:edge-shape-stroke "black"
11+
{:edge-shape-stroke :currentColor
1212
:edge-shape-fill "none"
13-
:node-shape-stroke "black"
13+
:node-shape-stroke :currentColor
1414
:node-shape-fill "none"
1515
:node-label-stroke "none"
16-
:node-label-fill "black"
16+
:node-label-fill :currentColor
1717
:node-label-font-size "12px"
1818
:node-label-font-family "sans-serif"
19-
:port-shape-stroke "black"
19+
:port-shape-stroke :currentColor
2020
:port-shape-fill "white"})
2121

2222
(defn edge-path [{:keys [sections]}]
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
^{:kindly/hide-code true
2+
:clay {:title "Collections as grids with borders"
3+
:quarto {:type :post
4+
:author [:timothypratley]
5+
:date "2025-07-20"
6+
:category :clojure
7+
:tags [:visualization]
8+
:keywords [:kindly :collections]}}}
9+
(ns scicloj.kindly-render.visualize-colls
10+
(:require [hiccup.core :as hiccup]
11+
[scicloj.kindly.v4.kind :as kind]
12+
[tablecloth.api :as tc]))
13+
14+
;; A key idea of Lisp is that all syntax is a list
15+
16+
()
17+
18+
;; Rich innovated by introducing collection literals
19+
20+
[]
21+
{}
22+
#{}
23+
24+
;; Data is well represented by these collections
25+
26+
(def data
27+
{:numbers [2 9 -1]
28+
:sets #{"hello" "world"}
29+
:mix [1 "hello world" (kind/hiccup [:div "hello" [:strong "world"] "hiccup"])]
30+
:nest {:markdown (kind/md "hello **markdown**")
31+
:number 9999
32+
:nothing-is-something #{nil #{} 0}}
33+
:dataset (tc/dataset {:x (range 3)
34+
:y [:A :B :C]})})
35+
36+
;; In a notebook, we like to visualize values.
37+
;; Often those visualizations summarize the data in some way.
38+
39+
;; 1. The collection represents something
40+
;; 2. The collection organizes some things
41+
;; 3. We are interested in the collection itself
42+
43+
;; In website design, everything is a grid.
44+
;; Grids organize and align content, achieving visual hierarchy.
45+
;; Can we apply this idea to data structures?
46+
;; Let's start with a vector:
47+
48+
(def v [1 2 3 4 5 6 7 8 9])
49+
50+
v
51+
52+
;; Pretty printing the vector is a fine choice,
53+
;; but what if we made it a grid?
54+
55+
(defn content [x columns]
56+
(kind/hiccup
57+
(into [:div {:style {:display :grid
58+
:grid-template-columns (str "repeat(" columns ", auto)")
59+
:align-items :center
60+
:justify-content :center
61+
:text-align :center
62+
:padding "10px"}}]
63+
x)))
64+
65+
(defn vis [x opt]
66+
(kind/hiccup
67+
[:div {:style {:display "grid"
68+
:grid-template-columns "auto auto auto"
69+
:gap "0.25em"
70+
:align-items "center"
71+
:justify-content "center"}}
72+
[:div {:style {:padding "0 0.25em"
73+
:font-weight :bold
74+
:align-self (when opt "start")
75+
:align-items (when-not opt "stretch")}}
76+
"["]
77+
(content x 1)
78+
[:div {:style {:padding "0.25em"
79+
:font-weight :bold
80+
:align-self (when opt "end")
81+
:align-items (when-not opt "stretch")}}
82+
"]"]]))
83+
84+
;; In some situations this feels better, especially when nesting visualizations.
85+
;; But it does raise the question of where the braces belong.
86+
87+
(vis v false)
88+
(vis v true)
89+
90+
;; Another idea is to use borders to indicate collections.
91+
92+
(defn vis2 [x]
93+
(kind/hiccup
94+
[:div {:style {:border-left "2px solid blue"
95+
:border-right "2px solid blue"}}
96+
(content x 1)]))
97+
98+
(vis2 v)
99+
100+
;; Borders can be stylized with images
101+
102+
(defn svg [& body]
103+
(kind/hiccup
104+
(into [:svg {:xmlns "http://www.w3.org/2000/svg"
105+
:width 100
106+
:height 100
107+
:viewBox [-100 -100 200 200]
108+
:stroke :currentColor
109+
:fill :none
110+
:stroke-width 4}
111+
body])))
112+
113+
(defn border-style [x]
114+
{:border "30px solid transparent"
115+
:border-image-slice "30"
116+
:border-image-source (str "url('data:image/svg+xml;utf8,"
117+
(hiccup/html {:mode :xml} x)
118+
"')")
119+
:border-image-repeat "round"})
120+
121+
;; We can create a curly brace shape to use as the border
122+
123+
(def curly-brace-path
124+
"M -10 -40 Q -20 -40, -20 -10, -20 0, -30 0 -20 0, -20 10, -20 40, -10 40")
125+
126+
(def map-svg
127+
(svg (for [r [0 90 180 270]]
128+
[:path {:transform (str "rotate(" r ") translate(-30) ")
129+
:d curly-brace-path}])))
130+
131+
map-svg
132+
133+
(def map-style
134+
(border-style map-svg))
135+
136+
(defn vis3 [style columns x]
137+
(kind/hiccup [:div {:style style} (content x columns)]))
138+
139+
;; We now have a style that can be used to indicate something is a map
140+
141+
(vis3 map-style 2 (vec (repeat 10 [:div "hahaha"])))
142+
143+
;; Usually we'd put this in a CSS rule,
144+
;; I'm just illustrating what it would look like.
145+
146+
(def set-svg
147+
(svg
148+
[:line {:x1 -80 :y1 -20 :x2 -80 :y2 20}]
149+
[:line {:x1 -70 :y1 -20 :x2 -70 :y2 20}]
150+
[:line {:x1 -90 :y1 10 :x2 -60 :y2 10}]
151+
[:line {:x1 -90 :y1 -10 :x2 -60 :y2 -10}]
152+
(for [r [0 90 180 270]]
153+
[:path {:transform (str "rotate(" r ") translate(-30) ")
154+
:d curly-brace-path}])))
155+
156+
;; A set could have a hash on the left
157+
158+
set-svg
159+
160+
(def set-style (border-style set-svg))
161+
162+
(vis3 set-style 1 (vec (repeat 10 [:div "hahaha"])))
163+
164+
;; Sets could instead have a Venn inspired border
165+
166+
(def set2-svg
167+
(svg
168+
(for [r [0 90 180 270]]
169+
[:g {:transform (str "rotate(" r ")")}
170+
[:ellipse {:cx -60 :cy -15 :rx 12 :ry 25}]
171+
[:ellipse {:cx -60 :cy 15 :rx 12 :ry 25}]])))
172+
173+
set2-svg
174+
175+
(def set2-style (border-style set2-svg))
176+
177+
(vis3 set2-style 1 (vec (repeat 10 [:div "hahaha"])))
178+
179+
;; But I think it's better to use the more obvious `#` hash.
180+
181+
(def sequence-svg
182+
(svg
183+
(for [r [0 90 180 270]]
184+
[:g {:transform (str "rotate(" r ")")}
185+
[:circle {:r 75}]])))
186+
187+
sequence-svg
188+
189+
;; Parenthesis style border for sequences
190+
191+
(def sequence-style (border-style sequence-svg))
192+
193+
(vis3 sequence-style 1 (vec (repeat 10 [:div "hahaha"])))
194+
195+
(def vector-svg
196+
(svg
197+
(for [r [0 90 180 270]]
198+
[:g {:transform (str "rotate(" r ")")}
199+
[:path {:d "M -65 -65 v 10 "}]
200+
[:path {:d "M -65 65 v -10 "}]
201+
[:path {:d "M -65 -30 H -75 V 30 H -65"}]])))
202+
203+
vector-svg
204+
205+
;; And rectangular brace style border for vectors
206+
207+
(def vector-style (border-style vector-svg))
208+
209+
(vis3 vector-style 1 (vec (repeat 10 [:div "hahaha"])))
210+
211+
;; Nesting collections
212+
213+
(vis3 map-style 2
214+
[:some-key (vis3 vector-style 1 v)
215+
:some-other (vis3 set-style 1 (repeat 5 (vis3 sequence-style 1 ["ha" "ha" "ha"])))])
216+
217+
;; I think this scheme achieves a visually stylized appearance.
218+
;; It maintains the expected hierarchy.
219+
;; And it is fairly obvious what collections are represented.
220+
221+
;; What do you think?

0 commit comments

Comments
 (0)