Skip to content

Commit 8eeb40d

Browse files
committed
composable plotting wip
1 parent b989f9b commit 8eeb40d

1 file changed

Lines changed: 64 additions & 19 deletions

File tree

src/data_visualization/aog/composable_plotting.clj

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,24 @@ mpg
226226
(map? spec) spec
227227
:else {:x (first spec) :y (second spec)}))
228228

229+
(defn column-ref?
230+
"True if v is a column reference (keyword), false if it's a fixed constant (string, number)."
231+
[v]
232+
(keyword? v))
233+
229234
(def column-keys
230235
"View keys whose values are column names in the dataset."
231236
#{:x :y :color :size :shape})
232237

233238
(defn validate-columns
234239
"Check that every column-referencing key in view-map names a real column in ds.
240+
Non-keyword values (strings, numbers) are fixed constants and skip validation.
235241
Also usable as (validate-columns ds :facet col) for a single named check."
236242
([ds view-map]
237243
(let [col-names (set (tc/column-names ds))]
238244
(doseq [k column-keys
239245
:let [col (get view-map k)]
240-
:when (and col (not (col-names col)))]
246+
:when (and col (column-ref? col) (not (col-names col)))]
241247
(throw (ex-info (str "Column " col " (from " k ") not found in dataset. Available: " (sort col-names))
242248
{:key k :column col :available (sort col-names)})))))
243249
([ds role col]
@@ -536,18 +542,29 @@ mpg
536542

537543
(defn resolve-view
538544
"Fill in derived properties: :x-type, :y-type, :color-type, :group, :mark, :stat.
539-
User-specified values always win."
545+
User-specified values always win.
546+
Fixed aesthetic values (strings, numbers) are split into :fixed-color, :fixed-size
547+
so downstream code only sees column references in :color, :size."
540548
[v]
541549
(if-not (:data v)
542550
v
543551
(let [ds (:data v)
544552
x-type (or (:x-type v) (column-type ds (:x v)))
545553
y-type (or (:y-type v) (when (and (:y v) (not= (:x v) (:y v)))
546554
(column-type ds (:y v))))
547-
c-type (when (:color v)
548-
(or (:color-type v) (column-type ds (:color v))))
555+
;; Color: only resolve column type for keyword (column) colors
556+
color-val (:color v)
557+
color-is-col? (and color-val (column-ref? color-val))
558+
c-type (when color-is-col?
559+
(or (:color-type v) (column-type ds color-val)))
560+
fixed-color (when (and color-val (not color-is-col?)) color-val)
561+
;; Size: split into column-ref vs fixed
562+
size-val (:size v)
563+
size-is-col? (and size-val (column-ref? size-val))
564+
fixed-size (when (and size-val (not size-is-col?)) size-val)
565+
;; Group only by column-ref colors
549566
group (or (:group v)
550-
(when (= c-type :categorical) [(:color v)])
567+
(when (= c-type :categorical) [color-val])
551568
[])
552569
;; Infer mark and stat from column types when not specified
553570
diagonal? (= (:x v) (:y v))
@@ -566,7 +583,12 @@ mpg
566583
mark (or (:mark v) default-mark)
567584
stat (or (:stat v) default-stat)]
568585
(assoc v :x-type x-type :y-type y-type :color-type c-type
569-
:group group :mark mark :stat stat))))
586+
:group group :mark mark :stat stat
587+
;; Normalize: column-ref stays, fixed goes to :fixed-*
588+
:color (when color-is-col? color-val)
589+
:fixed-color fixed-color
590+
:size (when size-is-col? size-val)
591+
:fixed-size fixed-size))))
570592

