|
537 | 537 | ;; |
538 | 538 | ;; **Why compute transforms ourselves?** |
539 | 539 |
|
540 | | -;; 1. Consistency - We want the isualizations to match the |
| 540 | +;; 1. Consistency - We want the visualizations to match the |
541 | 541 | ;; statistical computations of our Clojure libraries. |
542 | 542 | ;; 2. Efficiency - Especially with browser-based rendering targets, |
543 | 543 | ;; what we wish to pass to the target is summaries (say, 20 histogram bars) |
|
775 | 775 | [:or Layer [:vector Layer]]) |
776 | 776 |
|
777 | 777 | (def PlotSpec |
778 | | - "Schema for a complete plot specification. |
| 778 | + "Schema for a plot specification (complete or partial). |
779 | 779 | |
780 | | - A plot spec is a map containing: |
781 | | - - Layers: Vector of layer maps |
| 780 | + A plot spec is a map that can contain: |
| 781 | + - Layers: Vector of layer maps (optional - allows partial specs) |
782 | 782 | - Plot-level properties: target, width, height |
783 | | - - Plot-level scales (optional)" |
| 783 | + - Plot-level scales (optional) |
| 784 | + |
| 785 | + All fields are optional to support composition via =* and =+." |
784 | 786 | [:map |
785 | | - ;; Layers (required) |
786 | | - [:=layers [:vector Layer]] |
| 787 | + ;; Layers (optional - allows partial specs with just plot-level properties) |
| 788 | + [:=layers {:optional true} [:vector Layer]] |
787 | 789 |
|
788 | 790 | ;; Plot-level properties (all optional) |
789 | 791 | [:=target {:optional true} Backend] |
|
947 | 949 | (defn- plot-spec? |
948 | 950 | "Check if x is a plot spec (map with :=layers or plot-level keys). |
949 | 951 | |
950 | | - Plot specs are maps that can have: |
951 | | - - :=layers key with vector of layer maps |
952 | | - - Plot-level :=... keys like :=target, :=width, :=height" |
| 952 | + Plot specs are maps that have at least one key starting with := |
| 953 | + Uses Malli validation as a fallback check for well-formed specs." |
953 | 954 | [x] |
954 | 955 | (and (map? x) |
955 | | - (or (contains? x :=layers) |
956 | | - ;; Has plot-level keys |
957 | | - (some #(-> % name first (= \=)) |
958 | | - (keys x))))) |
| 956 | + ;; Has at least one := key |
| 957 | + (some #(-> % name first (= \=)) |
| 958 | + (keys x)) |
| 959 | + ;; Validates against PlotSpec schema (additional safety check) |
| 960 | + (valid? PlotSpec x))) |
959 | 961 |
|
960 | 962 | ;; ### ⚙️ Renderer |
961 | 963 |
|
|
1100 | 1102 | (reduce =* (=* x y) more)))) |
1101 | 1103 |
|
1102 | 1104 | ;; Test helper: check if result is a valid layer vector |
1103 | | -(defn- valid-layers? [x] |
1104 | | - (and (vector? x) |
1105 | | - (seq x) |
1106 | | - (every? map? x) |
1107 | | - (every? #(some (fn [[k _]] |
1108 | | - (-> k name first (= \=))) |
1109 | | - %) |
1110 | | - x))) |
| 1105 | +(defn- valid-layers? |
| 1106 | + "Check if x is a valid vector of layers using Malli validation. |
| 1107 | + |
| 1108 | + Note: This specifically checks for a vector of layers, not a single layer." |
| 1109 | + [x] |
| 1110 | + (valid? [:vector Layer] x)) |
1111 | 1111 |
|
1112 | 1112 | (defn =+ |
1113 | 1113 | "Combine multiple plot specifications for overlay (sum). |
@@ -2765,19 +2765,19 @@ iris |
2765 | 2765 |
|
2766 | 2766 | ;; Apply linear regression transform to points. |
2767 | 2767 | ;; |
2768 | | -;; Handles both single and grouped regression based on :group key in points. |
| 2768 | +;; Handles both single and grouped regression based on `:group` key in points. |
2769 | 2769 | ;; |
2770 | 2770 | ;; Args: |
2771 | | -;; - layer: Layer map containing transformation specification |
2772 | | -;; - points: Sequence of point maps with :x, :y, and optional :group keys |
| 2771 | +;; - `layer`: Layer map containing transformation specification |
| 2772 | +;; - `points`: Sequence of point maps with `:x`, `:y`, and optional `:group` keys |
2773 | 2773 | ;; |
2774 | 2774 | ;; Returns: |
2775 | | -;; - For ungrouped: {:type :regression :points points :fitted [p1 p2]} |
2776 | | -;; - For grouped: {:type :grouped-regression :points points :groups {group-val {:fitted [...] :points [...]}}} |
| 2775 | +;; - For ungrouped: `{:type :regression :points points :fitted [p1 p2]}` |
| 2776 | +;; - For grouped: `{:type :grouped-regression :points points :groups {group-val {:fitted [...] :points [...]}}}` |
2777 | 2777 | ;; |
2778 | 2778 | ;; Edge cases: |
2779 | 2779 | ;; - Returns original points if regression fails (< 2 points, degenerate data) |
2780 | | -;; - Handles nil fitted values gracefully (skipped during rendering) |
| 2780 | +;; - Handles `nil` fitted values gracefully (skipped during rendering) |
2781 | 2781 | (defmethod apply-transform :linear |
2782 | 2782 | [layer points] |
2783 | 2783 | (when-not (seq points) |
@@ -3030,19 +3030,19 @@ iris |
3030 | 3030 | ;; Apply histogram transform to points. |
3031 | 3031 | ;; |
3032 | 3032 | ;; Bins continuous x values and counts occurrences per bin. |
3033 | | -;; Handles both single and grouped histograms based on :group key in points. |
| 3033 | +;; Handles both single and grouped histograms based on `:group` key in points. |
3034 | 3034 | ;; |
3035 | 3035 | ;; Args: |
3036 | | -;; - layer: Layer map containing :=bins specification |
3037 | | -;; - points: Sequence of point maps with :x and optional :group keys |
| 3036 | +;; - `layer`: Layer map containing `:=bins` specification |
| 3037 | +;; - `points`: Sequence of point maps with `:x` and optional `:group` keys |
3038 | 3038 | ;; |
3039 | 3039 | ;; Returns: |
3040 | | -;; - For ungrouped: {:type :histogram :points points :bars [{:x-min :x-max :x-center :height}...]} |
3041 | | -;; - For grouped: {:type :grouped-histogram :points points :groups {group-val {:bars [...] :points [...]}}} |
| 3040 | +;; - For ungrouped: `{:type :histogram :points points :bars [{:x-min :x-max :x-center :height}...]}` |
| 3041 | +;; - For grouped: `{:type :grouped-histogram :points points :groups {group-val {:bars [...] :points [...]}}}` |
3042 | 3042 | ;; |
3043 | 3043 | ;; Edge cases: |
3044 | | -;; - Returns nil bars if compute-histogram fails (empty, non-numeric, or identical values) |
3045 | | -;; - Histogram with nil bars will not render (graceful degradation) |
| 3044 | +;; - Returns `nil` bars if `compute-histogram` fails (empty, non-numeric, or identical values) |
| 3045 | +;; - Histogram with `nil` bars will not render (graceful degradation) |
3046 | 3046 | (defmethod apply-transform :histogram |
3047 | 3047 | [layer points] |
3048 | 3048 | (when-not (seq points) |
|
0 commit comments