381381; ; The two phases compose through threading:
382382; ; `(-> data (view pairs) (lay (point)))` reads as
383383; ; "these column pairings, drawn as points" -- what, then how.
384+ ; ;
385+ ; ; > *The next sections build the rendering pipeline piece by piece.
386+ ; ; > To see results first and return for the implementation,
387+ ; ; > skip to [Scatter Plots](#scatter-plots).*
384388; ; ---
385389
386390; ; ## Theme and Colors
555559 [:rect :count ]
556560 [:bar :bin ])
557561 ; ; one categorical, one numerical → strip plot
558- (or ( not= x-type y-type) )
562+ (not= x-type y-type)
559563 [:point :identity ]
560564 ; ; both numerical → scatter
561565 :else [:point :identity ])
797801 (fn [coord-type] coord-type))
798802
799803(defmethod show-ticks? :default [_] true )
800- (defmethod show-ticks? :polar [_] false )
801804
802805(defmethod make-coord :cartesian [_ sx sy pw ph m]
803806 (fn [dx dy] [(sx dx) (sy dy)]))
813816 :center (coord-fn 5 50 )
814817 :top-right (coord-fn 10 100 )})
815818
816- ; ; ### ⚙️ Flip
817- ; ;
818- ; ; Swaps x and y axes. Histograms become horizontal, bar charts grow sideways.
819-
820- (defmethod make-coord :flip [_ sx sy pw ph m]
821- (fn [dx dy] [(sx dy) (sy dx)]))
822-
823- (defmethod render-grid :flip [_ sx sy pw ph m cfg]
824- (render-grid :cartesian sx sy pw ph m cfg))
825-
826819(defmethod render-grid :default [_ sx sy pw ph m cfg]
827820 (render-grid :cartesian sx sy pw ph m cfg))
828821
829- ; ; ### ⚙️ Polar
830- ; ;
831- ; ; [Polar coordinates](https://en.wikipedia.org/wiki/Polar_coordinate_system)
832- ; ; map x to angle and y to radius. Bars become wedges, scatters wrap into a disc.
833-
834- (defmethod make-coord :polar [_ sx sy pw ph m]
835- (let [cx (/ pw 2.0 ) cy (/ ph 2.0 )
836- r-max (- (min cx cy) m)
837- x-lo (double m) x-span (double (- pw m m))
838- y-lo (double m) y-span (double (- ph m m))]
839- (fn [dx dy]
840- (let [px (sx dx) py (sy dy)
841- ; ; Normalize to [0,1]: t-angle sweeps the full circle,
842- ; ; t-radius goes from center (0) to edge (1).
843- t-angle (/ (- px x-lo) (max 1.0 x-span))
844- t-radius (/ (- (+ y-lo y-span) py) (max 1.0 y-span))
845- angle (* 2.0 Math/PI t-angle)
846- radius (* r-max t-radius)]
847- [(+ cx (* radius (Math/cos (- angle (/ Math/PI 2.0 )))))
848- (+ cy (* radius (Math/sin (- angle (/ Math/PI 2.0 )))))]))))
849-
850- (defmethod make-coord-px :polar [_ sx sy pw ph m]
851- (let [cx (/ pw 2.0 ) cy (/ ph 2.0 )
852- r-max (- (min cx cy) m)
853- x-lo (double m) x-span (double (- pw m m))
854- y-lo (double m) y-span (double (- ph m m))]
855- (fn [px py]
856- (let [t-angle (/ (- px x-lo) (max 1.0 x-span))
857- t-radius (/ (- (+ y-lo y-span) py) (max 1.0 y-span))
858- angle (* 2.0 Math/PI t-angle)
859- radius (* r-max t-radius)]
860- [(+ cx (* radius (Math/cos (- angle (/ Math/PI 2.0 )))))
861- (+ cy (* radius (Math/sin (- angle (/ Math/PI 2.0 )))))]))))
862-
863- (defmethod render-grid :polar [_ sx sy pw ph m cfg]
864- (let [cfg (or cfg defaults )
865- cx (/ pw 2.0 ) cy (/ ph 2.0 )
866- r-max (- (min cx cy) m)]
867- (into [:g ]
868- (concat
869- (for [i (range 1 6 )
870- :let [r (* r-max (/ i 5.0 ))]]
871- [:circle {:cx cx :cy cy :r r :fill " none"
872- :stroke (:grid theme) :stroke-width (:grid-stroke-width cfg)}])
873- (for [i (range 8 )
874- :let [a (* i (/ Math/PI 4 ))]]
875- [:line {:x1 cx :y1 cy
876- :x2 (+ cx (* r-max (Math/cos a)))
877- :y2 (+ cy (* r-max (Math/sin a)))
878- :stroke (:grid theme) :stroke-width (:grid-stroke-width cfg)}])))))
879-
880822; ; ---
881823
882824; ; ## Drawing Marks
993935 n (tick-count (- pw (* 2 m)) (:tick-spacing-x cfg))
994936 ticks (ws/ticks sx n)
995937 labels (format-ticks sx ticks)]
996- (into [:g {:font-size (:font-size theme) :fill " #666" }]
938+ (into [:g {:font-size (:font-size theme) :fill " #666" :font-family " sans-serif " }]
997939 (map (fn [t label]
998940 [:text {:x (sx t) :y (- ph 2 ) :text-anchor " middle" } label])
999941 ticks labels))))
1007949 n (tick-count (- ph (* 2 m)) (:tick-spacing-y cfg))
1008950 ticks (ws/ticks sy n)
1009951 labels (format-ticks sy ticks)]
1010- (into [:g {:font-size (:font-size theme) :fill " #666" }]
952+ (into [:g {:font-size (:font-size theme) :fill " #666" :font-family " sans-serif " }]
1011953 (map (fn [t label]
1012954 [:text {:x (- m 3 ) :y (+ (sy t) 3 ) :text-anchor " end" } label])
1013955 ticks labels))))
@@ -1227,8 +1169,9 @@ mpg
12271169
12281170; ; ### ⚙️ `wrap-plot`
12291171; ;
1230- ; ; Wraps SVG as hiccup. Tooltip and brush interactions are added later
1231- ; ; in the [Interactivity](#interactivity) section.
1172+ ; ; Wraps SVG as hiccup. This initial definition is a passthrough;
1173+ ; ; it is **redefined** in the [Interactivity](#interactivity) section
1174+ ; ; to add tooltip and brush support.
12321175
12331176(defn wrap-plot
12341177 " Wrap SVG content as hiccup. Interaction modes are added later."
@@ -1460,6 +1403,7 @@ mpg
14601403 :y-domain (:y-domain stat)
14611404 :first-3-bins (mapv #(select-keys % [:min :max :count ])
14621405 (take 3 (:bin-maps (first (:bins stat)))))})
1406+
14631407; ; ### ⚙️ `render-mark` `:bar`
14641408; ;
14651409; ; Bars projected as 4-corner polygons, works with cartesian, flip, and polar.
@@ -1495,6 +1439,16 @@ mpg
14951439 (lay (histogram {:color :species }))
14961440 plot)
14971441
1442+ ; ; ### ⚙️ Flip
1443+ ; ;
1444+ ; ; Swaps x and y axes. Histograms become horizontal, bar charts grow sideways.
1445+
1446+ (defmethod make-coord :flip [_ sx sy pw ph m]
1447+ (fn [dx dy] [(sx dy) (sy dx)]))
1448+
1449+ (defmethod render-grid :flip [_ sx sy pw ph m cfg]
1450+ (render-grid :cartesian sx sy pw ph m cfg))
1451+
14981452; ; ### 🧪 Flipped Histogram
14991453; ;
15001454; ; `:flip` swaps the axes -- bars grow leftward:
@@ -1515,9 +1469,8 @@ mpg
15151469
15161470; ; ### ⚙️ Line Constructors
15171471
1518- (defn line-mark
1519- " Line mark with identity stat. Named `line-mark` to avoid shadowing
1520- SVG's [:line] element used elsewhere in this namespace."
1472+ (defn line
1473+ " Line mark with identity stat."
15211474 ([] {:mark :line :stat :identity })
15221475 ([opts] (merge {:mark :line :stat :identity } opts)))
15231476
@@ -1547,7 +1500,7 @@ mpg
15471500(-> (view {:year [2018 2019 2020 2021 2022 ]
15481501 :sales [10 15 13 17 20 ]}
15491502 [[:year :sales ]])
1550- (lay (line-mark ))
1503+ (lay (line ))
15511504 plot)
15521505
15531506; ; ### 🧪 Colored Line Chart
@@ -1557,7 +1510,7 @@ mpg
15571510 :region [" East" " East" " East" " East" " East"
15581511 " West" " West" " West" " West" " West" ]}
15591512 [[:year :sales ]])
1560- (lay (line-mark {:color :region }))
1513+ (lay (line {:color :region }))
15611514 plot)
15621515
15631516; ; ---
@@ -1574,15 +1527,15 @@ mpg
15741527; ; Base views duplicated, each copy with a different layer:
15751528
15761529(-> (view iris [[:sepal-length :sepal-width ]])
1577- (lay (point ) (line-mark ))
1530+ (lay (point ) (line ))
15781531 (->> (mapv #(select-keys % [:x :y :mark :stat ]))))
15791532
15801533; ; ### 🧪 Scatter + Line Overlay
15811534
15821535(-> (view {:year [2018 2019 2020 2021 2022 ]
15831536 :sales [10 15 13 17 20 ]}
15841537 [[:year :sales ]])
1585- (lay (point ) (line-mark ))
1538+ (lay (point ) (line ))
15861539 plot)
15871540
15881541; ; ### 🧪 Colored Scatter + Line
@@ -1592,7 +1545,7 @@ mpg
15921545 :region [" East" " East" " East" " East" " East"
15931546 " West" " West" " West" " West" " West" ]}
15941547 [[:year :sales ]])
1595- (lay (point {:color :region }) (line-mark {:color :region }))
1548+ (lay (point {:color :region }) (line {:color :region }))
15961549 plot)
15971550
15981551; ; ---
@@ -1653,6 +1606,7 @@ mpg
16531606 resolve-view
16541607 compute-stat
16551608 kind/pprint)
1609+
16561610; ; ### ⚙️ `compute-stat` `:loess`
16571611; ;
16581612; ; Loess via fastmath.interpolation (80 sample points, x values deduplicated).
@@ -1822,7 +1776,7 @@ mpg
18221776 (if coord-px
18231777 [:polygon {:points (arc-polygon-points coord-px x-lo x-hi py-lo py-hi 20 )
18241778 :fill color :opacity opacity}]
1825- [:rect {:x x-lo :y (clojure.core/ min py-lo py-hi)
1779+ [:rect {:x x-lo :y (min py-lo py-hi)
18261780 :width (- x-hi x-lo)
18271781 :height (Math/abs (- py-lo py-hi))
18281782 :fill color :opacity opacity}])))
@@ -1832,7 +1786,7 @@ mpg
18321786 (let [{:keys [all-colors sx sy coord-px position cfg]} ctx
18331787 cfg (or cfg defaults )
18341788 bw (ws/data sx :bandwidth )
1835- n-colors (clojure.core/ count (:bars stat))
1789+ n-colors (count (:bars stat))
18361790 cum-y (atom {})
18371791 active-map (when (= position :dodge )
18381792 (into {}
@@ -1845,28 +1799,28 @@ mpg
18451799 (into [:g ]
18461800 (mapcat (fn [bi {:keys [color counts]}]
18471801 (let [c (if color (color-for all-colors color) (:default-color cfg))]
1848- (for [{:keys [ category count] } counts
1849- :when (or (= position :stack ) (pos? count ))
1850- :let [band-info (sx category true )
1802+ (for [{cat : category cnt : count } counts
1803+ :when (or (= position :stack ) (pos? cnt ))
1804+ :let [band-info (sx cat true )
18511805 band-start (:rstart band-info)
18521806 band-end (:rend band-info)
18531807 band-mid (/ (+ band-start band-end) 2.0 )]]
18541808 (if (= position :stack )
1855- (let [base (get @cum-y category 0 )
1809+ (let [base (get @cum-y cat 0 )
18561810 py-lo (sy base)
1857- py-hi (sy (+ base count ))
1811+ py-hi (sy (+ base cnt ))
18581812 x-lo (- band-mid (* bw 0.4 ))
18591813 x-hi (+ band-mid (* bw 0.4 ))]
1860- (swap! cum-y assoc category (+ base count ))
1814+ (swap! cum-y assoc cat (+ base cnt ))
18611815 (render-bar-elem coord-px x-lo x-hi py-lo py-hi c cfg))
1862- (let [active (get active-map category )
1863- n-active (clojure.core/ count active)
1816+ (let [active (get active-map cat )
1817+ n-active (count active)
18641818 active-idx (.indexOf ^java.util.List active bi)
1865- sub-bw (/ (* bw 0.8 ) (clojure.core/ max 1 n-active))
1819+ sub-bw (/ (* bw 0.8 ) (max 1 n-active))
18661820 x-lo (+ (- band-mid (/ (* n-active sub-bw) 2.0 )) (* active-idx sub-bw))
18671821 x-hi (+ x-lo sub-bw)
18681822 py-lo (sy 0 )
1869- py-hi (sy count )]
1823+ py-hi (sy cnt )]
18701824 (render-bar-elem coord-px x-lo x-hi py-lo py-hi c cfg))))))
18711825 (range ) (:bars stat)))))
18721826
@@ -1876,13 +1830,13 @@ mpg
18761830 cfg (or cfg defaults )
18771831 bw (ws/data sx :bandwidth )
18781832 groups (:points stat)
1879- n-groups (clojure.core/ count groups)
1880- sub-bw (/ (* bw 0.8 ) (clojure.core/ max 1 n-groups))
1833+ n-groups (count groups)
1834+ sub-bw (/ (* bw 0.8 ) (max 1 n-groups))
18811835 cum-y (atom {})]
18821836 (into [:g ]
18831837 (mapcat (fn [gi {:keys [color xs ys]}]
18841838 (let [c (if color (color-for all-colors color) (:default-color cfg))]
1885- (for [i (range (clojure.core/ count xs))
1839+ (for [i (range (count xs))
18861840 :let [cat (nth xs i)
18871841 val (nth ys i)
18881842 band-info (sx cat true )
@@ -1916,15 +1870,15 @@ mpg
19161870(defmethod render-x-ticks :categorical [_ sx pw ph m cfg]
19171871 (let [ticks (ws/ticks sx)
19181872 labels (map str ticks)]
1919- (into [:g {:font-size (:font-size theme) :fill " #666" }]
1873+ (into [:g {:font-size (:font-size theme) :fill " #666" :font-family " sans-serif " }]
19201874 (map (fn [t label]
19211875 [:text {:x (sx t) :y (- ph 2 ) :text-anchor " middle" } label])
19221876 ticks labels))))
19231877
19241878(defmethod render-y-ticks :categorical [_ sy pw ph m cfg]
19251879 (let [ticks (ws/ticks sy)
19261880 labels (map str ticks)]
1927- (into [:g {:font-size (:font-size theme) :fill " #666" }]
1881+ (into [:g {:font-size (:font-size theme) :fill " #666" :font-family " sans-serif " }]
19281882 (map (fn [t label]
19291883 [:text {:x (- m 3 ) :y (+ (sy t) 3 ) :text-anchor " end" } label])
19301884 ticks labels))))
@@ -2227,6 +2181,7 @@ mpg
22272181 [:sepal-length :sepal-width :petal-length ]))
22282182 (when-off-diagonal {:color :species })
22292183 plot)
2184+
22302185; ; ### 🧪 Faceted Scatter
22312186; ;
22322187; ; One panel per species -- `facet` splits views by a column:
@@ -2364,6 +2319,59 @@ mpg
23642319 (scale :y :log )
23652320 plot)
23662321
2322+ ; ; ### ⚙️ Polar
2323+ ; ;
2324+ ; ; [Polar coordinates](https://en.wikipedia.org/wiki/Polar_coordinate_system)
2325+ ; ; map x to angle and y to radius. Bars become wedges, scatters wrap into a disc.
2326+
2327+ (defmethod show-ticks? :polar [_] false )
2328+
2329+ (defmethod make-coord :polar [_ sx sy pw ph m]
2330+ (let [cx (/ pw 2.0 ) cy (/ ph 2.0 )
2331+ r-max (- (min cx cy) m)
2332+ x-lo (double m) x-span (double (- pw m m))
2333+ y-lo (double m) y-span (double (- ph m m))]
2334+ (fn [dx dy]
2335+ (let [px (sx dx) py (sy dy)
2336+ ; ; Normalize to [0,1]: t-angle sweeps the full circle,
2337+ ; ; t-radius goes from center (0) to edge (1).
2338+ t-angle (/ (- px x-lo) (max 1.0 x-span))
2339+ t-radius (/ (- (+ y-lo y-span) py) (max 1.0 y-span))
2340+ angle (* 2.0 Math/PI t-angle)
2341+ radius (* r-max t-radius)]
2342+ [(+ cx (* radius (Math/cos (- angle (/ Math/PI 2.0 )))))
2343+ (+ cy (* radius (Math/sin (- angle (/ Math/PI 2.0 )))))]))))
2344+
2345+ (defmethod make-coord-px :polar [_ sx sy pw ph m]
2346+ (let [cx (/ pw 2.0 ) cy (/ ph 2.0 )
2347+ r-max (- (min cx cy) m)
2348+ x-lo (double m) x-span (double (- pw m m))
2349+ y-lo (double m) y-span (double (- ph m m))]
2350+ (fn [px py]
2351+ (let [t-angle (/ (- px x-lo) (max 1.0 x-span))
2352+ t-radius (/ (- (+ y-lo y-span) py) (max 1.0 y-span))
2353+ angle (* 2.0 Math/PI t-angle)
2354+ radius (* r-max t-radius)]
2355+ [(+ cx (* radius (Math/cos (- angle (/ Math/PI 2.0 )))))
2356+ (+ cy (* radius (Math/sin (- angle (/ Math/PI 2.0 )))))]))))
2357+
2358+ (defmethod render-grid :polar [_ sx sy pw ph m cfg]
2359+ (let [cfg (or cfg defaults )
2360+ cx (/ pw 2.0 ) cy (/ ph 2.0 )
2361+ r-max (- (min cx cy) m)]
2362+ (into [:g ]
2363+ (concat
2364+ (for [i (range 1 6 )
2365+ :let [r (* r-max (/ i 5.0 ))]]
2366+ [:circle {:cx cx :cy cy :r r :fill " none"
2367+ :stroke (:grid theme) :stroke-width (:grid-stroke-width cfg)}])
2368+ (for [i (range 8 )
2369+ :let [a (* i (/ Math/PI 4 ))]]
2370+ [:line {:x1 cx :y1 cy
2371+ :x2 (+ cx (* r-max (Math/cos a)))
2372+ :y2 (+ cy (* r-max (Math/sin a)))
2373+ :stroke (:grid theme) :stroke-width (:grid-stroke-width cfg)}])))))
2374+
23672375; ; ### 🧪 Polar Scatter
23682376; ;
23692377; ; `coord :polar` wraps the same scatter into polar space:
@@ -2423,6 +2431,7 @@ mpg
24232431[(hline 3.0 )
24242432 (vline 6.0 )
24252433 (hband 2.5 3.5 )]
2434+
24262435; ; ### ⚙️ `render-annotation` methods
24272436
24282437(defmethod render-annotation :rule-h [ann {:keys [coord-fn x-domain cfg]}]
@@ -2635,6 +2644,7 @@ mpg
26352644(-> (view iris [[:sepal-length :sepal-width ]])
26362645 (lay (point {:color :species }))
26372646 (plot {:tooltip true }))
2647+
26382648; ; ### 🧪 Brushable Scatter
26392649; ;
26402650; ; `:brush true` adds a drag-to-select rectangle:
0 commit comments