571593
;; ### 🧪 What `resolve-view` Produces
572594
;;
@@ -845,10 +867,10 @@ mpg
845867
shape-categories)))]
846868
(into [:g]
847869
(mapcat (fn [{:keys [color xs ys sizes shapes row-indices]}]
848-
(let [c (if color (color-for all-colors color) (:default-color cfg))]
870+
(let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))]
849871
(for [i (range (count xs))
850872
:let [[px py] (coord-fn (nth xs i) (nth ys i))
851-
r (if sizes (size-scale (nth sizes i)) (:point-radius cfg))
873+
r (if sizes (size-scale (nth sizes i)) (or (:fixed-size ctx) (:point-radius cfg)))
852874
sh (if shapes (get shape-map (nth shapes i) :circle) :circle)
853875
row-idx (when row-indices (nth row-indices i))
854876
tip (when tooltip-fn
@@ -1088,7 +1110,9 @@ mpg
10881110
(into [:g]
10891111
(mapcat (fn [{:keys [view stat]}]
10901112
(let [mark (:mark view)
1091-
mark-ctx (assoc ctx :position (or (:position view) :dodge))]
1113+
mark-ctx (cond-> (assoc ctx :position (or (:position view) :dodge))
1114+
(:fixed-color view) (assoc :fixed-color (:fixed-color view))
1115+
(:fixed-size view) (assoc :fixed-size (:fixed-size view)))]
10921116
[(render-mark mark stat mark-ctx)]))
10931117
view-stats))
10941118

@@ -1264,13 +1288,14 @@ mpg
12641288
(double (:panel-size cfg))
12651289
(double (/ height rows)))
12661290
stat-results (mapv (comp compute-stat #(assoc % :cfg cfg) resolve-view) non-ann-views)
1267-
all-colors (let [color-views (filter #(and (:color %) (:data %)) views)]
1291+
all-colors (let [color-views (filter #(and (column-ref? (:color %)) (:data %)) views)]
12681292
(when (seq color-views)
12691293
(distinct (mapcat #((:data %) (:color %)) color-views))))
1270-
color-cols (distinct (remove nil? (map :color views)))
1271-
shape-col (first (remove nil? (map :shape views)))
1294+
color-cols (distinct (keep #(when (column-ref? (:color %)) (:color %)) views))
1295+
shape-col (first (keep #(when (column-ref? (:shape %)) (:shape %)) views))
12721296
shape-categories (when shape-col
1273-
(distinct (mapcat (fn [v] (when (:data v) (map #(get % shape-col) (tc/rows (:data v) :as-maps))))
1297+
(distinct (mapcat (fn [v] (when (and (:data v) (column-ref? (:shape v)))
1298+
(map #(get % shape-col) (tc/rows (:data v) :as-maps))))
12741299
views)))
12751300
coord-type-main (or (:coord (first views)) :cartesian)
12761301
tooltip-fn (when tooltip
@@ -1353,6 +1378,26 @@ mpg
13531378
(lay (point {:color :species}))
13541379
plot)
13551380

1381+
;; ### 🧪 Fixed Aesthetics
1382+
;;
1383+
;; When an aesthetic is a keyword, it binds to a column.
1384+
;; When it's a string or number, it's a fixed value —
1385+
;; no grouping, no legend entry:
1386+
1387+
(-> {:x [1 2 3 4] :y [2 4 3 5]}
1388+
(view [[:x :y]])
1389+
(lay (point {:color "steelblue" :size 6}))
1390+
plot)
1391+
1392+
;; Mixed layers: column-bound color on points,
1393+
;; fixed color on the regression line:
1394+
1395+
(-> iris
1396+
(view [[:sepal-length :sepal-width]])
1397+
(lay (point {:color :species})
1398+
(lm {:color "black"}))
1399+
plot)
1400+
13561401
;; ---
13571402

13581403
;; ## Histograms
@@ -1413,7 +1458,7 @@ mpg
14131458
cfg (or cfg defaults)]
14141459
(into [:g]
14151460
(mapcat (fn [{:keys [color bin-maps]}]
1416-
(let [c (if color (color-for all-colors color) (:default-color cfg))]
1461+
(let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))]
14171462
(for [{:keys [min max count]} bin-maps
14181463
:let [[x1 y1] (coord-fn min 0)
14191464
[x2 y2] (coord-fn max 0)
@@ -1483,14 +1528,14 @@ mpg
14831528
(concat
14841529
(when-let [lines (:lines stat)]
14851530
(for [{:keys [color x1 y1 x2 y2]} lines
1486-
:let [c (if color (color-for all-colors color) (:default-color cfg))
1531+
:let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))
14871532
[px1 py1] (coord-fn x1 y1)
14881533
[px2 py2] (coord-fn x2 y2)]]
14891534
[:line {:x1 px1 :y1 py1 :x2 px2 :y2 py2
14901535
:stroke c :stroke-width (:line-width cfg)}]))
14911536
(when-let [pts (:points stat)]
14921537
(for [{:keys [color xs ys]} pts
1493-
:let [c (if color (color-for all-colors color) (:default-color cfg))
1538+
:let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))
14941539
projected (sort-by first (map (fn [x y] (coord-fn x y)) xs ys))]]
14951540
[:polyline {:points (str/join " " (map (fn [[px py]] (str px "," py)) projected))
14961541
:stroke c :stroke-width (:line-width cfg) :fill "none"}]))))))
@@ -1798,7 +1843,7 @@ mpg
17981843
(:bars stat))])))]
17991844
(into [:g]
18001845
(mapcat (fn [bi {:keys [color counts]}]
1801-
(let [c (if color (color-for all-colors color) (:default-color cfg))]
1846+
(let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))]
18021847
(for [{cat :category cnt :count} counts
18031848
:when (or (= position :stack) (pos? cnt))
18041849
:let [band-info (sx cat true)
@@ -1835,7 +1880,7 @@ mpg
18351880
cum-y (atom {})]
18361881
(into [:g]
18371882
(mapcat (fn [gi {:keys [color xs ys]}]
1838-
(let [c (if color (color-for all-colors color) (:default-color cfg))]
1883+
(let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))]
18391884
(for [i (range (count xs))
18401885
:let [cat (nth xs i)
18411886
val (nth ys i)
@@ -2471,7 +2516,7 @@ mpg
24712516
cfg (or cfg defaults)]
24722517
(into [:g {:font-size 9 :fill (:default-color cfg) :text-anchor "middle"}]
24732518
(mapcat (fn [{:keys [color xs ys labels]}]
2474-
(let [c (if color (color-for all-colors color) (:default-color cfg))]
2519+
(let [c (if color (color-for all-colors color) (or (:fixed-color ctx) (:default-color cfg)))]
24752520
(for [i (range (count xs))
24762521
:let [[px py] (coord-fn (nth xs i) (nth ys i))]]
24772522
[:text {:x px :y (- py 5) :fill c}

0 commit comments

Comments
 (0)