Skip to content

Commit 0892169

Browse files
committed
composable_plotting wip
1 parent 8eeb40d commit 0892169

1 file changed

Lines changed: 104 additions & 20 deletions

File tree

src/data_visualization/aog/composable_plotting.clj

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,9 @@ mpg
517517
;; Statistics
518518
:bin-method :sturges ;; Sturges' rule: bin count = ceil(log2(n) + 1)
519519
:domain-padding 0.05
520+
;; Labels and titles
521+
:label-font-size 11 :title-font-size 13
522+
:label-offset 18 :title-offset 18
520523
;; Fallback
521524
:default-color "#333"})
522525

@@ -1041,12 +1044,14 @@ mpg
10411044
stat-y-domains (keep #(get-in % [:stat :y-domain]) view-stats)
10421045

10431046
merged-x-dom (or x-domain
1047+
(:domain x-scale-spec) ;; user-specified domain
10441048
(if (categorical-domain? (first stat-x-domains))
10451049
(distinct (mapcat identity stat-x-domains))
10461050
(let [lo (reduce min (map first stat-x-domains))
10471051
hi (reduce max (map second stat-x-domains))]
10481052
(pad-domain [lo hi] x-scale-spec))))
10491053
merged-y-dom (or y-domain
1054+
(:domain y-scale-spec) ;; user-specified domain
10501055
(if (categorical-domain? (first stat-y-domains))
10511056
(distinct (mapcat identity stat-y-domains))
10521057
(let [lo (reduce min (map first stat-y-domains))
@@ -1255,9 +1260,11 @@ mpg
12551260
;; `aes` + `geom` from `theme` and rendering options.
12561261

12571262
(defn plot
1258-
"Render views as SVG. Options: :width :height :scales :coord :tooltip :brush :config"
1263+
"Render views as SVG. Options: :width :height :scales :coord :tooltip :brush :config
1264+
:x-label :y-label :title — axis labels auto-infer from column names, override here."
12591265
([views] (plot views {}))
1260-
([views {:keys [width height scales coord tooltip brush config]}]
1266+
([views {:keys [width height scales coord tooltip brush config
1267+
x-label y-label title] :as opts}]
12611268
(let [cfg (merge defaults config)
12621269
width (or width (:width cfg))
12631270
height (or height (:height cfg))
@@ -1303,13 +1310,30 @@ mpg
13031310
scale-mode (or scales :shared)
13041311
x-scale-spec (or (:x-scale (first non-ann-views)) {:type :linear})
13051312
y-scale-spec (or (:y-scale (first non-ann-views)) {:type :linear})
1306-
global-x-doms (when (#{:shared :free-y} scale-mode)
1307-
(collect-domain stat-results :x-domain x-scale-spec))
1308-
global-y-doms (when (#{:shared :free-x} scale-mode)
1309-
(compute-global-y-domain stat-results views y-scale-spec))
1313+
global-x-doms (or (:domain x-scale-spec)
1314+
(when (#{:shared :free-y} scale-mode)
1315+
(collect-domain stat-results :x-domain x-scale-spec)))
1316+
global-y-doms (or (:domain y-scale-spec)
1317+
(when (#{:shared :free-x} scale-mode)
1318+
(compute-global-y-domain stat-results views y-scale-spec)))
1319+
;; Axis labels: auto-infer unless multi-variable (SPLOM), allow override
1320+
auto-label? (not multi?)
1321+
eff-x-label (or x-label
1322+
(:label x-scale-spec)
1323+
(when auto-label?
1324+
(when-let [x (first x-vars)] (fmt-name x))))
1325+
eff-y-label (or y-label
1326+
(:label y-scale-spec)
1327+
(when auto-label?
1328+
(when-let [y (first y-vars)] (fmt-name y))))
1329+
eff-title title
1330+
;; Extra space for labels
1331+
x-label-pad (if eff-x-label (:label-offset cfg) 0)
1332+
y-label-pad (if eff-y-label (:label-offset cfg) 0)
1333+
title-pad (if eff-title (:title-offset cfg) 0)
13101334
legend-w (if (or all-colors shape-categories) (:legend-width cfg) 0)
1311-
total-w (+ (* cols pw) legend-w)
1312-
total-h (* rows ph)
1335+
total-w (+ y-label-pad (* cols pw) legend-w)
1336+
total-h (+ title-pad (* rows ph) x-label-pad)
13131337
ctx {:non-ann-views non-ann-views :ann-views ann-views
13141338
:pw pw :ph ph :m m :rows rows :cols cols
13151339
:all-colors all-colors :tooltip-fn tooltip-fn
@@ -1324,13 +1348,36 @@ mpg
13241348
"xmlns" "http://www.w3.org/2000/svg"
13251349
"xmlns:xlink" "http://www.w3.org/1999/xlink"
13261350
"version" "1.1"}
1351+
;; Plot title
1352+
(when eff-title
1353+
[:text {:x (+ y-label-pad (/ (* cols pw) 2))
1354+
:y 14
1355+
:text-anchor "middle" :font-size (:title-font-size cfg)
1356+
:fill "#333" :font-weight "bold" :font-family "sans-serif"}
1357+
eff-title])
1358+
;; Y-axis label (rotated)
1359+
(when eff-y-label
1360+
(let [cy (+ title-pad (/ (* rows ph) 2))]
1361+
[:text {:x 12 :y cy
1362+
:text-anchor "middle" :font-size (:label-font-size cfg)
1363+
:fill "#333" :font-family "sans-serif"
1364+
:transform (str "rotate(-90,12," cy ")")}
1365+
eff-y-label]))
1366+
;; X-axis label
1367+
(when eff-x-label
1368+
[:text {:x (+ y-label-pad (/ (* cols pw) 2))
1369+
:y (- total-h 3)
1370+
:text-anchor "middle" :font-size (:label-font-size cfg)
1371+
:fill "#333" :font-family "sans-serif"}
1372+
eff-x-label])
1373+
;; Legend (offset by label padding)
13271374
(when all-colors
13281375
(render-legend all-colors #(color-for all-colors %)
1329-
:x (+ (* cols pw) 10) :y 20
1376+
:x (+ y-label-pad (* cols pw) 10) :y (+ title-pad 20)
13301377
:title (first color-cols)))
13311378
(when shape-categories
1332-
(let [y-off (if all-colors (+ 20 (* (count all-colors) 16) 10) 20)
1333-
x-off (+ (* cols pw) 10)]
1379+
(let [y-off (+ title-pad (if all-colors (+ 20 (* (count all-colors) 16) 10) 20))
1380+
x-off (+ y-label-pad (* cols pw) 10)]
13341381
(into [:g {:font-family "sans-serif" :font-size 10}
13351382
(when shape-col [:text {:x x-off :y (- y-off 5) :fill "#333" :font-size 9}
13361383
(fmt-name shape-col)])]
@@ -1340,7 +1387,9 @@ mpg
13401387
(if all-colors (color-for all-colors cat) "#333") {})
13411388
[:text {:x (+ x-off 15) :y (+ y-off (* i 16) 4) :fill "#333"}
13421389
(str cat)]]))))
1343-
(into [:g] (remove nil? (arrange-panels layout-type ctx)))]]
1390+
;; Panels (offset by label padding)
1391+
[:g {:transform (str "translate(" y-label-pad "," title-pad ")")}
1392+
(into [:g] (remove nil? (arrange-panels layout-type ctx)))]]]
13441393
(wrap-plot (cond-> #{} tooltip (conj :tooltip) brush (conj :brush)) svg-content))))
13451394

13461395
;; ---
@@ -2325,8 +2374,13 @@ mpg
23252374
;; ### ⚙️ Scale and Coord Setters
23262375

23272376
(defn scale
2328-
"Set a scale type for :x or :y across all views."
2329-
([views channel type] (scale views channel type {}))
2377+
"Set scale options for :x or :y across all views.
2378+
Accepts (views channel type), (views channel type opts), or (views channel opts-map).
2379+
opts-map may include :type, :domain, and :label."
2380+
([views channel type-or-opts]
2381+
(if (map? type-or-opts)
2382+
(scale views channel (or (:type type-or-opts) :linear) (dissoc type-or-opts :type))
2383+
(scale views channel type-or-opts {})))
23302384
([views channel type opts]
23312385
(let [k (case channel :x :x-scale :y :y-scale)]
23322386
(mapv #(assoc % k (merge {:type type} opts)) views))))
@@ -2364,6 +2418,37 @@ mpg
23642418
(scale :y :log)
23652419
plot)
23662420

2421+
;; ### 🧪 Custom Domain
2422+
;;
2423+
;; The `scale` function also accepts an options map with `:domain`
2424+
;; to clip or expand the axis range:
2425+
2426+
(-> iris
2427+
(view [[:sepal-length :sepal-width]])
2428+
(lay (point {:color :species}))
2429+
(scale :x {:domain [4 8]})
2430+
plot)
2431+
2432+
;; ### 🧪 Axis Titles
2433+
;;
2434+
;; Axis labels are auto-inferred from column names.
2435+
;; Override with plot options:
2436+
2437+
(-> iris
2438+
(view [[:sepal-length :sepal-width]])
2439+
(lay (point {:color :species}))
2440+
(plot {:x-label "Sepal Length (cm)"
2441+
:y-label "Sepal Width (cm)"
2442+
:title "Iris Measurements"}))
2443+
2444+
;; Or via the scale constructor — the label travels with the scale:
2445+
2446+
(-> iris
2447+
(view [[:sepal-length :sepal-width]])
2448+
(lay (point {:color :species}))
2449+
(scale :x {:label "Length (cm)"})
2450+
plot)
2451+
23672452
;; ### ⚙️ Polar
23682453
;;
23692454
;; [Polar coordinates](https://en.wikipedia.org/wiki/Polar_coordinate_system)
@@ -2856,12 +2941,11 @@ mpg
28562941
;; into smaller steps -- a preparation phase, a rendering phase, and an
28572942
;; assembly phase -- would make it easier to understand and modify.
28582943
;;
2859-
;; **No axis labels on standalone plots.**
2860-
;; Single-panel plots show tick values but not axis titles like
2861-
;; "sepal length" or "count." Multi-panel layouts use column headers above
2862-
;; and beside the grid, which works well, but a standalone scatter plot
2863-
;; gives no indication of what the axes represent beyond the tick values
2864-
;; themselves.
2944+
;; **Axis labels are auto-inferred** from column names and shown on
2945+
;; standalone and faceted plots (but not SPLOM grids, which use column
2946+
;; headers). Override with `(plot views {:x-label "..." :title "..."})`
2947+
;; or `(scale :x {:label "..."})`. Custom domains also work:
2948+
;; `(scale :x {:domain [4 8]})`.
28652949
;;
28662950
;; **Partial validation.**
28672951
;; `view` checks that the specified columns actually exist in the

0 commit comments

Comments
 (0)