@@ -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))
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